ethlite 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a168744cd8aa2f76a80e7536064f8d831f3cbcef3e9148687c7f003403415d97
4
- data.tar.gz: 711fa922a92492be4ef33317a552de466a3f4b9e32428d80e6a79d55fc1b3f6d
3
+ metadata.gz: 783bf5485da84a9532d36a9285b08b7ebc6e8266b4c431ce623d4720ffefbaf4
4
+ data.tar.gz: 71e360e28ee30b99ee44accb575c56a6c6b08eb349ec257576dfa4cbb79982f8
5
5
  SHA512:
6
- metadata.gz: 9465a3704789b0c377e7dd7db33f7675bb2230b011f7792c9758bf4b72723b55516e18e594b897d015e4b5326f80359e0c7a5eecaaff1f957c5d9f354a2b827d
7
- data.tar.gz: 0fb31c94e3810430d01573dbb951787e34a8824a2d0ba2bc4305ea306c5258260bd189ab8a84b817c1260a6b1d5b978bc99b48a543390eec586c59a56726c583
6
+ metadata.gz: 77e14ee4da005f731958fc5c889d71c59aff89c9a565dec0ea58c0bd56562ba4d8cdf696182a58051a0201b95254ce28a2e4281459976dc0e8c650beeb53b0d7
7
+ data.tar.gz: b3d9aadf29db07b1afdd53c780a59947d94d0667b070ba0edf45097c3c65f76a492289a2b9f21a20f8a0dc62ea3a0b630d686cdd15f795e18355f32956e78ad9
data/Manifest.txt CHANGED
@@ -3,9 +3,6 @@ Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
5
  lib/ethlite.rb
6
- lib/ethlite/abi/codec.rb
7
- lib/ethlite/abi/type.rb
8
- lib/ethlite/constant.rb
9
6
  lib/ethlite/contract.rb
10
7
  lib/ethlite/utils.rb
11
8
  lib/ethlite/version.rb
data/README.md CHANGED
@@ -64,7 +64,7 @@ def eth_call( rpc,
64
64
  ## binary encode method sig(nature)
65
65
  signature = "#{name}(#{inputs.join(',')})"
66
66
  signature_hash = Ethlite::Utils.encode_hex(
67
- Ethlite::Utils.keccak256(signature))[0..7]
67
+ Ethlite::Utils.keccak256(signature)[0,4])
68
68
 
69
69
  pp signature
70
70
  # => "tokenURI(uint256)"
@@ -73,7 +73,7 @@ def eth_call( rpc,
73
73
 
74
74
  ## binary encode method arg(ument)s
75
75
  args_encoded = Ethlite::Utils.encode_hex(
76
- Ethlite::Abi.encode_abi( inputs, args) )
76
+ ABI.encode( inputs, args) )
77
77
 
78
78
  data = '0x' + signature_hash + args_encoded
79
79
 
@@ -90,10 +90,9 @@ def eth_call( rpc,
90
90
  puts "response:"
91
91
  pp response
92
92
 
93
- ## decode binary result
94
- string_data = Ethlite::Utils.decode_hex(
95
- Ethlite::Utils.remove_0x_head(response))
96
- result = Ethlite::Abi.decode_abi( outputs, string_data )
93
+ ## decode binary result (returned as a hex string starting with 0x)
94
+ bin = Ethlite::Utils.decode_hex( response )
95
+ result = ABI.decode( outputs, bin )
97
96
  result.length == 1 ? result[0] : result
98
97
  end
99
98
  ```
data/Rakefile CHANGED
@@ -20,8 +20,8 @@ Hoe.spec 'ethlite' do
20
20
 
21
21
  self.extra_deps = [
22
22
  ['cocos'],
23
- ['rlp-lite'],
24
23
  ['digest-lite'],
24
+ ['abicoder'],
25
25
  ]
26
26
 
27
27
  self.licenses = ['Public Domain']
@@ -1,31 +1,6 @@
1
1
  module Ethlite
2
2
  class ContractMethod
3
3
 
4
- def self.parse_abi( abi )
5
- ## convenience helper - auto-convert to json if string passed in
6
- abi = JSON.parse( abi ) if abi.is_a?( String )
7
-
8
- name = abi['name']
9
- constant = !!abi['constant'] || abi['stateMutability']=='view'
10
- input_types = abi['inputs'] ? abi['inputs'].map{|a| _parse_component_type( a ) } : []
11
- output_types = abi['outputs'] ? abi['outputs'].map{|a| _parse_component_type( a ) } : []
12
-
13
- new( name, inputs: input_types,
14
- outputs: output_types,
15
- constant: constant )
16
- end
17
-
18
- def self._parse_component_type( argument )
19
- if argument['type'] =~ /^tuple((\[[0-9]*\])*)/
20
- argument['components'] ? "(#{argument['components'].collect{ |c| _parse_component_type( c ) }.join(',')})#{$1}"
21
- : "()#{$1}"
22
- else
23
- argument['type']
24
- end
25
- end
26
-
27
-
28
-
29
4
 
30
5
  attr_reader :signature,
31
6
  :name,
@@ -34,21 +9,25 @@
34
9
  :output_types,
35
10
  :constant
36
11
 
37
- def initialize( name, inputs:,
12
+ def initialize( name, inputs: [],
38
13
  outputs: [],
39
14
  constant: true )
40
15
  @name = name
41
16
  @constant = constant
42
- @input_types = inputs
43
- @output_types = outputs
44
- @signature = "#{@name}(#{@input_types.join(',')})"
45
- @signature_hash = Utils.signature_hash( @signature, 8 )
17
+
18
+ ## parse inputs & outputs into types
19
+ @input_types = inputs.map { |str| ABI::Type.parse( str ) }
20
+ @output_types = outputs.map { |str| ABI::Type.parse( str ) }
21
+
22
+ types = @input_types.map {|type| type.format }.join(',')
23
+ @signature = "#{@name}(#{types})"
24
+ @signature_hash = Utils.encode_hex( Utils.keccak256( @signature)[0,4] )
46
25
  end
47
26
 
48
27
 
49
28
  def do_call( rpc, contract_address, args )
50
29
  data = '0x' + @signature_hash + Utils.encode_hex(
51
- Abi.encode_abi(@input_types, args) )
30
+ ABI.encode(@input_types, args) )
52
31
 
53
32
  method = 'eth_call'
54
33
  params = [{ to: contract_address,
@@ -56,23 +35,30 @@
56
35
  'latest']
57
36
  response = rpc.request( method, params )
58
37
 
38
+ if debug?
39
+ puts "response:"
40
+ pp response
41
+ end
59
42
 
60
- puts "response:"
61
- pp response
43
+ bin = Utils.decode_hex( response )
44
+ return nil if bin.empty?
62
45
 
63
- string_data = Utils.decode_hex(
64
- Utils.remove_0x_head(response))
65
- return nil if string_data.empty?
46
+ result = ABI.decode( @output_types, bin )
66
47
 
67
- result = Abi.decode_abi( @output_types, string_data )
68
- puts
69
- puts "result decode_abi:"
70
- pp result
71
48
 
49
+ if debug?
50
+ puts
51
+ puts "result decode_abi:"
52
+ pp result
53
+ end
72
54
 
73
55
  result.length==1 ? result.first : result
74
56
  end
75
57
 
58
+ ####
59
+ # private helpers
60
+ def debug?() Ethlite.debug?; end ## forward to global Ethlite config
76
61
  end # class ContractMethod
62
+
77
63
  end # module Ethlite
78
64
 
data/lib/ethlite/utils.rb CHANGED
@@ -1,225 +1,36 @@
1
1
  module Ethlite
2
2
 
3
3
 
4
- module UtilHelper
5
- ##
6
- # Not the keccak in sha3, although it's underlying lib named SHA3
7
- #
8
- def keccak256(x)
4
+ module Helpers
5
+ def keccak256( bin )
9
6
  # Digest::SHA3.new(256).digest(x)
10
7
  ## Digest::Keccak.digest(x, 256)
11
- Digest::KeccakLite.new(256).digest( x )
8
+ Digest::KeccakLite.new(256).digest( bin )
12
9
  end
13
10
 
14
- ## todo/check where required / in use - what for?
15
- def keccak512(x)
16
- # Digest::SHA3.new(512).digest(x)
17
- # Digest::Keccak.digest(x, 512)
18
- Digest::KeccakLite.new(512).digest( x )
11
+ def encode_hex( bin ) ## bin_to_hex
12
+ raise TypeError, "Value must be a string" unless bin.is_a?( String )
13
+ ## note: always return a hex string with default encoding e.g. utf-8 - why? why not?
14
+ bin.unpack("H*").first.force_encoding( Encoding::UTF_8 )
19
15
  end
16
+ alias_method :bin_to_hex, :encode_hex
20
17
 
18
+ def decode_hex( hex ) ## hex_to_bin
19
+ raise TypeError, "Value must be a string" unless hex.is_a?( String )
20
+ raise TypeError, 'Non-hexadecimal char found' unless hex.empty? || hex =~ /\A(0x)?[0-9a-fA-F]{2,}\z/
21
21
 
22
- def keccak256_rlp(x)
23
- keccak256 RLP.encode(x)
24
- end
25
-
26
- def sha256(x)
27
- Digest::SHA256.digest x
28
- end
29
-
30
- def double_sha256(x)
31
- sha256 sha256(x)
32
- end
33
-
34
- def ripemd160(x)
35
- Digest::RMD160.digest x
36
- end
37
-
38
- def hash160(x)
39
- ripemd160 sha256(x)
40
- end
41
-
42
- def hash160_hex(x)
43
- encode_hex hash160(x)
44
- end
45
-
46
- def mod_exp(x, y, n)
47
- x.to_bn.mod_exp(y, n).to_i
48
- end
49
-
50
- def mod_mul(x, y, n)
51
- x.to_bn.mod_mul(y, n).to_i
52
- end
53
-
54
- def to_signed(i)
55
- i > INT_MAX ? (i-TT256) : i
56
- end
57
-
58
-
59
- def ceil32(x)
60
- x % 32 == 0 ? x : (x + 32 - x%32)
61
- end
62
-
63
- def encode_hex(b)
64
- raise TypeError, "Value must be an instance of String" unless b.instance_of?(String)
65
- b.unpack("H*").first
66
- end
67
-
68
- def decode_hex(str)
69
- raise TypeError, "Value must be an instance of string" unless str.instance_of?(String)
70
- raise TypeError, 'Non-hexadecimal digit found' unless str =~ /\A[0-9a-fA-F]*\z/
71
- [str].pack("H*")
72
- end
73
-
74
-
75
-
76
-
77
- def int_to_big_endian(n)
78
- RLP::Sedes.big_endian_int.serialize n
79
- end
80
-
81
- def big_endian_to_int(s)
82
- RLP::Sedes.big_endian_int.deserialize s.sub(/\A(\x00)+/, '')
83
- end
84
-
85
-
86
- def encode_int(n)
87
- raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
88
- int_to_big_endian n
89
- end
90
-
91
- def decode_int(v)
92
- raise ArgumentError, "No leading zero bytes allowed for integers" if v.size > 0 && (v[0] == BYTE_ZERO || v[0] == 0)
93
- big_endian_to_int v
94
- end
95
-
96
-
97
-
98
-
99
- def lpad(x, symbol, l)
100
- return x if x.size >= l
101
- symbol * (l - x.size) + x
102
- end
103
-
104
- def rpad(x, symbol, l)
105
- return x if x.size >= l
106
- x + symbol * (l - x.size)
107
- end
108
-
109
- def zpad(x, l)
110
- lpad x, BYTE_ZERO, l
111
- end
112
-
113
- def zunpad(x)
114
- x.sub /\A\x00+/, ''
115
- end
116
-
117
- def zpad_int(n, l=32)
118
- zpad encode_int(n), l
119
- end
120
-
121
- def zpad_hex(s, l=32)
122
- zpad decode_hex(s), l
123
- end
124
-
125
- def int_to_addr(x)
126
- zpad_int x, 20
127
- end
128
-
129
-
130
- def bytearray_to_int(arr)
131
- o = 0
132
- arr.each {|x| o = (o << 8) + x }
133
- o
134
- end
135
-
136
- def int_array_to_bytes(arr)
137
- arr.pack('C*')
138
- end
139
-
140
- def bytes_to_int_array(bytes)
141
- bytes.unpack('C*')
142
- end
22
+ ## allow optional starting 0x - why? why not?
23
+ hex = hex[2..-1] if hex[0,2] == '0x'
143
24
 
144
-
145
- def coerce_to_int(x)
146
- if x.is_a?(Numeric)
147
- x
148
- elsif x.size == 40
149
- big_endian_to_int decode_hex(x)
150
- else
151
- big_endian_to_int x
152
- end
153
- end
154
-
155
- def coerce_to_bytes(x)
156
- if x.is_a?(Numeric)
157
- int_to_big_endian x
158
- elsif x.size == 40
159
- decode_hex(x)
160
- else
161
- x
162
- end
163
- end
164
-
165
- def coerce_addr_to_hex(x)
166
- if x.is_a?(Numeric)
167
- encode_hex zpad(int_to_big_endian(x), 20)
168
- elsif x.size == 40 || x.size == 0
169
- x
170
- else
171
- encode_hex zpad(x, 20)[-20..-1]
172
- end
173
- end
174
-
175
-
176
-
177
- def parse_int_or_hex(s)
178
- if s.is_a?(Numeric)
179
- s
180
- elsif s[0,2] == '0x'
181
- big_endian_to_int decode_hex(normalize_hex_without_prefix(s))
182
- else
183
- s.to_i
184
- end
185
- end
186
-
187
-
188
- =begin
189
- ## add? moved over from the old module Utility
190
- def hex( num )
191
- '0x' + num.to_s(16)
192
- end
193
-
194
- def from_hex( h )
195
- h.nil? ? 0 : (h.kind_of?(String) ? h.to_i(16) : h)
196
- end
197
- =end
198
-
199
-
200
- def remove_0x_head( s )
201
- return s if !s || s.length<2
202
- s[0,2] == '0x' ? s[2..-1] : s
25
+ [hex].pack("H*")
203
26
  end
204
-
205
- def normalize_hex_without_prefix(s)
206
- if s[0,2] == '0x'
207
- (s.size % 2 == 1 ? '0' : '') + s[2..-1]
208
- else
209
- s
210
- end
211
- end
212
-
213
- def signature_hash( signature, length=8 )
214
- encode_hex( keccak256(signature) )[0...length]
215
- end
216
-
217
- end # module UtilHelper
27
+ alias_method :hex_to_bin, :decode_hex
28
+ end # module Helpers
218
29
 
219
30
 
220
31
 
221
32
  module Utils
222
- extend UtilHelper
33
+ extend Helpers
223
34
  end
224
35
 
225
36
  end # module Ethlite
@@ -1,9 +1,9 @@
1
1
 
2
2
 
3
3
  module Ethlite
4
- MAJOR = 0
5
- MINOR = 4
6
- PATCH = 1
4
+ MAJOR = 1
5
+ MINOR = 0
6
+ PATCH = 0
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
data/lib/ethlite.rb CHANGED
@@ -19,18 +19,14 @@ end
19
19
  load_env
20
20
 
21
21
 
22
- require 'uri'
23
- require 'net/http'
24
- require 'net/https'
25
- require 'json'
26
-
27
22
 
28
23
  require 'openssl'
29
24
  require 'digest'
30
25
 
26
+
31
27
  ## 3rd party gems
32
- require 'rlp-lite'
33
28
  require 'digest-lite'
29
+ require 'abicoder'
34
30
 
35
31
 
36
32
  require_relative 'jsonrpc/jsonrpc'
@@ -38,15 +34,7 @@ require_relative 'jsonrpc/jsonrpc'
38
34
 
39
35
  ## our own code
40
36
  require_relative 'ethlite/version' # note: let version always go first
41
-
42
- require_relative 'ethlite/constant'
43
37
  require_relative 'ethlite/utils'
44
-
45
-
46
- require_relative 'ethlite/abi/type'
47
- require_relative 'ethlite/abi/codec'
48
-
49
-
50
38
  require_relative 'ethlite/contract'
51
39
 
52
40
 
@@ -61,6 +49,10 @@ class Configuration
61
49
  value
62
50
  end
63
51
  end
52
+
53
+ ## add "global" debug switch - why? why not?
54
+ def debug?() (@debug || false) == true; end
55
+ def debug=(value) @debug = value; end
64
56
  end # class Configuration
65
57
 
66
58
 
@@ -70,6 +62,9 @@ end # class Configuration
70
62
  ## end
71
63
  def self.configure() yield( config ); end
72
64
  def self.config() @config ||= Configuration.new; end
65
+
66
+ ## add debug convenience config shortcut - forwarding to config.debug? - why? why not?
67
+ def self.debug?() config.debug?; end
73
68
  end # module Ethlite
74
69
 
75
70
 
@@ -8,6 +8,16 @@
8
8
 
9
9
 
10
10
  class JsonRpc
11
+
12
+ ### add a global debug switch - why? why not?
13
+ def self.debug?() (@debug || false) == true; end
14
+ def self.debug( value ) @debug = value; end
15
+ ####
16
+ # private helpers
17
+ def debug?() self.class.debug?; end
18
+
19
+
20
+
11
21
  def initialize( uri )
12
22
  @uri = uri ## assume uri always as string for now
13
23
  @request_id = 1
@@ -23,8 +33,10 @@ class JsonRpc
23
33
 
24
34
  @request_id += 1
25
35
 
26
- puts "json_rpc POST payload:"
27
- puts data.to_json
36
+ if debug?
37
+ puts "json_rpc POST payload:"
38
+ puts data.to_json
39
+ end
28
40
 
29
41
  response = Webclient.post( @uri, json: data )
30
42
 
@@ -33,8 +45,10 @@ class JsonRpc
33
45
  raise "Error code #{response.status.code} on request #{@uri} #{data}"
34
46
  end
35
47
 
36
- puts "json_rpc response.body:"
37
- puts response.body
48
+ if debug?
49
+ puts "json_rpc response.body:"
50
+ puts response.body
51
+ end
38
52
 
39
53
 
40
54
  body = JSON.parse( response.body, max_nesting: 1500 )
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethlite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-12 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocos
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rlp-lite
28
+ name: digest-lite
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: digest-lite
42
+ name: abicoder
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -101,9 +101,6 @@ files:
101
101
  - README.md
102
102
  - Rakefile
103
103
  - lib/ethlite.rb
104
- - lib/ethlite/abi/codec.rb
105
- - lib/ethlite/abi/type.rb
106
- - lib/ethlite/constant.rb
107
104
  - lib/ethlite/contract.rb
108
105
  - lib/ethlite/utils.rb
109
106
  - lib/ethlite/version.rb
@@ -1,436 +0,0 @@
1
-
2
-
3
- module Ethlite
4
- module Abi
5
-
6
- ##
7
- # Contract ABI encoding and decoding.
8
- #
9
- # @see https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
10
- #
11
- class Codec
12
- class EncodingError < StandardError; end
13
- class DecodingError < StandardError; end
14
- class ValueError < StandardError; end
15
-
16
- class ValueOutOfBounds < ValueError; end
17
-
18
- ##
19
- # Encodes multiple arguments using the head/tail mechanism.
20
- #
21
- def encode_abi(types, args)
22
- parsed_types = types.map {|t| Type.parse(t) }
23
-
24
- head_size = (0...args.size)
25
- .map {|i| parsed_types[i].size || 32 }
26
- .reduce(0, &:+)
27
-
28
- head, tail = '', ''
29
- args.each_with_index do |arg, i|
30
- if parsed_types[i].dynamic?
31
- head += encode_type(Type.size_type, head_size + tail.size)
32
- tail += encode_type(parsed_types[i], arg)
33
- else
34
- head += encode_type(parsed_types[i], arg)
35
- end
36
- end
37
-
38
- "#{head}#{tail}"
39
- end
40
-
41
- ##
42
- # Encodes a single value (static or dynamic).
43
- #
44
- # @param type [Ethereum::ABI::Type] value type
45
- # @param arg [Object] value
46
- #
47
- # @return [String] encoded bytes
48
- #
49
- def encode_type(type, arg)
50
- if %w(string bytes).include?(type.base) && type.sub.empty?
51
- encode_primitive_type type, arg
52
- elsif type.dynamic?
53
- raise ArgumentError, "arg must be an array" unless arg.instance_of?(Array)
54
-
55
- head, tail = '', ''
56
- if type.dims.last == 0
57
- head += encode_type(Type.size_type, arg.size)
58
- else
59
- raise ArgumentError, "Wrong array size: found #{arg.size}, expecting #{type.dims.last}" unless arg.size == type.dims.last
60
- end
61
-
62
- sub_type = type.subtype
63
- sub_size = type.subtype.size
64
- arg.size.times do |i|
65
- if sub_size.nil?
66
- head += encode_type(Type.size_type, 32*arg.size + tail.size)
67
- tail += encode_type(sub_type, arg[i])
68
- else
69
- head += encode_type(sub_type, arg[i])
70
- end
71
- end
72
-
73
- "#{head}#{tail}"
74
- else # static type
75
- if type.dims.empty?
76
- encode_primitive_type type, arg
77
- else
78
- arg.map {|x| encode_type(type.subtype, x) }.join
79
- end
80
- end
81
- end
82
-
83
- def encode_primitive_type(type, arg)
84
- case type.base
85
- when 'uint'
86
- begin
87
- real_size = type.sub.to_i
88
- i = get_uint arg
89
-
90
- raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size
91
- Utils.zpad_int i
92
- rescue EncodingError
93
- raise ValueOutOfBounds, arg
94
- end
95
- when 'bool'
96
- raise ArgumentError, "arg is not bool: #{arg}" unless arg.instance_of?(TrueClass) || arg.instance_of?(FalseClass)
97
- Utils.zpad_int(arg ? 1 : 0)
98
- when 'int'
99
- begin
100
- real_size = type.sub.to_i
101
- i = get_int arg
102
-
103
- raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1)
104
- Utils.zpad_int(i % 2**type.sub.to_i)
105
- rescue EncodingError
106
- raise ValueOutOfBounds, arg
107
- end
108
- when 'ufixed'
109
- high, low = type.sub.split('x').map(&:to_i)
110
-
111
- raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**high
112
- Utils.zpad_int((arg * 2**low).to_i)
113
- when 'fixed'
114
- high, low = type.sub.split('x').map(&:to_i)
115
-
116
- raise ValueOutOfBounds, arg unless arg >= -2**(high - 1) && arg < 2**(high - 1)
117
-
118
- i = (arg * 2**low).to_i
119
- Utils.zpad_int(i % 2**(high+low))
120
- when 'string'
121
- if arg.encoding.name == 'UTF-8'
122
- arg = arg.b
123
- else
124
- begin
125
- arg.unpack('U*')
126
- rescue ArgumentError
127
- raise ValueError, "string must be UTF-8 encoded"
128
- end
129
- end
130
-
131
- if type.sub.empty? # variable length type
132
- raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
133
- size = Utils.zpad_int arg.size
134
- value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
135
- "#{size}#{value}"
136
- else # fixed length type
137
- sub = type.sub.to_i
138
- raise ValueOutOfBounds, "invalid string length #{sub}" if arg.size > sub
139
- raise ValueOutOfBounds, "invalid string length #{sub}" if sub < 0 || sub > 32
140
- Utils.rpad(arg, BYTE_ZERO, 32)
141
- end
142
- when 'bytes'
143
- raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String)
144
- arg = arg.b
145
-
146
- if type.sub.empty? # variable length type
147
- raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
148
- size = Utils.zpad_int arg.size
149
- value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
150
- "#{size}#{value}"
151
- else # fixed length type
152
- sub = type.sub.to_i
153
- raise ValueOutOfBounds, "invalid bytes length #{sub}" if arg.size > sub
154
- raise ValueOutOfBounds, "invalid bytes length #{sub}" if sub < 0 || sub > 32
155
- Utils.rpad(arg, BYTE_ZERO, 32)
156
- end
157
- when 'hash'
158
- size = type.sub.to_i
159
- raise EncodingError, "too long: #{arg}" unless size > 0 && size <= 32
160
-
161
- if arg.is_a?(Integer)
162
- Utils.zpad_int(arg)
163
- elsif arg.size == size
164
- Utils.zpad arg, 32
165
- elsif arg.size == size * 2
166
- Utils.zpad_hex arg
167
- else
168
- raise EncodingError, "Could not parse hash: #{arg}"
169
- end
170
- when 'address'
171
- if arg.is_a?(Integer)
172
- Utils.zpad_int arg
173
- elsif arg.size == 20
174
- Utils.zpad arg, 32
175
- elsif arg.size == 40
176
- Utils.zpad_hex arg
177
- elsif arg.size == 42 && arg[0,2] == '0x'
178
- Utils.zpad_hex arg[2..-1]
179
- else
180
- raise EncodingError, "Could not parse address: #{arg}"
181
- end
182
- else
183
- raise EncodingError, "Unhandled type: #{type.base} #{type.sub}"
184
- end
185
- end
186
-
187
-
188
- def min_data_size types
189
- types.size*32
190
- end
191
-
192
- ##
193
- # Decodes multiple arguments using the head/tail mechanism.
194
- #
195
- def decode_abi types, data, raise_errors = false
196
- parsed_types = types.map {|t| Type.parse(t) }
197
-
198
- outputs = [nil] * types.size
199
- start_positions = [nil] * types.size + [data.size]
200
-
201
- # TODO: refactor, a reverse iteration will be better
202
- pos = 0
203
- parsed_types.each_with_index do |t, i|
204
- # If a type is static, grab the data directly, otherwise record its
205
- # start position
206
- if t.dynamic?
207
-
208
- if raise_errors && pos>data.size-1
209
- raise DecodingError, "Position out of bounds #{pos}>#{data.size-1}"
210
- end
211
-
212
- start_positions[i] = Utils.big_endian_to_int(data[pos, 32])
213
-
214
- if raise_errors && start_positions[i]>data.size-1
215
- raise DecodingError, "Start position out of bounds #{start_positions[i]}>#{data.size-1}"
216
- end
217
-
218
- j = i - 1
219
- while j >= 0 && start_positions[j].nil?
220
- start_positions[j] = start_positions[i]
221
- j -= 1
222
- end
223
-
224
- pos += 32
225
- else
226
- outputs[i] = zero_padding data, pos, t.size, start_positions
227
- pos += t.size
228
- end
229
- end
230
-
231
- # We add a start position equal to the length of the entire data for
232
- # convenience.
233
- j = types.size - 1
234
- while j >= 0 && start_positions[j].nil?
235
- start_positions[j] = start_positions[types.size]
236
- j -= 1
237
- end
238
-
239
- if raise_errors && pos > data.size
240
- raise DecodingError, "Not enough data for head"
241
- end
242
-
243
-
244
- parsed_types.each_with_index do |t, i|
245
- if t.dynamic?
246
- offset, next_offset = start_positions[i, 2]
247
- if offset<=data.size && next_offset<=data.size
248
- outputs[i] = data[offset...next_offset]
249
- end
250
- end
251
- end
252
-
253
- if raise_errors && outputs.include?(nil)
254
- raise DecodingError, "Not all data can be parsed"
255
- end
256
-
257
- parsed_types.zip(outputs).map {|(type, out)| decode_type(type, out) }
258
- end
259
-
260
-
261
- def zero_padding data, pos, count, start_positions
262
- if pos >= data.size
263
- start_positions[start_positions.size-1] += count
264
- "\x00"*count
265
- elsif pos + count > data.size
266
- start_positions[start_positions.size-1] += ( count - (data.size-pos))
267
- data[pos,data.size-pos] + "\x00"*( count - (data.size-pos))
268
- else
269
- data[pos, count]
270
- end
271
- end
272
-
273
- def decode_typed_data type_name, data
274
- decode_primitive_type Type.parse(type_name), data
275
- end
276
-
277
- def decode_type(type, arg)
278
- return nil if arg.nil? || arg.empty?
279
- if type.kind_of?(Tuple) && type.dims.empty?
280
- arg ? decode_abi(type.types, arg) : []
281
- elsif %w(string bytes).include?(type.base) && type.sub.empty?
282
- l = Utils.big_endian_to_int arg[0,32]
283
- data = arg[32..-1]
284
- data[0, l]
285
- elsif !type.dims.empty? && (l = type.dims.last)>0 # static-sized arrays
286
- subtype = type.subtype
287
- if subtype.dynamic?
288
- start_positions = (0...l).map {|i| Utils.big_endian_to_int(arg[32*i, 32]) }
289
- start_positions.push arg.size
290
-
291
- outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
292
-
293
- outputs.map {|out| decode_type(subtype, out) }
294
- else
295
- (0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
296
- end
297
-
298
- elsif type.dynamic?
299
- l = Utils.big_endian_to_int arg[0,32]
300
- raise DecodingError, "Too long length: #{l}" if l>100000
301
- subtype = type.subtype
302
-
303
- if subtype.dynamic?
304
- raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
305
-
306
- start_positions = (1..l).map {|i| 32+Utils.big_endian_to_int(arg[32*i, 32]) }
307
- start_positions.push arg.size
308
-
309
- outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
310
-
311
- outputs.map {|out| decode_type(subtype, out) }
312
- else
313
- (0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
314
- end
315
-
316
- else
317
- decode_primitive_type type, arg
318
- end
319
- end
320
-
321
- def decode_primitive_type(type, data)
322
- case type.base
323
- when 'address'
324
- Utils.encode_hex data[12..-1]
325
- when 'string', 'bytes'
326
- if type.sub.empty? # dynamic
327
- if data.length==32
328
- data[0..32]
329
- else
330
- size = Utils.big_endian_to_int data[0,32]
331
- data[32..-1][0,size]
332
- end
333
- else # fixed
334
- data[0, type.sub.to_i]
335
- end
336
- when 'hash'
337
- data[(32 - type.sub.to_i), type.sub.to_i]
338
- when 'uint'
339
- Utils.big_endian_to_int data
340
- when 'int'
341
- u = Utils.big_endian_to_int data
342
- u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u
343
- when 'ufixed'
344
- high, low = type.sub.split('x').map(&:to_i)
345
- Utils.big_endian_to_int(data) * 1.0 / 2**low
346
- when 'fixed'
347
- high, low = type.sub.split('x').map(&:to_i)
348
- u = Utils.big_endian_to_int data
349
- i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u
350
- i * 1.0 / 2**low
351
- when 'bool'
352
- data[-1] == BYTE_ONE
353
- else
354
- raise DecodingError, "Unknown primitive type: #{type.base}"
355
- end
356
- end
357
-
358
- private
359
-
360
- def get_uint(n)
361
- case n
362
- when Integer
363
- raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN
364
- n
365
- when String
366
- i = if n.size == 40
367
- Utils.decode_hex(n)
368
- elsif n.size <= 32
369
- n
370
- else
371
- raise EncodingError, "String too long: #{n}"
372
- end
373
- i = Utils.big_endian_to_int i
374
-
375
- raise EncodingError, "Number out of range: #{i}" if i > UINT_MAX || i < UINT_MIN
376
- i
377
- when true
378
- 1
379
- when false, nil
380
- 0
381
- else
382
- raise EncodingError, "Cannot decode uint: #{n}"
383
- end
384
- end
385
-
386
- def get_int(n)
387
- case n
388
- when Integer
389
- raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN
390
- n
391
- when String
392
- i = if n.size == 40
393
- Utils.decode_hex(n)
394
- elsif n.size <= 32
395
- n
396
- else
397
- raise EncodingError, "String too long: #{n}"
398
- end
399
- i = Utils.big_endian_to_int i
400
-
401
- i = i > INT_MAX ? (i-TT256) : i
402
- raise EncodingError, "Number out of range: #{i}" if i > INT_MAX || i < INT_MIN
403
- i
404
- when true
405
- 1
406
- when false, nil
407
- 0
408
- else
409
- raise EncodingError, "Cannot decode int: #{n}"
410
- end
411
- end
412
- end # class Codec
413
-
414
-
415
-
416
-
417
-
418
-
419
- def self.codec
420
- @codec ||= Codec.new
421
- end
422
-
423
- def self.encode_abi(types, args)
424
- codec.encode_abi( types, args )
425
- end
426
-
427
- def self.decode_abi(types, data, raise_errors = false)
428
- codec.decode_abi( types, data, raise_errors )
429
- end
430
-
431
-
432
- end # module Abi
433
- end # module Ethlite
434
-
435
-
436
-
@@ -1,200 +0,0 @@
1
- module Ethlite
2
- module Abi
3
- class Type
4
-
5
- class ParseError < StandardError; end
6
-
7
-
8
- ##
9
- # Crazy regexp to seperate out base type component (eg. uint), size (eg.
10
- # 256, 128x128, nil), array component (eg. [], [45], nil)
11
- #
12
- def self.parse( type )
13
-
14
- return parse('uint256') if type=='trcToken'
15
-
16
- if type =~ /^\((.*)\)((\[[0-9]*\])*)/
17
- return Tuple.parse $1, $2.scan(/\[[0-9]*\]/)
18
- end
19
-
20
- _, base, sub, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
21
-
22
- dims = dimension.scan(/\[[0-9]*\]/)
23
- raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
24
-
25
- case base
26
- when ''
27
- return parse 'address'
28
- when 'bytes', 'string'
29
- raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub.empty? || sub.to_i <= 32
30
- when 'uint', 'int'
31
- raise ParseError, "Integer type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
32
-
33
- size = sub.to_i
34
- raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
35
- raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
36
- when 'fixed', 'ufixed'
37
- raise ParseError, "Fixed type must have suffix of form <high>x<low>, e.g. 128x128" unless sub =~ /\A[0-9]+x[0-9]+\z/
38
-
39
- high, low = sub.split('x').map(&:to_i)
40
- total = high + low
41
-
42
- raise ParseError, "Fixed size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
43
- raise ParseError, "Fixed high size must be multiple of 8" unless high % 8 == 0
44
- raise ParseError, "Low sizes must be 0 to 80" unless low>0 && low<=80
45
- when 'hash'
46
- raise ParseError, "Hash type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
47
- when 'address'
48
- raise ParseError, "Address cannot have suffix" unless sub.empty?
49
- when 'bool'
50
- raise ParseError, "Bool cannot have suffix" unless sub.empty?
51
- else
52
- raise ParseError, "Unrecognized type base: #{base}"
53
- end
54
-
55
- new(base, sub, dims.map {|x| x[1...-1].to_i})
56
- end
57
-
58
- def self.size_type
59
- @size_type ||= new('uint', 256, [])
60
- end
61
-
62
-
63
- attr :base, :sub, :dims
64
-
65
- ##
66
- # @param base [String] base name of type, e.g. uint for uint256[4]
67
- # @param sub [String] subscript of type, e.g. 256 for uint256[4]
68
- # @param dims [Array[Integer]] dimensions of array type, e.g. [1,2,0]
69
- # for uint256[1][2][], [] for non-array type
70
- #
71
- def initialize( base, sub, dims )
72
- @base = base
73
- @sub = sub
74
- @dims = dims
75
- end
76
-
77
- def ==(another_type)
78
- base == another_type.base &&
79
- sub == another_type.sub &&
80
- dims == another_type.dims
81
- end
82
-
83
- ##
84
- # Get the static size of a type, or nil if dynamic.
85
- #
86
- # @return [Integer, NilClass] size of static type, or nil for dynamic
87
- # type
88
- #
89
- def size
90
- @size ||= if dims.empty?
91
- if %w(string bytes).include?(base) && sub.empty?
92
- nil
93
- else
94
- 32
95
- end
96
- else
97
- if dims.last == 0 # 0 for dynamic array []
98
- nil
99
- else
100
- subtype.dynamic? ? nil : dims.last * subtype.size
101
- end
102
- end
103
- end
104
-
105
- def dynamic?
106
- size.nil?
107
- end
108
-
109
- ##
110
- # Type with one dimension lesser.
111
- #
112
- # @example
113
- # Type.parse("uint256[2][]").subtype # => Type.new('uint', 256, [2])
114
- #
115
- # @return [Ethereum::ABI::Type]
116
- #
117
- def subtype
118
- @subtype ||= self.class.new(base, sub, dims[0...-1])
119
- end
120
- end # class Type
121
-
122
-
123
- class Tuple < Type
124
-
125
- def self.parse( types, dims )
126
-
127
- depth = 0
128
- collected = []
129
- current = ''
130
-
131
- types.split('').each do |c|
132
- case c
133
- when ',' then
134
- if depth==0
135
- collected << current
136
- current = ''
137
- else
138
- current += c
139
- end
140
- when '(' then
141
- depth += 1
142
- current += c
143
- when ')' then
144
- depth -= 1
145
- current += c
146
- else
147
- current += c
148
- end
149
-
150
- end
151
- collected << current unless current.empty?
152
-
153
- Tuple.new collected, dims.map {|x| x[1...-1].to_i}
154
-
155
- end
156
-
157
- attr_reader :types, :parsed_types
158
-
159
- def initialize( types, dims )
160
- super('tuple', '', dims)
161
- @types = types
162
- @parsed_types = types.map{|t| Type.parse t}
163
- end
164
-
165
- def ==(another_type)
166
- another_type.kind_of?(Tuple) &&
167
- another_type.types == types &&
168
- another_type.dims == dims
169
- end
170
-
171
- def size
172
- @size ||= calculate_size
173
- end
174
-
175
- def calculate_size
176
- if dims.empty?
177
- s = 0
178
- parsed_types.each do |type|
179
- ts = type.size
180
- return nil if ts.nil?
181
- s += ts
182
- end
183
- s
184
- else
185
- if dims.last == 0 # 0 for dynamic array []
186
- nil
187
- else
188
- subtype.dynamic? ? nil : dims.last * subtype.size
189
- end
190
- end
191
- end
192
-
193
- def subtype
194
- @subtype ||= Tuple.new(types, dims[0...-1])
195
- end
196
- end # class Tuple
197
-
198
-
199
- end # module Abi
200
- end # module Ethlite
@@ -1,35 +0,0 @@
1
-
2
- module Ethlite
3
-
4
-
5
- ## todo/check - use encoding -ascii-8bit for source file or ? - why? why not?
6
- ## use #b/.b to ensure binary encoding? - why? why not?
7
-
8
- ## todo/check: use auto-freeze string literals magic comment - why? why not?
9
- BYTE_EMPTY = "".b.freeze
10
- BYTE_ZERO = "\x00".b.freeze
11
- BYTE_ONE = "\x01".b.freeze
12
-
13
-
14
- TT32 = 2**32
15
- TT40 = 2**40
16
- TT160 = 2**160
17
- TT256 = 2**256
18
- TT64M1 = 2**64 - 1
19
-
20
- UINT_MAX = 2**256 - 1
21
- UINT_MIN = 0
22
- INT_MAX = 2**255 - 1
23
- INT_MIN = -2**255
24
-
25
- HASH_ZERO = ("\x00"*32).b.freeze
26
-
27
-
28
- PUBKEY_ZERO = ("\x00"*32).b.freeze
29
- PRIVKEY_ZERO = ("\x00"*32).b.freeze
30
-
31
- PRIVKEY_ZERO_HEX = ('0'*64).freeze
32
-
33
- CONTRACT_CODE_SIZE_LIMIT = 0x6000
34
-
35
- end # module Ethlite