ethlite 0.2.4 → 0.3.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: 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