block_given 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -1
- data/README.md +92 -55
- data/lib/block_given/abi/codec/decoder.rb +111 -0
- data/lib/block_given/abi/codec.rb +170 -0
- data/lib/block_given/abi/coder.rb +122 -11
- data/lib/block_given/abi/custom_error.rb +33 -1
- data/lib/block_given/abi/event.rb +85 -3
- data/lib/block_given/abi/function.rb +94 -2
- data/lib/block_given/abi/interface.rb +99 -4
- data/lib/block_given/abi/parameter.rb +60 -3
- data/lib/block_given/abi/standards/erc1155.rb +41 -0
- data/lib/block_given/abi/standards/erc20.rb +33 -0
- data/lib/block_given/abi/standards/erc4626.rb +41 -0
- data/lib/block_given/abi/standards/erc721.rb +47 -0
- data/lib/block_given/abi/standards.rb +124 -0
- data/lib/block_given/abi/type.rb +138 -0
- data/lib/block_given/chain.rb +114 -2
- data/lib/block_given/client.rb +322 -20
- data/lib/block_given/configuration.rb +85 -3
- data/lib/block_given/connectors/alchemy.rb +38 -4
- data/lib/block_given/connectors/base.rb +32 -5
- data/lib/block_given/connectors/http.rb +91 -5
- data/lib/block_given/connectors/stub.rb +70 -4
- data/lib/block_given/contract.rb +422 -26
- data/lib/block_given/crypto/keccak.rb +152 -0
- data/lib/block_given/crypto/secp256k1.rb +168 -0
- data/lib/block_given/crypto.rb +22 -0
- data/lib/block_given/eip712.rb +199 -0
- data/lib/block_given/errors.rb +134 -14
- data/lib/block_given/event.rb +51 -1
- data/lib/block_given/normalizer.rb +27 -2
- data/lib/block_given/poller.rb +177 -15
- data/lib/block_given/receipt.rb +63 -3
- data/lib/block_given/rlp.rb +146 -0
- data/lib/block_given/signed_transaction.rb +148 -47
- data/lib/block_given/transaction.rb +103 -12
- data/lib/block_given/transaction_envelope/fields.rb +104 -0
- data/lib/block_given/transaction_envelope.rb +183 -0
- data/lib/block_given/utils.rb +143 -9
- data/lib/block_given/version.rb +2 -1
- data/lib/block_given/wallet.rb +216 -35
- data/lib/block_given.rb +67 -4
- metadata +19 -23
data/lib/block_given/errors.rb
CHANGED
|
@@ -1,24 +1,62 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module BlockGiven
|
|
4
|
-
# Base class for every error raised by BlockGiven.
|
|
4
|
+
# Base class for every error raised by BlockGiven; rescue it to catch anything coming out of the gem.
|
|
5
5
|
class Error < StandardError; end
|
|
6
6
|
|
|
7
|
+
# Raised when the global configuration is missing or invalid: no connector, no chain, no RPC URL, a chain
|
|
8
|
+
# unknown to {Chains.resolve}, or a chain Alchemy does not serve.
|
|
7
9
|
class ConfigurationError < Error; end
|
|
10
|
+
|
|
11
|
+
# Raised when a method receives a value it cannot use: malformed hex or decimal, unknown block tag,
|
|
12
|
+
# unexpected keyword on a contract method, duplicate or unknown watcher id.
|
|
8
13
|
class InvalidArgumentError < Error; end
|
|
14
|
+
|
|
15
|
+
# Raised when a value is not a 20-byte `0x` hex address (see {Utils.checksum_address}).
|
|
9
16
|
class InvalidAddressError < InvalidArgumentError; end
|
|
17
|
+
|
|
18
|
+
# Raised when a contract write (or its preparation) is attempted on a contract without a wallet.
|
|
10
19
|
class WalletRequiredError < Error; end
|
|
20
|
+
|
|
21
|
+
# Raised when a blocking poll, such as waiting for a transaction receipt, exceeds its timeout.
|
|
11
22
|
class TimeoutError < Error; end
|
|
12
23
|
|
|
24
|
+
# Base class for ABI problems: missing or unreadable ABI file, unknown function or event.
|
|
13
25
|
class AbiError < Error; end
|
|
26
|
+
|
|
27
|
+
# Raised when a value cannot be ABI-encoded for its type: out-of-bounds integer, `bytesN` value longer than
|
|
28
|
+
# N bytes, wrong array length, malformed address.
|
|
29
|
+
class AbiEncodingError < AbiError; end
|
|
30
|
+
|
|
31
|
+
# Raised when data cannot be ABI-decoded for the expected types: too short, or an offset or length pointing
|
|
32
|
+
# outside of it.
|
|
33
|
+
class AbiDecodingError < AbiError; end
|
|
34
|
+
|
|
35
|
+
# Raised when an ABI has no function matching the requested name, signature or argument list.
|
|
14
36
|
class FunctionNotFoundError < AbiError; end
|
|
37
|
+
|
|
38
|
+
# Raised when an ABI has no event with the requested name.
|
|
15
39
|
class EventNotFoundError < AbiError; end
|
|
40
|
+
|
|
41
|
+
# Raised when a function name is overloaded and several overloads accept the given arguments; call it with the
|
|
42
|
+
# full signature instead.
|
|
16
43
|
class AmbiguousFunctionError < AbiError; end
|
|
17
44
|
|
|
18
|
-
# Transport-level failure
|
|
45
|
+
# Transport-level failure: non-2xx HTTP status, connection refused, invalid JSON body or exhausted retries.
|
|
46
|
+
#
|
|
47
|
+
# Endpoints in the message are redacted so API keys never leak into logs.
|
|
48
|
+
#
|
|
49
|
+
# @!attribute [r] status
|
|
50
|
+
# @return [Integer, nil] HTTP status code when the server answered, nil for connection failures
|
|
51
|
+
# @!attribute [r] body
|
|
52
|
+
# @return [String, nil] raw response body when one was received (truncated in the message, complete here)
|
|
19
53
|
class HttpError < Error
|
|
20
54
|
attr_reader :status, :body
|
|
21
55
|
|
|
56
|
+
# @param message [String] human readable description
|
|
57
|
+
# @param status [Integer, nil] HTTP status code
|
|
58
|
+
# @param body [String, nil] raw response body
|
|
59
|
+
# @return [HttpError]
|
|
22
60
|
def initialize(message, status: nil, body: nil)
|
|
23
61
|
super(message)
|
|
24
62
|
@status = status
|
|
@@ -26,10 +64,25 @@ module BlockGiven
|
|
|
26
64
|
end
|
|
27
65
|
end
|
|
28
66
|
|
|
29
|
-
# JSON-RPC error object returned by the node.
|
|
67
|
+
# JSON-RPC error object (`{ code, message, data }`) returned by the node for a request that reached it.
|
|
68
|
+
#
|
|
69
|
+
# Reverts are surfaced as the {ContractRevertError} subclass by {.from_payload}, which connectors use to turn
|
|
70
|
+
# payloads into exceptions.
|
|
71
|
+
#
|
|
72
|
+
# @!attribute [r] code
|
|
73
|
+
# @return [Integer, nil] JSON-RPC error code (`-32000` for generic node errors, `3` for execution reverted)
|
|
74
|
+
# @!attribute [r] data
|
|
75
|
+
# @return [String, Hash, nil] raw `error.data` field, untouched, as the provider returned it
|
|
76
|
+
# @!attribute [r] rpc_method
|
|
77
|
+
# @return [String, nil] JSON-RPC method whose call failed (`"eth_call"`, `"eth_sendRawTransaction"`)
|
|
30
78
|
class RpcError < Error
|
|
31
79
|
attr_reader :code, :data, :rpc_method
|
|
32
80
|
|
|
81
|
+
# @param message [String] `error.message` from the payload
|
|
82
|
+
# @param code [Integer, nil] `error.code`
|
|
83
|
+
# @param data [String, Hash, nil] `error.data`
|
|
84
|
+
# @param rpc_method [String, nil] JSON-RPC method that was called
|
|
85
|
+
# @return [RpcError]
|
|
33
86
|
def initialize(message, code: nil, data: nil, rpc_method: nil)
|
|
34
87
|
super(message)
|
|
35
88
|
@code = code
|
|
@@ -37,8 +90,18 @@ module BlockGiven
|
|
|
37
90
|
@rpc_method = rpc_method
|
|
38
91
|
end
|
|
39
92
|
|
|
40
|
-
#
|
|
41
|
-
#
|
|
93
|
+
# Build the most specific error for a JSON-RPC error payload.
|
|
94
|
+
#
|
|
95
|
+
# Payloads carrying `0x` revert bytes in `data` (or nested under `data.data`), or whose message mentions
|
|
96
|
+
# "revert", become a {ContractRevertError} with the revert bytes attached; anything else becomes a plain
|
|
97
|
+
# {RpcError}. A payload that is not a Hash is wrapped with its string form as message.
|
|
98
|
+
#
|
|
99
|
+
# @param error [Hash, Object] JSON-RPC `error` object with String keys `"code"`, `"message"` and `"data"`
|
|
100
|
+
# @param rpc_method [String, nil] method that was called, kept on the error for context
|
|
101
|
+
# @return [BlockGiven::RpcError, BlockGiven::ContractRevertError]
|
|
102
|
+
# @example
|
|
103
|
+
# BlockGiven::RpcError.from_payload({ "code" => 3, "message" => "execution reverted", "data" => "0x08c3..." })
|
|
104
|
+
# # => #<BlockGiven::ContractRevertError: execution reverted: insufficient balance>
|
|
42
105
|
def self.from_payload(error, rpc_method: nil)
|
|
43
106
|
error = { "message" => error.to_s } unless error.is_a?(Hash)
|
|
44
107
|
code = error["code"]
|
|
@@ -53,8 +116,14 @@ module BlockGiven
|
|
|
53
116
|
end
|
|
54
117
|
end
|
|
55
118
|
|
|
56
|
-
#
|
|
57
|
-
#
|
|
119
|
+
# Extract the `0x` revert bytes from an error `data` field.
|
|
120
|
+
#
|
|
121
|
+
# Providers disagree on where the revert bytes live: Alchemy/geth put them in `data`, Hardhat/Anvil nest
|
|
122
|
+
# them under `data.data`.
|
|
123
|
+
#
|
|
124
|
+
# @api private
|
|
125
|
+
# @param data [String, Hash, nil] raw `error.data`
|
|
126
|
+
# @return [String, nil] `0x` hex string, or nil when no revert bytes are present
|
|
58
127
|
def self.extract_revert_data(data)
|
|
59
128
|
case data
|
|
60
129
|
when String then data.start_with?("0x") ? data : nil
|
|
@@ -63,10 +132,27 @@ module BlockGiven
|
|
|
63
132
|
end
|
|
64
133
|
end
|
|
65
134
|
|
|
66
|
-
# eth_call
|
|
135
|
+
# `eth_call`, `eth_estimateGas` or `eth_sendRawTransaction` rejected by the EVM with a revert.
|
|
136
|
+
#
|
|
137
|
+
# The message is rewritten to `"execution reverted: <reason>"` when the revert data is a Solidity
|
|
138
|
+
# `Error(string)` or `Panic(uint256)`. Custom errors (`error InsufficientBalance(uint256)`) are decoded on
|
|
139
|
+
# demand with {#decode_with} against a contract ABI; {Contract} and {SignedTransaction} do this automatically
|
|
140
|
+
# before re-raising.
|
|
141
|
+
#
|
|
142
|
+
# @!attribute [r] revert_data
|
|
143
|
+
# @return [String, nil] ABI-encoded revert bytes as a `0x` hex string (4-byte selector followed by the
|
|
144
|
+
# encoded arguments), nil when the node returned none
|
|
145
|
+
# @!attribute [r] error_name
|
|
146
|
+
# @return [String, nil] name of the custom error, set once decoded with {#decode_with}
|
|
147
|
+
# @!attribute [r] args
|
|
148
|
+
# @return [Hash{Symbol => Object}, nil] custom error arguments keyed by snake_case name, set once decoded
|
|
149
|
+
# with {#decode_with}
|
|
67
150
|
class ContractRevertError < RpcError
|
|
151
|
+
# 4-byte selector of Solidity's built-in `Error(string)`, the revert with a reason string.
|
|
68
152
|
ERROR_STRING_SELECTOR = "0x08c379a0"
|
|
153
|
+
# 4-byte selector of Solidity's built-in `Panic(uint256)` (assertion failures, overflows, ...).
|
|
69
154
|
PANIC_SELECTOR = "0x4e487b71"
|
|
155
|
+
# Human readable description of each Solidity panic code carried by `Panic(uint256)`.
|
|
70
156
|
PANIC_REASONS = {
|
|
71
157
|
0x00 => "generic compiler inserted panic",
|
|
72
158
|
0x01 => "assertion failed",
|
|
@@ -82,6 +168,15 @@ module BlockGiven
|
|
|
82
168
|
|
|
83
169
|
attr_reader :revert_data, :error_name, :args
|
|
84
170
|
|
|
171
|
+
# @param message [String] message from the RPC error payload; replaced by the decoded built-in reason when
|
|
172
|
+
# one is found and the original does not already contain it, or by `"execution reverted"` when empty
|
|
173
|
+
# @param code [Integer, nil] JSON-RPC error code
|
|
174
|
+
# @param data [String, Hash, nil] raw `error.data`
|
|
175
|
+
# @param rpc_method [String, nil] JSON-RPC method that was called
|
|
176
|
+
# @param revert_data [String, nil] `0x` revert bytes
|
|
177
|
+
# @param error_name [String, nil] decoded custom error name (set by {#decode_with})
|
|
178
|
+
# @param args [Hash{Symbol => Object}, nil] decoded custom error arguments (set by {#decode_with})
|
|
179
|
+
# @return [ContractRevertError]
|
|
85
180
|
def initialize(message, code: nil, data: nil, rpc_method: nil, revert_data: nil, error_name: nil, args: nil)
|
|
86
181
|
@revert_data = revert_data
|
|
87
182
|
@error_name = error_name
|
|
@@ -89,23 +184,43 @@ module BlockGiven
|
|
|
89
184
|
super(build_message(message), code: code, data: data, rpc_method: rpc_method)
|
|
90
185
|
end
|
|
91
186
|
|
|
92
|
-
# Human readable revert reason when
|
|
187
|
+
# Human readable revert reason when the revert data is a built-in `Error(string)` or `Panic(uint256)`.
|
|
188
|
+
#
|
|
189
|
+
# Memoised. Custom errors and undecodable data yield nil; use {#decode_with} for custom errors.
|
|
190
|
+
#
|
|
191
|
+
# @return [String, nil] the `Error(string)` text, or `"Panic(0x11): arithmetic overflow or underflow"`
|
|
93
192
|
def reason
|
|
94
193
|
return @reason if defined?(@reason)
|
|
95
194
|
|
|
96
195
|
@reason = decode_builtin_reason
|
|
97
196
|
end
|
|
98
197
|
|
|
198
|
+
# 4-byte selector at the start of the revert data.
|
|
199
|
+
#
|
|
200
|
+
# @return [String, nil] `0x` followed by 8 hex chars, nil without revert data
|
|
99
201
|
def selector
|
|
100
202
|
revert_data && revert_data.length >= 10 ? revert_data[0, 10] : nil
|
|
101
203
|
end
|
|
102
204
|
|
|
205
|
+
# Whether the revert data is a custom error, i.e. its selector is neither `Error(string)` nor `Panic`.
|
|
206
|
+
#
|
|
207
|
+
# @return [Boolean]
|
|
103
208
|
def custom_error?
|
|
104
209
|
!selector.nil? && ![ERROR_STRING_SELECTOR, PANIC_SELECTOR].include?(selector)
|
|
105
210
|
end
|
|
106
211
|
|
|
107
|
-
#
|
|
108
|
-
#
|
|
212
|
+
# Return a copy of this error enriched with a custom error decoded from an ABI interface.
|
|
213
|
+
#
|
|
214
|
+
# @param interface [BlockGiven::Abi::Interface, nil] interface whose custom errors are matched by selector
|
|
215
|
+
# @return [BlockGiven::ContractRevertError] a new error whose message is `Name(arg1, arg2)` with
|
|
216
|
+
# {#error_name} and {#args} set; `self` when the data is not a custom error, no interface is given or the
|
|
217
|
+
# interface does not know the selector
|
|
218
|
+
# @example
|
|
219
|
+
# begin
|
|
220
|
+
# token.transfer(to, amount)
|
|
221
|
+
# rescue BlockGiven::ContractRevertError => e
|
|
222
|
+
# e.decode_with(token.interface).error_name # => "ERC20InsufficientBalance"
|
|
223
|
+
# end
|
|
109
224
|
def decode_with(interface)
|
|
110
225
|
return self unless custom_error? && interface
|
|
111
226
|
|
|
@@ -135,9 +250,9 @@ module BlockGiven
|
|
|
135
250
|
payload = "0x#{revert_data[10..]}"
|
|
136
251
|
case selector
|
|
137
252
|
when ERROR_STRING_SELECTOR
|
|
138
|
-
|
|
253
|
+
Abi::Codec.decode(["string"], Utils.hex_to_bin(payload)).first
|
|
139
254
|
when PANIC_SELECTOR
|
|
140
|
-
panic_code =
|
|
255
|
+
panic_code = Abi::Codec.decode(["uint256"], Utils.hex_to_bin(payload)).first
|
|
141
256
|
"Panic(0x#{panic_code.to_s(16).rjust(2, '0')}): #{PANIC_REASONS.fetch(panic_code, 'unknown panic')}"
|
|
142
257
|
end
|
|
143
258
|
rescue StandardError
|
|
@@ -145,10 +260,15 @@ module BlockGiven
|
|
|
145
260
|
end
|
|
146
261
|
end
|
|
147
262
|
|
|
148
|
-
# Raised by Transaction#wait! when the mined receipt has a failed status.
|
|
263
|
+
# Raised by {Transaction#wait!} when the mined receipt has a failed status (`status == 0`).
|
|
264
|
+
#
|
|
265
|
+
# @!attribute [r] receipt
|
|
266
|
+
# @return [BlockGiven::Receipt] the mined receipt carrying the failed status
|
|
149
267
|
class TransactionRevertedError < Error
|
|
150
268
|
attr_reader :receipt
|
|
151
269
|
|
|
270
|
+
# @param receipt [BlockGiven::Receipt] receipt whose `transaction_hash` and `block_number` build the message
|
|
271
|
+
# @return [TransactionRevertedError]
|
|
152
272
|
def initialize(receipt)
|
|
153
273
|
@receipt = receipt
|
|
154
274
|
super("transaction #{receipt.transaction_hash} reverted in block #{receipt.block_number}")
|
data/lib/block_given/event.rb
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module BlockGiven
|
|
4
|
-
# A decoded event
|
|
4
|
+
# A log decoded against an ABI event definition (see `Abi::Event#decode`).
|
|
5
5
|
#
|
|
6
|
+
# {#args} holds the decoded parameters keyed by their snake_case name, in ABI declaration order (indexed and
|
|
7
|
+
# non-indexed inputs interleaved as declared). Indexed parameters of dynamic type (`string`, `bytes`,
|
|
8
|
+
# arrays, tuples) cannot be recovered from a log: their value is the keccak-256 topic hash as `0x` hex.
|
|
9
|
+
# {#log} keeps the raw normalized log the event was decoded from.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
6
12
|
# event.name # => "Transfer"
|
|
7
13
|
# event.args # => { from: "0x...", to: "0x...", value: 1000000 }
|
|
8
14
|
# event[:value] # => 1000000
|
|
15
|
+
# event["value"] # => 1000000 (String keys are snake_cased and symbolized)
|
|
9
16
|
# event.block_number # => 12345
|
|
10
17
|
class Event
|
|
18
|
+
# @!attribute [r] name
|
|
19
|
+
# @return [String] the event name as declared in the ABI, e.g. `"Transfer"`
|
|
20
|
+
# @!attribute [r] signature
|
|
21
|
+
# @return [String] the canonical signature, e.g. `"Transfer(address,address,uint256)"`
|
|
22
|
+
# @!attribute [r] args
|
|
23
|
+
# @return [Hash{Symbol => Object}] decoded parameters keyed by snake_case name, in ABI declaration order;
|
|
24
|
+
# addresses checksummed, integers as Integer, static bytes as `0x` hex, indexed dynamic types as their
|
|
25
|
+
# topic hash
|
|
26
|
+
# @!attribute [r] log
|
|
27
|
+
# @return [Hash{Symbol => Object}] the raw normalized log (`:address`, `:topics`, `:data`, `:block_number`,
|
|
28
|
+
# `:transaction_hash`, `:log_index`, `:removed`...)
|
|
11
29
|
attr_reader :name, :signature, :args, :log
|
|
12
30
|
|
|
31
|
+
# Builds a decoded event. Instances are normally created by `Abi::Event#decode` rather than directly.
|
|
32
|
+
#
|
|
33
|
+
# @param name [String] the event name
|
|
34
|
+
# @param signature [String] the canonical event signature
|
|
35
|
+
# @param args [Hash{Symbol => Object}] the decoded parameters, keyed by snake_case Symbol
|
|
36
|
+
# @param log [Hash{Symbol => Object}] the normalized log the event was decoded from
|
|
13
37
|
def initialize(name:, signature:, args:, log:)
|
|
14
38
|
@name = name
|
|
15
39
|
@signature = signature
|
|
@@ -17,20 +41,46 @@ module BlockGiven
|
|
|
17
41
|
@log = log
|
|
18
42
|
end
|
|
19
43
|
|
|
44
|
+
# Reads a decoded parameter by name.
|
|
45
|
+
#
|
|
46
|
+
# @param key [Symbol, String] the parameter name, in camelCase or snake_case (`:tokenId`, `"token_id"`...)
|
|
47
|
+
# @return [Object, nil] the decoded value, or nil when the event has no such parameter
|
|
20
48
|
def [](key) = args[Utils.snake_case(key).to_sym]
|
|
49
|
+
|
|
50
|
+
# @return [String, nil] the EIP-55 checksummed address of the contract that emitted the log, or nil when
|
|
51
|
+
# the log carries no address
|
|
21
52
|
def address = log[:address] && Utils.checksum_address(log[:address])
|
|
53
|
+
|
|
54
|
+
# @return [Integer, nil] the number of the block containing the log (nil for a pending log)
|
|
22
55
|
def block_number = log[:block_number]
|
|
56
|
+
|
|
57
|
+
# @return [String, nil] the hash of the containing block as `0x` hex
|
|
23
58
|
def block_hash = log[:block_hash]
|
|
59
|
+
|
|
60
|
+
# @return [String, nil] the hash of the emitting transaction as `0x` hex
|
|
24
61
|
def transaction_hash = log[:transaction_hash]
|
|
62
|
+
|
|
63
|
+
# @return [Integer, nil] the position of the emitting transaction in its block
|
|
25
64
|
def transaction_index = log[:transaction_index]
|
|
65
|
+
|
|
66
|
+
# @return [Integer, nil] the position of the log in its block
|
|
26
67
|
def log_index = log[:log_index]
|
|
68
|
+
|
|
69
|
+
# @return [Boolean] true when the node flagged the log as removed by a chain reorganisation
|
|
27
70
|
def removed? = !!log[:removed]
|
|
28
71
|
|
|
72
|
+
# A compact Hash view, handy for persistence or logging.
|
|
73
|
+
#
|
|
74
|
+
# @return [Hash{Symbol => Object}] `:name`, `:args`, `:address` (checksummed), `:block_number`,
|
|
75
|
+
# `:transaction_hash` and `:log_index`
|
|
29
76
|
def to_h
|
|
30
77
|
{ name: name, args: args, address: address, block_number: block_number,
|
|
31
78
|
transaction_hash: transaction_hash, log_index: log_index }
|
|
32
79
|
end
|
|
33
80
|
|
|
81
|
+
# Debug representation with the name, decoded args and block number.
|
|
82
|
+
#
|
|
83
|
+
# @return [String] e.g. `#<BlockGiven::Event Transfer {:from=>"0x...", ...} block=123>`
|
|
34
84
|
def inspect = "#<BlockGiven::Event #{name} #{args.inspect} block=#{block_number}>"
|
|
35
85
|
end
|
|
36
86
|
end
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module BlockGiven
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# Convert raw JSON-RPC objects (blocks, transactions, receipts, logs) into Ruby-friendly hashes.
|
|
5
|
+
#
|
|
6
|
+
# Keys become snake_case symbols (`"blockNumber"` -> `:block_number`) and the fields listed in
|
|
7
|
+
# {QUANTITY_KEYS} are decoded from hex QUANTITY strings to Integers. Nested hashes and arrays are normalised
|
|
8
|
+
# recursively, except a log's `topics`, which stay an Array of `0x` hex strings. Addresses, hashes and `data`
|
|
9
|
+
# fields are left exactly as the node returned them. {Client} runs every block, transaction, receipt and log
|
|
10
|
+
# through this module before handing it back.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# BlockGiven::Normalizer.normalize({ "blockNumber" => "0x10", "logIndex" => "0x0", "topics" => ["0xddf2"] })
|
|
14
|
+
# # => { block_number: 16, log_index: 0, topics: ["0xddf2"] }
|
|
6
15
|
module Normalizer
|
|
7
16
|
module_function
|
|
8
17
|
|
|
18
|
+
# Snake_case keys whose values are JSON-RPC QUANTITY hex strings, decoded to Integers by {.normalize}.
|
|
19
|
+
#
|
|
20
|
+
# Covers block, transaction and receipt fields, EIP-4844 blob fields, and the L1 fee fields added by
|
|
21
|
+
# OP Stack chains (Base, Optimism).
|
|
9
22
|
QUANTITY_KEYS = %i[
|
|
10
23
|
number timestamp gas_used gas_limit base_fee_per_gas block_number transaction_index log_index
|
|
11
24
|
cumulative_gas_used effective_gas_price status value gas gas_price max_fee_per_gas
|
|
@@ -14,6 +27,11 @@ module BlockGiven
|
|
|
14
27
|
deposit_nonce deposit_receipt_version
|
|
15
28
|
].freeze
|
|
16
29
|
|
|
30
|
+
# Normalise a raw RPC value recursively.
|
|
31
|
+
#
|
|
32
|
+
# @param value [Hash, Array, Object] a Hash is rebuilt with snake_case Symbol keys and normalised values,
|
|
33
|
+
# an Array is mapped element by element, anything else is returned untouched
|
|
34
|
+
# @return [Hash{Symbol => Object}, Array, Object]
|
|
17
35
|
def normalize(value)
|
|
18
36
|
case value
|
|
19
37
|
when Hash then value.each_with_object({}) do |(k, v), h|
|
|
@@ -24,6 +42,13 @@ module BlockGiven
|
|
|
24
42
|
end
|
|
25
43
|
end
|
|
26
44
|
|
|
45
|
+
# Normalise one field of an RPC hash, given its already snake_cased key.
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
48
|
+
# @param key [Symbol] snake_case key
|
|
49
|
+
# @param value [Object] raw value
|
|
50
|
+
# @return [Object] an Integer for a {QUANTITY_KEYS} key holding hex (nil when the node returned an empty
|
|
51
|
+
# `"0x"`), the untouched Array for `:topics`, otherwise {.normalize} of the value
|
|
27
52
|
def normalize_field(key, value)
|
|
28
53
|
if QUANTITY_KEYS.include?(key) && Utils.hex?(value)
|
|
29
54
|
value == "0x" ? nil : Utils.hex_to_int(value)
|