Add support for decode failures by returning Result type

This commit is contained in:
Mark Spanbroek 2021-12-01 13:01:28 +01:00
parent 319418cab7
commit a43c7b21a4
2 changed files with 60 additions and 46 deletions

View File

@ -1,6 +1,7 @@
import pkg/stint
import pkg/stew/endians2
import pkg/stew/byteutils
import pkg/questionable/results
import pkg/upraises
import ./encoding
@ -20,7 +21,7 @@ type
padRight
UInt = SomeUnsignedInt | StUint
func read*(decoder: var AbiDecoder, T: type): T
func read*(decoder: var AbiDecoder, T: type): ?!T
func init(_: type Tuple, offset: int, dynamic: bool): Tuple =
Tuple(start: offset, index: offset, dynamic: dynamic)
@ -41,55 +42,60 @@ func updateLast(decoder: var AbiDecoder, index: int) =
if index > decoder.last:
decoder.last = index
func advance(decoder: var AbiDecoder, amount: int) =
func advance(decoder: var AbiDecoder, amount: int): ?!void =
decoder.index += amount
decoder.updateLast(decoder.index)
success()
func read(decoder: var AbiDecoder, amount: int, padding = padLeft): seq[byte] =
func read(decoder: var AbiDecoder, amount: int, padding=padLeft): ?!seq[byte] =
let padlen = (32 - amount mod 32) mod 32
if padding == padLeft:
decoder.advance(padlen)
result = decoder.bytes[decoder.index..<decoder.index+amount]
decoder.advance(amount)
?decoder.advance(padlen)
let index = decoder.index
?decoder.advance(amount)
result = success decoder.bytes[index..<index+amount]
if padding == padRight:
decoder.advance(padlen)
?decoder.advance(padlen)
func decode(decoder: var AbiDecoder, T: type UInt): T =
T.fromBytesBE(decoder.read(sizeof(T)))
func decode(decoder: var AbiDecoder, T: type UInt): ?!T =
success T.fromBytesBE(?decoder.read(sizeof(T)))
func decode(decoder: var AbiDecoder, T: type bool): T =
decoder.read(uint8) != 0
func decode(decoder: var AbiDecoder, T: type bool): ?!T =
success (?decoder.read(uint8) != 0)
func decode(decoder: var AbiDecoder, T: type enum): T =
T(decoder.read(uint64))
func decode(decoder: var AbiDecoder, T: type enum): ?!T =
success T(?decoder.read(uint64))
func decode[I](decoder: var AbiDecoder, T: type array[I, byte]): T =
result[0..<result.len] = decoder.read(result.len, padRight)
func decode[I](decoder: var AbiDecoder, T: type array[I, byte]): ?!T =
var arr: T
arr[0..<arr.len] = ?decoder.read(arr.len, padRight)
success arr
func readOffset(decoder: var AbiDecoder): int =
let offset = decoder.read(uint64)
decoder.currentTuple.start + offset.int
func readOffset(decoder: var AbiDecoder): ?!int =
let offset = ?decoder.read(uint64)
success decoder.currentTuple.start + offset.int
func readTail(decoder: var AbiDecoder, T: type seq[byte]): T =
let offset = decoder.readOffset()
func readTail(decoder: var AbiDecoder): ?!seq[byte] =
let offset = ?decoder.readOffset()
var tailDecoder = AbiDecoder.init(decoder.bytes, offset)
result = tailDecoder.read(T)
result = tailDecoder.read(seq[byte])
decoder.updateLast(tailDecoder.index)
func decode(decoder: var AbiDecoder, T: type seq[byte]): T =
func decode(decoder: var AbiDecoder, T: type seq[byte]): ?!T =
if decoder.currentTuple.dynamic:
decoder.readTail(T)
decoder.readTail()
else:
let len = decoder.read(uint64).int
decoder.read(len, padRight)
let len = ?decoder.read(uint64)
decoder.read(len.int, padRight)
func startTuple(decoder: var AbiDecoder, dynamic: bool) =
func startTuple(decoder: var AbiDecoder, dynamic: bool): ?!void =
var start: int
if decoder.currentTuple.dynamic and dynamic:
start = decoder.readOffset()
start = ?decoder.readOffset()
else:
start = decoder.index
decoder.stack.add(Tuple.init(start, dynamic))
success()
func finishTuple(decoder: var AbiDecoder) =
doAssert decoder.stack.len > 1, "unable to finish a tuple that hasn't started"
@ -97,14 +103,16 @@ func finishTuple(decoder: var AbiDecoder) =
if not tupl.dynamic:
decoder.index = tupl.index
func decode(decoder: var AbiDecoder, T: type tuple): T =
const dynamic = AbiEncoder.isDynamic(typeof(result))
decoder.startTuple(dynamic)
for element in result.fields:
element = decoder.read(typeof(element))
func decode[T: tuple](decoder: var AbiDecoder, _: typedesc[T]): ?!T =
const dynamic = AbiEncoder.isDynamic(T)
var tupl: T
?decoder.startTuple(dynamic)
for element in tupl.fields:
element = ?decoder.read(typeof(element))
decoder.finishTuple()
success tupl
func read*(decoder: var AbiDecoder, T: type): T =
func read*(decoder: var AbiDecoder, T: type): ?!T =
decoder.decode(T)
func finish(decoder: var AbiDecoder) =
@ -112,24 +120,29 @@ func finish(decoder: var AbiDecoder) =
doAssert decoder.last == decoder.bytes.len, "unread trailing bytes found"
doAssert decoder.last mod 32 == 0, "encoding variant broken"
func decode[T](decoder: var AbiDecoder, _: type seq[T]): seq[T] =
let len = decoder.read(uint64)
decoder.startTuple(dynamic=true)
func decode[T](decoder: var AbiDecoder, _: type seq[T]): ?!seq[T] =
var sequence: seq[T]
let len = ?decoder.read(uint64)
?decoder.startTuple(dynamic=true)
for _ in 0..<len:
result.add(decoder.read(T))
sequence.add(?decoder.read(T))
decoder.finishTuple()
success sequence
func decode[I,T](decoder: var AbiDecoder, _: type array[I,T]): array[I,T] =
func decode[I,T](decoder: var AbiDecoder, _: type array[I,T]): ?!array[I,T] =
var arr: array[I, T]
const dynamic = AbiEncoder.isDynamic(T)
decoder.startTuple(dynamic)
for i in 0..<result.len:
result[i] = decoder.read(T)
?decoder.startTuple(dynamic)
for i in 0..<arr.len:
arr[i] = ?decoder.read(T)
decoder.finishTuple()
success arr
func decode(decoder: var AbiDecoder, T: type string): T =
string.fromBytes(decoder.read(seq[byte]))
func decode(decoder: var AbiDecoder, T: type string): ?!T =
success string.fromBytes(?decoder.read(seq[byte]))
func decode*(_: type AbiDecoder, bytes: seq[byte], T: type): T =
func decode*(_: type AbiDecoder, bytes: seq[byte], T: type): ?!T =
var decoder = AbiDecoder.init(bytes)
result = decoder.decode(T)
var value = ?decoder.decode(T)
decoder.finish()
success value

View File

@ -1,4 +1,5 @@
import std/unittest
import pkg/questionable/results
import contractabi
import ./examples
@ -6,7 +7,7 @@ suite "ABI decoding":
proc checkDecode[T](value: T) =
let encoded = AbiEncoder.encode(value)
check AbiDecoder.decode(encoded, T) == value
check !AbiDecoder.decode(encoded, T) == value
proc checkDecode(T: type) =
checkDecode(T.example)