eth 0.5.14 → 0.5.16
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 +4 -4
- data/.github/workflows/codeql.yml +4 -4
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/spec.yml +31 -13
- data/CHANGELOG.md +53 -0
- data/CODE_OF_CONDUCT.md +3 -5
- data/Gemfile +3 -3
- data/README.md +8 -6
- data/SECURITY.md +2 -2
- data/eth.gemspec +10 -1
- data/lib/eth/abi/decoder.rb +94 -39
- data/lib/eth/abi/encoder.rb +85 -58
- data/lib/eth/abi/function.rb +124 -0
- data/lib/eth/abi/type.rb +69 -5
- data/lib/eth/abi.rb +2 -0
- data/lib/eth/bls.rb +68 -0
- data/lib/eth/chain.rb +3 -0
- data/lib/eth/client/http.rb +5 -8
- data/lib/eth/client/ws.rb +323 -0
- data/lib/eth/client.rb +44 -40
- data/lib/eth/contract/error.rb +62 -0
- data/lib/eth/contract/function.rb +21 -0
- data/lib/eth/contract/function_output.rb +11 -3
- data/lib/eth/contract.rb +55 -4
- data/lib/eth/eip712.rb +48 -13
- data/lib/eth/key.rb +1 -1
- data/lib/eth/tx/eip1559.rb +32 -7
- data/lib/eth/tx/eip2930.rb +31 -6
- data/lib/eth/tx/eip4844.rb +401 -0
- data/lib/eth/tx/eip7702.rb +34 -9
- data/lib/eth/tx/legacy.rb +30 -6
- data/lib/eth/tx.rb +45 -4
- data/lib/eth/util.rb +19 -7
- data/lib/eth/version.rb +1 -1
- data/lib/eth.rb +1 -0
- metadata +53 -15
data/lib/eth/abi/encoder.rb
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
# -*- encoding : ascii-8bit -*-
|
|
16
|
+
require "bigdecimal"
|
|
16
17
|
|
|
17
18
|
# Provides the {Eth} module.
|
|
18
19
|
module Eth
|
|
@@ -33,55 +34,18 @@ module Eth
|
|
|
33
34
|
def type(type, arg)
|
|
34
35
|
if %w(string bytes).include? type.base_type and type.sub_type.empty? and type.dimensions.empty?
|
|
35
36
|
raise EncodingError, "Argument must be a String" unless arg.instance_of? String
|
|
37
|
+
arg = handle_hex_string arg, type
|
|
36
38
|
|
|
37
39
|
# encodes strings and bytes
|
|
38
40
|
size = type Type.size_type, arg.size
|
|
39
41
|
padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
|
|
40
42
|
"#{size}#{arg}#{padding}"
|
|
41
|
-
elsif type.base_type == "tuple" && type.dimensions.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
result
|
|
46
|
-
elsif type.dynamic? && arg.is_a?(Array)
|
|
47
|
-
|
|
48
|
-
# encodes dynamic-sized arrays
|
|
49
|
-
head, tail = "", ""
|
|
50
|
-
head += type(Type.size_type, arg.size)
|
|
51
|
-
nested_sub = type.nested_sub
|
|
52
|
-
|
|
53
|
-
# calculate offsets
|
|
54
|
-
if %w(string bytes).include?(type.base_type) && type.sub_type.empty?
|
|
55
|
-
offset = 0
|
|
56
|
-
arg.size.times do |i|
|
|
57
|
-
if i == 0
|
|
58
|
-
offset = arg.size * 32
|
|
59
|
-
else
|
|
60
|
-
number_of_words = ((arg[i - 1].size + 32 - 1) / 32).floor
|
|
61
|
-
total_bytes_length = number_of_words * 32
|
|
62
|
-
offset += total_bytes_length + 32
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
head += type(Type.size_type, offset)
|
|
66
|
-
end
|
|
67
|
-
elsif nested_sub.base_type == "tuple" && nested_sub.dynamic?
|
|
68
|
-
head += struct_offsets(nested_sub, arg)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
arg.size.times do |i|
|
|
72
|
-
head += type nested_sub, arg[i]
|
|
73
|
-
end
|
|
74
|
-
"#{head}#{tail}"
|
|
43
|
+
elsif type.base_type == "tuple" && type.dimensions.empty?
|
|
44
|
+
tuple arg, type
|
|
45
|
+
elsif !type.dimensions.empty?
|
|
46
|
+
encode_array type, arg
|
|
75
47
|
else
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# encode a primitive type
|
|
79
|
-
primitive_type type, arg
|
|
80
|
-
else
|
|
81
|
-
|
|
82
|
-
# encode static-size arrays
|
|
83
|
-
arg.map { |x| type(type.nested_sub, x) }.join
|
|
84
|
-
end
|
|
48
|
+
primitive_type type, arg
|
|
85
49
|
end
|
|
86
50
|
end
|
|
87
51
|
|
|
@@ -122,6 +86,7 @@ module Eth
|
|
|
122
86
|
|
|
123
87
|
# Properly encodes unsigned integers.
|
|
124
88
|
def uint(arg, type)
|
|
89
|
+
arg = coerce_number arg
|
|
125
90
|
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
|
126
91
|
raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::UINT_MAX or arg < Constant::UINT_MIN
|
|
127
92
|
real_size = type.sub_type.to_i
|
|
@@ -132,6 +97,7 @@ module Eth
|
|
|
132
97
|
|
|
133
98
|
# Properly encodes signed integers.
|
|
134
99
|
def int(arg, type)
|
|
100
|
+
arg = coerce_number arg
|
|
135
101
|
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
|
136
102
|
raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::INT_MAX or arg < Constant::INT_MIN
|
|
137
103
|
real_size = type.sub_type.to_i
|
|
@@ -148,6 +114,7 @@ module Eth
|
|
|
148
114
|
|
|
149
115
|
# Properly encodes unsigned fixed-point numbers.
|
|
150
116
|
def ufixed(arg, type)
|
|
117
|
+
arg = coerce_number arg
|
|
151
118
|
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
|
152
119
|
high, low = type.sub_type.split("x").map(&:to_i)
|
|
153
120
|
raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high
|
|
@@ -156,6 +123,7 @@ module Eth
|
|
|
156
123
|
|
|
157
124
|
# Properly encodes signed fixed-point numbers.
|
|
158
125
|
def fixed(arg, type)
|
|
126
|
+
arg = coerce_number arg
|
|
159
127
|
raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
|
|
160
128
|
high, low = type.sub_type.split("x").map(&:to_i)
|
|
161
129
|
raise ValueOutOfBounds, arg unless arg >= -2 ** (high - 1) and arg < 2 ** (high - 1)
|
|
@@ -185,8 +153,11 @@ module Eth
|
|
|
185
153
|
|
|
186
154
|
# Properly encodes tuples.
|
|
187
155
|
def tuple(arg, type)
|
|
188
|
-
|
|
156
|
+
unless arg.is_a?(Hash) || arg.is_a?(Array)
|
|
157
|
+
raise EncodingError, "Expecting Hash or Array: #{arg}"
|
|
158
|
+
end
|
|
189
159
|
raise EncodingError, "Expecting #{type.components.size} elements: #{arg}" unless arg.size == type.components.size
|
|
160
|
+
arg = arg.transform_keys(&:to_s) if arg.is_a?(Hash) # because component_type.name is String
|
|
190
161
|
|
|
191
162
|
static_size = 0
|
|
192
163
|
type.components.each_with_index do |component, i|
|
|
@@ -209,28 +180,84 @@ module Eth
|
|
|
209
180
|
dynamic_values << dynamic_value
|
|
210
181
|
dynamic_offset += dynamic_value.size
|
|
211
182
|
else
|
|
212
|
-
offsets_and_static_values << type(component_type, arg.is_a?(Array) ? arg[i] : arg
|
|
183
|
+
offsets_and_static_values << type(component_type, arg.is_a?(Array) ? arg[i] : arg.fetch(component_type.name))
|
|
213
184
|
end
|
|
214
185
|
end
|
|
215
186
|
|
|
216
187
|
offsets_and_static_values.join + dynamic_values.join
|
|
217
188
|
end
|
|
218
189
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
190
|
+
def coerce_number(arg)
|
|
191
|
+
return arg if arg.is_a? Numeric
|
|
192
|
+
return arg.to_i(0) if arg.is_a?(String) && arg.match?(/^-?(0x)?[0-9a-fA-F]+$/)
|
|
193
|
+
return BigDecimal(arg) if arg.is_a?(String) && arg.match?(/^-?\d+(\.\d+)?$/)
|
|
194
|
+
arg
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Encodes array values of any dimensionality.
|
|
198
|
+
#
|
|
199
|
+
# @param type [Eth::Abi::Type] the type describing the array.
|
|
200
|
+
# @param values [Array] the Ruby values to encode.
|
|
201
|
+
# @return [String] ABI encoded array payload.
|
|
202
|
+
# @raise [EncodingError] if the value cardinality does not match static dimensions.
|
|
203
|
+
def encode_array(type, values)
|
|
204
|
+
raise EncodingError, "Expecting Array value" unless values.is_a?(Array)
|
|
205
|
+
|
|
206
|
+
required_length = type.dimensions.last
|
|
207
|
+
if required_length != 0 && values.size != required_length
|
|
208
|
+
raise EncodingError, "Expecting #{required_length} elements: #{values.size} provided"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
nested_sub = type.nested_sub
|
|
212
|
+
|
|
213
|
+
if required_length.zero?
|
|
214
|
+
encode_dynamic_array(nested_sub, values)
|
|
215
|
+
else
|
|
216
|
+
encode_static_array(nested_sub, values)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Encodes dynamic-sized arrays, including nested tuples.
|
|
221
|
+
#
|
|
222
|
+
# @param nested_sub [Eth::Abi::Type] the element type.
|
|
223
|
+
# @param values [Array] elements to encode.
|
|
224
|
+
# @return [String] ABI encoded dynamic array payload.
|
|
225
|
+
def encode_dynamic_array(nested_sub, values)
|
|
226
|
+
head = type(Type.size_type, values.size)
|
|
227
|
+
element_heads, element_tails = encode_array_elements(nested_sub, values)
|
|
228
|
+
head + element_heads + element_tails
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Encodes static-sized arrays, including nested tuples.
|
|
232
|
+
#
|
|
233
|
+
# @param nested_sub [Eth::Abi::Type] the element type.
|
|
234
|
+
# @param values [Array] elements to encode.
|
|
235
|
+
# @return [String] ABI encoded static array payload.
|
|
236
|
+
def encode_static_array(nested_sub, values)
|
|
237
|
+
element_heads, element_tails = encode_array_elements(nested_sub, values)
|
|
238
|
+
element_heads + element_tails
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Encodes the head/tail portions for array elements.
|
|
242
|
+
#
|
|
243
|
+
# @param nested_sub [Eth::Abi::Type] the element type.
|
|
244
|
+
# @param values [Array] elements to encode.
|
|
245
|
+
# @return [Array<String, String>] head/tail encoded segments.
|
|
246
|
+
def encode_array_elements(nested_sub, values)
|
|
247
|
+
if nested_sub.dynamic?
|
|
248
|
+
head = ""
|
|
249
|
+
tail = ""
|
|
250
|
+
offset = values.size * 32
|
|
251
|
+
values.each do |value|
|
|
252
|
+
encoded = type(nested_sub, value)
|
|
253
|
+
head += type(Type.size_type, offset)
|
|
254
|
+
tail += encoded
|
|
255
|
+
offset += encoded.size
|
|
229
256
|
end
|
|
230
|
-
|
|
231
|
-
|
|
257
|
+
[head, tail]
|
|
258
|
+
else
|
|
259
|
+
[values.map { |value| type(nested_sub, value) }.join, ""]
|
|
232
260
|
end
|
|
233
|
-
result
|
|
234
261
|
end
|
|
235
262
|
|
|
236
263
|
# Properly encodes hash-strings.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright (c) 2016-2025 The Ruby-Eth Contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# -*- encoding : ascii-8bit -*-
|
|
16
|
+
|
|
17
|
+
# Provides the {Eth} module.
|
|
18
|
+
module Eth
|
|
19
|
+
|
|
20
|
+
# Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI).
|
|
21
|
+
module Abi
|
|
22
|
+
|
|
23
|
+
# Provides a module to decode transaction input data.
|
|
24
|
+
module Function
|
|
25
|
+
extend self
|
|
26
|
+
|
|
27
|
+
# Build function signature string from ABI interface.
|
|
28
|
+
#
|
|
29
|
+
# @param interface [Hash] ABI function interface.
|
|
30
|
+
# @return [String] interface signature string.
|
|
31
|
+
def signature(interface)
|
|
32
|
+
name = interface.fetch("name")
|
|
33
|
+
inputs = interface.fetch("inputs", [])
|
|
34
|
+
types = inputs.map { |i| type(i) }
|
|
35
|
+
"#{name}(#{types.join(",")})"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Compute selector for ABI function interface.
|
|
39
|
+
#
|
|
40
|
+
# @param interface [Hash] ABI function interface.
|
|
41
|
+
# @return [String] a hex-string selector.
|
|
42
|
+
def selector(interface)
|
|
43
|
+
sig = signature(interface)
|
|
44
|
+
Util.prefix_hex(Util.bin_to_hex(Util.keccak256(sig))[0, 8])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Gets the input type for functions.
|
|
48
|
+
#
|
|
49
|
+
# @param input [Hash] function input.
|
|
50
|
+
# @return [String] input type.
|
|
51
|
+
def type(input)
|
|
52
|
+
if input["type"] == "tuple"
|
|
53
|
+
"(#{input["components"].map { |c| type(c) }.join(",")})"
|
|
54
|
+
elsif input["type"] == "enum"
|
|
55
|
+
"uint8"
|
|
56
|
+
else
|
|
57
|
+
input["type"]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# A decoded function call.
|
|
62
|
+
class CallDescription
|
|
63
|
+
# The function ABI interface used to decode the call.
|
|
64
|
+
attr_accessor :function_interface
|
|
65
|
+
|
|
66
|
+
# The positional arguments of the call.
|
|
67
|
+
attr_accessor :args
|
|
68
|
+
|
|
69
|
+
# The named arguments of the call.
|
|
70
|
+
attr_accessor :kwargs
|
|
71
|
+
|
|
72
|
+
# The function selector.
|
|
73
|
+
attr_accessor :selector
|
|
74
|
+
|
|
75
|
+
# Creates a description object for a decoded function call.
|
|
76
|
+
#
|
|
77
|
+
# @param function_interface [Hash] function ABI type.
|
|
78
|
+
# @param selector [String] function selector hex-string.
|
|
79
|
+
# @param args [Array] decoded positional arguments.
|
|
80
|
+
# @param kwargs [Hash] decoded keyword arguments.
|
|
81
|
+
def initialize(function_interface, selector, args, kwargs)
|
|
82
|
+
@function_interface = function_interface
|
|
83
|
+
@selector = selector
|
|
84
|
+
@args = args
|
|
85
|
+
@kwargs = kwargs
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The function name. (e.g. transfer)
|
|
89
|
+
def name
|
|
90
|
+
@name ||= function_interface.fetch("name")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The function signature. (e.g. transfer(address,uint256))
|
|
94
|
+
def signature
|
|
95
|
+
@signature ||= Function.signature(function_interface)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Decodes a transaction input with a set of ABI interfaces.
|
|
100
|
+
#
|
|
101
|
+
# @param interfaces [Array] function ABI types.
|
|
102
|
+
# @param data [String] transaction input data.
|
|
103
|
+
# @return [CallDescription, nil] a CallDescription object or nil if selector unknown.
|
|
104
|
+
def decode(interfaces, data)
|
|
105
|
+
data = Util.remove_hex_prefix(data)
|
|
106
|
+
selector = Util.prefix_hex(data[0, 8])
|
|
107
|
+
payload = Util.prefix_hex(data[8..] || "")
|
|
108
|
+
|
|
109
|
+
selector_to_interfaces = Hash[interfaces.map { |i| [selector(i), i] }]
|
|
110
|
+
if (interface = selector_to_interfaces[selector])
|
|
111
|
+
inputs = interface.fetch("inputs", [])
|
|
112
|
+
types = inputs.map { |i| type(i) }
|
|
113
|
+
args = Abi.decode(types, payload)
|
|
114
|
+
kwargs = {}
|
|
115
|
+
inputs.each_with_index do |input, i|
|
|
116
|
+
name = input.fetch("name", "")
|
|
117
|
+
kwargs[name.to_sym] = args[i] unless name.empty?
|
|
118
|
+
end
|
|
119
|
+
CallDescription.new(interface, selector, args, kwargs)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/eth/abi/type.rb
CHANGED
|
@@ -80,10 +80,27 @@ module Eth
|
|
|
80
80
|
return
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
# ensure the type string is reasonable before attempting to parse
|
|
84
|
+
raise ParseError, "Invalid type format" unless type.is_a? String
|
|
85
|
+
|
|
86
|
+
if type.start_with?("tuple(") || type.start_with?("(")
|
|
87
|
+
tuple_str = type.start_with?("tuple(") ? type : "tuple#{type}"
|
|
88
|
+
inner, rest = extract_tuple(tuple_str)
|
|
89
|
+
inner_types = split_tuple_types(inner)
|
|
90
|
+
inner_types.each { |t| Type.parse(t) }
|
|
91
|
+
base_type = "tuple"
|
|
92
|
+
sub_type = ""
|
|
93
|
+
dimension = rest
|
|
94
|
+
components ||= inner_types.map { |t| { "type" => t } }
|
|
95
|
+
else
|
|
96
|
+
match = /\A([a-z]+)([0-9]*x?[0-9]*)((?:\[\d+\]|\[\])*)\z/.match(type)
|
|
97
|
+
raise ParseError, "Invalid type format" unless match
|
|
98
|
+
_, base_type, sub_type, dimension = match.to_a
|
|
99
|
+
sub_type = "256" if %w[uint int].include?(base_type) && sub_type.empty?
|
|
100
|
+
end
|
|
84
101
|
|
|
85
|
-
# type dimension can only be numeric
|
|
86
|
-
dims = dimension.scan(/\[[
|
|
102
|
+
# type dimension can only be numeric or empty for dynamic arrays
|
|
103
|
+
dims = dimension.scan(/\[\d+\]|\[\]/)
|
|
87
104
|
raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
|
|
88
105
|
|
|
89
106
|
# enforce base types
|
|
@@ -93,7 +110,7 @@ module Eth
|
|
|
93
110
|
sub_type = sub_type.to_s
|
|
94
111
|
@base_type = base_type
|
|
95
112
|
@sub_type = sub_type
|
|
96
|
-
@dimensions = dims.map { |x| x[1...-1].to_i }
|
|
113
|
+
@dimensions = dims.map { |x| x == "[]" ? 0 : x[1...-1].to_i }
|
|
97
114
|
@components = components.map { |component| Abi::Type.parse(component["type"], component.dig("components"), component.dig("name")) } if components&.any?
|
|
98
115
|
@name = component_name
|
|
99
116
|
end
|
|
@@ -123,7 +140,7 @@ module Eth
|
|
|
123
140
|
if dimensions.empty?
|
|
124
141
|
if !(["string", "bytes", "tuple"].include?(base_type) and sub_type.empty?)
|
|
125
142
|
s = 32
|
|
126
|
-
elsif base_type == "tuple" and components
|
|
143
|
+
elsif base_type == "tuple" and components&.none?(&:dynamic?)
|
|
127
144
|
s = components.sum(&:size)
|
|
128
145
|
end
|
|
129
146
|
elsif dimensions.last != 0 and !nested_sub.dynamic?
|
|
@@ -215,6 +232,53 @@ module Eth
|
|
|
215
232
|
raise ParseError, "Unknown base type"
|
|
216
233
|
end
|
|
217
234
|
end
|
|
235
|
+
|
|
236
|
+
# Extracts the inner type list and trailing dimensions from an inline tuple definition.
|
|
237
|
+
def extract_tuple(type)
|
|
238
|
+
idx = 6 # skip "tuple("
|
|
239
|
+
depth = 1
|
|
240
|
+
while idx < type.length && depth > 0
|
|
241
|
+
case type[idx]
|
|
242
|
+
when "("
|
|
243
|
+
depth += 1
|
|
244
|
+
when ")"
|
|
245
|
+
depth -= 1
|
|
246
|
+
end
|
|
247
|
+
idx += 1
|
|
248
|
+
end
|
|
249
|
+
raise ParseError, "Invalid tuple format" unless depth.zero?
|
|
250
|
+
inner = type[6...(idx - 1)]
|
|
251
|
+
rest = type[idx..] || ""
|
|
252
|
+
[inner, rest]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Splits a tuple component list into individual type strings, handling nested tuples.
|
|
256
|
+
def split_tuple_types(str)
|
|
257
|
+
types = []
|
|
258
|
+
depth = 0
|
|
259
|
+
current = ""
|
|
260
|
+
str.each_char do |ch|
|
|
261
|
+
case ch
|
|
262
|
+
when "("
|
|
263
|
+
depth += 1
|
|
264
|
+
current << ch
|
|
265
|
+
when ")"
|
|
266
|
+
depth -= 1
|
|
267
|
+
current << ch
|
|
268
|
+
when ","
|
|
269
|
+
if depth.zero?
|
|
270
|
+
types << current
|
|
271
|
+
current = ""
|
|
272
|
+
else
|
|
273
|
+
current << ch
|
|
274
|
+
end
|
|
275
|
+
else
|
|
276
|
+
current << ch
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
types << current unless current.empty?
|
|
280
|
+
types
|
|
281
|
+
end
|
|
218
282
|
end
|
|
219
283
|
end
|
|
220
284
|
end
|
data/lib/eth/abi.rb
CHANGED
|
@@ -44,6 +44,7 @@ module Eth
|
|
|
44
44
|
return solidity_packed(types, args) if packed
|
|
45
45
|
types = [types] unless types.instance_of? Array
|
|
46
46
|
args = [args] unless args.instance_of? Array
|
|
47
|
+
raise ArgumentError, "Types and values must be the same length" if types.length != args.length
|
|
47
48
|
|
|
48
49
|
# parse all types
|
|
49
50
|
parsed_types = types.map { |t| Type === t ? t : Type.parse(t) }
|
|
@@ -151,4 +152,5 @@ require "eth/abi/packed/encoder"
|
|
|
151
152
|
require "eth/abi/decoder"
|
|
152
153
|
require "eth/abi/encoder"
|
|
153
154
|
require "eth/abi/event"
|
|
155
|
+
require "eth/abi/function"
|
|
154
156
|
require "eth/abi/type"
|
data/lib/eth/bls.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bls"
|
|
4
|
+
|
|
5
|
+
module Eth
|
|
6
|
+
# Helper methods for interacting with BLS12-381 points and signatures
|
|
7
|
+
module Bls
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Decode a compressed G1 public key from hex.
|
|
11
|
+
# @param [String] hex a compressed G1 point
|
|
12
|
+
# @return [BLS::PointG1]
|
|
13
|
+
def decode_public_key(hex)
|
|
14
|
+
BLS::PointG1.from_hex Util.remove_hex_prefix(hex)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Encode a G1 public key to compressed hex.
|
|
18
|
+
# @param [BLS::PointG1] point
|
|
19
|
+
# @return [String] hex string prefixed with 0x
|
|
20
|
+
def encode_public_key(point)
|
|
21
|
+
Util.prefix_hex point.to_hex(compressed: true)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Decode a compressed G2 signature from hex.
|
|
25
|
+
# @param [String] hex a compressed G2 point
|
|
26
|
+
# @return [BLS::PointG2]
|
|
27
|
+
def decode_signature(hex)
|
|
28
|
+
BLS::PointG2.from_hex Util.remove_hex_prefix(hex)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Encode a G2 signature to compressed hex.
|
|
32
|
+
# @param [BLS::PointG2] point
|
|
33
|
+
# @return [String] hex string prefixed with 0x
|
|
34
|
+
def encode_signature(point)
|
|
35
|
+
Util.prefix_hex point.to_hex(compressed: true)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Derive a compressed public key from a private key.
|
|
39
|
+
# @param [String] priv_hex private key as hex
|
|
40
|
+
# @return [String] compressed G1 public key (hex)
|
|
41
|
+
def get_public_key(priv_hex)
|
|
42
|
+
key = BLS.get_public_key Util.remove_hex_prefix(priv_hex)
|
|
43
|
+
encode_public_key key
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Sign a message digest with the given private key.
|
|
47
|
+
# @param [String] message message digest (hex)
|
|
48
|
+
# @param [String] priv_hex private key as hex
|
|
49
|
+
# @return [String] compressed G2 signature (hex)
|
|
50
|
+
def sign(message, priv_hex)
|
|
51
|
+
sig = BLS.sign Util.remove_hex_prefix(message),
|
|
52
|
+
Util.remove_hex_prefix(priv_hex)
|
|
53
|
+
encode_signature sig
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Verify a BLS signature using pairings. This mirrors the behaviour of
|
|
57
|
+
# the BLS12-381 pairing precompile.
|
|
58
|
+
# @param [String] message message digest (hex)
|
|
59
|
+
# @param [String] signature_hex compressed G2 signature (hex)
|
|
60
|
+
# @param [String] pubkey_hex compressed G1 public key (hex)
|
|
61
|
+
# @return [Boolean] verification result
|
|
62
|
+
def verify(message, signature_hex, pubkey_hex)
|
|
63
|
+
signature = decode_signature(signature_hex)
|
|
64
|
+
pubkey = decode_public_key(pubkey_hex)
|
|
65
|
+
BLS.verify(signature, Util.remove_hex_prefix(message), pubkey)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/eth/chain.rb
CHANGED
data/lib/eth/client/http.rb
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
require "
|
|
15
|
+
require "uri"
|
|
16
|
+
require "httpx"
|
|
16
17
|
|
|
17
18
|
# Provides the {Eth} module.
|
|
18
19
|
module Eth
|
|
@@ -57,6 +58,7 @@ module Eth
|
|
|
57
58
|
else
|
|
58
59
|
@uri = uri
|
|
59
60
|
end
|
|
61
|
+
@client = HTTPX.plugin(:persistent).with(headers: { "Content-Type" => "application/json" })
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
# Sends an RPC request to the connected HTTP client.
|
|
@@ -64,13 +66,8 @@ module Eth
|
|
|
64
66
|
# @param payload [Hash] the RPC request parameters.
|
|
65
67
|
# @return [String] a JSON-encoded response.
|
|
66
68
|
def send_request(payload)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
header = { "Content-Type" => "application/json" }
|
|
70
|
-
request = Net::HTTP::Post.new(@uri, header)
|
|
71
|
-
request.body = payload
|
|
72
|
-
response = http.request(request)
|
|
73
|
-
response.body
|
|
69
|
+
response = @client.post(@uri, body: payload)
|
|
70
|
+
response.body.to_s
|
|
74
71
|
end
|
|
75
72
|
end
|
|
76
73
|
|