Decoding of basic types
This commit is contained in:
parent
6c7dc7123a
commit
767c4ab588
|
@ -1,3 +1,5 @@
|
||||||
import contractabi/encoding
|
import contractabi/encoding
|
||||||
|
import contractabi/decoding
|
||||||
|
|
||||||
export encoding
|
export encoding
|
||||||
|
export decoding
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import pkg/stint
|
||||||
|
import pkg/stew/endians2
|
||||||
|
import pkg/upraises
|
||||||
|
|
||||||
|
push: {.upraises:[].}
|
||||||
|
|
||||||
|
type
|
||||||
|
AbiDecoder* = object
|
||||||
|
bytes: seq[byte]
|
||||||
|
index: int
|
||||||
|
stack: seq[Tuple]
|
||||||
|
Tuple = object
|
||||||
|
start: int
|
||||||
|
finish: int
|
||||||
|
dynamic: bool
|
||||||
|
Padding = enum
|
||||||
|
padLeft,
|
||||||
|
padRight
|
||||||
|
UInt = SomeUnsignedInt | StUint
|
||||||
|
|
||||||
|
func init*(_: type AbiDecoder, bytes: seq[byte], offset=0): AbiDecoder =
|
||||||
|
AbiDecoder(bytes: bytes, index: offset, stack: @[Tuple()])
|
||||||
|
|
||||||
|
func currentTuple(decoder: var AbiDecoder): var Tuple =
|
||||||
|
decoder.stack[^1]
|
||||||
|
|
||||||
|
func updateFinish(decoder: var AbiDecoder, index: int) =
|
||||||
|
if index > decoder.currentTuple.finish:
|
||||||
|
decoder.currentTuple.finish = index
|
||||||
|
|
||||||
|
func advance(decoder: var AbiDecoder, amount: int) =
|
||||||
|
decoder.index += amount
|
||||||
|
decoder.updateFinish(decoder.index)
|
||||||
|
|
||||||
|
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)
|
||||||
|
if padding == padRight:
|
||||||
|
decoder.advance(padlen)
|
||||||
|
|
||||||
|
func read*(decoder: var AbiDecoder, T: type UInt): T =
|
||||||
|
T.fromBytesBE(decoder.read(sizeof(T)))
|
||||||
|
|
||||||
|
func read*(decoder: var AbiDecoder, T: type bool): T =
|
||||||
|
decoder.read(uint8) != 0
|
||||||
|
|
||||||
|
func read*(decoder: var AbiDecoder, T: type enum): T =
|
||||||
|
T(decoder.read(uint64))
|
||||||
|
|
||||||
|
func read*[I: static int](decoder: var AbiDecoder, T: type array[I, byte]): T =
|
||||||
|
result[0..<I] = decoder.read(I, padRight)
|
||||||
|
|
||||||
|
func read*(decoder: var AbiDecoder, T: type seq[byte]): T
|
||||||
|
|
||||||
|
func readOffset(decoder: var AbiDecoder): int =
|
||||||
|
let offset = decoder.read(uint64)
|
||||||
|
decoder.currentTuple.start + offset.int
|
||||||
|
|
||||||
|
func readTail(decoder: var AbiDecoder, T: type seq[byte]): T =
|
||||||
|
let offset = decoder.readOffset()
|
||||||
|
var tailDecoder = AbiDecoder.init(decoder.bytes, offset)
|
||||||
|
result = tailDecoder.read(T)
|
||||||
|
decoder.updateFinish(tailDecoder.index)
|
||||||
|
|
||||||
|
func read*(decoder: var AbiDecoder, T: type seq[byte]): T =
|
||||||
|
if decoder.currentTuple.dynamic:
|
||||||
|
decoder.readTail(T)
|
||||||
|
else:
|
||||||
|
let len = decoder.read(uint64).int
|
||||||
|
let bytes = decoder.read(len, padRight)
|
||||||
|
bytes
|
||||||
|
|
||||||
|
func startTuple*(decoder: var AbiDecoder, dynamic: bool) =
|
||||||
|
var start: int
|
||||||
|
if decoder.currentTuple.dynamic and dynamic:
|
||||||
|
start = decoder.readOffset()
|
||||||
|
else:
|
||||||
|
start = decoder.index
|
||||||
|
decoder.stack.add(Tuple(start: start, finish: start, dynamic: dynamic))
|
||||||
|
decoder.index = decoder.currentTuple.start
|
||||||
|
|
||||||
|
func finishTuple*(decoder: var AbiDecoder) =
|
||||||
|
doAssert decoder.stack.len > 1, "unable to finish a tuple that hasn't started"
|
||||||
|
let tupl = decoder.stack.pop()
|
||||||
|
decoder.updateFinish(tupl.finish)
|
||||||
|
decoder.index = tupl.finish
|
||||||
|
|
||||||
|
func finish*(decoder: var AbiDecoder) =
|
||||||
|
doAssert decoder.stack.len == 1, "not all tuples were finished"
|
||||||
|
doAssert decoder.index == decoder.bytes.len, "unread trailing bytes found"
|
||||||
|
doAssert decoder.index mod 32 == 0, "encoding variant broken"
|
||||||
|
|
||||||
|
func decode*(_: type AbiDecoder, bytes: seq[byte], T: type): T =
|
||||||
|
var decoder = AbiDecoder.init(bytes)
|
||||||
|
result = decoder.read(T)
|
||||||
|
decoder.finish()
|
|
@ -0,0 +1,188 @@
|
||||||
|
import std/unittest
|
||||||
|
import contractabi
|
||||||
|
import ./examples
|
||||||
|
|
||||||
|
suite "ABI decoding":
|
||||||
|
|
||||||
|
proc checkDecode[T](value: T) =
|
||||||
|
let encoded = AbiEncoder.encode(value)
|
||||||
|
check AbiDecoder.decode(encoded, T) == value
|
||||||
|
|
||||||
|
proc checkDecode(T: type) =
|
||||||
|
checkDecode(T.example)
|
||||||
|
checkDecode(T.low)
|
||||||
|
checkDecode(T.high)
|
||||||
|
|
||||||
|
test "decodes uint8, uint16, 32, 64":
|
||||||
|
checkDecode(uint8)
|
||||||
|
checkDecode(uint16)
|
||||||
|
checkDecode(uint32)
|
||||||
|
checkDecode(uint64)
|
||||||
|
|
||||||
|
# TODO: failure to decode when prefix not all zeroes
|
||||||
|
# TODO: failure when trailing bytes
|
||||||
|
|
||||||
|
test "decodes booleans":
|
||||||
|
checkDecode(false)
|
||||||
|
checkDecode(true)
|
||||||
|
|
||||||
|
# TODO: failure to decode when value not 0 or 1
|
||||||
|
|
||||||
|
test "decodes ranges":
|
||||||
|
type SomeRange = range[0x0000'u16..0xAAAA'u16]
|
||||||
|
checkDecode(SomeRange(42))
|
||||||
|
checkDecode(SomeRange.low)
|
||||||
|
checkDecode(SomeRange.high)
|
||||||
|
|
||||||
|
# TODO: failure to decode when value not in range
|
||||||
|
|
||||||
|
test "decodes enums":
|
||||||
|
type SomeEnum = enum
|
||||||
|
one = 1
|
||||||
|
two = 2
|
||||||
|
checkDecode(one)
|
||||||
|
checkDecode(two)
|
||||||
|
|
||||||
|
# TODO: failure to decode when invalid enum value
|
||||||
|
|
||||||
|
test "decodes stints":
|
||||||
|
checkDecode(UInt128)
|
||||||
|
checkDecode(UInt256)
|
||||||
|
|
||||||
|
test "decodes byte arrays":
|
||||||
|
checkDecode([1'u8, 2'u8, 3'u8])
|
||||||
|
checkDecode(array[32, byte].example)
|
||||||
|
checkDecode(array[33, byte].example)
|
||||||
|
|
||||||
|
# TODO: failure to decode when byte array of wrong length
|
||||||
|
|
||||||
|
test "decodes byte sequences":
|
||||||
|
checkDecode(@[1'u8, 2'u8, 3'u8])
|
||||||
|
checkDecode(@(array[32, byte].example))
|
||||||
|
checkDecode(@(array[33, byte].example))
|
||||||
|
|
||||||
|
# TODO: failure to decode when not enough bytes
|
||||||
|
|
||||||
|
test "decodes tuples":
|
||||||
|
let a = true
|
||||||
|
let b = @[1'u8, 2'u8, 3'u8]
|
||||||
|
let c = 0xAABBCCDD'u32
|
||||||
|
let d = @[4'u8, 5'u8, 6'u8]
|
||||||
|
|
||||||
|
var encoder= AbiEncoder.init()
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.write(a)
|
||||||
|
encoder.write(b)
|
||||||
|
encoder.write(c)
|
||||||
|
encoder.write(d)
|
||||||
|
encoder.finishTuple()
|
||||||
|
let encoding = encoder.finish()
|
||||||
|
|
||||||
|
var decoder = AbiDecoder.init(encoding)
|
||||||
|
decoder.startTuple(dynamic=true)
|
||||||
|
check decoder.read(bool) == a
|
||||||
|
check decoder.read(seq[byte]) == b
|
||||||
|
check decoder.read(uint32) == c
|
||||||
|
check decoder.read(seq[byte]) == d
|
||||||
|
decoder.finishTuple()
|
||||||
|
decoder.finish()
|
||||||
|
|
||||||
|
test "decodes nested tuples":
|
||||||
|
let a = true
|
||||||
|
let b = @[1'u8, 2'u8, 3'u8]
|
||||||
|
let c = 0xAABBCCDD'u32
|
||||||
|
let d = @[4'u8, 5'u8, 6'u8]
|
||||||
|
|
||||||
|
var encoder= AbiEncoder.init()
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.write(a)
|
||||||
|
encoder.write(b)
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.write(c)
|
||||||
|
encoder.write(d)
|
||||||
|
encoder.finishTuple()
|
||||||
|
encoder.finishTuple()
|
||||||
|
let encoding = encoder.finish()
|
||||||
|
|
||||||
|
var decoder = AbiDecoder.init(encoding)
|
||||||
|
decoder.startTuple(dynamic=true)
|
||||||
|
check decoder.read(bool) == a
|
||||||
|
check decoder.read(seq[byte]) == b
|
||||||
|
decoder.startTuple(dynamic=true)
|
||||||
|
check decoder.read(uint32) == c
|
||||||
|
check decoder.read(seq[byte]) == d
|
||||||
|
decoder.finishTuple()
|
||||||
|
decoder.finishTuple()
|
||||||
|
decoder.finish()
|
||||||
|
|
||||||
|
test "reads elements after dynamic tuple":
|
||||||
|
let a = @[1'u8, 2'u8, 3'u8]
|
||||||
|
let b = 0xAABBCCDD'u32
|
||||||
|
var encoder = AbiEncoder.init()
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.write(a)
|
||||||
|
encoder.finishTuple()
|
||||||
|
encoder.write(b)
|
||||||
|
let encoding = encoder.finish()
|
||||||
|
|
||||||
|
var decoder = AbiDecoder.init(encoding)
|
||||||
|
decoder.startTuple(dynamic=true)
|
||||||
|
check decoder.read(seq[byte]) == a
|
||||||
|
decoder.finishTuple()
|
||||||
|
check decoder.read(uint32) == b
|
||||||
|
decoder.finish()
|
||||||
|
|
||||||
|
test "reads elements after static tuple":
|
||||||
|
let a = 0x123'u16
|
||||||
|
let b = 0xAABBCCDD'u32
|
||||||
|
var encoder = AbiEncoder.init()
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.write(a)
|
||||||
|
encoder.finishTuple()
|
||||||
|
encoder.write(b)
|
||||||
|
let encoding = encoder.finish()
|
||||||
|
|
||||||
|
var decoder = AbiDecoder.init(encoding)
|
||||||
|
decoder.startTuple(dynamic=false)
|
||||||
|
check decoder.read(uint16) == a
|
||||||
|
decoder.finishTuple()
|
||||||
|
check decoder.read(uint32) == b
|
||||||
|
decoder.finish()
|
||||||
|
|
||||||
|
test "reads static tuple inside dynamic tuple":
|
||||||
|
let a = @[1'u8, 2'u8, 3'u8]
|
||||||
|
let b = 0xAABBCCDD'u32
|
||||||
|
|
||||||
|
var encoder= AbiEncoder.init()
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.write(a)
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.write(b)
|
||||||
|
encoder.finishTuple()
|
||||||
|
encoder.finishTuple()
|
||||||
|
let encoding = encoder.finish()
|
||||||
|
|
||||||
|
var decoder = AbiDecoder.init(encoding)
|
||||||
|
decoder.startTuple(dynamic=true)
|
||||||
|
check decoder.read(seq[byte]) == a
|
||||||
|
decoder.startTuple(dynamic=false)
|
||||||
|
check decoder.read(uint32) == b
|
||||||
|
decoder.finishTuple()
|
||||||
|
decoder.finishTuple()
|
||||||
|
decoder.finish()
|
||||||
|
|
||||||
|
test "reads empty tuples":
|
||||||
|
var encoder = AbiEncoder.init()
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.startTuple()
|
||||||
|
encoder.finishTuple()
|
||||||
|
encoder.finishTuple()
|
||||||
|
let encoding = encoder.finish()
|
||||||
|
|
||||||
|
var decoder = AbiDecoder.init(encoding)
|
||||||
|
decoder.startTuple(dynamic=false)
|
||||||
|
decoder.startTuple(dynamic=false)
|
||||||
|
decoder.finishTuple()
|
||||||
|
decoder.finishTuple()
|
||||||
|
decoder.finish()
|
||||||
|
|
Loading…
Reference in New Issue