ethlite 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6a9601ad8c91c592541ffc721dbe9bbb8572683ef88c995a1bb810f7da79e33
4
- data.tar.gz: 4ddf8c5a91c9a370e580bc9519f4d7c9832f863f31e4dfb8fa2b0505c2c6d6e0
3
+ metadata.gz: f4c28e1b125fefa2964c88a020a14de2d0e95c412e16f50ba5a329382465ccf8
4
+ data.tar.gz: 324e941a6825091bbf88539eb31acd03b80e8732c61442580a092bbdefe4e845
5
5
  SHA512:
6
- metadata.gz: 12d4cd45f40f8ecf3835f058e2ac5d4e9a9b361e3cd91fd5bca193e982c7fd726ff3ffab08ed7bedb543c3800e76808364c51f2992d4971816629ac170f700f6
7
- data.tar.gz: 236164ce176d5b50187e7402729a895130354f929df269e3b9d7b04472300b1661b9992a5579ba53064cbe0b7aee95e21aeeaae65225519ca55707caad771533
6
+ metadata.gz: df7dae52f733638a324dceb46371e1ecf6043f75515dc75ced197dfe1e24194b91e75f693f441861a122294e5d2408338584ee2b41a94ff64a277c0739fc9f8f
7
+ data.tar.gz: 171eea690f22474462316642eea60971eed702b6604681c3126b5900a134c196eb7cd07111d304086410e3ff46ccf7d73c61b2570c3430e563502ee6bf7e55ba
data/Manifest.txt CHANGED
@@ -5,10 +5,10 @@ Rakefile
5
5
  lib/digest/keccak.rb
6
6
  lib/digest/sha3.rb
7
7
  lib/ethlite.rb
8
- lib/ethlite/abi/abi_coder.rb
8
+ lib/ethlite/abi/codec.rb
9
9
  lib/ethlite/abi/type.rb
10
- lib/ethlite/abi/utils.rb
11
10
  lib/ethlite/constant.rb
12
11
  lib/ethlite/contract.rb
12
+ lib/ethlite/utils.rb
13
13
  lib/ethlite/version.rb
14
14
  lib/jsonrpc/jsonrpc.rb
data/README.md CHANGED
@@ -11,10 +11,179 @@ ethlite - light-weight machinery to query / call ethereum (blockchain contract)
11
11
 
12
12
 
13
13
 
14
-
15
14
  ## Usage
16
15
 
17
- to be done
16
+
17
+ ### Step 0: Setup JSON RPC Client
18
+
19
+ ```ruby
20
+ ## let's use a simple JSON RPC client
21
+ ## get the eth node uri via the INFURA_URI enviroment variable / key
22
+ ## e.g. https://mainnet.infura.io/v3/<YOUR_KEY_HERE>
23
+ ETH_NODE = JsonRpc.new( ENV['INFURA_URI'] )
24
+ ```
25
+
26
+
27
+
28
+ ### Do-It-Yourself (DIY) JSON RPC eth_call With "Hand-Coded" To-The-Metal ABI Encoding/Decoding
29
+
30
+
31
+ Let's try to build a JSON-RPC request from scratch calling
32
+ a (constant/read-only/view) blockchain contract method via `eth_call`.
33
+
34
+ Let's try `function tokenURI(uint256 tokenId) returns (string)`
35
+ that lets you request the metdata uri for a (non-fungible) token.
36
+
37
+ Let's try the Moonbirds contract @ [0x23581767a106ae21c074b2276d25e5c3e136a68b](https://etherscan.io/address/0x23581767a106ae21c074b2276d25e5c3e136a68b):
38
+
39
+ ```ruby
40
+ ## contract address - let's try moonbirds
41
+ contract_address = '0x23581767a106ae21c074b2276d25e5c3e136a68b'
42
+
43
+ token_ids = (0..9)
44
+ token_ids.each do |token_id|
45
+
46
+ puts "==> calling tokenURI(#{token_id})..."
47
+ tokenURI = eth_call( ETH_NODE, contract_address,
48
+ 'tokenURI', ['uint256'], ['string'],
49
+ [token_id] )
50
+ puts " ...returns: #{tokenURI}"
51
+ end
52
+ ```
53
+
54
+
55
+ And the "magic" hand-coded to-the-metal `eth_call` machinery:
56
+
57
+ ```ruby
58
+ ## construct a json-rpc call from scratch
59
+ def eth_call( rpc,
60
+ contract_address,
61
+ name, inputs, outputs,
62
+ args )
63
+
64
+ ## binary encode method sig(nature)
65
+ signature = "#{name}(#{inputs.join(',')})"
66
+ signature_hash = Ethlite::Utils.encode_hex(
67
+ Ethlite::Utils.keccak256(signature))[0..7]
68
+
69
+ pp signature
70
+ # => "tokenURI(uint256)"
71
+ pp signature_hash
72
+ # => "c87b56dd"
73
+
74
+ ## binary encode method arg(ument)s
75
+ args_encoded = Ethlite::Utils.encode_hex(
76
+ Ethlite::Abi.encode_abi( inputs, args) )
77
+
78
+ data = '0x' + signature_hash + args_encoded
79
+
80
+ ## json-rpc method and params
81
+ method = 'eth_call'
82
+ params = [{ to: contract_address,
83
+ data: data},
84
+ 'latest'
85
+ ]
86
+
87
+ ## do the json-rpc request
88
+ response = rpc.request( method, params )
89
+
90
+ puts "response:"
91
+ pp response
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 )
97
+ result.length == 1 ? result[0] : result
98
+ end
99
+ ```
100
+
101
+ resulting in (with debug json rpc request/reponse output):
102
+
103
+
104
+ calling `tokenURI(0)`... json_rpc POST payload request:
105
+
106
+ ``` json
107
+ {"jsonrpc":"2.0",
108
+ "method":"eth_call",
109
+ "params":[
110
+ {"to":"0x23581767a106ae21c074b2276d25e5c3e136a68b",
111
+ "data":"0xc87b56dd0000000000000000000000000000000000000000000000000000000000000000"},
112
+ "latest"],
113
+ "id":1
114
+ }
115
+ ```
116
+
117
+ json_rpc response:
118
+
119
+ ``` json
120
+ {"jsonrpc":"2.0",
121
+ "id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003a68747470733a2f2f6c6976652d2d2d6d657461646174612d35636f767071696a61612d75632e612e72756e2e6170702f6d657461646174612f30000000000000"
122
+ }
123
+ ```
124
+
125
+ ...returns: `https://live---metadata-5covpqijaa-uc.a.run.app/metadata/0`
126
+
127
+
128
+ calling `tokenURI(1)...` json_rpc POST payload request:
129
+
130
+ ``` json
131
+ {"jsonrpc":"2.0",
132
+ "method":"eth_call",
133
+ "params":[
134
+ {"to":"0x23581767a106ae21c074b2276d25e5c3e136a68b",
135
+ "data":"0xc87b56dd0000000000000000000000000000000000000000000000000000000000000001"},
136
+ "latest"],
137
+ "id":2
138
+ }
139
+ ```
140
+
141
+ json_rpc response:
142
+
143
+ ``` json
144
+ {"jsonrpc":"2.0",
145
+ "id":2,"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003a68747470733a2f2f6c6976652d2d2d6d657461646174612d35636f767071696a61612d75632e612e72756e2e6170702f6d657461646174612f31000000000000"
146
+ }
147
+ ```
148
+
149
+ ...returns: `https://live---metadata-5covpqijaa-uc.a.run.app/metadata/1`
150
+
151
+
152
+
153
+ ### JSON RPC eth_call With ContractMethod Helper
154
+
155
+ Let's retry using the convenience builtin
156
+ `ContractMethod` helper:
157
+
158
+ ```ruby
159
+ ## contract address - let's try moonbirds
160
+ contract_address = '0x23581767a106ae21c074b2276d25e5c3e136a68b'
161
+
162
+ ETH_tokenURI = Ethlite::ContractMethod.new( 'tokenURI',
163
+ inputs: ['uint256'],
164
+ outputs: ['string'] )
165
+
166
+ token_ids = (0..9)
167
+ token_ids.each do |token_id|
168
+ puts "==> tokenURI(#{token_id}) returns:"
169
+ pp ETH_tokenURI.do_call( ETH_NODE, contract_address, [token_id] )
170
+ end
171
+ ```
172
+
173
+ resulting in:
174
+
175
+ ```
176
+ ==> tokenURI(0) returns:
177
+ "https://live---metadata-5covpqijaa-uc.a.run.app/metadata/0"
178
+ ==> tokenURI(1) returns:
179
+ "https://live---metadata-5covpqijaa-uc.a.run.app/metadata/1"
180
+ ==> tokenURI(2) returns:
181
+ "https://live---metadata-5covpqijaa-uc.a.run.app/metadata/3"
182
+ ...
183
+ ```
184
+
185
+
186
+ And so on and so forth.
18
187
 
19
188
 
20
189
 
@@ -2,19 +2,16 @@
2
2
 
3
3
  module Ethlite
4
4
  module Abi
5
+ extend self
5
6
 
6
7
  ##
7
8
  # Contract ABI encoding and decoding.
8
9
  #
9
10
  # @see https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
10
11
  #
11
- module AbiCoder
12
-
12
+ module Codec
13
13
  extend self
14
14
 
15
- include Constant
16
-
17
-
18
15
  class EncodingError < StandardError; end
19
16
  class DecodingError < StandardError; end
20
17
  class ValueError < StandardError; end
@@ -43,7 +40,6 @@ module Ethlite
43
40
 
44
41
  "#{head}#{tail}"
45
42
  end
46
- alias :encode :encode_abi
47
43
 
48
44
  ##
49
45
  # Encodes a single value (static or dynamic).
@@ -263,7 +259,7 @@ module Ethlite
263
259
 
264
260
  parsed_types.zip(outputs).map {|(type, out)| decode_type(type, out) }
265
261
  end
266
- alias :decode :decode_abi
262
+
267
263
 
268
264
  def zero_padding data, pos, count, start_positions
269
265
  if pos >= data.size
@@ -416,8 +412,21 @@ module Ethlite
416
412
  raise EncodingError, "Cannot decode int: #{n}"
417
413
  end
418
414
  end
415
+ end # module Codec
416
+
417
+
418
+
419
+
420
+
421
+
422
+ def encode_abi(types, args)
423
+ Codec.encode_abi( types, args )
424
+ end
425
+
426
+ def decode_abi(types, data, raise_errors = false)
427
+ Codec.decode_abi( types, data, raise_errors )
428
+ end
419
429
 
420
- end
421
430
 
422
431
  end # module Abi
423
432
  end # module Ethlite
@@ -1,6 +1,5 @@
1
1
 
2
2
  module Ethlite
3
- module Constant
4
3
 
5
4
 
6
5
  ## todo/check - use encoding -ascii-8bit for source file or ? - why? why not?
@@ -33,6 +32,4 @@ module Ethlite
33
32
 
34
33
  CONTRACT_CODE_SIZE_LIMIT = 0x6000
35
34
 
36
-
37
- end # module Constant
38
35
  end # module Ethlite
@@ -9,12 +9,10 @@
9
9
  constant = !!abi['constant'] || abi['stateMutability']=='view'
10
10
  input_types = abi['inputs'] ? abi['inputs'].map{|a| _parse_component_type( a ) } : []
11
11
  output_types = abi['outputs'] ? abi['outputs'].map{|a| _parse_component_type( a ) } : []
12
- type = abi['type'] || 'function'
13
12
 
14
13
  new( name, inputs: input_types,
15
14
  outputs: output_types,
16
- constant: constant,
17
- type: type )
15
+ constant: constant )
18
16
  end
19
17
 
20
18
  def self._parse_component_type( argument )
@@ -38,20 +36,19 @@
38
36
 
39
37
  def initialize( name, inputs:,
40
38
  outputs: [],
41
- constant: true,
42
- type: 'function' )
39
+ constant: true )
43
40
  @name = name
44
41
  @constant = constant
45
42
  @input_types = inputs
46
43
  @output_types = outputs
47
-
48
- @signature = Abi::Utils.function_signature( @name, @input_types )
49
- @signature_hash = Abi::Utils.signature_hash( @signature, type=='event' ? 64 : 8)
44
+ @signature = "#{@name}(#{@input_types.join(',')})"
45
+ @signature_hash = Utils.signature_hash( @signature, 8 )
50
46
  end
51
47
 
48
+
52
49
  def do_call( rpc, contract_address, args )
53
- data = '0x' + @signature_hash + Abi::Utils.encode_hex(
54
- Abi::AbiCoder.encode_abi(@input_types, args) )
50
+ data = '0x' + @signature_hash + Utils.encode_hex(
51
+ Abi.encode_abi(@input_types, args) )
55
52
 
56
53
  method = 'eth_call'
57
54
  params = [{ to: contract_address,
@@ -63,10 +60,11 @@
63
60
  puts "response:"
64
61
  pp response
65
62
 
66
- string_data = [Abi::Utils.remove_0x_head(response)].pack('H*')
63
+ string_data = Utils.decode_hex(
64
+ Utils.remove_0x_head(response))
67
65
  return nil if string_data.empty?
68
66
 
69
- result = Abi::AbiCoder.decode_abi( @output_types, string_data )
67
+ result = Abi.decode_abi( @output_types, string_data )
70
68
  puts
71
69
  puts "result decode_abi:"
72
70
  pp result
@@ -1,11 +1,8 @@
1
1
  module Ethlite
2
- module Abi
3
- module Utils
2
+ module Utils
4
3
 
5
4
  extend self
6
5
 
7
- include Constant
8
-
9
6
  ##
10
7
  # Not the keccak in sha3, although it's underlying lib named SHA3
11
8
  #
@@ -70,7 +67,7 @@ module Abi
70
67
  end
71
68
 
72
69
  def decode_hex(str)
73
- raise TypeError, "Value must be an instance of string" unless sstr.instance_of?(String)
70
+ raise TypeError, "Value must be an instance of string" unless str.instance_of?(String)
74
71
  raise TypeError, 'Non-hexadecimal digit found' unless str =~ /\A[0-9a-fA-F]*\z/
75
72
  [str].pack("H*")
76
73
  end
@@ -215,15 +212,11 @@ module Abi
215
212
  end
216
213
 
217
214
 
218
- def function_signature method_name, arg_types
219
- "#{method_name}(#{arg_types.join(',')})"
220
- end
221
215
 
222
- def signature_hash signature, length=64
223
- encode_hex(keccak256(signature))[0...length]
216
+ def signature_hash( signature, length=8 )
217
+ encode_hex( keccak256(signature) )[0...length]
224
218
  end
225
- end
226
219
 
227
- end # module Abi
220
+ end # module Utils
228
221
  end # module Ethlite
229
222
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Ethlite
4
4
  MAJOR = 0
5
- MINOR = 2
6
- PATCH = 4
5
+ MINOR = 3
6
+ PATCH = 0
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
data/lib/ethlite.rb CHANGED
@@ -24,11 +24,11 @@ require_relative 'jsonrpc/jsonrpc'
24
24
  require_relative 'ethlite/version' # note: let version always go first
25
25
 
26
26
  require_relative 'ethlite/constant'
27
+ require_relative 'ethlite/utils'
27
28
 
28
29
 
29
30
  require_relative 'ethlite/abi/type'
30
- require_relative 'ethlite/abi/utils'
31
- require_relative 'ethlite/abi/abi_coder'
31
+ require_relative 'ethlite/abi/codec'
32
32
 
33
33
 
34
34
  require_relative 'ethlite/contract'
@@ -23,7 +23,7 @@ class JsonRpc
23
23
 
24
24
  @request_id += 1
25
25
 
26
- puts "json POST payload:"
26
+ puts "json_rpc POST payload:"
27
27
  puts data.to_json
28
28
 
29
29
  response = Webclient.post( @uri, json: data )
@@ -33,6 +33,9 @@ class JsonRpc
33
33
  raise "Error code #{response.status.code} on request #{@uri} #{data}"
34
34
  end
35
35
 
36
+ puts "json_rpc response.body:"
37
+ puts response.body
38
+
36
39
 
37
40
  body = JSON.parse( response.body, max_nesting: 1500 )
38
41
 
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.2.4
4
+ version: 0.3.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-11-19 00:00:00.000000000 Z
11
+ date: 2022-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocos
@@ -89,11 +89,11 @@ files:
89
89
  - lib/digest/keccak.rb
90
90
  - lib/digest/sha3.rb
91
91
  - lib/ethlite.rb
92
- - lib/ethlite/abi/abi_coder.rb
92
+ - lib/ethlite/abi/codec.rb
93
93
  - lib/ethlite/abi/type.rb
94
- - lib/ethlite/abi/utils.rb
95
94
  - lib/ethlite/constant.rb
96
95
  - lib/ethlite/contract.rb
96
+ - lib/ethlite/utils.rb
97
97
  - lib/ethlite/version.rb
98
98
  - lib/jsonrpc/jsonrpc.rb
99
99
  homepage: https://github.com/pixelartexchange/artbase