Support distinct types for Event fields

Add support for indexed (and non-indexed) Event fields types that are distinct `ValueType` or `SmallByteArray`. For example,
```nim
type
  DistinctAlias = distinct array[32, byte]
  MyEvent = object of Event
    a {.indexed.}: DistinctAlias
    b: DistinctAlias # also allowed for non-indexed fields

## The below funcs generally need to be included for ABI
## encoding/decoding purposes when implementing distinct types.

func toArray(value: DistinctAlias): array[32, byte] =
  array[32, byte](value)

func encode*(encoder: var AbiEncoder, value: DistinctAlias) =
  encoder.write(value.toArray)

func decode*(decoder: var AbiDecoder,
             T: type DistinctAlias): ?!T =
  let d = ?decoder.read(type array[32, byte])
  success DistinctAlias(d)
```
This commit is contained in:
Eric Mastro 2022-08-19 15:09:37 +10:00
parent e8d0fdf1a9
commit ff3173986f
No known key found for this signature in database
GPG Key ID: 141E3048D95A4E63
3 changed files with 74 additions and 2 deletions

View File

@ -106,6 +106,35 @@ type Transfer = object of Event
Notice that `Transfer` inherits from `Event`, and that some event parameters are Notice that `Transfer` inherits from `Event`, and that some event parameters are
marked with `{.indexed.}` to match the definition in Solidity. marked with `{.indexed.}` to match the definition in Solidity.
Note that valid types of indexed parameters are:
```nim
uint8 | uint16 | uint32 | uint64 | UInt256 | UInt128 |
int8 | int16 | int32 | int64 | Int256 | Int128 |
bool | Address | array[ 1..32, byte]
```
Distinct types of valid types are also supported for indexed fields, eg:
```nim
type
DistinctAlias = distinct array[32, byte]
MyEvent = object of Event
a {.indexed.}: DistinctAlias
b: DistinctAlias # also allowed for non-indexed fields
## The below funcs generally need to be included for ABI
## encoding/decoding purposes when implementing distinct types.
func toArray(value: DistinctAlias): array[32, byte] =
array[32, byte](value)
func encode*(encoder: var AbiEncoder, value: DistinctAlias) =
encoder.write(value.toArray)
func decode*(decoder: var AbiDecoder,
T: type DistinctAlias): ?!T =
let d = ?decoder.read(type array[32, byte])
success DistinctAlias(d)
```
You can now subscribe to Transfer events by calling `subscribe` on the contract You can now subscribe to Transfer events by calling `subscribe` on the contract
instance. instance.

View File

@ -1,4 +1,5 @@
import std/macros import std/macros
import std/typetraits
import pkg/contractabi import pkg/contractabi
import ./basics import ./basics
import ./provider import ./provider
@ -40,7 +41,10 @@ func decode*[E: Event](_: type E, data: seq[byte], topics: seq[Topic]): ?!E =
if field.hasCustomPragma(indexed): if field.hasCustomPragma(indexed):
if i >= topics.len: if i >= topics.len:
return failure "indexed event parameter not found" return failure "indexed event parameter not found"
if typeof(field) is ValueType or typeof(field) is SmallByteArray: if typeof(field) is ValueType or
field = ?AbiDecoder.decode(@(topics[i]), typeof(field)) typeof(field) is SmallByteArray or
typeof(field).distinctBase is ValueType or
typeof(field).distinctBase is SmallByteArray:
field = ?AbiDecoder.decode(@(topics[i]), typeof(field))
inc i inc i
success event success event

View File

@ -3,6 +3,26 @@ import pkg/ethers
import pkg/contractabi import pkg/contractabi
import ./examples import ./examples
## Define outside the scope of the suite to allow for exporting
## To use custom distinct types, these procs will generally need
## to be defined in the application code anyway, to support ABI
## encoding/decoding
type
DistinctAlias = distinct array[32, byte]
proc `==`*(x, y: DistinctAlias): bool {.borrow.}
func toArray(value: DistinctAlias): array[32, byte] =
array[32, byte](value)
func encode*(encoder: var AbiEncoder, value: DistinctAlias) =
encoder.write(value.toArray)
func decode*(decoder: var AbiDecoder,
T: type DistinctAlias): ?!T =
let d = ?decoder.read(type array[32, byte])
success DistinctAlias(d)
suite "Events": suite "Events":
type type
@ -25,6 +45,9 @@ suite "Events":
d {.indexed.}: seq[byte] d {.indexed.}: seq[byte]
e {.indexed.}: (Address, UInt256) e {.indexed.}: (Address, UInt256)
f {.indexed.}: array[33, byte] f {.indexed.}: array[33, byte]
IndexedWithDistinctType = object of Event
a {.indexed.}: DistinctAlias
b: DistinctAlias
proc example(_: type SimpleEvent): SimpleEvent = proc example(_: type SimpleEvent): SimpleEvent =
SimpleEvent( SimpleEvent(
@ -47,6 +70,14 @@ suite "Events":
e: array[32, byte].example e: array[32, byte].example
) )
proc example(_: type IndexedWithDistinctType): IndexedWithDistinctType =
IndexedWithDistinctType(
a: DistinctAlias(array[32, byte].example)
)
func encode(_: type AbiEncoder, value: DistinctAlias): seq[byte] =
@(value.toArray)
func encode[T](_: type Topic, value: T): Topic = func encode[T](_: type Topic, value: T): Topic =
let encoded = AbiEncoder.encode(value) let encoded = AbiEncoder.encode(value)
result[0..<Topic.len] = encoded[0..<Topic.len] result[0..<Topic.len] = encoded[0..<Topic.len]
@ -71,6 +102,14 @@ suite "Events":
let data = AbiEncoder.encode( (event.a, event.c) ) let data = AbiEncoder.encode( (event.a, event.c) )
check IndexedEvent.decode(data, topics) == success event check IndexedEvent.decode(data, topics) == success event
test "decodes indexed fields with distinct types":
let event = IndexedWithDistinctType.example
var topics: seq[Topic]
topics.add Topic.default
topics.add Topic.encode(event.a)
let data = AbiEncoder.encode( (event.b,) )
check IndexedWithDistinctType.decode(data, topics) == success event
test "fails when data is incomplete": test "fails when data is incomplete":
let event = SimpleEvent.example let event = SimpleEvent.example
let invalid = AbiEncoder.encode( (event.a,) ) let invalid = AbiEncoder.encode( (event.a,) )