Module: Klay::Abi
Overview
Provides a Ruby implementation of the Klaytn Applicatoin Binary Interface (ABI).
Defined Under Namespace
Classes: DecodingError, EncodingError, Type, ValueOutOfBounds
Class Method Summary collapse
-
.decode(types, data) ⇒ Array
Decodes Application Binary Interface (ABI) data.
-
.decode_primitive_type(type, data) ⇒ String
Decodes primitive types.
-
.decode_type(type, arg) ⇒ String
Decodes a specific value, either static or dynamic.
-
.encode(types, args) ⇒ String
Encodes Application Binary Interface (ABI) data.
-
.encode_primitive_type(type, arg) ⇒ String
Encodes primitive types.
-
.encode_type(type, arg) ⇒ String
Encodes a specific value, either static or dynamic.
Instance Method Summary collapse
-
#decode(types, data) ⇒ Array
Decodes Application Binary Interface (ABI) data.
-
#decode_primitive_type(type, data) ⇒ String
Decodes primitive types.
-
#decode_type(type, arg) ⇒ String
Decodes a specific value, either static or dynamic.
-
#encode(types, args) ⇒ String
Encodes Application Binary Interface (ABI) data.
-
#encode_primitive_type(type, arg) ⇒ String
Encodes primitive types.
-
#encode_type(type, arg) ⇒ String
Encodes a specific value, either static or dynamic.
Class Method Details
.decode(types, data) ⇒ Array
Decodes Application Binary Interface (ABI) data. It accepts multiple arguments and decodes using the head/tail mechanism.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/klay/abi.rb', line 148 def decode(types, data) # accept hex abi but decode it first data = Util.hex_to_bin data if Util.is_hex? data # parse all types parsed_types = types.map { |t| Type.parse(t) } # prepare output data outputs = [nil] * types.size start_positions = [nil] * types.size + [data.size] pos = 0 parsed_types.each_with_index do |t, i| if t.is_dynamic? # record start position for dynamic type start_positions[i] = Util.deserialize_big_endian_to_int(data[pos, 32]) j = i - 1 while j >= 0 and start_positions[j].nil? start_positions[j] = start_positions[i] j -= 1 end pos += 32 else # get data directly for static types outputs[i] = data[pos, t.size] pos += t.size end end # add start position equal the length of the entire data j = types.size - 1 while j >= 0 and start_positions[j].nil? start_positions[j] = start_positions[types.size] j -= 1 end raise DecodingError, "Not enough data for head" unless pos <= data.size # add dynamic types parsed_types.each_with_index do |t, i| if t.is_dynamic? offset, next_offset = start_positions[i, 2] outputs[i] = data[offset...next_offset] end end # return the decoded ABI types and data return parsed_types.zip(outputs).map { |(type, out)| decode_type(type, out) } end |
.decode_primitive_type(type, data) ⇒ String
Decodes primitive types.
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/klay/abi.rb', line 241 def decode_primitive_type(type, data) case type.base_type when "address" # decoded address with 0x-prefix return "0x#{Util.bin_to_hex data[12..-1]}" when "string", "bytes" if type.sub_type.empty? size = Util.deserialize_big_endian_to_int data[0, 32] # decoded dynamic-sized array return data[32..-1][0, size] else # decoded static-sized array return data[0, type.sub_type.to_i] end when "hash" # decoded hash return data[(32 - type.sub_type.to_i), type.sub_type.to_i] when "uint" # decoded unsigned integer return Util.deserialize_big_endian_to_int data when "int" u = Util.deserialize_big_endian_to_int data i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** type.sub_type.to_i) : u # decoded integer return i when "ureal", "ufixed" high, low = type.sub_type.split("x").map(&:to_i) # decoded unsigned fixed point numeric return Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low when "real", "fixed" high, low = type.sub_type.split("x").map(&:to_i) u = Util.deserialize_big_endian_to_int data i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u # decoded fixed point numeric return i * 1.0 / 2 ** low when "bool" # decoded boolean return data[-1] == Constant::BYTE_ONE else raise DecodingError, "Unknown primitive type: #{type.base_type}" end end |
.decode_type(type, arg) ⇒ String
Decodes a specific value, either static or dynamic.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/klay/abi.rb', line 205 def decode_type(type, arg) if %w(string bytes).include?(type.base_type) and type.sub_type.empty? l = Util.deserialize_big_endian_to_int arg[0, 32] data = arg[32..-1] raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Util.ceil32(l) # decoded strings and bytes return data[0, l] elsif type.is_dynamic? l = Util.deserialize_big_endian_to_int arg[0, 32] nested_sub = type.nested_sub # ref https://github.com/ethereum/tests/issues/691 raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic? # decoded dynamic-sized arrays return (0...l).map { |i| decode_type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) } elsif !type.dimensions.empty? l = type.dimensions.last[0] nested_sub = type.nested_sub # decoded static-size arrays return (0...l).map { |i| decode_type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) } else # decoded primitive types return decode_primitive_type type, arg end end |
.encode(types, args) ⇒ String
Encodes Application Binary Interface (ABI) data. It accepts multiple arguments and encodes using the head/tail mechanism.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/klay/abi.rb', line 44 def encode(types, args) # prase all types parsed_types = types.map { |t| Type.parse(t) } # prepare the "head" head_size = (0...args.size) .map { |i| parsed_types[i].size or 32 } .reduce(0, &:+) head, tail = "", "" # encode types and arguments args.each_with_index do |arg, i| if parsed_types[i].is_dynamic? head += encode_type Type.size_type, head_size + tail.size tail += encode_type parsed_types[i], arg else head += encode_type parsed_types[i], arg end end # return the encoded ABI blob return "#{head}#{tail}" end |
.encode_primitive_type(type, arg) ⇒ String
Encodes primitive types.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/klay/abi.rb', line 119 def encode_primitive_type(type, arg) case type.base_type when "uint" return encode_uint arg, type when "bool" return encode_bool arg when "int" return encode_int arg, type when "ureal", "ufixed" return encode_ufixed arg, type when "real", "fixed" return encode_fixed arg, type when "string", "bytes" return encode_bytes arg, type when "hash" return encode_hash arg, type when "address" return encode_address arg else raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}" end end |
.encode_type(type, arg) ⇒ String
Encodes a specific value, either static or dynamic.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/klay/abi.rb', line 75 def encode_type(type, arg) if %w(string bytes).include? type.base_type and type.sub_type.empty? raise EncodingError, "Argument must be a String" unless arg.instance_of? String # encodes strings and bytes size = encode_type Type.size_type, arg.size padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) return "#{size}#{arg}#{padding}" elsif type.is_dynamic? raise EncodingError, "Argument must be an Array" unless arg.instance_of? Array # encodes dynamic-sized arrays head, tail = "", "" head += encode_type Type.size_type, arg.size nested_sub = type.nested_sub nested_sub_size = type.nested_sub.size arg.size.times do |i| # ref https://github.com/ethereum/tests/issues/691 raise NotImplementedError, "Encoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic? head += encode_type nested_sub, arg[i] end return "#{head}#{tail}" else if type.dimensions.empty? # encode a primitive type return encode_primitive_type type, arg else # encode static-size arrays return arg.map { |x| encode_type(type.nested_sub, x) }.join end end end |
Instance Method Details
#decode(types, data) ⇒ Array
Decodes Application Binary Interface (ABI) data. It accepts multiple arguments and decodes using the head/tail mechanism.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/klay/abi.rb', line 148 def decode(types, data) # accept hex abi but decode it first data = Util.hex_to_bin data if Util.is_hex? data # parse all types parsed_types = types.map { |t| Type.parse(t) } # prepare output data outputs = [nil] * types.size start_positions = [nil] * types.size + [data.size] pos = 0 parsed_types.each_with_index do |t, i| if t.is_dynamic? # record start position for dynamic type start_positions[i] = Util.deserialize_big_endian_to_int(data[pos, 32]) j = i - 1 while j >= 0 and start_positions[j].nil? start_positions[j] = start_positions[i] j -= 1 end pos += 32 else # get data directly for static types outputs[i] = data[pos, t.size] pos += t.size end end # add start position equal the length of the entire data j = types.size - 1 while j >= 0 and start_positions[j].nil? start_positions[j] = start_positions[types.size] j -= 1 end raise DecodingError, "Not enough data for head" unless pos <= data.size # add dynamic types parsed_types.each_with_index do |t, i| if t.is_dynamic? offset, next_offset = start_positions[i, 2] outputs[i] = data[offset...next_offset] end end # return the decoded ABI types and data return parsed_types.zip(outputs).map { |(type, out)| decode_type(type, out) } end |
#decode_primitive_type(type, data) ⇒ String
Decodes primitive types.
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/klay/abi.rb', line 241 def decode_primitive_type(type, data) case type.base_type when "address" # decoded address with 0x-prefix return "0x#{Util.bin_to_hex data[12..-1]}" when "string", "bytes" if type.sub_type.empty? size = Util.deserialize_big_endian_to_int data[0, 32] # decoded dynamic-sized array return data[32..-1][0, size] else # decoded static-sized array return data[0, type.sub_type.to_i] end when "hash" # decoded hash return data[(32 - type.sub_type.to_i), type.sub_type.to_i] when "uint" # decoded unsigned integer return Util.deserialize_big_endian_to_int data when "int" u = Util.deserialize_big_endian_to_int data i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** type.sub_type.to_i) : u # decoded integer return i when "ureal", "ufixed" high, low = type.sub_type.split("x").map(&:to_i) # decoded unsigned fixed point numeric return Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low when "real", "fixed" high, low = type.sub_type.split("x").map(&:to_i) u = Util.deserialize_big_endian_to_int data i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u # decoded fixed point numeric return i * 1.0 / 2 ** low when "bool" # decoded boolean return data[-1] == Constant::BYTE_ONE else raise DecodingError, "Unknown primitive type: #{type.base_type}" end end |
#decode_type(type, arg) ⇒ String
Decodes a specific value, either static or dynamic.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/klay/abi.rb', line 205 def decode_type(type, arg) if %w(string bytes).include?(type.base_type) and type.sub_type.empty? l = Util.deserialize_big_endian_to_int arg[0, 32] data = arg[32..-1] raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Util.ceil32(l) # decoded strings and bytes return data[0, l] elsif type.is_dynamic? l = Util.deserialize_big_endian_to_int arg[0, 32] nested_sub = type.nested_sub # ref https://github.com/ethereum/tests/issues/691 raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic? # decoded dynamic-sized arrays return (0...l).map { |i| decode_type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) } elsif !type.dimensions.empty? l = type.dimensions.last[0] nested_sub = type.nested_sub # decoded static-size arrays return (0...l).map { |i| decode_type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) } else # decoded primitive types return decode_primitive_type type, arg end end |
#encode(types, args) ⇒ String
Encodes Application Binary Interface (ABI) data. It accepts multiple arguments and encodes using the head/tail mechanism.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/klay/abi.rb', line 44 def encode(types, args) # prase all types parsed_types = types.map { |t| Type.parse(t) } # prepare the "head" head_size = (0...args.size) .map { |i| parsed_types[i].size or 32 } .reduce(0, &:+) head, tail = "", "" # encode types and arguments args.each_with_index do |arg, i| if parsed_types[i].is_dynamic? head += encode_type Type.size_type, head_size + tail.size tail += encode_type parsed_types[i], arg else head += encode_type parsed_types[i], arg end end # return the encoded ABI blob return "#{head}#{tail}" end |
#encode_primitive_type(type, arg) ⇒ String
Encodes primitive types.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/klay/abi.rb', line 119 def encode_primitive_type(type, arg) case type.base_type when "uint" return encode_uint arg, type when "bool" return encode_bool arg when "int" return encode_int arg, type when "ureal", "ufixed" return encode_ufixed arg, type when "real", "fixed" return encode_fixed arg, type when "string", "bytes" return encode_bytes arg, type when "hash" return encode_hash arg, type when "address" return encode_address arg else raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}" end end |
#encode_type(type, arg) ⇒ String
Encodes a specific value, either static or dynamic.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/klay/abi.rb', line 75 def encode_type(type, arg) if %w(string bytes).include? type.base_type and type.sub_type.empty? raise EncodingError, "Argument must be a String" unless arg.instance_of? String # encodes strings and bytes size = encode_type Type.size_type, arg.size padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size) return "#{size}#{arg}#{padding}" elsif type.is_dynamic? raise EncodingError, "Argument must be an Array" unless arg.instance_of? Array # encodes dynamic-sized arrays head, tail = "", "" head += encode_type Type.size_type, arg.size nested_sub = type.nested_sub nested_sub_size = type.nested_sub.size arg.size.times do |i| # ref https://github.com/ethereum/tests/issues/691 raise NotImplementedError, "Encoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.is_dynamic? head += encode_type nested_sub, arg[i] end return "#{head}#{tail}" else if type.dimensions.empty? # encode a primitive type return encode_primitive_type type, arg else # encode static-size arrays return arg.map { |x| encode_type(type.nested_sub, x) }.join end end end |