ethlite 0.1.0 → 0.2.1

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.
@@ -0,0 +1,29 @@
1
+
2
+ module Ethlite
3
+ module Abi
4
+
5
+ class DeprecatedError < StandardError; end
6
+ class ChecksumError < StandardError; end
7
+ class FormatError < StandardError; end
8
+ class ValidationError < StandardError; end
9
+ class ValueError < StandardError; end
10
+ class AssertError < StandardError; end
11
+
12
+ class UnknownParentError < StandardError; end
13
+ class InvalidBlock < ValidationError; end
14
+ class InvalidUncles < ValidationError; end
15
+
16
+ class InvalidTransaction < ValidationError; end
17
+ class UnsignedTransactionError < InvalidTransaction; end
18
+ class InvalidNonce < InvalidTransaction; end
19
+ class InsufficientStartGas < InvalidTransaction; end
20
+ class InsufficientBalance < InvalidTransaction; end
21
+ class BlockGasLimitReached < InvalidTransaction; end
22
+
23
+ class InvalidSPVProof < ValidationError; end
24
+
25
+ class ContractCreationFailed < StandardError; end
26
+ class TransactionFailed < StandardError; end
27
+
28
+ end # module Abi
29
+ end # module Ethlite
@@ -0,0 +1,203 @@
1
+ module Ethlite
2
+ module Abi
3
+ class Type
4
+
5
+ class ParseError < StandardError;
6
+ end
7
+
8
+ class <<self
9
+ ##
10
+ # Crazy regexp to seperate out base type component (eg. uint), size (eg.
11
+ # 256, 128x128, nil), array component (eg. [], [45], nil)
12
+ #
13
+ def parse(type)
14
+
15
+ return parse('uint256') if type=='trcToken'
16
+
17
+ if type =~ /^\((.*)\)((\[[0-9]*\])*)/
18
+ return Tuple.parse $1, $2.scan(/\[[0-9]*\]/)
19
+ end
20
+
21
+ _, base, sub, dimension = /([a-z]*)([0-9]*x?[0-9]*)((\[[0-9]*\])*)/.match(type).to_a
22
+
23
+ dims = dimension.scan(/\[[0-9]*\]/)
24
+ raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
25
+
26
+ case base
27
+ when ''
28
+ return parse 'address'
29
+ when 'bytes', 'string'
30
+ raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub.empty? || sub.to_i <= 32
31
+ when 'uint', 'int'
32
+ raise ParseError, "Integer type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
33
+
34
+ size = sub.to_i
35
+ raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
36
+ raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
37
+ when 'fixed', 'ufixed'
38
+ raise ParseError, "Fixed type must have suffix of form <high>x<low>, e.g. 128x128" unless sub =~ /\A[0-9]+x[0-9]+\z/
39
+
40
+ high, low = sub.split('x').map(&:to_i)
41
+ total = high + low
42
+
43
+ raise ParseError, "Fixed size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
44
+ raise ParseError, "Fixed high size must be multiple of 8" unless high % 8 == 0
45
+ raise ParseError, "Low sizes must be 0 to 80" unless low>0 && low<=80
46
+ when 'hash'
47
+ raise ParseError, "Hash type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
48
+ when 'address'
49
+ raise ParseError, "Address cannot have suffix" unless sub.empty?
50
+ when 'bool'
51
+ raise ParseError, "Bool cannot have suffix" unless sub.empty?
52
+ else
53
+ raise ParseError, "Unrecognized type base: #{base}"
54
+ end
55
+
56
+ new(base, sub, dims.map {|x| x[1...-1].to_i})
57
+ end
58
+
59
+ def size_type
60
+ @size_type ||= new('uint', 256, [])
61
+ end
62
+ end
63
+
64
+ attr :base, :sub, :dims
65
+
66
+ ##
67
+ # @param base [String] base name of type, e.g. uint for uint256[4]
68
+ # @param sub [String] subscript of type, e.g. 256 for uint256[4]
69
+ # @param dims [Array[Integer]] dimensions of array type, e.g. [1,2,0]
70
+ # for uint256[1][2][], [] for non-array type
71
+ #
72
+ def initialize(base, sub, dims)
73
+ @base = base
74
+ @sub = sub
75
+ @dims = dims
76
+ end
77
+
78
+ def ==(another_type)
79
+ base == another_type.base &&
80
+ sub == another_type.sub &&
81
+ dims == another_type.dims
82
+ end
83
+
84
+ ##
85
+ # Get the static size of a type, or nil if dynamic.
86
+ #
87
+ # @return [Integer, NilClass] size of static type, or nil for dynamic
88
+ # type
89
+ #
90
+ def size
91
+ @size ||= if dims.empty?
92
+ if %w(string bytes).include?(base) && sub.empty?
93
+ nil
94
+ else
95
+ 32
96
+ end
97
+ else
98
+ if dims.last == 0 # 0 for dynamic array []
99
+ nil
100
+ else
101
+ subtype.dynamic? ? nil : dims.last * subtype.size
102
+ end
103
+ end
104
+ end
105
+
106
+ def dynamic?
107
+ size.nil?
108
+ end
109
+
110
+ ##
111
+ # Type with one dimension lesser.
112
+ #
113
+ # @example
114
+ # Type.parse("uint256[2][]").subtype # => Type.new('uint', 256, [2])
115
+ #
116
+ # @return [Ethereum::ABI::Type]
117
+ #
118
+ def subtype
119
+ @subtype ||= self.class.new(base, sub, dims[0...-1])
120
+ end
121
+
122
+ end
123
+
124
+ class Tuple < Type
125
+
126
+ def self.parse types, dims
127
+
128
+ depth = 0
129
+ collected = []
130
+ current = ''
131
+
132
+ types.split('').each do |c|
133
+ case c
134
+ when ',' then
135
+ if depth==0
136
+ collected << current
137
+ current = ''
138
+ else
139
+ current += c
140
+ end
141
+ when '(' then
142
+ depth += 1
143
+ current += c
144
+ when ')' then
145
+ depth -= 1
146
+ current += c
147
+ else
148
+ current += c
149
+ end
150
+
151
+ end
152
+ collected << current unless current.empty?
153
+
154
+ Tuple.new collected, dims.map {|x| x[1...-1].to_i}
155
+
156
+ end
157
+
158
+ attr_reader :types, :parsed_types
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
+
192
+
193
+ end
194
+
195
+ def subtype
196
+ @subtype ||= Tuple.new(types, dims[0...-1])
197
+ end
198
+
199
+ end
200
+
201
+
202
+ end # module Abi
203
+ end # module Ethlite
@@ -0,0 +1,227 @@
1
+ module Ethlite
2
+ module Abi
3
+ module Utils
4
+
5
+ extend self
6
+
7
+ include Constant
8
+
9
+ ##
10
+ # Not the keccak in sha3, although it's underlying lib named SHA3
11
+ #
12
+ def keccak256(x)
13
+ # Digest::SHA3.new(256).digest(x)
14
+ ## Digest::Keccak.digest(x, 256)
15
+ Digest::Keccak256.digest( x )
16
+ end
17
+
18
+ =begin
19
+ ## check where required / in use - what for?
20
+ def keccak512(x)
21
+ # Digest::SHA3.new(512).digest(x)
22
+ Digest::Keccak.digest(x, 512)
23
+ end
24
+ =end
25
+
26
+ def keccak256_rlp(x)
27
+ keccak256 RLP.encode(x)
28
+ end
29
+
30
+ def sha256(x)
31
+ Digest::SHA256.digest x
32
+ end
33
+
34
+ def double_sha256(x)
35
+ sha256 sha256(x)
36
+ end
37
+
38
+ def ripemd160(x)
39
+ Digest::RMD160.digest x
40
+ end
41
+
42
+ def hash160(x)
43
+ ripemd160 sha256(x)
44
+ end
45
+
46
+ def hash160_hex(x)
47
+ encode_hex hash160(x)
48
+ end
49
+
50
+ def mod_exp(x, y, n)
51
+ x.to_bn.mod_exp(y, n).to_i
52
+ end
53
+
54
+ def mod_mul(x, y, n)
55
+ x.to_bn.mod_mul(y, n).to_i
56
+ end
57
+
58
+ def to_signed(i)
59
+ i > Constant::INT_MAX ? (i-Constant::TT256) : i
60
+ end
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
+
77
+ def ceil32(x)
78
+ x % 32 == 0 ? x : (x + 32 - x%32)
79
+ end
80
+
81
+ def encode_hex(b)
82
+ RLP::Utils.encode_hex b
83
+ end
84
+
85
+ def decode_hex(s)
86
+ RLP::Utils.decode_hex s
87
+ end
88
+
89
+ def big_endian_to_int(s)
90
+ RLP::Sedes.big_endian_int.deserialize s.sub(/\A(\x00)+/, '')
91
+ end
92
+
93
+ def int_to_big_endian(n)
94
+ RLP::Sedes.big_endian_int.serialize n
95
+ end
96
+
97
+ def lpad(x, symbol, l)
98
+ return x if x.size >= l
99
+ symbol * (l - x.size) + x
100
+ end
101
+
102
+ def rpad(x, symbol, l)
103
+ return x if x.size >= l
104
+ x + symbol * (l - x.size)
105
+ end
106
+
107
+ def zpad(x, l)
108
+ lpad x, BYTE_ZERO, l
109
+ end
110
+
111
+ def zunpad(x)
112
+ x.sub /\A\x00+/, ''
113
+ end
114
+
115
+ def zpad_int(n, l=32)
116
+ zpad encode_int(n), l
117
+ end
118
+
119
+ def zpad_hex(s, l=32)
120
+ zpad decode_hex(s), l
121
+ end
122
+
123
+ def int_to_addr(x)
124
+ zpad_int x, 20
125
+ end
126
+
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
+
137
+ def bytearray_to_int(arr)
138
+ o = 0
139
+ arr.each {|x| o = (o << 8) + x }
140
+ o
141
+ end
142
+
143
+ def int_array_to_bytes(arr)
144
+ arr.pack('C*')
145
+ end
146
+
147
+ def bytes_to_int_array(bytes)
148
+ bytes.unpack('C*')
149
+ end
150
+
151
+ def coerce_to_int(x)
152
+ if x.is_a?(Numeric)
153
+ x
154
+ elsif x.size == 40
155
+ big_endian_to_int decode_hex(x)
156
+ else
157
+ big_endian_to_int x
158
+ end
159
+ end
160
+
161
+ def coerce_to_bytes(x)
162
+ if x.is_a?(Numeric)
163
+ int_to_big_endian x
164
+ elsif x.size == 40
165
+ decode_hex(x)
166
+ else
167
+ x
168
+ end
169
+ end
170
+
171
+ def coerce_addr_to_hex(x)
172
+ if x.is_a?(Numeric)
173
+ encode_hex zpad(int_to_big_endian(x), 20)
174
+ elsif x.size == 40 || x.size == 0
175
+ x
176
+ else
177
+ encode_hex zpad(x, 20)[-20..-1]
178
+ end
179
+ end
180
+
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
+
195
+
196
+ def parse_int_or_hex(s)
197
+ if s.is_a?(Numeric)
198
+ s
199
+ elsif s[0,2] == '0x'
200
+ big_endian_to_int decode_hex(normalize_hex_without_prefix(s))
201
+ else
202
+ s.to_i
203
+ end
204
+ end
205
+
206
+ def normalize_hex_without_prefix(s)
207
+ if s[0,2] == '0x'
208
+ (s.size % 2 == 1 ? '0' : '') + s[2..-1]
209
+ else
210
+ s
211
+ end
212
+ end
213
+
214
+ def function_signature method_name, arg_types
215
+ "#{method_name}(#{arg_types.join(',')})"
216
+ end
217
+
218
+ def signature_hash signature, length=64
219
+ encode_hex(keccak256(signature))[0...length]
220
+ end
221
+
222
+
223
+ end
224
+
225
+ end # module Abi
226
+ end # module Ethlite
227
+
@@ -0,0 +1,64 @@
1
+ module Ethlite
2
+ class ContractMethod
3
+ include Utility
4
+
5
+ attr_reader :abi,
6
+ :signature,
7
+ :name,
8
+ :signature_hash,
9
+ :input_types,
10
+ :output_types,
11
+ :constant
12
+
13
+ def initialize( abi )
14
+ ## convenience helper - auto-convert to json if string passed in
15
+ abi = JSON.parse( abi ) if abi.is_a?( String )
16
+
17
+ @abi = abi
18
+ @name = abi['name']
19
+ @constant = !!abi['constant'] || abi['stateMutability']=='view'
20
+ @input_types = abi['inputs'] ? abi['inputs'].map{|a| parse_component_type a } : []
21
+ @output_types = abi['outputs'] ? abi['outputs'].map{|a| parse_component_type a } : nil
22
+ @signature = Abi::Utils.function_signature( @name, @input_types )
23
+ @signature_hash = Abi::Utils.signature_hash( @signature, abi['type']=='event' ? 64 : 8)
24
+ end
25
+
26
+ def parse_component_type( argument )
27
+ if argument['type'] =~ /^tuple((\[[0-9]*\])*)/
28
+ argument['components'] ? "(#{argument['components'].collect{|c| parse_component_type( c ) }.join(',')})#{$1}"
29
+ : "()#{$1}"
30
+ else
31
+ argument['type']
32
+ end
33
+ end
34
+
35
+
36
+ def do_call( rpc, contract_address, args )
37
+ data = '0x' + @signature_hash + Abi::Utils.encode_hex(
38
+ Abi::AbiCoder.encode_abi(@input_types, args) )
39
+
40
+ method = 'eth_call'
41
+ params = [{ to: contract_address,
42
+ data: data},
43
+ 'latest']
44
+ response = rpc.request( method, params )
45
+
46
+
47
+ puts "response:"
48
+ pp response
49
+
50
+ string_data = [remove_0x_head(response)].pack('H*')
51
+ return nil if string_data.empty?
52
+
53
+ result = Abi::AbiCoder.decode_abi( @output_types, string_data )
54
+ puts
55
+ puts "result decode_abi:"
56
+ pp result
57
+
58
+
59
+ result.length==1 ? result.first : result
60
+ end
61
+
62
+ end # class ContractMethod
63
+ end # module Ethlite
64
+
@@ -0,0 +1,48 @@
1
+ module Ethlite
2
+
3
+ class Rpc
4
+ def initialize( uri )
5
+ @client_id = Random.rand( 10000000 )
6
+
7
+ @uri = URI.parse( uri )
8
+ end
9
+
10
+ def request( method, params=[] )
11
+ opts = {}
12
+ if @uri.instance_of?( URI::HTTPS )
13
+ opts[:use_ssl] = true
14
+ opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
15
+ end
16
+
17
+ Net::HTTP.start( @uri.host, @uri.port, **opts ) do |http|
18
+ headers = {"Content-Type" => "application/json"}
19
+ request = Net::HTTP::Post.new( @uri.request_uri, headers )
20
+
21
+ json = { jsonrpc: '2.0',
22
+ method: method,
23
+ params: params,
24
+ id: @client_id }.to_json
25
+
26
+ puts "json POST payload:"
27
+ puts json
28
+
29
+ request.body = json
30
+ response = http.request( request )
31
+
32
+ raise "Error code #{response.code} on request #{@uri.to_s} #{request.body}" unless response.kind_of?( Net::HTTPOK )
33
+
34
+ body = JSON.parse(response.body, max_nesting: 1500)
35
+
36
+ if body['result']
37
+ body['result']
38
+ elsif body['error']
39
+ raise "Error #{@uri.to_s} #{body['error']} on request #{@uri.to_s} #{request.body}"
40
+ else
41
+ raise "No response on request #{@uri.to_s} #{request.body}"
42
+ end
43
+ end
44
+ end
45
+ end # class Rpc
46
+
47
+ end # module Ethlite
48
+
@@ -0,0 +1,23 @@
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
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Ethlite
4
4
  MAJOR = 0
5
- MINOR = 1
6
- PATCH = 0
5
+ MINOR = 2
6
+ PATCH = 1
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
data/lib/ethlite.rb CHANGED
@@ -1,9 +1,40 @@
1
1
  require 'cocos'
2
2
 
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'net/https'
6
+ require 'json'
7
+
8
+
9
+ require 'openssl'
10
+ require 'digest'
11
+
12
+ ## 3rd party gems
13
+ require 'rlp' ## gem rlp - see https://rubygems.org/gems/rlp
14
+
15
+ ## bundled require 'digest/keccak' ## gem keccak - see https://rubygems.org/gems/keccak
16
+ require 'digest/keccak256'
17
+
18
+
19
+
20
+
3
21
  ## our own code
4
22
  require_relative 'ethlite/version' # note: let version always go first
5
23
 
6
24
 
25
+ require_relative 'ethlite/abi/type'
26
+ require_relative 'ethlite/abi/constant'
27
+ require_relative 'ethlite/abi/exceptions'
28
+ require_relative 'ethlite/abi/utils'
29
+ require_relative 'ethlite/abi/abi_coder'
30
+
31
+
32
+ require_relative 'ethlite/rpc'
33
+
34
+ require_relative 'ethlite/utility'
35
+ require_relative 'ethlite/contract'
36
+
37
+
7
38
 
8
39
  ## add convenience alternate spelling
9
40
  EthLite = Ethlite
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethlite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rlp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rdoc
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,7 +86,17 @@ files:
72
86
  - Manifest.txt
73
87
  - README.md
74
88
  - Rakefile
89
+ - lib/digest/keccak256.rb
90
+ - lib/digest/sha3.rb
75
91
  - lib/ethlite.rb
92
+ - lib/ethlite/abi/abi_coder.rb
93
+ - lib/ethlite/abi/constant.rb
94
+ - lib/ethlite/abi/exceptions.rb
95
+ - lib/ethlite/abi/type.rb
96
+ - lib/ethlite/abi/utils.rb
97
+ - lib/ethlite/contract.rb
98
+ - lib/ethlite/rpc.rb
99
+ - lib/ethlite/utility.rb
76
100
  - lib/ethlite/version.rb
77
101
  homepage: https://github.com/pixelartexchange/artbase
78
102
  licenses: