support custom errors with arguments

This commit is contained in:
Mark Spanbroek 2024-03-20 11:24:53 +01:00 committed by markspanbroek
parent 74f15fca9c
commit 9c76803302
3 changed files with 53 additions and 8 deletions

View File

@ -1,3 +1,4 @@
import pkg/contractabi
import pkg/contractabi/selector
import ./basics
@ -7,15 +8,34 @@ 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"
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:
body

View File

@ -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

View File

@ -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)