From 9c76803302910153d09de9334c3dd9c3155aa93a Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Wed, 20 Mar 2024 11:24:53 +0100 Subject: [PATCH] support custom errors with arguments --- ethers/errors.nim | 34 +++++++++++++++++++++++++------- testmodule/testCustomErrors.nim | 13 ++++++++++++ testmodule/testErrorDecoding.nim | 14 ++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/ethers/errors.nim b/ethers/errors.nim index f302bbd..00b6ccc 100644 --- a/ethers/errors.nim +++ b/ethers/errors.nim @@ -1,3 +1,4 @@ +import pkg/contractabi import pkg/contractabi/selector import ./basics @@ -7,14 +8,33 @@ type SolidityError* = object of EthersError template errors*(types) {.pragma.} +func selector(E: type): FunctionSelector = + when compiles(E.arguments): + selector($E, typeof(E.arguments)) + else: + selector($E, tuple[]) + +func matchesSelector(E: type, data: seq[byte]): bool = + const selector = E.selector.toArray + data.len >= 4 and selector[0..<4] == data[0..<4] + +func decodeArguments(E: type, data: seq[byte]): auto = + AbiDecoder.decode(data[4..^1], E.arguments) + func decode*[E: SolidityError](_: type E, data: seq[byte]): ?!(ref E) = - const name = $E - const selector = selector(name, typeof(())) - if data.len < 4: - return failure "unable to decode " & name & ": signature too short" - if selector.toArray[0..<4] != data[0..<4]: - return failure "unable to decode " & name & ": signature doesn't match" - success (ref E)() + if not E.matchesSelector(data): + return failure "unable to decode " & $E & ": selector doesn't match" + when compiles(E.arguments): + without arguments =? E.decodeArguments(data), error: + return failure "unable to decode " & $E & ": " & error.msg + success (ref E)(arguments: arguments) + else: + success (ref E)() + +func encode*[E: SolidityError](_: type AbiEncoder, error: ref E): seq[byte] = + result = @(E.selector.toArray) + when compiles(error.arguments): + result &= AbiEncoder.encode(error.arguments) template convertCustomErrors*[ErrorTypes: tuple](body: untyped): untyped = try: diff --git a/testmodule/testCustomErrors.nim b/testmodule/testCustomErrors.nim index e01d90d..2104622 100644 --- a/testmodule/testCustomErrors.nim +++ b/testmodule/testCustomErrors.nim @@ -8,6 +8,8 @@ suite "Contract custom errors": type TestCustomErrors = ref object of Contract SimpleError = object of SolidityError + ErrorWithArguments = object of SolidityError + arguments: tuple[one: UInt256, two: bool] var contract: TestCustomErrors var provider: JsonRpcProvider @@ -30,3 +32,14 @@ suite "Contract custom errors": expect SimpleError: await contract.revertsSimpleError() + + 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 diff --git a/testmodule/testErrorDecoding.nim b/testmodule/testErrorDecoding.nim index fa1d67f..6112d28 100644 --- a/testmodule/testErrorDecoding.nim +++ b/testmodule/testErrorDecoding.nim @@ -1,10 +1,14 @@ import std/unittest import pkg/questionable/results +import pkg/contractabi import pkg/ethers/errors suite "Decoding of custom errors": - type SimpleError = object of SolidityError + type + SimpleError = object of SolidityError + ErrorWithArguments = object of SolidityError + arguments: tuple[one: UInt256, two: bool] test "decodes a simple error": let decoded = SimpleError.decode(@[0xc2'u8, 0xbb, 0x94, 0x7c]) @@ -12,6 +16,14 @@ suite "Decoding of custom errors": check decoded.isSuccess check (!decoded) != nil + test "decodes error with arguments": + let expected = (ref ErrorWithArguments)(arguments: (1.u256, true)) + let encoded = AbiEncoder.encode(expected) + let decoded = ErrorWithArguments.decode(encoded) + check decoded.isSuccess + check (!decoded).arguments.one == 1.u256 + check (!decoded).arguments.two == true + test "returns failure when decoding fails": let invalid = @[0xc2'u8, 0xbb, 0x94, 0x0] # last byte is wrong let decoded = SimpleError.decode(invalid)