Module: BlockGiven::Abi::Coder
- Defined in:
- lib/block_given/abi/coder.rb
Overview
Turns Ruby values into what the ABI encoder expects, and decoded values into idiomatic Ruby (checksummed addresses, hex bytes, named tuples as Hash).
Class Method Summary collapse
-
.coerce(value, param) ⇒ Object
Ruby -> encoder input.
- .coerce_address(value, param) ⇒ Object
- .coerce_bool(value, param) ⇒ Object
- .coerce_bytes(value, param) ⇒ Object
- .coerce_integer(value, param) ⇒ Object
- .coerce_tuple(value, param) ⇒ Object
- .decode(params, hex) ⇒ Object
- .encode(params, values) ⇒ Object
-
.format(value, param) ⇒ Object
Decoded value -> Ruby.
- .format_tuple(values, param) ⇒ Object
- .tuple_values_from_hash(hash, param) ⇒ Object
Class Method Details
.coerce(value, param) ⇒ Object
Ruby -> encoder input.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/block_given/abi/coder.rb', line 35 def coerce(value, param) if param.array? raise InvalidArgumentError, "#{param.name} expects an Array, got #{value.inspect}" unless value.is_a?(Array) return value.map { |v| coerce(v, param.element) } end return coerce_tuple(value, param) if param.tuple? case param.raw_type when /\A(u?int)\d*\z/ then coerce_integer(value, param) when "address" then coerce_address(value, param) when "bool" then coerce_bool(value, param) when "string" then value.to_s when /\Abytes\d*\z/ then coerce_bytes(value, param) else value end end |
.coerce_address(value, param) ⇒ Object
83 84 85 86 87 88 |
# File 'lib/block_given/abi/coder.rb', line 83 def coerce_address(value, param) value = value.address if value.respond_to?(:address) && !value.is_a?(String) raise InvalidAddressError, "#{param.name}: invalid address #{value.inspect}" unless Utils.address?(value) value end |
.coerce_bool(value, param) ⇒ Object
90 91 92 93 94 |
# File 'lib/block_given/abi/coder.rb', line 90 def coerce_bool(value, param) return value if [true, false].include?(value) raise InvalidArgumentError, "#{param.name} expects true/false, got #{value.inspect}" end |
.coerce_bytes(value, param) ⇒ Object
96 97 98 99 100 |
# File 'lib/block_given/abi/coder.rb', line 96 def coerce_bytes(value, param) raise InvalidArgumentError, "#{param.name} expects a hex or binary String" unless value.is_a?(String) Utils.hex?(value) ? value : Utils.bin_to_hex(value) end |
.coerce_integer(value, param) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/block_given/abi/coder.rb', line 66 def coerce_integer(value, param) case value when Integer then value when Float, BigDecimal, Rational unless value.finite? && value == value.floor raise InvalidArgumentError, "#{param.name}: #{value} is not an integer (use BlockGiven::Utils.parse_units)" end value.to_i when String Utils.hex?(value) ? Utils.hex_to_int(value) : Integer(value, 10) else raise InvalidArgumentError, "#{param.name} expects an integer, got #{value.inspect}" end rescue ::ArgumentError raise InvalidArgumentError, "#{param.name} expects an integer, got #{value.inspect}" end |
.coerce_tuple(value, param) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/block_given/abi/coder.rb', line 102 def coerce_tuple(value, param) values = case value when Hash then tuple_values_from_hash(value, param) when Array then value else raise InvalidArgumentError, "#{param.name} expects a Hash or Array for tuple #{param.type}" end if values.size != param.components.size raise InvalidArgumentError, "#{param.name}: tuple #{param.type} expects #{param.components.size} values" end param.components.zip(values).map { |component, v| coerce(v, component) } end |
.decode(params, hex) ⇒ Object
22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/block_given/abi/coder.rb', line 22 def decode(params, hex) return [] if params.empty? data = Utils.strip_hex(hex.to_s) raise AbiError, "cannot decode empty data (does the contract exist at this address?)" if data.empty? values = Eth::Abi.decode(params.map(&:type), "0x#{data}") params.zip(values).map { |param, value| format(value, param) } rescue Eth::Abi::DecodingError => e raise AbiError, "ABI decoding failed: #{e.message}" end |
.encode(params, values) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 |
# File 'lib/block_given/abi/coder.rb', line 10 def encode(params, values) if params.size != values.size raise InvalidArgumentError, "expected #{params.size} argument(s), got #{values.size}" end coerced = params.zip(values).map { |param, value| coerce(value, param) } Utils.bin_to_hex(Eth::Abi.encode(params.map(&:type), coerced)) rescue Eth::Abi::EncodingError, Eth::Abi::ValueOutOfBounds => e raise AbiError, "ABI encoding failed: #{e.message}" end |
.format(value, param) ⇒ Object
Decoded value -> Ruby.
54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/block_given/abi/coder.rb', line 54 def format(value, param) return value.map { |v| format(v, param.element) } if param.array? return format_tuple(value, param) if param.tuple? case param.raw_type when "address" then Utils.checksum_address(value) when /\Abytes\d*\z/ then value.is_a?(String) && !Utils.hex?(value) ? Utils.bin_to_hex(value) : value when "string" then value.to_s.dup.force_encoding(Encoding::UTF_8) else value end end |
.format_tuple(values, param) ⇒ Object
126 127 128 129 130 131 |
# File 'lib/block_given/abi/coder.rb', line 126 def format_tuple(values, param) formatted = param.components.zip(values).map { |component, v| format(v, component) } return formatted if param.components.any?(&:unnamed?) param.components.map(&:ruby_name).zip(formatted).to_h end |
.tuple_values_from_hash(hash, param) ⇒ Object
116 117 118 119 120 121 122 123 124 |
# File 'lib/block_given/abi/coder.rb', line 116 def tuple_values_from_hash(hash, param) lookup = hash.transform_keys { |k| Utils.snake_case(k) } param.components.map do |component| key = Utils.snake_case(component.name) raise InvalidArgumentError, "#{param.name}: missing tuple field #{component.name}" unless lookup.key?(key) lookup[key] end end |