ethlite 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: