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
|
@@ -3,21 +3,65 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
5
|
module BlockGiven
|
|
6
|
+
# ABI layer: parsed interfaces, functions, events, custom errors, parameters and value coercion.
|
|
7
|
+
#
|
|
8
|
+
# {Abi::Interface.parse} turns an ABI (Array, artifact Hash, JSON String or file) into typed objects
|
|
9
|
+
# that {BlockGiven::Contract} relies on to encode calls, decode outputs and logs, and name reverts.
|
|
6
10
|
module Abi
|
|
7
11
|
# Parsed ABI: functions (with overload resolution), events and custom errors.
|
|
12
|
+
#
|
|
13
|
+
# Functions are grouped by snake_case Ruby name so that overloads (`safeMint(address)` and
|
|
14
|
+
# `safeMint(address,bytes)`) can be resolved from the call arguments or from a full signature.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# interface = BlockGiven::Abi::Interface.parse(File.read("abis/erc20.json"))
|
|
18
|
+
# interface.function(:balance_of).encode([owner]) # => "0x70a08231..."
|
|
19
|
+
# interface.event(:Transfer).topic # => "0xddf252ad..."
|
|
20
|
+
# interface.error_by_selector("0xe450d38c") # => #<BlockGiven::Abi::CustomError ...>
|
|
8
21
|
class Interface
|
|
22
|
+
# @!attribute [r] functions
|
|
23
|
+
# @return [Array<Function>] every `function` definition, in ABI order
|
|
24
|
+
# @!attribute [r] events
|
|
25
|
+
# @return [Array<Event>] every `event` definition, in ABI order
|
|
26
|
+
# @!attribute [r] errors
|
|
27
|
+
# @return [Array<CustomError>] every `error` definition, in ABI order
|
|
28
|
+
# @!attribute [r] constructor
|
|
29
|
+
# @return [Hash, nil] the raw `constructor` definition (String keys), or `nil` when the ABI has
|
|
30
|
+
# none; it is kept for reference only and cannot be encoded through this class
|
|
31
|
+
# @!attribute [r] raw
|
|
32
|
+
# @return [Array<Hash>] the ABI definitions as given, with keys converted to Strings
|
|
9
33
|
attr_reader :functions, :events, :errors, :constructor, :raw
|
|
10
34
|
|
|
11
|
-
#
|
|
35
|
+
# Parses an ABI from any supported source.
|
|
36
|
+
#
|
|
37
|
+
# @param source [Array<Hash>, Hash, String, Symbol, Pathname, File, Interface] an ABI Array, a Hardhat/Foundry
|
|
38
|
+
# artifact Hash with an `"abi"` (or `:abi`) key, a JSON String, the Symbol name of a shipped standard
|
|
39
|
+
# ({Standards.fetch}: `:erc20`, `:erc721`, `:erc1155`, `:erc4626`), a Pathname/File to read, or an Interface
|
|
40
|
+
# (returned as is)
|
|
41
|
+
# @return [Interface]
|
|
42
|
+
# @raise [BlockGiven::AbiError] when the JSON is invalid, the Hash has no `abi` key or the source
|
|
43
|
+
# type is not supported
|
|
44
|
+
# @example
|
|
45
|
+
# BlockGiven::Abi::Interface.parse(JSON.parse(File.read("artifacts/Usdc.json")))
|
|
46
|
+
# BlockGiven::Abi::Interface.parse(Pathname.new("abis/erc20.json"))
|
|
47
|
+
# BlockGiven::Abi::Interface.parse(:erc20)
|
|
48
|
+
# BlockGiven::Abi::Interface.parse([{ "type" => "function", "name" => "decimals", ... }])
|
|
12
49
|
def self.parse(source)
|
|
13
50
|
return source if source.is_a?(Interface)
|
|
14
51
|
|
|
15
52
|
new(load_definitions(source))
|
|
16
53
|
end
|
|
17
54
|
|
|
55
|
+
# Turns a source accepted by {.parse} into an Array of raw ABI definitions.
|
|
56
|
+
#
|
|
57
|
+
# @api private
|
|
58
|
+
# @param source [Array<Hash>, Hash, String, Pathname, File] see {.parse}
|
|
59
|
+
# @return [Array<Hash>] the ABI definitions
|
|
60
|
+
# @raise [BlockGiven::AbiError] when the source cannot be turned into an ABI Array
|
|
18
61
|
def self.load_definitions(source)
|
|
19
62
|
case source
|
|
20
63
|
when Array then source
|
|
64
|
+
when Symbol then Standards.fetch(source)
|
|
21
65
|
when Hash then source["abi"] || source[:abi] || raise(AbiError, "Hash has no 'abi' key")
|
|
22
66
|
when Pathname, File then load_definitions(JSON.parse(File.read(source)))
|
|
23
67
|
when String then load_definitions(JSON.parse(source))
|
|
@@ -27,6 +71,13 @@ module BlockGiven
|
|
|
27
71
|
raise AbiError, "invalid ABI JSON: #{e.message}"
|
|
28
72
|
end
|
|
29
73
|
|
|
74
|
+
# Builds an interface from raw ABI definitions; prefer {.parse}.
|
|
75
|
+
#
|
|
76
|
+
# Definitions typed `function` (or without a `type`, as in very old ABIs) become {Function}s,
|
|
77
|
+
# `event` become {Event}s, `error` become {CustomError}s and the `constructor` is kept raw.
|
|
78
|
+
# Other types (`fallback`, `receive`) are ignored.
|
|
79
|
+
#
|
|
80
|
+
# @param definitions [Array<Hash>] ABI definitions with String or Symbol keys
|
|
30
81
|
def initialize(definitions)
|
|
31
82
|
@raw = definitions.map { |d| d.transform_keys(&:to_s) }
|
|
32
83
|
@functions = []
|
|
@@ -44,11 +95,30 @@ module BlockGiven
|
|
|
44
95
|
@functions_by_name = @functions.group_by(&:ruby_name)
|
|
45
96
|
end
|
|
46
97
|
|
|
98
|
+
# Distinct snake_case function names (overloads share one name).
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<Symbol>]
|
|
47
101
|
def function_names = @functions_by_name.keys
|
|
48
102
|
|
|
49
|
-
# Finds a function by name
|
|
50
|
-
#
|
|
51
|
-
#
|
|
103
|
+
# Finds a function by name or full signature, resolving overloads from the call arguments.
|
|
104
|
+
#
|
|
105
|
+
# `name` may be snake_case or camelCase, or a full signature such as `"transfer(address,uint256)"`
|
|
106
|
+
# (spaces are ignored). When several functions share the name, the overload is chosen by positional
|
|
107
|
+
# arity (`args.size`) or by the set of snake_cased keyword names (`kwargs.keys`).
|
|
108
|
+
#
|
|
109
|
+
# @param name [String, Symbol] function name or signature
|
|
110
|
+
# @param args [Array] positional arguments of the intended call, used to pick an overload by arity
|
|
111
|
+
# @param kwargs [Hash] keyword arguments of the intended call, used to pick an overload by names
|
|
112
|
+
# @return [Function]
|
|
113
|
+
# @raise [BlockGiven::FunctionNotFoundError] when no function has this name or signature, or when
|
|
114
|
+
# no overload matches the given arguments
|
|
115
|
+
# @raise [BlockGiven::AmbiguousFunctionError] when several overloads match the arguments
|
|
116
|
+
# @example
|
|
117
|
+
# interface.function(:balance_of)
|
|
118
|
+
# interface.function("balanceOf")
|
|
119
|
+
# interface.function(:safe_mint, args: [to, data]) # overload by arity
|
|
120
|
+
# interface.function(:safe_mint, kwargs: { to: to, data: data }) # overload by keyword names
|
|
121
|
+
# interface.function("safeMint(address,bytes)") # explicit signature
|
|
52
122
|
def function(name, args: [], kwargs: {})
|
|
53
123
|
name = name.to_s
|
|
54
124
|
return function_by_signature(name) if name.include?("(")
|
|
@@ -77,15 +147,29 @@ module BlockGiven
|
|
|
77
147
|
"no overload of #{name} matches the given arguments (#{candidates.map(&:signature).join(' | ')})"
|
|
78
148
|
end
|
|
79
149
|
|
|
150
|
+
# Finds a function by its exact canonical signature.
|
|
151
|
+
#
|
|
152
|
+
# @param signature [String] for example `"transfer(address,uint256)"`; spaces are removed
|
|
153
|
+
# @return [Function]
|
|
154
|
+
# @raise [BlockGiven::FunctionNotFoundError] when no function has this signature
|
|
80
155
|
def function_by_signature(signature)
|
|
81
156
|
@functions.find { |f| f.signature == signature.delete(" ") } ||
|
|
82
157
|
raise(FunctionNotFoundError, "no function with signature #{signature.inspect}")
|
|
83
158
|
end
|
|
84
159
|
|
|
160
|
+
# Finds a function by its 4-byte selector.
|
|
161
|
+
#
|
|
162
|
+
# @param selector [String] `0x`-prefixed 4-byte selector, any case
|
|
163
|
+
# @return [Function, nil] `nil` when unknown
|
|
85
164
|
def function_by_selector(selector)
|
|
86
165
|
@functions.find { |f| f.selector == selector.downcase }
|
|
87
166
|
end
|
|
88
167
|
|
|
168
|
+
# Finds an event by name (snake_case or camelCase) or by full signature.
|
|
169
|
+
#
|
|
170
|
+
# @param name [String, Symbol] for example `:Transfer`, `:transfer` or `"Transfer(address,address,uint256)"`
|
|
171
|
+
# @return [Event]
|
|
172
|
+
# @raise [BlockGiven::EventNotFoundError] when the ABI has no such event
|
|
89
173
|
def event(name)
|
|
90
174
|
name = name.to_s
|
|
91
175
|
key = Utils.snake_case(name).to_sym
|
|
@@ -93,14 +177,25 @@ module BlockGiven
|
|
|
93
177
|
raise(EventNotFoundError, "no event #{name.inspect} in ABI (known: #{@events.map(&:name).join(', ')})")
|
|
94
178
|
end
|
|
95
179
|
|
|
180
|
+
# Finds a non-anonymous event by its `topics[0]` hash.
|
|
181
|
+
#
|
|
182
|
+
# @param topic [String] `0x`-prefixed 32-byte keccak hash of the event signature, any case
|
|
183
|
+
# @return [Event, nil] `nil` when unknown
|
|
96
184
|
def event_by_topic(topic)
|
|
97
185
|
@events.find { |e| e.topic == topic.to_s.downcase }
|
|
98
186
|
end
|
|
99
187
|
|
|
188
|
+
# Finds a custom error by its 4-byte selector (the first 4 bytes of revert data).
|
|
189
|
+
#
|
|
190
|
+
# @param selector [String] `0x`-prefixed 4-byte selector, any case
|
|
191
|
+
# @return [CustomError, nil] `nil` when unknown
|
|
100
192
|
def error_by_selector(selector)
|
|
101
193
|
@errors.find { |e| e.selector == selector.to_s.downcase }
|
|
102
194
|
end
|
|
103
195
|
|
|
196
|
+
# Compact representation with the number of functions, events and errors.
|
|
197
|
+
#
|
|
198
|
+
# @return [String]
|
|
104
199
|
def inspect
|
|
105
200
|
"#<BlockGiven::Abi::Interface functions=#{@functions.size} events=#{@events.size} errors=#{@errors.size}>"
|
|
106
201
|
end
|
|
@@ -2,10 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module BlockGiven
|
|
4
4
|
module Abi
|
|
5
|
-
# One ABI input/output. Knows its canonical Solidity type ("(uint256,address)[]").
|
|
5
|
+
# One ABI input/output. Knows its canonical Solidity type (`"(uint256,address)[]"`).
|
|
6
|
+
#
|
|
7
|
+
# Parameters describe function inputs and outputs, event parameters, custom error arguments and
|
|
8
|
+
# tuple components. Unnamed parameters get a positional name (`arg0`, `arg1`...) so they can still
|
|
9
|
+
# be addressed, and are flagged with {#unnamed?}.
|
|
6
10
|
class Parameter
|
|
11
|
+
# @!attribute [r] name
|
|
12
|
+
# @return [String] the ABI name, or `"argN"` (N = position) when the ABI leaves it empty
|
|
13
|
+
# @!attribute [r] raw_type
|
|
14
|
+
# @return [String] the type as written in the ABI (`"uint256"`, `"tuple[]"`, `"address[2]"`)
|
|
15
|
+
# @!attribute [r] components
|
|
16
|
+
# @return [Array<Parameter>] the tuple components (empty for non-tuple types)
|
|
17
|
+
# @!attribute [r] indexed
|
|
18
|
+
# @return [Boolean] whether an event parameter is `indexed`
|
|
19
|
+
# @!attribute [r] internal_type
|
|
20
|
+
# @return [String, nil] the Solidity-side type from the ABI (`"struct Order"`, `"contract IERC20"`)
|
|
21
|
+
# when present
|
|
7
22
|
attr_reader :name, :raw_type, :components, :indexed, :internal_type
|
|
8
23
|
|
|
24
|
+
# Builds a parameter from its ABI definition.
|
|
25
|
+
#
|
|
26
|
+
# @param definition [Hash] the ABI entry (`"name"`, `"type"`, `"components"`, `"indexed"`,
|
|
27
|
+
# `"internalType"`), String or Symbol keys
|
|
28
|
+
# @param index [Integer] position of the parameter, used to name unnamed ones (`"arg#{index}"`)
|
|
9
29
|
def initialize(definition, index: 0)
|
|
10
30
|
definition = definition.transform_keys(&:to_s)
|
|
11
31
|
@name = definition["name"].to_s
|
|
@@ -17,21 +37,46 @@ module BlockGiven
|
|
|
17
37
|
@components = Array(definition["components"]).each_with_index.map { |c, i| Parameter.new(c, index: i) }
|
|
18
38
|
end
|
|
19
39
|
|
|
40
|
+
# Whether the ABI gives no name to this parameter (keyword arguments are then unavailable).
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean]
|
|
20
43
|
def unnamed? = @unnamed
|
|
44
|
+
|
|
45
|
+
# Whether this event parameter is `indexed`.
|
|
46
|
+
#
|
|
47
|
+
# @return [Boolean]
|
|
21
48
|
def indexed? = indexed
|
|
49
|
+
|
|
50
|
+
# The snake_case Symbol used for keyword arguments and decoded Hash keys (`_to` becomes `:to`,
|
|
51
|
+
# `tokenId` becomes `:token_id`).
|
|
52
|
+
#
|
|
53
|
+
# @return [Symbol]
|
|
22
54
|
def ruby_name = Utils.snake_case(name).to_sym
|
|
23
55
|
|
|
56
|
+
# Whether the type is a tuple or an array of tuples (`"tuple"`, `"tuple[]"`, `"tuple[2]"`).
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean]
|
|
24
59
|
def tuple? = raw_type.start_with?("tuple")
|
|
60
|
+
|
|
61
|
+
# Whether the type is an array, fixed (`"uint256[2]"`) or dynamic (`"uint256[]"`).
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean]
|
|
25
64
|
def array? = raw_type.end_with?("]")
|
|
26
65
|
|
|
27
|
-
#
|
|
66
|
+
# The canonical type used in signatures, with tuples expanded from their components.
|
|
67
|
+
#
|
|
68
|
+
# @return [String] for example `"(uint256,address)[]"` for `"tuple[]"` with two components
|
|
28
69
|
def type
|
|
29
70
|
return raw_type unless tuple?
|
|
30
71
|
|
|
31
72
|
raw_type.sub("tuple", "(#{components.map(&:type).join(',')})")
|
|
32
73
|
end
|
|
33
74
|
|
|
34
|
-
#
|
|
75
|
+
# The element parameter of an array type, i.e. this type without its outermost array dimension.
|
|
76
|
+
#
|
|
77
|
+
# @return [Parameter] a parameter with the same name and components, for `"uint256[][2]"` the
|
|
78
|
+
# element is `"uint256[]"`
|
|
79
|
+
# @return [nil] when the type is not an array
|
|
35
80
|
def element
|
|
36
81
|
return nil unless array?
|
|
37
82
|
|
|
@@ -41,11 +86,23 @@ module BlockGiven
|
|
|
41
86
|
)
|
|
42
87
|
end
|
|
43
88
|
|
|
89
|
+
# Whether the type is dynamically sized in the ABI encoding.
|
|
90
|
+
#
|
|
91
|
+
# True for `string`, `bytes`, dynamic arrays, tuples with a dynamic component and fixed arrays of a
|
|
92
|
+
# dynamic element. Indexed event parameters of dynamic type are stored hashed in topics.
|
|
93
|
+
#
|
|
94
|
+
# @return [Boolean]
|
|
44
95
|
def dynamic?
|
|
45
96
|
raw_type == "string" || raw_type == "bytes" || raw_type.end_with?("[]") ||
|
|
46
97
|
(tuple? && components.any?(&:dynamic?)) || (array? && element.dynamic?)
|
|
47
98
|
end
|
|
48
99
|
|
|
100
|
+
# The ABI definition of this parameter, rebuilt with String keys.
|
|
101
|
+
#
|
|
102
|
+
# Unnamed parameters get an empty `"name"`, `"components"` is only present for tuples and
|
|
103
|
+
# `"indexed"` only when true; `internalType` is not included.
|
|
104
|
+
#
|
|
105
|
+
# @return [Hash{String => Object}]
|
|
49
106
|
def to_h
|
|
50
107
|
h = { "name" => unnamed? ? "" : name, "type" => raw_type }
|
|
51
108
|
h["components"] = components.map(&:to_h) if tuple?
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockGiven
|
|
4
|
+
module Abi
|
|
5
|
+
module Standards
|
|
6
|
+
# EIP-1155 with ERC-165, the metadata URI extension and the ERC-6093 errors (OpenZeppelin 5 naming).
|
|
7
|
+
ERC1155 = Definition.deep_freeze(
|
|
8
|
+
[
|
|
9
|
+
Definition.view("supportsInterface", ["bytes4 interfaceId"], ["bool"]),
|
|
10
|
+
Definition.view("balanceOf", ["address account", "uint256 id"], ["uint256"]),
|
|
11
|
+
Definition.view("balanceOfBatch", ["address[] accounts", "uint256[] ids"], ["uint256[]"]),
|
|
12
|
+
Definition.view("isApprovedForAll", ["address account", "address operator"], ["bool"]),
|
|
13
|
+
Definition.view("uri", ["uint256 id"], ["string"]),
|
|
14
|
+
Definition.write("setApprovalForAll", ["address operator", "bool approved"]),
|
|
15
|
+
Definition.write("safeTransferFrom",
|
|
16
|
+
["address from", "address to", "uint256 id", "uint256 value", "bytes data"]),
|
|
17
|
+
Definition.write("safeBatchTransferFrom",
|
|
18
|
+
["address from", "address to", "uint256[] ids", "uint256[] values", "bytes data"]),
|
|
19
|
+
|
|
20
|
+
Definition.event("TransferSingle",
|
|
21
|
+
["address operator indexed", "address from indexed", "address to indexed",
|
|
22
|
+
"uint256 id", "uint256 value"]),
|
|
23
|
+
Definition.event("TransferBatch",
|
|
24
|
+
["address operator indexed", "address from indexed", "address to indexed",
|
|
25
|
+
"uint256[] ids", "uint256[] values"]),
|
|
26
|
+
Definition.event("ApprovalForAll", ["address account indexed", "address operator indexed", "bool approved"]),
|
|
27
|
+
Definition.event("URI", ["string value", "uint256 id indexed"]),
|
|
28
|
+
|
|
29
|
+
Definition.error("ERC1155InsufficientBalance",
|
|
30
|
+
["address sender", "uint256 balance", "uint256 needed", "uint256 tokenId"]),
|
|
31
|
+
Definition.error("ERC1155InvalidSender", ["address sender"]),
|
|
32
|
+
Definition.error("ERC1155InvalidReceiver", ["address receiver"]),
|
|
33
|
+
Definition.error("ERC1155MissingApprovalForAll", ["address operator", "address owner"]),
|
|
34
|
+
Definition.error("ERC1155InvalidApprover", ["address approver"]),
|
|
35
|
+
Definition.error("ERC1155InvalidOperator", ["address operator"]),
|
|
36
|
+
Definition.error("ERC1155InvalidArrayLength", ["uint256 idsLength", "uint256 valuesLength"])
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockGiven
|
|
4
|
+
module Abi
|
|
5
|
+
module Standards
|
|
6
|
+
# EIP-20 with the optional metadata functions and the ERC-6093 errors, with the input names of
|
|
7
|
+
# OpenZeppelin 5 (`to` / `value`): the version those errors come from, and the names of the events.
|
|
8
|
+
ERC20 = Definition.deep_freeze(
|
|
9
|
+
[
|
|
10
|
+
Definition.view("name", [], ["string"]),
|
|
11
|
+
Definition.view("symbol", [], ["string"]),
|
|
12
|
+
Definition.view("decimals", [], ["uint8"]),
|
|
13
|
+
Definition.view("totalSupply", [], ["uint256"]),
|
|
14
|
+
Definition.view("balanceOf", ["address account"], ["uint256"]),
|
|
15
|
+
Definition.view("allowance", ["address owner", "address spender"], ["uint256"]),
|
|
16
|
+
Definition.write("approve", ["address spender", "uint256 value"], ["bool"]),
|
|
17
|
+
Definition.write("transfer", ["address to", "uint256 value"], ["bool"]),
|
|
18
|
+
Definition.write("transferFrom", ["address from", "address to", "uint256 value"], ["bool"]),
|
|
19
|
+
|
|
20
|
+
Definition.event("Transfer", ["address from indexed", "address to indexed", "uint256 value"]),
|
|
21
|
+
Definition.event("Approval", ["address owner indexed", "address spender indexed", "uint256 value"]),
|
|
22
|
+
|
|
23
|
+
Definition.error("ERC20InsufficientBalance", ["address sender", "uint256 balance", "uint256 needed"]),
|
|
24
|
+
Definition.error("ERC20InvalidSender", ["address sender"]),
|
|
25
|
+
Definition.error("ERC20InvalidReceiver", ["address receiver"]),
|
|
26
|
+
Definition.error("ERC20InsufficientAllowance", ["address spender", "uint256 allowance", "uint256 needed"]),
|
|
27
|
+
Definition.error("ERC20InvalidApprover", ["address approver"]),
|
|
28
|
+
Definition.error("ERC20InvalidSpender", ["address spender"])
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockGiven
|
|
4
|
+
module Abi
|
|
5
|
+
module Standards
|
|
6
|
+
# EIP-4626 tokenized vault: the full ERC20 interface plus the vault functions, events and
|
|
7
|
+
# OpenZeppelin 5 errors.
|
|
8
|
+
ERC4626 = Definition.deep_freeze(
|
|
9
|
+
ERC20 + [
|
|
10
|
+
Definition.view("asset", [], ["address"]),
|
|
11
|
+
Definition.view("totalAssets", [], ["uint256"]),
|
|
12
|
+
Definition.view("convertToShares", ["uint256 assets"], ["uint256"]),
|
|
13
|
+
Definition.view("convertToAssets", ["uint256 shares"], ["uint256"]),
|
|
14
|
+
Definition.view("maxDeposit", ["address receiver"], ["uint256"]),
|
|
15
|
+
Definition.view("previewDeposit", ["uint256 assets"], ["uint256"]),
|
|
16
|
+
Definition.write("deposit", ["uint256 assets", "address receiver"], ["uint256"]),
|
|
17
|
+
Definition.view("maxMint", ["address receiver"], ["uint256"]),
|
|
18
|
+
Definition.view("previewMint", ["uint256 shares"], ["uint256"]),
|
|
19
|
+
Definition.write("mint", ["uint256 shares", "address receiver"], ["uint256"]),
|
|
20
|
+
Definition.view("maxWithdraw", ["address owner"], ["uint256"]),
|
|
21
|
+
Definition.view("previewWithdraw", ["uint256 assets"], ["uint256"]),
|
|
22
|
+
Definition.write("withdraw", ["uint256 assets", "address receiver", "address owner"], ["uint256"]),
|
|
23
|
+
Definition.view("maxRedeem", ["address owner"], ["uint256"]),
|
|
24
|
+
Definition.view("previewRedeem", ["uint256 shares"], ["uint256"]),
|
|
25
|
+
Definition.write("redeem", ["uint256 shares", "address receiver", "address owner"], ["uint256"]),
|
|
26
|
+
|
|
27
|
+
Definition.event("Deposit",
|
|
28
|
+
["address sender indexed", "address owner indexed", "uint256 assets", "uint256 shares"]),
|
|
29
|
+
Definition.event("Withdraw",
|
|
30
|
+
["address sender indexed", "address receiver indexed", "address owner indexed",
|
|
31
|
+
"uint256 assets", "uint256 shares"]),
|
|
32
|
+
|
|
33
|
+
Definition.error("ERC4626ExceededMaxDeposit", ["address receiver", "uint256 assets", "uint256 max"]),
|
|
34
|
+
Definition.error("ERC4626ExceededMaxMint", ["address receiver", "uint256 shares", "uint256 max"]),
|
|
35
|
+
Definition.error("ERC4626ExceededMaxWithdraw", ["address owner", "uint256 assets", "uint256 max"]),
|
|
36
|
+
Definition.error("ERC4626ExceededMaxRedeem", ["address owner", "uint256 shares", "uint256 max"])
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockGiven
|
|
4
|
+
module Abi
|
|
5
|
+
module Standards
|
|
6
|
+
# EIP-721 with ERC-165, the metadata and enumerable extensions and the ERC-6093 / enumerable errors.
|
|
7
|
+
# Mutability follows OpenZeppelin (nonpayable) where EIP-721 says payable. Both `safeTransferFrom`
|
|
8
|
+
# overloads are present: pick one by arity or full signature ("safeTransferFrom(address,address,uint256,bytes)").
|
|
9
|
+
ERC721 = Definition.deep_freeze(
|
|
10
|
+
[
|
|
11
|
+
Definition.view("supportsInterface", ["bytes4 interfaceId"], ["bool"]),
|
|
12
|
+
Definition.view("balanceOf", ["address owner"], ["uint256"]),
|
|
13
|
+
Definition.view("ownerOf", ["uint256 tokenId"], ["address"]),
|
|
14
|
+
Definition.view("name", [], ["string"]),
|
|
15
|
+
Definition.view("symbol", [], ["string"]),
|
|
16
|
+
Definition.view("tokenURI", ["uint256 tokenId"], ["string"]),
|
|
17
|
+
Definition.view("getApproved", ["uint256 tokenId"], ["address"]),
|
|
18
|
+
Definition.view("isApprovedForAll", ["address owner", "address operator"], ["bool"]),
|
|
19
|
+
Definition.view("totalSupply", [], ["uint256"]),
|
|
20
|
+
Definition.view("tokenOfOwnerByIndex", ["address owner", "uint256 index"], ["uint256"]),
|
|
21
|
+
Definition.view("tokenByIndex", ["uint256 index"], ["uint256"]),
|
|
22
|
+
Definition.write("approve", ["address to", "uint256 tokenId"]),
|
|
23
|
+
Definition.write("setApprovalForAll", ["address operator", "bool approved"]),
|
|
24
|
+
Definition.write("transferFrom", ["address from", "address to", "uint256 tokenId"]),
|
|
25
|
+
Definition.write("safeTransferFrom", ["address from", "address to", "uint256 tokenId"]),
|
|
26
|
+
Definition.write("safeTransferFrom", ["address from", "address to", "uint256 tokenId", "bytes data"]),
|
|
27
|
+
|
|
28
|
+
Definition.event("Transfer", ["address from indexed", "address to indexed", "uint256 tokenId indexed"]),
|
|
29
|
+
Definition.event("Approval",
|
|
30
|
+
["address owner indexed", "address approved indexed", "uint256 tokenId indexed"]),
|
|
31
|
+
Definition.event("ApprovalForAll", ["address owner indexed", "address operator indexed", "bool approved"]),
|
|
32
|
+
|
|
33
|
+
Definition.error("ERC721InvalidOwner", ["address owner"]),
|
|
34
|
+
Definition.error("ERC721NonexistentToken", ["uint256 tokenId"]),
|
|
35
|
+
Definition.error("ERC721IncorrectOwner", ["address sender", "uint256 tokenId", "address owner"]),
|
|
36
|
+
Definition.error("ERC721InvalidSender", ["address sender"]),
|
|
37
|
+
Definition.error("ERC721InvalidReceiver", ["address receiver"]),
|
|
38
|
+
Definition.error("ERC721InsufficientApproval", ["address operator", "uint256 tokenId"]),
|
|
39
|
+
Definition.error("ERC721InvalidApprover", ["address approver"]),
|
|
40
|
+
Definition.error("ERC721InvalidOperator", ["address operator"]),
|
|
41
|
+
Definition.error("ERC721OutOfBoundsIndex", ["address owner", "uint256 index"]),
|
|
42
|
+
Definition.error("ERC721EnumerableForbiddenBatchMint", [])
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BlockGiven
|
|
4
|
+
module Abi
|
|
5
|
+
# Standard token interfaces shipped with the gem as plain ABI arrays (the same shape as a parsed
|
|
6
|
+
# JSON ABI): ERC20, ERC721, ERC1155 and ERC4626, with their ERC-6093 custom errors.
|
|
7
|
+
#
|
|
8
|
+
# class Usdc < BlockGiven::Contract
|
|
9
|
+
# abi :erc20 # or: abi BlockGiven::Abi::Standards::ERC20
|
|
10
|
+
# address "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# Your own contracts keep their ABIs in your application (see Contract.abi_file).
|
|
14
|
+
module Standards
|
|
15
|
+
# @return [Array<Symbol>] the names of the shipped standards, as {fetch} and `Contract.abi` accept them
|
|
16
|
+
NAMES = %i[erc20 erc721 erc1155 erc4626].freeze
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# Shipped ABI by name: `:erc20`, `"ERC721"`, `"erc-1155"` all work.
|
|
20
|
+
#
|
|
21
|
+
# @param name [Symbol, String] the standard's name, case, dashes and underscores ignored
|
|
22
|
+
# @return [Array<Hash>] the deep-frozen JSON-ABI definitions of the standard
|
|
23
|
+
# @raise [BlockGiven::AbiError] when no shipped standard has that name
|
|
24
|
+
# @example
|
|
25
|
+
# BlockGiven::Abi::Standards.fetch("ERC-721") # => [{"type"=>"function", "name"=>"balanceOf", ...}, ...]
|
|
26
|
+
def fetch(name)
|
|
27
|
+
key = name.to_s.downcase.delete("-_").to_sym
|
|
28
|
+
raise AbiError, "unknown ABI #{name.inspect} (shipped: #{NAMES.join(', ')})" unless NAMES.include?(key)
|
|
29
|
+
|
|
30
|
+
const_get(key.to_s.upcase, false)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The names of the shipped standards.
|
|
34
|
+
#
|
|
35
|
+
# @return [Array<Symbol>] {NAMES}
|
|
36
|
+
# @example
|
|
37
|
+
# BlockGiven::Abi::Standards.names # => [:erc20, :erc721, :erc1155, :erc4626]
|
|
38
|
+
def names = NAMES
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Builds JSON-ABI definitions from Solidity-like parameter strings ("address to", "uint256 id indexed").
|
|
42
|
+
# @api private
|
|
43
|
+
module Definition
|
|
44
|
+
module_function
|
|
45
|
+
|
|
46
|
+
# A `view` function definition.
|
|
47
|
+
#
|
|
48
|
+
# @param name [String] the function name
|
|
49
|
+
# @param inputs [Array<String>] Solidity-like parameters, see {params}
|
|
50
|
+
# @param outputs [Array<String>] Solidity-like return values, see {params}
|
|
51
|
+
# @return [Hash] the JSON-ABI function definition
|
|
52
|
+
def view(name, inputs, outputs) = function(name, inputs, outputs, "view")
|
|
53
|
+
|
|
54
|
+
# A `nonpayable` function definition.
|
|
55
|
+
#
|
|
56
|
+
# @param name [String] the function name
|
|
57
|
+
# @param inputs [Array<String>] Solidity-like parameters, see {params}
|
|
58
|
+
# @param outputs [Array<String>] Solidity-like return values, see {params}
|
|
59
|
+
# @return [Hash] the JSON-ABI function definition
|
|
60
|
+
def write(name, inputs, outputs = []) = function(name, inputs, outputs, "nonpayable")
|
|
61
|
+
|
|
62
|
+
# A function definition with an explicit state mutability.
|
|
63
|
+
#
|
|
64
|
+
# @param name [String] the function name
|
|
65
|
+
# @param inputs [Array<String>] Solidity-like parameters, see {params}
|
|
66
|
+
# @param outputs [Array<String>] Solidity-like return values, see {params}
|
|
67
|
+
# @param mutability [String] `"view"`, `"pure"`, `"nonpayable"` or `"payable"`
|
|
68
|
+
# @return [Hash] the JSON-ABI function definition
|
|
69
|
+
def function(name, inputs, outputs, mutability)
|
|
70
|
+
{ "type" => "function", "name" => name, "stateMutability" => mutability,
|
|
71
|
+
"inputs" => params(inputs), "outputs" => params(outputs) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# A non-anonymous event definition.
|
|
75
|
+
#
|
|
76
|
+
# @param name [String] the event name
|
|
77
|
+
# @param inputs [Array<String>] Solidity-like parameters, `indexed` as a third word, see {params}
|
|
78
|
+
# @return [Hash] the JSON-ABI event definition
|
|
79
|
+
def event(name, inputs)
|
|
80
|
+
{ "type" => "event", "name" => name, "anonymous" => false, "inputs" => params(inputs) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# A custom error definition.
|
|
84
|
+
#
|
|
85
|
+
# @param name [String] the error name
|
|
86
|
+
# @param inputs [Array<String>] Solidity-like parameters, see {params}
|
|
87
|
+
# @return [Hash] the JSON-ABI error definition
|
|
88
|
+
def error(name, inputs)
|
|
89
|
+
{ "type" => "error", "name" => name, "inputs" => params(inputs) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# JSON-ABI parameters from Solidity-like strings: `"<type> [name] [indexed]"`.
|
|
93
|
+
#
|
|
94
|
+
# @param list [Array<String>] e.g. `["address from indexed", "uint256 value"]`
|
|
95
|
+
# @return [Array<Hash>] the parameters, `"indexed" => true` when the third word is `indexed`
|
|
96
|
+
def params(list)
|
|
97
|
+
list.map do |entry|
|
|
98
|
+
type, name, indexed = entry.split
|
|
99
|
+
param = { "name" => name.to_s, "type" => type }
|
|
100
|
+
param["indexed"] = true if indexed == "indexed"
|
|
101
|
+
param
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Freezes a definition and everything it contains.
|
|
106
|
+
#
|
|
107
|
+
# @param value [Object] a Hash, an Array or a leaf value
|
|
108
|
+
# @return [Object] the same value, frozen with all its nested Hashes and Arrays
|
|
109
|
+
def deep_freeze(value)
|
|
110
|
+
case value
|
|
111
|
+
when Hash then value.each_value { |v| deep_freeze(v) }
|
|
112
|
+
when Array then value.each { |v| deep_freeze(v) }
|
|
113
|
+
end
|
|
114
|
+
value.freeze
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
require_relative "standards/erc20"
|
|
122
|
+
require_relative "standards/erc721"
|
|
123
|
+
require_relative "standards/erc1155"
|
|
124
|
+
require_relative "standards/erc4626"
|