diff --git a/contractabi.nim b/contractabi.nim index 61905fa..0b87083 100644 --- a/contractabi.nim +++ b/contractabi.nim @@ -1,3 +1,5 @@ import contractabi/encoding +import contractabi/decoding export encoding +export decoding diff --git a/contractabi/decoding.nim b/contractabi/decoding.nim new file mode 100644 index 0000000..6ca7823 --- /dev/null +++ b/contractabi/decoding.nim @@ -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.. 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() diff --git a/tests/contractabi/testDecoding.nim b/tests/contractabi/testDecoding.nim new file mode 100644 index 0000000..6ddff96 --- /dev/null +++ b/tests/contractabi/testDecoding.nim @@ -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() +