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
@@ -2,13 +2,34 @@
2
2
 
3
3
  module BlockGiven
4
4
  module Connectors
5
- # Alchemy JSON-RPC connector. The endpoint is derived from the chain, so one
6
- # connector instance can serve any Alchemy-supported network.
5
+ # Alchemy JSON-RPC connector, an {Http} transport whose endpoint is derived from the chain.
7
6
  #
8
- # BlockGiven::Connectors::Alchemy.new(api_key: ENV["ALCHEMY_API_KEY"])
7
+ # The URL is `https://<chain.alchemy_network>.g.alchemy.com/v2/<api_key>`, so a single instance can
8
+ # serve every Alchemy-supported network declared in {Chains} (`alchemy_network` set). Retries, backoff,
9
+ # timeouts and headers behave exactly as in {Http}.
10
+ #
11
+ # The API key never leaks: {#inspect} shows only its first 4 characters and {#redact} strips it from any
12
+ # endpoint that ends up in a log line or an error message.
13
+ #
14
+ # @example
15
+ # connector = BlockGiven::Connectors::Alchemy.new(api_key: ENV["ALCHEMY_API_KEY"])
16
+ # BlockGiven::Client.new(chain: :base, connector: connector).block_number
17
+ # connector.inspect # => "#<BlockGiven::Connectors::Alchemy api_key=abcd…>"
9
18
  class Alchemy < Http
19
+ # @!attribute [r] api_key
20
+ # @return [String] the Alchemy API key (as a String); do not log it, use {#inspect} or {#redact}
21
+
10
22
  attr_reader :api_key
11
23
 
24
+ # Builds an Alchemy transport. There is no `url:` argument: the endpoint is computed per chain.
25
+ #
26
+ # @param api_key [String, #to_s] Alchemy API key
27
+ # @param timeout [Numeric] seconds for the open, read and write timeouts of each HTTP request
28
+ # @param retries [Integer] number of retries after the first failed attempt
29
+ # @param retry_delay [Numeric] seconds slept before the first retry; doubled on each further retry
30
+ # @param logger [Logger, nil] receives retry notices at debug level. Defaults to `BlockGiven.config.logger`.
31
+ # @param headers [Hash{String => String}] extra headers merged over the {Http} defaults
32
+ # @raise [ConfigurationError] when `api_key` is nil or empty
12
33
  def initialize(api_key:, timeout: 30, retries: 3, retry_delay: 0.5, logger: nil, headers: {})
13
34
  raise ConfigurationError, "Alchemy api_key is required" if api_key.nil? || api_key.to_s.empty?
14
35
 
@@ -16,6 +37,11 @@ module BlockGiven
16
37
  super(url: nil, headers: headers, timeout: timeout, retries: retries, retry_delay: retry_delay, logger: logger)
17
38
  end
18
39
 
40
+ # Alchemy endpoint for a chain: `https://<alchemy_network>.g.alchemy.com/v2/<api_key>`.
41
+ #
42
+ # @param chain [Chain] chain being queried; its `alchemy_network` (e.g. `"base-mainnet"`) selects the host
43
+ # @return [String] the full endpoint URL, including the API key
44
+ # @raise [ConfigurationError] when `chain` is nil or has no `alchemy_network`
19
45
  def endpoint(chain)
20
46
  raise ConfigurationError, "Alchemy connector needs a chain" if chain.nil?
21
47
  raise ConfigurationError, "#{chain.name} is not available on Alchemy" unless chain.alchemy_network
@@ -23,9 +49,17 @@ module BlockGiven
23
49
  "https://#{chain.alchemy_network}.g.alchemy.com/v2/#{api_key}"
24
50
  end
25
51
 
52
+ # Short description with the API key masked (first 4 characters followed by an ellipsis).
53
+ #
54
+ # @return [String] e.g. `"#<BlockGiven::Connectors::Alchemy api_key=abcd…>"`
26
55
  def inspect = "#<BlockGiven::Connectors::Alchemy api_key=#{redacted_key}>"
27
56
 
28
- # Keeps the network host visible, masks the key: https://base-mainnet.g.alchemy.com/v2/abcd…
57
+ # Masks the API key inside an endpoint while keeping the network host visible.
58
+ #
59
+ # @example
60
+ # connector.redact(connector.endpoint(chain)) # => "https://base-mainnet.g.alchemy.com/v2/abcd…"
61
+ # @param endpoint [String, #to_s] URL possibly containing the API key
62
+ # @return [String] the URL with every occurrence of the key replaced by its masked form
29
63
  def redact(endpoint) = endpoint.to_s.sub(api_key, redacted_key)
30
64
 
31
65
  private
@@ -1,17 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BlockGiven
4
+ # JSON-RPC transports: {Connectors::Base} defines the interface, {Connectors::Http} speaks JSON-RPC over HTTP(S),
5
+ # {Connectors::Alchemy} derives Alchemy endpoints from the chain and {Connectors::Stub} serves canned responses
6
+ # in tests. A connector is set globally with `BlockGiven.config.connector` or passed to {Client#initialize}.
4
7
  module Connectors
5
- # A connector is a JSON-RPC transport. Subclasses implement #request; the
6
- # chain is passed so multi-network providers (Alchemy) can pick the endpoint.
8
+ # Abstract JSON-RPC transport every connector derives from.
9
+ #
10
+ # A connector only knows how to send a JSON-RPC method with its params and hand back the `result`.
11
+ # The {Chain} is passed on each call so that multi-network providers ({Alchemy}) can derive the endpoint
12
+ # from it; single-endpoint transports ({Http} with an explicit `url:`) may ignore it. Concrete
13
+ # connectors: {Http}, {Alchemy} and, for tests, {Stub}.
14
+ #
15
+ # @abstract Subclass and override {#request}; override {#batch} when the transport supports real batching.
7
16
  class Base
8
- # @return the JSON-RPC `result` (raw JSON value). Raises BlockGiven::RpcError on error.
17
+ # Sends one JSON-RPC request and returns its `result`.
18
+ #
19
+ # @abstract
20
+ # @param method [String] JSON-RPC method name, e.g. `"eth_blockNumber"`
21
+ # @param params [Array<Object>] positional JSON-RPC params
22
+ # @param chain [Chain, nil] chain being queried, used by multi-network transports to pick an endpoint
23
+ # @return [Object] the raw JSON-RPC `result` (String, Hash, Array, nil...), undecoded
24
+ # @raise [RpcError] when the node answers with a JSON-RPC error object
25
+ # @raise [NotImplementedError] when called on {Base} itself
9
26
  def request(method, params = [], chain: nil)
10
27
  raise NotImplementedError, "#{self.class}#request"
11
28
  end
12
29
 
13
- # Naive batch: one request per call. Transports with real batching override it.
14
- # Returns an array of results; failed calls are returned as RpcError instances.
30
+ # Executes several calls and returns their results in order.
31
+ #
32
+ # This naive implementation issues one {#request} per call; transports with real JSON-RPC batching
33
+ # override it. A call that fails with an {RpcError} does not abort the batch: the error instance takes
34
+ # the place of its result.
35
+ #
36
+ # @param calls [Array<Array(String, Array)>] `[method, params]` pairs; a missing params entry means `[]`
37
+ # @param chain [Chain, nil] chain being queried, forwarded to {#request}
38
+ # @return [Array<Object, RpcError>] one raw result (or {RpcError}) per call, in the same order
15
39
  def batch(calls, chain: nil)
16
40
  calls.map do |(method, params)|
17
41
  request(method, params || [], chain: chain)
@@ -20,6 +44,9 @@ module BlockGiven
20
44
  end
21
45
  end
22
46
 
47
+ # Short lowercase name of the transport, derived from the class name.
48
+ #
49
+ # @return [String] e.g. `"http"`, `"alchemy"` or `"stub"`
23
50
  def name = self.class.name.split("::").last.downcase
24
51
  end
25
52
  end
@@ -6,18 +6,55 @@ require "uri"
6
6
 
7
7
  module BlockGiven
8
8
  module Connectors
9
- # Generic JSON-RPC over HTTP(S) transport with retries and exponential backoff.
9
+ # Generic JSON-RPC 2.0 over HTTP(S) transport built on Net::HTTP, with retries and exponential backoff.
10
10
  #
11
+ # Each request is a POST of a JSON body to the endpoint. Transient failures are retried up to `retries`
12
+ # times, sleeping `retry_delay * 2**(attempt - 1)` seconds between attempts: HTTP statuses listed in
13
+ # {RETRIABLE_STATUSES}, JSON-RPC error codes listed in {RETRIABLE_RPC_CODES} (never a
14
+ # {ContractRevertError}) and the network exceptions listed in {RETRIABLE_EXCEPTIONS}. Anything else is
15
+ # raised immediately as {HttpError} or {RpcError}.
16
+ #
17
+ # Endpoints frequently embed an API key in their path, so {#inspect}, log lines and error messages only
18
+ # ever show the redacted form produced by {#redact} (scheme and host, never the path).
19
+ #
20
+ # @example Explicit endpoint (local node, Infura, QuickNode...)
11
21
  # BlockGiven::Connectors::Http.new(url: "http://127.0.0.1:8545")
12
- # BlockGiven::Connectors::Http.new # -> falls back to chain.rpc_urls.first
22
+ # BlockGiven::Connectors::Http.new(url: "https://mainnet.infura.io/v3/KEY", timeout: 10, retries: 5)
23
+ # @example Public RPC of the chain
24
+ # BlockGiven::Connectors::Http.new # -> uses chain.rpc_urls.first of the chain passed on each request
13
25
  class Http < Base
26
+ # HTTP status codes that trigger a retry (timeouts, rate limiting, server and gateway errors).
14
27
  RETRIABLE_STATUSES = [408, 425, 429, 500, 502, 503, 504].freeze
28
+ # JSON-RPC error codes that trigger a retry: provider rate limit (-32005), internal error (-32603), 429.
15
29
  RETRIABLE_RPC_CODES = [-32_005, -32_603, 429].freeze # rate limited / internal error
30
+ # Network-level exceptions that trigger a retry; they surface as {HttpError} once retries are exhausted.
16
31
  RETRIABLE_EXCEPTIONS = [Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED,
17
32
  Errno::EHOSTUNREACH, EOFError, SocketError, OpenSSL::SSL::SSLError].freeze
18
33
 
34
+ # @!attribute [r] url
35
+ # @return [String, nil] fixed endpoint, or nil when the endpoint comes from the chain's `rpc_urls`
36
+ # @!attribute [r] headers
37
+ # @return [Hash{String => String}] request headers sent with every POST (`Content-Type`, `User-Agent`
38
+ # and the custom ones given to the constructor)
39
+ # @!attribute [r] timeout
40
+ # @return [Numeric] seconds applied to the open, read and write timeouts of each HTTP request
41
+ # @!attribute [r] retries
42
+ # @return [Integer] maximum number of retries after the first attempt
43
+ # @!attribute [r] retry_delay
44
+ # @return [Numeric] base delay in seconds before the first retry; doubled on each further retry
45
+
19
46
  attr_reader :url, :headers, :timeout, :retries, :retry_delay
20
47
 
48
+ # Builds an HTTP transport.
49
+ #
50
+ # @param url [String, nil] JSON-RPC endpoint. When nil, {#endpoint} falls back to the first `rpc_urls`
51
+ # entry of the chain given on each request.
52
+ # @param headers [Hash{String => String}] extra headers merged over the defaults
53
+ # (`Content-Type: application/json`, `User-Agent: block_given/<version>`); may override them
54
+ # @param timeout [Numeric] seconds for the open, read and write timeouts of each HTTP request
55
+ # @param retries [Integer] number of retries after the first failed attempt (0 disables retrying)
56
+ # @param retry_delay [Numeric] seconds slept before the first retry; each further retry doubles it
57
+ # @param logger [Logger, nil] receives retry notices at debug level. Defaults to `BlockGiven.config.logger`.
21
58
  def initialize(url: nil, headers: {}, timeout: 30, retries: 3, retry_delay: 0.5, logger: nil)
22
59
  super()
23
60
  @url = url
@@ -31,6 +68,11 @@ module BlockGiven
31
68
  @mutex = Mutex.new
32
69
  end
33
70
 
71
+ # Resolves the endpoint used for a request: the fixed `url`, or the chain's first public RPC url.
72
+ #
73
+ # @param chain [Chain, nil] chain being queried
74
+ # @return [String] the endpoint URL (may contain credentials: pass it through {#redact} before logging)
75
+ # @raise [ConfigurationError] when no `url` was given and the chain has no `rpc_urls`
34
76
  def endpoint(chain)
35
77
  return url if url
36
78
 
@@ -38,12 +80,38 @@ module BlockGiven
38
80
  raise(ConfigurationError, "no RPC url: pass url: to the connector or use a chain with rpc_urls")
39
81
  end
40
82
 
83
+ # Sends one JSON-RPC 2.0 request (with an auto-incremented id) and returns its `result`.
84
+ #
85
+ # Retriable failures are retried with exponential backoff, see the class description.
86
+ #
87
+ # @param method [String] JSON-RPC method name
88
+ # @param params [Array<Object>] positional JSON-RPC params
89
+ # @param chain [Chain, nil] chain being queried, used to resolve the endpoint when no `url` is fixed
90
+ # @return [Object] the raw JSON-RPC `result`
91
+ # @raise [ContractRevertError] when the node reports an EVM revert (never retried)
92
+ # @raise [RpcError] when the node answers with a JSON-RPC error, or the body is not a JSON object;
93
+ # retriable codes are raised only once retries are exhausted
94
+ # @raise [HttpError] on a non-2xx status, an invalid JSON body or a network error (after retries)
95
+ # @raise [ConfigurationError] when no endpoint can be resolved
41
96
  def request(method, params = [], chain: nil)
42
97
  payload = { jsonrpc: "2.0", id: next_id, method: method, params: params }
43
98
  body = with_retries(method) { post(endpoint(chain), payload) }
44
99
  handle_single(body, method)
45
100
  end
46
101
 
102
+ # Sends several calls in one JSON-RPC batch request (a single HTTP POST with an array body).
103
+ #
104
+ # Responses are matched to calls by id, so the node may answer in any order. A call whose response
105
+ # carries an `error` (or is missing from the response) yields an {RpcError} instance in its slot instead
106
+ # of raising. Only transport-level failures of the whole batch are retried.
107
+ #
108
+ # @param calls [Array<Array(String, Array)>] `[method, params]` pairs; a missing params entry means `[]`
109
+ # @param chain [Chain, nil] chain being queried, used to resolve the endpoint when no `url` is fixed
110
+ # @return [Array<Object, RpcError>] one raw result (or {RpcError}) per call, in call order; `[]` for
111
+ # an empty `calls` (no request is sent)
112
+ # @raise [RpcError] when the node does not answer with a JSON array
113
+ # @raise [HttpError] on a non-2xx status, an invalid JSON body or a network error (after retries)
114
+ # @raise [ConfigurationError] when no endpoint can be resolved
47
115
  def batch(calls, chain: nil)
48
116
  return [] if calls.empty?
49
117
 
@@ -62,10 +130,21 @@ module BlockGiven
62
130
  end
63
131
  end
64
132
 
133
+ # Short description showing the redacted endpoint (or `chain default`), never the full URL.
134
+ #
135
+ # @return [String]
65
136
  def inspect = "#<#{self.class.name} url=#{url ? redact(url).inspect : 'chain default'}>"
66
137
 
67
- # Endpoint as it may appear in logs and error messages. RPC URLs usually carry
68
- # the API key in their path (Infura, QuickNode, ...), so only scheme and host are kept.
138
+ # Endpoint as it may appear in logs and error messages.
139
+ #
140
+ # RPC URLs usually carry the API key in their path (Infura, QuickNode, ...), so only the scheme and the
141
+ # host (plus a non-default port) are kept; a non-empty path is replaced by `/…`.
142
+ #
143
+ # @example
144
+ # connector.redact("https://mainnet.infura.io/v3/SECRET") # => "https://mainnet.infura.io/…"
145
+ # connector.redact("http://127.0.0.1:8545") # => "http://127.0.0.1:8545"
146
+ # @param endpoint [String, URI::Generic] URL to redact
147
+ # @return [String] the redacted URL, or `"<invalid url>"` when it cannot be parsed
69
148
  def redact(endpoint)
70
149
  uri = URI.parse(endpoint.to_s)
71
150
  host = uri.port && uri.port != uri.default_port ? "#{uri.host}:#{uri.port}" : uri.host
@@ -95,10 +174,17 @@ module BlockGiven
95
174
  body["result"]
96
175
  end
97
176
 
98
- # Internal signal used to retry on retriable JSON-RPC errors.
177
+ # Internal signal raised by {Http#handle_single} to retry on a retriable JSON-RPC error; the original
178
+ # {RpcError} is re-raised once retries are exhausted.
179
+ #
180
+ # @api private
99
181
  class Retry < StandardError
182
+ # The retriable JSON-RPC error that triggered the retry.
183
+ #
184
+ # @return [RpcError]
100
185
  attr_reader :cause_error
101
186
 
187
+ # @param cause_error [RpcError] the retriable JSON-RPC error; its message becomes this error's message
102
188
  def initialize(cause_error)
103
189
  @cause_error = cause_error
104
190
  super(cause_error.message)
@@ -2,29 +2,70 @@
2
2
 
3
3
  module BlockGiven
4
4
  module Connectors
5
- # In-memory connector for tests. Responses can be static values, sequences
6
- # (consumed in order, last value repeats) or procs receiving the params.
5
+ # In-memory connector for tests: no network, canned responses, and a record of every call.
7
6
  #
7
+ # Responses are registered per JSON-RPC method name and can be:
8
+ # - a static value, returned on every call;
9
+ # - a {Sequence} built with {Stub.sequence}, consumed in order, the last value repeating forever;
10
+ # - a Proc receiving the request params, for param-dependent answers;
11
+ # - an Exception instance, raised instead of returned (e.g. an {RpcError} to simulate a node error).
12
+ # A handler block given to {Stub#initialize} answers every method without a registered response. Methods with
13
+ # neither raise an {RpcError} with code -32601 ("method not found").
14
+ #
15
+ # @example Static values, a sequence and a proc
8
16
  # stub = BlockGiven::Connectors::Stub.new(
17
+ # "eth_chainId" => "0x2105",
9
18
  # "eth_blockNumber" => BlockGiven::Connectors::Stub.sequence("0x10", "0x11"),
10
- # "eth_call" => ->(params) { "0x" + "0" * 63 + "1" }
19
+ # "eth_call" => ->(params) { params.first[:to] == usdc ? "0x" + "0" * 63 + "1" : "0x" }
11
20
  # )
12
- # stub.calls # => [["eth_blockNumber", []], ...]
21
+ # client = BlockGiven::Client.new(chain: :base, connector: stub)
22
+ # client.block_number # => 16
23
+ # client.block_number # => 17 (and 17 again afterwards)
24
+ # stub.calls # => [["eth_blockNumber", []], ["eth_blockNumber", []]]
25
+ # stub.calls_for("eth_call") # => params of each eth_call
26
+ # @example Fallback handler and incremental stubbing
27
+ # stub = BlockGiven::Connectors::Stub.new { |method, params, chain| raise "unexpected #{method}" }
28
+ # stub.stub("eth_gasPrice", "0x3b9aca00").stub("eth_estimateGas") { |params| "0x5208" }
13
29
  class Stub < Base
30
+ # Ordered list of canned responses: each call consumes the next value, the last one repeats forever.
31
+ # Build one with {Stub.sequence}.
14
32
  class Sequence
33
+ # @param values [Array<Object>] responses in the order they should be served (copied, not mutated)
15
34
  def initialize(values)
16
35
  @values = values.dup
17
36
  end
18
37
 
38
+ # Consumes and returns the next value; once a single value is left it is returned on every call.
39
+ #
40
+ # @return [Object, nil] the next response (nil for an empty sequence)
19
41
  def next
20
42
  @values.size > 1 ? @values.shift : @values.first
21
43
  end
22
44
  end
23
45
 
46
+ # Builds a {Sequence} of responses for a stubbed method.
47
+ #
48
+ # @example
49
+ # stub.stub("eth_blockNumber", BlockGiven::Connectors::Stub.sequence("0x10", "0x11", "0x12"))
50
+ # @param values [Array<Object>] responses served in order; the last one repeats once reached
51
+ # @return [Sequence]
24
52
  def self.sequence(*values) = Sequence.new(values)
25
53
 
54
+ # @!attribute [r] calls
55
+ # @return [Array<Array(String, Array)>] every request received so far as `[method, params]` pairs, in
56
+ # order, including those that raised; cleared by {#reset!}
57
+
26
58
  attr_reader :calls
27
59
 
60
+ # Builds a stub connector with an initial set of canned responses and an optional fallback handler.
61
+ #
62
+ # @param responses [Hash{String, Symbol => Object}] method name to response (static value, {Sequence},
63
+ # Proc receiving the params, or Exception to raise); each entry goes through {#stub}
64
+ # @yield [method, params, chain] for every request whose method has no registered response
65
+ # @yieldparam method [String] JSON-RPC method name
66
+ # @yieldparam params [Array<Object>] request params
67
+ # @yieldparam chain [Chain, nil] chain passed by the client
68
+ # @yieldreturn [Object] the value to use as the JSON-RPC `result` (raised when it is an Exception)
28
69
  def initialize(responses = {}, &handler)
29
70
  super()
30
71
  @responses = {}
@@ -33,11 +74,28 @@ module BlockGiven
33
74
  responses.each { |method, value| stub(method, value) }
34
75
  end
35
76
 
77
+ # Registers (or replaces) the response of one JSON-RPC method.
78
+ #
79
+ # @param method [String, Symbol] JSON-RPC method name
80
+ # @param value [Object, Sequence, Proc, Exception, nil] static value, {Sequence}, Proc receiving the params,
81
+ # or Exception instance to raise; ignored when a block is given
82
+ # @yield [params] when a block is given it computes the response on every call
83
+ # @yieldparam params [Array<Object>] request params
84
+ # @yieldreturn [Object] the value to use as the JSON-RPC `result` (raised when it is an Exception)
85
+ # @return [self] for chaining
36
86
  def stub(method, value = nil, &block)
37
87
  @responses[method.to_s] = block || value
38
88
  self
39
89
  end
40
90
 
91
+ # Records the call and returns the canned response for `method`.
92
+ #
93
+ # @param method [String, Symbol] JSON-RPC method name
94
+ # @param params [Array<Object>] request params, recorded and passed to Proc responses / the handler
95
+ # @param chain [Chain, nil] chain passed by the client, forwarded to the handler block only
96
+ # @return [Object] the resolved response (a {Sequence} is advanced, a Proc is called with `params`)
97
+ # @raise [Exception] the resolved response itself when it is an Exception instance
98
+ # @raise [RpcError] with code -32601 when the method has no response and no handler was given
41
99
  def request(method, params = [], chain: nil)
42
100
  @calls << [method.to_s, params]
43
101
  value = resolve(method.to_s, params, chain)
@@ -46,7 +104,15 @@ module BlockGiven
46
104
  value
47
105
  end
48
106
 
107
+ # Params of every recorded call to one method, in order.
108
+ #
109
+ # @param method [String, Symbol] JSON-RPC method name
110
+ # @return [Array<Array<Object>>] one params Array per call (empty when the method was never called)
49
111
  def calls_for(method) = calls.select { |(m, _)| m == method.to_s }.map(&:last)
112
+
113
+ # Forgets every recorded call; registered responses (and sequence positions) are kept.
114
+ #
115
+ # @return [Array] the emptied {#calls} Array
50
116
  def reset! = @calls.clear
51
117
 
52
118
  private