abicoder 0.1.0 → 1.0.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.
@@ -0,0 +1,158 @@
1
+ module ABI
2
+ class Type
3
+
4
+ class ParseError < StandardError; end
5
+
6
+
7
+ ## nested inside Type - why? why not?
8
+ class Parser
9
+
10
+ TUPLE_TYPE_RX = /^\((.*)\)
11
+ ((\[[0-9]*\])*)
12
+ /x
13
+
14
+ def self.parse( type )
15
+ if type =~ TUPLE_TYPE_RX
16
+ types = _parse_tuple_type( $1 )
17
+ dims = _parse_dims( $2 )
18
+
19
+ parsed_types = types.map{ |t| parse( t ) }
20
+
21
+ return _parse_array_type( Tuple.new( parsed_types ), dims )
22
+ end
23
+
24
+ base, sub, dims = _parse_base_type( type )
25
+
26
+ _validate_base_type( base, sub )
27
+
28
+ subtype = case base
29
+ when 'string' then String.new
30
+ when 'bytes' then sub ? FixedBytes.new( sub ) : Bytes.new
31
+ when 'uint' then Uint.new( sub )
32
+ when 'int' then Int.new( sub )
33
+ when 'address' then Address.new
34
+ when 'bool' then Bool.new
35
+ else
36
+ ## puts " type: >#{type}<"
37
+ raise ParseError, "Unrecognized type base: #{base}"
38
+ end
39
+
40
+ _parse_array_type( subtype, dims )
41
+ end
42
+
43
+ ##
44
+ # Crazy regexp to seperate out base type component (eg. uint), size (eg.
45
+ # 256, 128, nil), array component (eg. [], [45], nil)
46
+ #
47
+ BASE_TYPE_RX = /([a-z]*)
48
+ ([0-9]*)
49
+ ((\[[0-9]*\])*)
50
+ /x
51
+
52
+ def self._parse_base_type( str )
53
+ _, base, subscript, dimension = BASE_TYPE_RX.match( str ).to_a
54
+
55
+ ## note: use [Integer,Integer] array in the future for sub
56
+ ## for fixed (e.g. 128x128 => [128,128]) or such
57
+ ## for now always assume single integer (as string)
58
+ sub = subscript == '' ? nil : subscript.to_i
59
+
60
+ ## e.g. turn "[][1][2]" into [-1,1,2]
61
+ ## or "" into [] -- that is, empty array
62
+ dims = _parse_dims( dimension )
63
+
64
+ [base, sub, dims]
65
+ end
66
+
67
+ def self._parse_dims( str )
68
+ dims = str.scan( /\[[0-9]*\]/ )
69
+
70
+ ## note: return -1 for dynamic array size e.g. []
71
+ ## e.g. "[]"[1...-1] => ""
72
+ ## "[0]"[1...-1] => "0"
73
+ ## "[1]"[1...-1] => "1"
74
+ dims = dims.map do |dim|
75
+ size = dim[1...-1]
76
+ size == '' ? -1 : size.to_i
77
+ end
78
+ dims
79
+ end
80
+
81
+ def self._parse_array_type( subtype, dims )
82
+ ##
83
+ ## todo/check - double check if the order in reverse
84
+ ## in solidity / abi encoding / decoding?
85
+ ##
86
+ dims.each do |dim|
87
+ subtype = if dim == -1
88
+ Array.new( subtype )
89
+ else
90
+ FixedArray.new( subtype, dim )
91
+ end
92
+ end
93
+
94
+ subtype
95
+ end
96
+
97
+
98
+ def self._validate_base_type( base, sub )
99
+ case base
100
+ when 'string'
101
+ # note: string can not have any suffix
102
+ raise ParseError, "String cannot have suffix" if sub
103
+ when 'bytes'
104
+ raise ParseError, "Maximum 32 bytes for fixed-length bytes" if sub && sub > 32
105
+ when 'uint', 'int'
106
+ raise ParseError, "Integer type must have numerical suffix" unless sub
107
+ raise ParseError, "Integer size out of bounds" unless sub >= 8 && sub <= 256
108
+ raise ParseError, "Integer size must be multiple of 8" unless sub % 8 == 0
109
+ when 'address'
110
+ raise ParseError, "Address cannot have suffix" if sub
111
+ when 'bool'
112
+ raise ParseError, "Bool cannot have suffix" if sub
113
+ else
114
+ ## puts " type: >#{type}<"
115
+ raise ParseError, "Unrecognized type base: #{base}"
116
+ end
117
+ end
118
+
119
+
120
+
121
+ def self._parse_tuple_type( str )
122
+ ## note: types assumes string WITHOUT enclosing () e.g.
123
+ ## tuple(string,string,bool) => expected as "string,string,bool"
124
+
125
+ depth = 0
126
+ collected = []
127
+ current = ''
128
+
129
+ ### todo/fix: replace with a simple parser!!!
130
+ ## allow () and move verbose tuple() too!!!
131
+ str.each_char do |c|
132
+ case c
133
+ when ',' then
134
+ if depth == 0
135
+ collected << current
136
+ current = ''
137
+ else
138
+ current += c
139
+ end
140
+ when '(' then
141
+ depth += 1
142
+ current += c
143
+ when ')' then
144
+ depth -= 1
145
+ current += c
146
+ else
147
+ current += c
148
+ end
149
+ end
150
+ collected << current unless current.empty?
151
+
152
+ collected
153
+ end
154
+
155
+ end # class Parser
156
+ end # class Type
157
+ end # module ABI
158
+
@@ -0,0 +1,155 @@
1
+ module ABI
2
+
3
+
4
+ #######
5
+ ## for now use (get inspired)
6
+ ## by the type names used by abi coder in rust
7
+ ## see https://github.com/rust-ethereum/ethabi/blob/master/ethabi/src/param_type/param_type.rs
8
+
9
+
10
+ class Type
11
+
12
+ def self.parse( type ) ## convenience helper
13
+ Parser.parse( type )
14
+ end
15
+
16
+ ##
17
+ # Get the static size of a type, or nil if dynamic.
18
+ #
19
+ # @return [Integer, NilClass] size of static type, or nil for dynamic type
20
+ #
21
+ def size
22
+ ## check/todo: what error to raise for not implemented / method not defined???
23
+ raise ArgumentError, "no required size method defined for Type subclass #{self.class.name}; sorry"
24
+ end
25
+ def dynamic?() size.nil?; end
26
+
27
+ def format
28
+ ## check/todo: what error to raise for not implemented / method not defined???
29
+ raise ArgumentError, "no required format method defined for Type subclass #{self.class.name}; sorry"
30
+ end
31
+
32
+ ####
33
+ ## default implementation
34
+ ## assume equal if class match (e.g. Address == Address)
35
+ ## - use format string for generic compare - why? why not?
36
+ end
37
+
38
+
39
+
40
+ class Address < Type
41
+ def size() 32; end ## note: address is always 20 bytes; BUT uses 32 bytes (with padding)
42
+ def format() 'address'; end
43
+ def ==(another_type) another_type.kind_of?( Address ); end
44
+ end # class Address
45
+
46
+ class Bytes < Type
47
+ def size() nil; end ## note: dynamic (not known at compile-time)
48
+ def format() 'bytes'; end
49
+ def ==(another_type) another_type.kind_of?( Bytes ); end
50
+ end # class Bytes
51
+
52
+ class FixedBytes < Type
53
+ attr_reader :length
54
+ def initialize( length )
55
+ @length = length # in bytes (1,2,...32)
56
+ end
57
+ def size() 32; end ## note: always uses 32 bytes (with padding)
58
+ def format() "bytes#{@length}"; end
59
+ def ==(another_type)
60
+ another_type.kind_of?( FixedBytes ) && @length == another_type.length
61
+ end
62
+ end # class FixedBytes
63
+
64
+ class Int < Type
65
+ attr_reader :bits
66
+ def initialize( bits=256 )
67
+ @bits = bits # in bits (8,16,...256)
68
+ end
69
+ def size() 32; end ## note: always uses 32 bytes (with padding)
70
+ def format() "int#{@bits}"; end
71
+ def ==(another_type)
72
+ another_type.kind_of?( Int ) && @bits == another_type.bits
73
+ end
74
+ end # class Int
75
+
76
+ class Uint < Type
77
+ attr_reader :bits
78
+ def initialize( bits=256 )
79
+ @bits = bits # in bits (8,16,...256)
80
+ end
81
+ def size() 32; end ## note: always uses 32 bytes (with padding)
82
+ def format() "uint#{@bits}"; end
83
+ def ==(another_type)
84
+ another_type.kind_of?( Uint ) && @bits == another_type.bits
85
+ end
86
+ end # class Uint
87
+
88
+ class Bool < Type
89
+ def size() 32; end ## note: always uses 32 bytes (with padding)
90
+ def format() 'bool'; end
91
+ def ==(another_type) another_type.kind_of?( Bool ); end
92
+ end # class Bool
93
+
94
+ class String < Type
95
+ def size() nil; end ## note: dynamic (not known at compile-time)
96
+ def format() 'string'; end
97
+ def ==(another_type) another_type.kind_of?( String ); end
98
+ end # class String
99
+
100
+ class Array < Type
101
+ attr_reader :subtype
102
+ def initialize( subtype )
103
+ @subtype = subtype
104
+ end
105
+ def size() nil; end ## note: dynamic (not known at compile-time)
106
+ def format() "#{@subtype.format}[]"; end
107
+ def ==(another_type)
108
+ another_type.kind_of?( Array ) && @subtype == another_type.subtype
109
+ end
110
+ end # class Array
111
+
112
+ class FixedArray < Type
113
+ attr_reader :subtype
114
+ attr_reader :dim
115
+ def initialize( subtype, dim )
116
+ @subtype = subtype
117
+ @dim = dim
118
+ end
119
+
120
+ def size
121
+ @subtype.dynamic? ? nil : @dim * subtype.size
122
+ end
123
+ def format() "#{@subtype.format}[#{@dim}]"; end
124
+ def ==(another_type)
125
+ another_type.kind_of?( FixedArray ) &&
126
+ @dim == another_type.dim &&
127
+ @subtype == another_type.subtype
128
+ end
129
+ end # class FixedArray
130
+
131
+ class Tuple < Type
132
+ attr_reader :types
133
+
134
+ def initialize( types )
135
+ @types = types
136
+ end
137
+
138
+ def size
139
+ s = 0
140
+ @types.each do |type|
141
+ ts = type.size
142
+ return nil if ts.nil?
143
+ s += ts
144
+ end
145
+ s
146
+ end
147
+ def format
148
+ "(#{@types.map {|t| t.format }.join(',')})" ## rebuild minimal string
149
+ end
150
+ def ==(another_type)
151
+ another_type.kind_of?( Tuple ) && @types == another_type.types
152
+ end
153
+ end # class Tuple
154
+ end # module ABI
155
+
@@ -1,7 +1,7 @@
1
1
 
2
2
  module ABICoder
3
- MAJOR = 0
4
- MINOR = 1
3
+ MAJOR = 1
4
+ MINOR = 0
5
5
  PATCH = 0
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
data/lib/abicoder.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  ## our own code
4
4
  require_relative 'abicoder/version' # note: let version always go first
5
5
 
6
+
6
7
  module ABI
7
8
  ###################
8
9
  ### some (shared) constants (move to constants.rb or such - why? why not?)
@@ -12,34 +13,55 @@ module ABI
12
13
  ## todo/fix: move BYTE_EMPTY, BYTE_ZERO, BYTE_ONE to upstream to bytes gem
13
14
  ## and make "global" constants - why? why not?
14
15
 
15
- BYTE_EMPTY = "".b.freeze
16
+ ## BYTE_EMPTY = "".b.freeze
16
17
  BYTE_ZERO = "\x00".b.freeze
17
18
  BYTE_ONE = "\x01".b.freeze ## note: used for encoding bool for now
18
19
 
19
20
 
20
- UINT_MAX = 2**256 - 1
21
+ UINT_MAX = 2**256 - 1 ## same as 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
21
22
  UINT_MIN = 0
22
- INT_MAX = 2**255 - 1
23
- INT_MIN = -2**255
24
-
23
+ INT_MAX = 2**255 - 1 ## same as 57896044618658097711785492504343953926634992332820282019728792003956564819967
24
+ INT_MIN = -2**255 ## same as -57896044618658097711785492504343953926634992332820282019728792003956564819968
25
25
 
26
- ### todo/check - what is TT the abbrevation for ???
27
- TT32 = 2**32
28
- TT40 = 2**40 ## todo/check - used where and why???
29
- TT160 = 2**160 ## todo/check - used where and why???
30
- TT256 = 2**256 ## todo/check - used where and why???
31
- TT64M1 = 2**64 - 1 ## todo/check - used where and why???
32
26
  end # module ABI
33
27
 
34
- require_relative 'abicoder/utils'
35
28
 
29
+ require_relative 'abicoder/types'
30
+ require_relative 'abicoder/parser'
31
+
32
+
33
+ require_relative 'abicoder/encoder'
34
+ require_relative 'abicoder/decoder'
35
+
36
+
37
+ module ABI
38
+ def self.encoder
39
+ @encoder ||= Encoder.new
40
+ end
41
+ def self.decoder
42
+ @decoder ||= Decoder.new
43
+ end
44
+
45
+
46
+ def self.encode( types, args )
47
+ encoder.encode( types, args )
48
+ end
36
49
 
37
- require_relative 'abicoder/type'
38
- require_relative 'abicoder/type_tuple'
50
+ def self.decode( types, data, raise_errors = false )
51
+ decoder.decode( types, data, raise_errors )
52
+ end
39
53
 
40
- require_relative 'abicoder/codec'
54
+ ## add alternate _abi names - why? why not?
55
+ class << self
56
+ alias_method :encode_abi, :encode
57
+ alias_method :decode_abi, :decode
58
+ end
59
+ end ## module ABI
41
60
 
42
61
 
62
+ ################
63
+ ## add convenience alternate spellings - why? why not?
64
+ Abi = ABI
43
65
 
44
66
 
45
67
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abicoder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.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-12-30 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc
@@ -45,8 +45,8 @@ dependencies:
45
45
  - !ruby/object:Gem::Version
46
46
  version: '3.23'
47
47
  description: abicoder - 'lite' application binary interface (abi) encoding / decoding
48
- machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies
49
- for easy (re)use
48
+ machinery / helper (incl. nested arrays and/or tuples) for Ethereum & Co. (blockchain)
49
+ contracts with zero-dependencies for easy (re)use
50
50
  email: wwwmake@googlegroups.com
51
51
  executables: []
52
52
  extensions: []
@@ -60,10 +60,10 @@ files:
60
60
  - README.md
61
61
  - Rakefile
62
62
  - lib/abicoder.rb
63
- - lib/abicoder/codec.rb
64
- - lib/abicoder/type.rb
65
- - lib/abicoder/type_tuple.rb
66
- - lib/abicoder/utils.rb
63
+ - lib/abicoder/decoder.rb
64
+ - lib/abicoder/encoder.rb
65
+ - lib/abicoder/parser.rb
66
+ - lib/abicoder/types.rb
67
67
  - lib/abicoder/version.rb
68
68
  homepage: https://github.com/rubycocos/blockchain
69
69
  licenses:
@@ -90,6 +90,6 @@ rubygems_version: 3.3.7
90
90
  signing_key:
91
91
  specification_version: 4
92
92
  summary: abicoder - 'lite' application binary interface (abi) encoding / decoding
93
- machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies
94
- for easy (re)use
93
+ machinery / helper (incl. nested arrays and/or tuples) for Ethereum & Co. (blockchain)
94
+ contracts with zero-dependencies for easy (re)use
95
95
  test_files: []