fix: modify unsubscribe cleanup routine and tests (#84)
* fix: modify unsubscribe cleanup routine
Ignore exceptions (other than CancelledError) if uninstallation of the filter fails. If it's the last step in the subscription cleanup, then filter changes for this filter will no longer be polled so if the filter continues to live on in geth for whatever reason, then it doesn't matter.
This includes a number of fixes:
- `CancelledError` is now caught inside of `getChanges`. This was causing conditions during `subscriptions.close`, where the `CancelledError` would get consumed by the `except CatchableError`, if there was an ongoing `poll` happening at the time of close.
- After creating a new filter inside of `getChanges`, the new filter is polled for changes before returning.
- `getChanges` also does not swallow `CatchableError` by returning an empty array, and instead re-raises the error if it is not `filter not found`.
- The tests were simplified by accessing the private fields of `PollingSubscriptions`. That way, there wasn't a race condition for the `newFilterId` counter inside of the mock.
- The `MockRpcHttpServer` was simplified by keeping track of the active filters only, and invalidation simply removes the filter. The tests then only needed to rely on the fact that the filter id changed in the mapping.
- Because of the above changes, we no longer needed to sleep inside of the tests, so the sleeps were removed, and the polling interval could be changed to 1ms, which not only makes the tests faster, but would further highlight any race conditions if present.
* docs: rpc custom port documentation
---------
Co-authored-by: Adam Uhlíř <adam@uhlir.dev>
2024-10-25 03:58:45 +00:00
|
|
|
import std/os
|
2024-03-19 14:27:51 +00:00
|
|
|
import std/json
|
|
|
|
import pkg/asynctest
|
|
|
|
import pkg/ethers
|
|
|
|
import ./hardhat
|
|
|
|
|
|
|
|
suite "Contract custom errors":
|
|
|
|
|
|
|
|
type
|
|
|
|
TestCustomErrors = ref object of Contract
|
|
|
|
SimpleError = object of SolidityError
|
2024-03-20 10:24:53 +00:00
|
|
|
ErrorWithArguments = object of SolidityError
|
|
|
|
arguments: tuple[one: UInt256, two: bool]
|
2024-03-20 10:40:59 +00:00
|
|
|
ErrorWithStaticStruct = object of SolidityError
|
|
|
|
arguments: tuple[one: Static, two: Static]
|
|
|
|
ErrorWithDynamicStruct = object of SolidityError
|
|
|
|
arguments: tuple[one: Dynamic, two: Dynamic]
|
|
|
|
ErrorWithDynamicAndStaticStruct = object of SolidityError
|
|
|
|
arguments: tuple[one: Dynamic, two: Static]
|
|
|
|
Static = (UInt256, UInt256)
|
|
|
|
Dynamic = (string, UInt256)
|
2024-03-19 14:27:51 +00:00
|
|
|
|
|
|
|
var contract: TestCustomErrors
|
|
|
|
var provider: JsonRpcProvider
|
|
|
|
var snapshot: JsonNode
|
fix: modify unsubscribe cleanup routine and tests (#84)
* fix: modify unsubscribe cleanup routine
Ignore exceptions (other than CancelledError) if uninstallation of the filter fails. If it's the last step in the subscription cleanup, then filter changes for this filter will no longer be polled so if the filter continues to live on in geth for whatever reason, then it doesn't matter.
This includes a number of fixes:
- `CancelledError` is now caught inside of `getChanges`. This was causing conditions during `subscriptions.close`, where the `CancelledError` would get consumed by the `except CatchableError`, if there was an ongoing `poll` happening at the time of close.
- After creating a new filter inside of `getChanges`, the new filter is polled for changes before returning.
- `getChanges` also does not swallow `CatchableError` by returning an empty array, and instead re-raises the error if it is not `filter not found`.
- The tests were simplified by accessing the private fields of `PollingSubscriptions`. That way, there wasn't a race condition for the `newFilterId` counter inside of the mock.
- The `MockRpcHttpServer` was simplified by keeping track of the active filters only, and invalidation simply removes the filter. The tests then only needed to rely on the fact that the filter id changed in the mapping.
- Because of the above changes, we no longer needed to sleep inside of the tests, so the sleeps were removed, and the polling interval could be changed to 1ms, which not only makes the tests faster, but would further highlight any race conditions if present.
* docs: rpc custom port documentation
---------
Co-authored-by: Adam Uhlíř <adam@uhlir.dev>
2024-10-25 03:58:45 +00:00
|
|
|
let providerUrl = getEnv("ETHERS_TEST_PROVIDER", "localhost:8545")
|
2024-03-19 14:27:51 +00:00
|
|
|
|
|
|
|
setup:
|
fix: modify unsubscribe cleanup routine and tests (#84)
* fix: modify unsubscribe cleanup routine
Ignore exceptions (other than CancelledError) if uninstallation of the filter fails. If it's the last step in the subscription cleanup, then filter changes for this filter will no longer be polled so if the filter continues to live on in geth for whatever reason, then it doesn't matter.
This includes a number of fixes:
- `CancelledError` is now caught inside of `getChanges`. This was causing conditions during `subscriptions.close`, where the `CancelledError` would get consumed by the `except CatchableError`, if there was an ongoing `poll` happening at the time of close.
- After creating a new filter inside of `getChanges`, the new filter is polled for changes before returning.
- `getChanges` also does not swallow `CatchableError` by returning an empty array, and instead re-raises the error if it is not `filter not found`.
- The tests were simplified by accessing the private fields of `PollingSubscriptions`. That way, there wasn't a race condition for the `newFilterId` counter inside of the mock.
- The `MockRpcHttpServer` was simplified by keeping track of the active filters only, and invalidation simply removes the filter. The tests then only needed to rely on the fact that the filter id changed in the mapping.
- Because of the above changes, we no longer needed to sleep inside of the tests, so the sleeps were removed, and the polling interval could be changed to 1ms, which not only makes the tests faster, but would further highlight any race conditions if present.
* docs: rpc custom port documentation
---------
Co-authored-by: Adam Uhlíř <adam@uhlir.dev>
2024-10-25 03:58:45 +00:00
|
|
|
provider = JsonRpcProvider.new("http://" & providerUrl)
|
2024-03-19 14:27:51 +00:00
|
|
|
snapshot = await provider.send("evm_snapshot")
|
|
|
|
let deployment = readDeployment()
|
|
|
|
let address = !deployment.address(TestCustomErrors)
|
|
|
|
contract = TestCustomErrors.new(address, provider)
|
|
|
|
|
|
|
|
teardown:
|
|
|
|
discard await provider.send("evm_revert", @[snapshot])
|
|
|
|
await provider.close()
|
|
|
|
|
|
|
|
test "handles simple errors":
|
|
|
|
proc revertsSimpleError(contract: TestCustomErrors)
|
|
|
|
{.contract, pure, errors:[SimpleError].}
|
|
|
|
|
|
|
|
expect SimpleError:
|
|
|
|
await contract.revertsSimpleError()
|
2024-03-20 10:24:53 +00:00
|
|
|
|
|
|
|
test "handles error with arguments":
|
|
|
|
proc revertsErrorWithArguments(contract: TestCustomErrors)
|
|
|
|
{.contract, pure, errors:[ErrorWithArguments].}
|
|
|
|
|
|
|
|
try:
|
|
|
|
await contract.revertsErrorWithArguments()
|
|
|
|
fail()
|
|
|
|
except ErrorWithArguments as error:
|
|
|
|
check error.arguments.one == 1
|
|
|
|
check error.arguments.two == true
|
2024-03-20 10:40:59 +00:00
|
|
|
|
|
|
|
test "handles error with static struct arguments":
|
|
|
|
proc revertsErrorWithStaticStruct(contract: TestCustomErrors)
|
|
|
|
{.contract, pure, errors:[ErrorWithStaticStruct].}
|
|
|
|
|
|
|
|
try:
|
|
|
|
await contract.revertsErrorWithStaticStruct()
|
|
|
|
fail()
|
|
|
|
except ErrorWithStaticStruct as error:
|
|
|
|
check error.arguments.one == (1.u256, 2.u256)
|
|
|
|
check error.arguments.two == (3.u256, 4.u256)
|
|
|
|
|
|
|
|
test "handles error with dynamic struct arguments":
|
|
|
|
proc revertsErrorWithDynamicStruct(contract: TestCustomErrors)
|
|
|
|
{.contract, pure, errors:[ErrorWithDynamicStruct].}
|
|
|
|
|
|
|
|
try:
|
|
|
|
await contract.revertsErrorWithDynamicStruct()
|
|
|
|
fail()
|
|
|
|
except ErrorWithDynamicStruct as error:
|
|
|
|
check error.arguments.one == ("1", 2.u256)
|
|
|
|
check error.arguments.two == ("3", 4.u256)
|
|
|
|
|
|
|
|
test "handles error with dynamic and static struct arguments":
|
|
|
|
proc revertsErrorWithDynamicAndStaticStruct(contract: TestCustomErrors)
|
|
|
|
{.contract, pure, errors:[ErrorWithDynamicAndStaticStruct].}
|
|
|
|
|
|
|
|
try:
|
|
|
|
await contract.revertsErrorWithDynamicAndStaticStruct()
|
|
|
|
fail()
|
|
|
|
except ErrorWithDynamicAndStaticStruct as error:
|
|
|
|
check error.arguments.one == ("1", 2.u256)
|
|
|
|
check error.arguments.two == (3.u256, 4.u256)
|
2024-03-21 07:21:12 +00:00
|
|
|
|
2024-05-15 11:43:42 +00:00
|
|
|
test "handles multiple error types":
|
|
|
|
proc revertsMultipleErrors(contract: TestCustomErrors, simple: bool)
|
|
|
|
{.contract, errors:[SimpleError, ErrorWithArguments].}
|
|
|
|
|
|
|
|
let contract = contract.connect(provider.getSigner())
|
|
|
|
expect SimpleError:
|
|
|
|
await contract.revertsMultipleErrors(simple = true)
|
|
|
|
expect ErrorWithArguments:
|
|
|
|
await contract.revertsMultipleErrors(simple = false)
|
|
|
|
|
2024-03-21 07:21:12 +00:00
|
|
|
test "handles gas estimation errors":
|
|
|
|
proc revertsTransaction(contract: TestCustomErrors)
|
|
|
|
{.contract, errors:[ErrorWithArguments].}
|
|
|
|
|
|
|
|
let contract = contract.connect(provider.getSigner())
|
|
|
|
try:
|
|
|
|
await contract.revertsTransaction()
|
|
|
|
fail()
|
|
|
|
except ErrorWithArguments as error:
|
|
|
|
check error.arguments.one == 1.u256
|
|
|
|
check error.arguments.two == true
|
|
|
|
|
|
|
|
test "handles transaction submission errors":
|
|
|
|
proc revertsTransaction(contract: TestCustomErrors)
|
|
|
|
{.contract, errors:[ErrorWithArguments].}
|
|
|
|
|
|
|
|
# skip gas estimation
|
|
|
|
let overrides = TransactionOverrides(gasLimit: some 1000000.u256)
|
|
|
|
|
|
|
|
let contract = contract.connect(provider.getSigner())
|
|
|
|
try:
|
|
|
|
await contract.revertsTransaction(overrides = overrides)
|
|
|
|
fail()
|
|
|
|
except ErrorWithArguments as error:
|
|
|
|
check error.arguments.one == 1.u256
|
|
|
|
check error.arguments.two == true
|
2024-03-21 08:32:15 +00:00
|
|
|
|
|
|
|
test "handles transaction confirmation errors":
|
2024-05-14 09:00:45 +00:00
|
|
|
proc revertsTransaction(contract: TestCustomErrors): Confirmable
|
2024-03-21 08:32:15 +00:00
|
|
|
{.contract, errors:[ErrorWithArguments].}
|
|
|
|
|
|
|
|
# skip gas estimation
|
|
|
|
let overrides = TransactionOverrides(gasLimit: some 1000000.u256)
|
|
|
|
|
|
|
|
# ensure that transaction is not immediately checked by hardhat
|
|
|
|
discard await provider.send("evm_setAutomine", @[%false])
|
|
|
|
|
|
|
|
let contract = contract.connect(provider.getSigner())
|
|
|
|
try:
|
|
|
|
let future = contract.revertsTransaction(overrides = overrides).confirm(0)
|
|
|
|
await sleepAsync(100.millis) # wait for transaction to be submitted
|
|
|
|
discard await provider.send("evm_mine", @[]) # mine the transaction
|
|
|
|
discard await future # wait for confirmation
|
|
|
|
fail()
|
|
|
|
except ErrorWithArguments as error:
|
|
|
|
check error.arguments.one == 1.u256
|
|
|
|
check error.arguments.two == true
|
|
|
|
|
|
|
|
# re-enable auto mining
|
|
|
|
discard await provider.send("evm_setAutomine", @[%true])
|