ethlite 0.2.3 → 0.2.5

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: ed8e8c05d930dceca2c2bf5f4b2e21ebeaaef68309b0c16474b47df667da2a89
4
- data.tar.gz: 203ef7c86aa93e602028b1e0450e70c1c2c3a1388273263ce1e90fc503a18b08
3
+ metadata.gz: 3cd659223f8049ca63302c5bb595da6faf483d29d776ed747b5970dde11b415e
4
+ data.tar.gz: fde6fca03683dac98d474d82bc520a82dc45e74b08eaabbc0c521e2635ca3d3b
5
5
  SHA512:
6
- metadata.gz: 1527a1fccb6908164d7744e0b06772c48a18f2f67bc60068f72c7577f512d2de295e9dfe8d917c5ca3c724963396cfb02ddff0aac6611be65d79575aaa90c34f
7
- data.tar.gz: 92f93f217349b9ef33b1fc1ec7c0da36e50d172e2b44e06b9b8281d008fc770b4da081ad64f46e2be1819bbb7fa423dbe14a6519da4ddae6f782d9ea9f056108
6
+ metadata.gz: 050226ff51c20a38cb8f1db254e246a555d07baf8a6e12766985850400749a463698e89818780cd031e5ed3cab6bb05e2afae37588149084160138d5e6783e0f
7
+ data.tar.gz: 0d16770efc489cf88f08ec8ef471563905b2197c1d8330c58558348f1a9076dd717f654b5c221ea78052d31855bb458105c79f2f7ec1021a04451a202088b722
data/Manifest.txt CHANGED
@@ -10,6 +10,5 @@ lib/ethlite/abi/type.rb
10
10
  lib/ethlite/abi/utils.rb
11
11
  lib/ethlite/constant.rb
12
12
  lib/ethlite/contract.rb
13
- lib/ethlite/utility.rb
14
13
  lib/ethlite/version.rb
15
14
  lib/jsonrpc/jsonrpc.rb
data/README.md CHANGED
@@ -11,10 +11,178 @@ 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( 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( contract_address,
60
+ name, inputs, outputs,
61
+ args )
62
+
63
+ ## binary encode method sig(nature)
64
+ signature = "#{name}(#{inputs.join(',')})"
65
+ signature_hash = Ethlite::Abi::Utils.encode_hex(
66
+ Ethlite::Abi::Utils.keccak256(signature))[0..7]
67
+
68
+ pp signature
69
+ # => "tokenURI(uint256)"
70
+ pp signature_hash
71
+ # => "c87b56dd"
72
+
73
+ ## binary encode method arg(ument)s
74
+ args_encoded = Ethlite::Abi::Utils.encode_hex(
75
+ Ethlite::Abi::AbiCoder.encode_abi( inputs, args) )
76
+
77
+ data = '0x' + signature_hash + args_encoded
78
+
79
+ ## json-rpc method and params
80
+ method = 'eth_call'
81
+ params = [{ to: contract_address,
82
+ data: data},
83
+ 'latest'
84
+ ]
85
+
86
+ ## do the json-rpc request
87
+ response = ETH_NODE.request( method, params )
88
+
89
+ puts "response:"
90
+ pp response
91
+
92
+ ## decode binary result
93
+ string_data = Ethlite::Abi::Utils.decode_hex(
94
+ Ethlite::Abi::Utils.remove_0x_head(response))
95
+ result = Ethlite::Abi::AbiCoder.decode_abi( outputs, string_data )
96
+ result.length == 1 ? result[0] : result
97
+ end
98
+ ```
99
+
100
+ resulting in (with debug json rpc request/reponse output):
101
+
102
+
103
+ calling `tokenURI(0)`... json_rpc POST payload request:
104
+
105
+ ``` json
106
+ {"jsonrpc":"2.0",
107
+ "method":"eth_call",
108
+ "params":[
109
+ {"to":"0x23581767a106ae21c074b2276d25e5c3e136a68b",
110
+ "data":"0xc87b56dd0000000000000000000000000000000000000000000000000000000000000000"},
111
+ "latest"],
112
+ "id":1
113
+ }
114
+ ```
115
+
116
+ json_rpc response:
117
+
118
+ ``` json
119
+ {"jsonrpc":"2.0",
120
+ "id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003a68747470733a2f2f6c6976652d2d2d6d657461646174612d35636f767071696a61612d75632e612e72756e2e6170702f6d657461646174612f30000000000000"
121
+ }
122
+ ```
123
+
124
+ ...returns: `https://live---metadata-5covpqijaa-uc.a.run.app/metadata/0`
125
+
126
+
127
+ calling `tokenURI(1)...` json_rpc POST payload request:
128
+
129
+ ``` json
130
+ {"jsonrpc":"2.0",
131
+ "method":"eth_call",
132
+ "params":[
133
+ {"to":"0x23581767a106ae21c074b2276d25e5c3e136a68b",
134
+ "data":"0xc87b56dd0000000000000000000000000000000000000000000000000000000000000001"},
135
+ "latest"],
136
+ "id":2
137
+ }
138
+ ```
139
+
140
+ json_rpc response:
141
+
142
+ ``` json
143
+ {"jsonrpc":"2.0",
144
+ "id":2,"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003a68747470733a2f2f6c6976652d2d2d6d657461646174612d35636f767071696a61612d75632e612e72756e2e6170702f6d657461646174612f31000000000000"
145
+ }
146
+ ```
147
+
148
+ ...returns: `https://live---metadata-5covpqijaa-uc.a.run.app/metadata/1`
149
+
150
+
151
+
152
+ ### JSON RPC eth_call With ContractMethod Helper
153
+
154
+ Let's retry using the convenience builtin
155
+ `ContractMethod` helper:
156
+
157
+ ```ruby
158
+ ## contract address - let's try moonbirds
159
+ contract_address = '0x23581767a106ae21c074b2276d25e5c3e136a68b'
160
+
161
+ ETH_tokenURI = Ethlite::ContractMethod.new( 'tokenURI',
162
+ inputs: ['uint256'],
163
+ outputs: ['string'] )
164
+
165
+ token_ids = (0..9)
166
+ token_ids.each do |token_id|
167
+ puts "==> tokenURI(#{token_id}) returns:"
168
+ pp ETH_tokenURI.do_call( ETH_NODE, contract_address, [token_id] )
169
+ end
170
+ ```
171
+
172
+ resulting in:
173
+
174
+ ```
175
+ ==> tokenURI(0) returns:
176
+ "https://live---metadata-5covpqijaa-uc.a.run.app/metadata/0"
177
+ ==> tokenURI(1) returns:
178
+ "https://live---metadata-5covpqijaa-uc.a.run.app/metadata/1"
179
+ ==> tokenURI(2) returns:
180
+ "https://live---metadata-5covpqijaa-uc.a.run.app/metadata/3"
181
+ ...
182
+ ```
183
+
184
+
185
+ And so on and so forth.
18
186
 
19
187
 
20
188
 
data/Rakefile CHANGED
@@ -21,7 +21,7 @@ Hoe.spec 'ethlite' do
21
21
  self.extra_deps = [
22
22
  ['cocos'],
23
23
  ## ['keccak'], -- try using bundled ruby version for now
24
- ['rlp'],
24
+ ['rlp-lite'],
25
25
  ]
26
26
 
27
27
  self.licenses = ['Public Domain']
@@ -56,44 +56,50 @@ module Abi
56
56
  end
57
57
 
58
58
  def to_signed(i)
59
- i > Constant::INT_MAX ? (i-Constant::TT256) : i
59
+ i > INT_MAX ? (i-TT256) : i
60
60
  end
61
61
 
62
- def base58_check_to_bytes(s)
63
- leadingzbytes = s.match(/\A1*/)[0]
64
- data = Constant::BYTE_ZERO * leadingzbytes.size + BaseConvert.convert(s, 58, 256)
65
-
66
- raise ChecksumError, "double sha256 checksum doesn't match" unless double_sha256(data[0...-4])[0,4] == data[-4..-1]
67
- data[1...-4]
68
- end
69
-
70
- def bytes_to_base58_check(bytes, magicbyte=0)
71
- bs = "#{magicbyte.chr}#{bytes}"
72
- leadingzbytes = bs.match(/\A#{Constant::BYTE_ZERO}*/)[0]
73
- checksum = double_sha256(bs)[0,4]
74
- '1'*leadingzbytes.size + BaseConvert.convert("#{bs}#{checksum}", 256, 58)
75
- end
76
62
 
77
63
  def ceil32(x)
78
64
  x % 32 == 0 ? x : (x + 32 - x%32)
79
65
  end
80
66
 
81
67
  def encode_hex(b)
82
- RLP::Utils.encode_hex b
68
+ raise TypeError, "Value must be an instance of String" unless b.instance_of?(String)
69
+ b.unpack("H*").first
70
+ end
71
+
72
+ def decode_hex(str)
73
+ raise TypeError, "Value must be an instance of string" unless str.instance_of?(String)
74
+ raise TypeError, 'Non-hexadecimal digit found' unless str =~ /\A[0-9a-fA-F]*\z/
75
+ [str].pack("H*")
83
76
  end
84
77
 
85
- def decode_hex(s)
86
- RLP::Utils.decode_hex s
78
+
79
+
80
+
81
+ def int_to_big_endian(n)
82
+ RLP::Sedes.big_endian_int.serialize n
87
83
  end
88
84
 
89
85
  def big_endian_to_int(s)
90
86
  RLP::Sedes.big_endian_int.deserialize s.sub(/\A(\x00)+/, '')
91
87
  end
92
88
 
93
- def int_to_big_endian(n)
94
- RLP::Sedes.big_endian_int.serialize n
89
+
90
+ def encode_int(n)
91
+ raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
92
+ int_to_big_endian n
93
+ end
94
+
95
+ def decode_int(v)
96
+ raise ArgumentError, "No leading zero bytes allowed for integers" if v.size > 0 && (v[0] == BYTE_ZERO || v[0] == 0)
97
+ big_endian_to_int v
95
98
  end
96
99
 
100
+
101
+
102
+
97
103
  def lpad(x, symbol, l)
98
104
  return x if x.size >= l
99
105
  symbol * (l - x.size) + x
@@ -124,15 +130,6 @@ module Abi
124
130
  zpad_int x, 20
125
131
  end
126
132
 
127
- def encode_int(n)
128
- raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
129
- int_to_big_endian n
130
- end
131
-
132
- def decode_int(v)
133
- raise ArgumentError, "No leading zero bytes allowed for integers" if v.size > 0 && (v[0] == Constant::BYTE_ZERO || v[0] == 0)
134
- big_endian_to_int v
135
- end
136
133
 
137
134
  def bytearray_to_int(arr)
138
135
  o = 0
@@ -148,6 +145,7 @@ module Abi
148
145
  bytes.unpack('C*')
149
146
  end
150
147
 
148
+
151
149
  def coerce_to_int(x)
152
150
  if x.is_a?(Numeric)
153
151
  x
@@ -178,19 +176,6 @@ module Abi
178
176
  end
179
177
  end
180
178
 
181
- def normalize_address(x, allow_blank: false)
182
- address = Address.new(x)
183
- raise ValueError, "address is blank" if !allow_blank && address.blank?
184
- address.to_bytes
185
- end
186
-
187
- def mk_contract_address(sender, nonce)
188
- keccak256_rlp([normalize_address(sender), nonce])[12..-1]
189
- end
190
-
191
- def mk_metropolis_contract_address(sender, initcode)
192
- keccak256(normalize_address(sender) + initcode)[12..-1]
193
- end
194
179
 
195
180
 
196
181
  def parse_int_or_hex(s)
@@ -203,6 +188,24 @@ module Abi
203
188
  end
204
189
  end
205
190
 
191
+
192
+ =begin
193
+ ## add? moved over from the old module Utility
194
+ def hex( num )
195
+ '0x' + num.to_s(16)
196
+ end
197
+
198
+ def from_hex( h )
199
+ h.nil? ? 0 : (h.kind_of?(String) ? h.to_i(16) : h)
200
+ end
201
+ =end
202
+
203
+
204
+ def remove_0x_head( s )
205
+ return s if !s || s.length<2
206
+ s[0,2] == '0x' ? s[2..-1] : s
207
+ end
208
+
206
209
  def normalize_hex_without_prefix(s)
207
210
  if s[0,2] == '0x'
208
211
  (s.size % 2 == 1 ? '0' : '') + s[2..-1]
@@ -211,6 +214,7 @@ module Abi
211
214
  end
212
215
  end
213
216
 
217
+
214
218
  def function_signature method_name, arg_types
215
219
  "#{method_name}(#{arg_types.join(',')})"
216
220
  end
@@ -2,9 +2,15 @@
2
2
  module Ethlite
3
3
  module Constant
4
4
 
5
- BYTE_EMPTY = "".freeze
6
- BYTE_ZERO = "\x00".freeze
7
- BYTE_ONE = "\x01".freeze
5
+
6
+ ## todo/check - use encoding -ascii-8bit for source file or ? - why? why not?
7
+ ## use #b/.b to ensure binary encoding? - why? why not?
8
+
9
+ ## todo/check: use auto-freeze string literals magic comment - why? why not?
10
+ BYTE_EMPTY = "".b.freeze
11
+ BYTE_ZERO = "\x00".b.freeze
12
+ BYTE_ONE = "\x01".b.freeze
13
+
8
14
 
9
15
  TT32 = 2**32
10
16
  TT40 = 2**40
@@ -17,35 +23,16 @@ module Ethlite
17
23
  INT_MAX = 2**255 - 1
18
24
  INT_MIN = -2**255
19
25
 
20
- HASH_ZERO = ("\x00"*32).freeze
26
+ HASH_ZERO = ("\x00"*32).b.freeze
21
27
 
22
28
 
23
- PUBKEY_ZERO = ("\x00"*32).freeze
24
- PRIVKEY_ZERO = ("\x00"*32).freeze
29
+ PUBKEY_ZERO = ("\x00"*32).b.freeze
30
+ PRIVKEY_ZERO = ("\x00"*32).b.freeze
31
+
25
32
  PRIVKEY_ZERO_HEX = ('0'*64).freeze
26
33
 
27
34
  CONTRACT_CODE_SIZE_LIMIT = 0x6000
28
35
 
29
36
 
30
- =begin
31
- # The RLP short length limit.
32
- SHORT_LENGTH_LIMIT = 56.freeze
33
-
34
- # The RLP long length limit.
35
- LONG_LENGTH_LIMIT = (256 ** 8).freeze
36
-
37
- # The RLP primitive type offset.
38
- PRIMITIVE_PREFIX_OFFSET = 0x80.freeze
39
-
40
- # The RLP array type offset.
41
- LIST_PREFIX_OFFSET = 0xc0.freeze
42
-
43
- # The binary encoding is ASCII (8-bit).
44
- BINARY_ENCODING = "ASCII-8BIT".freeze
45
-
46
- # Infinity as constant for convenience.
47
- INFINITY = (1.0 / 0.0).freeze
48
-
49
- =end
50
37
  end # module Constant
51
38
  end # module Ethlite
@@ -1,7 +1,5 @@
1
1
  module Ethlite
2
2
  class ContractMethod
3
- include Utility
4
-
5
3
 
6
4
  def self.parse_abi( abi )
7
5
  ## convenience helper - auto-convert to json if string passed in
@@ -31,8 +29,7 @@
31
29
 
32
30
 
33
31
 
34
- attr_reader :abi,
35
- :signature,
32
+ attr_reader :signature,
36
33
  :name,
37
34
  :signature_hash,
38
35
  :input_types,
@@ -66,7 +63,8 @@
66
63
  puts "response:"
67
64
  pp response
68
65
 
69
- string_data = [remove_0x_head(response)].pack('H*')
66
+ string_data = Abi::Utils.decode_hex(
67
+ Abi::Utils.remove_0x_head(response))
70
68
  return nil if string_data.empty?
71
69
 
72
70
  result = Abi::AbiCoder.decode_abi( @output_types, string_data )
@@ -3,7 +3,7 @@
3
3
  module Ethlite
4
4
  MAJOR = 0
5
5
  MINOR = 2
6
- PATCH = 3
6
+ PATCH = 5
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
data/lib/ethlite.rb CHANGED
@@ -10,7 +10,7 @@ require 'openssl'
10
10
  require 'digest'
11
11
 
12
12
  ## 3rd party gems
13
- require 'rlp' ## gem rlp - see https://rubygems.org/gems/rlp
13
+ require 'rlp-lite'
14
14
 
15
15
  ## bundled require 'digest/keccak' ## gem keccak - see https://rubygems.org/gems/keccak
16
16
  require_relative 'digest/keccak'
@@ -31,7 +31,6 @@ require_relative 'ethlite/abi/utils'
31
31
  require_relative 'ethlite/abi/abi_coder'
32
32
 
33
33
 
34
- require_relative 'ethlite/utility'
35
34
  require_relative 'ethlite/contract'
36
35
 
37
36
 
@@ -9,49 +9,43 @@
9
9
 
10
10
  class JsonRpc
11
11
  def initialize( uri )
12
- @client_id = Random.rand( 10000000 )
13
-
14
- @uri = URI.parse( uri )
12
+ @uri = uri ## assume uri always as string for now
13
+ @request_id = 1
15
14
  end
16
15
 
17
16
 
18
17
  def request( method, params=[] )
19
- opts = {}
20
- if @uri.instance_of?( URI::HTTPS )
21
- opts[:use_ssl] = true
22
- opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
23
- end
24
-
25
- Net::HTTP.start( @uri.host, @uri.port, **opts ) do |http|
26
- headers = {"Content-Type" => "application/json"}
27
- request = Net::HTTP::Post.new( @uri.request_uri, headers )
28
18
 
29
- json = { jsonrpc: '2.0',
19
+ data = { jsonrpc: '2.0',
30
20
  method: method,
31
21
  params: params,
32
- id: @client_id }.to_json
22
+ id: @request_id }
23
+
24
+ @request_id += 1
33
25
 
34
- puts "json POST payload:"
35
- puts json
26
+ puts "json_rpc POST payload:"
27
+ puts data.to_json
36
28
 
37
- request.body = json
38
- response = http.request( request )
29
+ response = Webclient.post( @uri, json: data )
39
30
 
40
- unless response.kind_of?( Net::HTTPOK )
41
- raise "Error code #{response.code} on request #{@uri.to_s} #{request.body}"
31
+
32
+ if response.status.nok?
33
+ raise "Error code #{response.status.code} on request #{@uri} #{data}"
42
34
  end
43
35
 
36
+ puts "json_rpc response.body:"
37
+ puts response.body
38
+
44
39
 
45
40
  body = JSON.parse( response.body, max_nesting: 1500 )
46
41
 
47
42
  if body['result']
48
43
  body['result']
49
44
  elsif body['error']
50
- raise "Error #{@uri.to_s} #{body['error']} on request #{@uri.to_s} #{request.body}"
45
+ raise "Error #{@uri} #{body['error']} on request #{@uri} #{data}"
51
46
  else
52
- raise "No response on request #{@uri.to_s} #{request.body}"
47
+ raise "No response on request #{@uri} #{data}"
53
48
  end
54
- end
55
- end
49
+ end # method request
56
50
  end # class JsonRpc
57
51
 
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.3
4
+ version: 0.2.5
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-15 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
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rlp
28
+ name: rlp-lite
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -94,7 +94,6 @@ files:
94
94
  - lib/ethlite/abi/utils.rb
95
95
  - lib/ethlite/constant.rb
96
96
  - lib/ethlite/contract.rb
97
- - lib/ethlite/utility.rb
98
97
  - lib/ethlite/version.rb
99
98
  - lib/jsonrpc/jsonrpc.rb
100
99
  homepage: https://github.com/pixelartexchange/artbase
@@ -1,23 +0,0 @@
1
- module Ethlite
2
-
3
- module Utility
4
-
5
- def hex( num )
6
- '0x' + num.to_s(16)
7
- end
8
-
9
- def from_hex( h )
10
- h.nil? ? 0 : (h.kind_of?(String) ? h.to_i(16) : h)
11
- end
12
-
13
- def remove_0x_head( s )
14
- return s if !s || s.length<2
15
- s[0,2] == '0x' ? s[2..-1] : s
16
- end
17
-
18
-
19
- def wei_to_ether( wei )
20
- 1.0 * wei / 10**18
21
- end
22
- end
23
- end # module Ethlite