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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -1
  3. data/README.md +92 -55
  4. data/lib/block_given/abi/codec/decoder.rb +111 -0
  5. data/lib/block_given/abi/codec.rb +170 -0
  6. data/lib/block_given/abi/coder.rb +122 -11
  7. data/lib/block_given/abi/custom_error.rb +33 -1
  8. data/lib/block_given/abi/event.rb +85 -3
  9. data/lib/block_given/abi/function.rb +94 -2
  10. data/lib/block_given/abi/interface.rb +99 -4
  11. data/lib/block_given/abi/parameter.rb +60 -3
  12. data/lib/block_given/abi/standards/erc1155.rb +41 -0
  13. data/lib/block_given/abi/standards/erc20.rb +33 -0
  14. data/lib/block_given/abi/standards/erc4626.rb +41 -0
  15. data/lib/block_given/abi/standards/erc721.rb +47 -0
  16. data/lib/block_given/abi/standards.rb +124 -0
  17. data/lib/block_given/abi/type.rb +138 -0
  18. data/lib/block_given/chain.rb +114 -2
  19. data/lib/block_given/client.rb +322 -20
  20. data/lib/block_given/configuration.rb +85 -3
  21. data/lib/block_given/connectors/alchemy.rb +38 -4
  22. data/lib/block_given/connectors/base.rb +32 -5
  23. data/lib/block_given/connectors/http.rb +91 -5
  24. data/lib/block_given/connectors/stub.rb +70 -4
  25. data/lib/block_given/contract.rb +422 -26
  26. data/lib/block_given/crypto/keccak.rb +152 -0
  27. data/lib/block_given/crypto/secp256k1.rb +168 -0
  28. data/lib/block_given/crypto.rb +22 -0
  29. data/lib/block_given/eip712.rb +199 -0
  30. data/lib/block_given/errors.rb +134 -14
  31. data/lib/block_given/event.rb +51 -1
  32. data/lib/block_given/normalizer.rb +27 -2
  33. data/lib/block_given/poller.rb +177 -15
  34. data/lib/block_given/receipt.rb +63 -3
  35. data/lib/block_given/rlp.rb +146 -0
  36. data/lib/block_given/signed_transaction.rb +148 -47
  37. data/lib/block_given/transaction.rb +103 -12
  38. data/lib/block_given/transaction_envelope/fields.rb +104 -0
  39. data/lib/block_given/transaction_envelope.rb +183 -0
  40. data/lib/block_given/utils.rb +143 -9
  41. data/lib/block_given/version.rb +2 -1
  42. data/lib/block_given/wallet.rb +216 -35
  43. data/lib/block_given.rb +67 -4
  44. 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
- # Accepts an ABI Array, a Hardhat/Foundry artifact Hash (with "abi"), a JSON String or a Pathname.
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 (snake_case or camelCase) or by full signature
50
- # ("transfer(address,uint256)"). Overloads are disambiguated by argument
51
- # count or keyword names.
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
- # "tuple[]" with components -> "(uint256,address)[]"
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
- # Base type without the outermost array dimension.
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"