From 8184529abd8b903fea42670cc3f0cbb61d6b5374 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 25 Nov 2021 09:33:32 +0100 Subject: [PATCH] ABI Encoder extracted from nim-nitro module --- .editorconfig | 5 ++ .gitignore | 3 + .tool-versions | 1 + Readme.md | 45 +++++++++++ contractabi.nim | 3 + contractabi.nimble | 7 ++ contractabi/encoding.nim | 124 ++++++++++++++++++++++++++++++ nim.cfg | 1 + tests/examples.nim | 25 +++++++ tests/nim.cfg | 1 + tests/testEncoding.nim | 158 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 373 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 Readme.md create mode 100644 contractabi.nim create mode 100644 contractabi.nimble create mode 100644 contractabi/encoding.nim create mode 100644 nim.cfg create mode 100644 tests/examples.nim create mode 100644 tests/nim.cfg create mode 100644 tests/testEncoding.nim diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1963f8d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +[*] +indent_style = space +insert_final_newline = true +indent_size = 2 +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..becd7f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +* +!*/ +!*.* diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..40f67df --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nim 1.6.0 diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..314bbf3 --- /dev/null +++ b/Readme.md @@ -0,0 +1,45 @@ +Contract ABI +============ + +Implements encoding of parameters according to the Ethereum +[Contract ABI Specification][1]. + +Installation +------------ + +Use the [Nimble][2] package manager to add `contractabi` to an existing project. +Add the following to its .nimble file: + +```nim +requires "https://github.com/status-im/nim-contract-abi >= 0.1.0 & < 0.2.0" +``` + +Usage +----- + +```nim +import contractabi +import stint + +# encode unsigned integers, booleans, enums +AbiEncoder.encode(42'u8) + +# encode uint256 +AbiEncoder.encode(42.u256) + +# encode byte arrays and sequences +AbiEncoder.encode([1'u8, 2'u8, 3'u8]) +AbiEncoder.encode(@[1'u8, 2'u8, 3'u8]) + +# encode tuples +var encoder = AbiEncoder.init() +encoder.startTuple() +encoder.write(42'u8) +encoder.write(@[1'u8, 2'u8, 3'u8]) +encoder.write(true) +encoder.finishTuple() +encoder.finish() +``` + +[1]: https://docs.soliditylang.org/en/latest/abi-spec.html +[2]: https://github.com/nim-lang/nimble diff --git a/contractabi.nim b/contractabi.nim new file mode 100644 index 0000000..61905fa --- /dev/null +++ b/contractabi.nim @@ -0,0 +1,3 @@ +import contractabi/encoding + +export encoding diff --git a/contractabi.nimble b/contractabi.nimble new file mode 100644 index 0000000..ca7af33 --- /dev/null +++ b/contractabi.nimble @@ -0,0 +1,7 @@ +version = "0.1.0" +author = "Contract ABI Authors" +description = "ABI Encoding for Ethereum contracts" +license = "MIT" + +requires "stint" +requires "upraises >= 0.1.0 & < 0.2.0" diff --git a/contractabi/encoding.nim b/contractabi/encoding.nim new file mode 100644 index 0000000..5feb6a9 --- /dev/null +++ b/contractabi/encoding.nim @@ -0,0 +1,124 @@ +import pkg/stint +import pkg/upraises + +push: {.upraises:[].} + +type + AbiEncoder* = object + stack: seq[Tuple] + Tuple = object + bytes: seq[byte] + postponed: seq[Split] + dynamic: bool + Split = object + head: Slice[int] + tail: seq[byte] + +func write*[T](encoder: var AbiEncoder, value: T) +func encode*[T](_: type AbiEncoder, value: T): seq[byte] + +func init*(_: type AbiEncoder): AbiEncoder = + AbiEncoder(stack: @[Tuple()]) + +func append(tupl: var Tuple, bytes: openArray[byte]) = + tupl.bytes.add(bytes) + +func postpone(tupl: var Tuple, bytes: seq[byte]) = + var split: Split + split.head.a = tupl.bytes.len + tupl.append(AbiEncoder.encode(0'u64)) + split.head.b = tupl.bytes.high + split.tail = bytes + tupl.postponed.add(split) + +func finish(tupl: Tuple): seq[byte] = + var bytes = tupl.bytes + for split in tupl.postponed: + let offset = bytes.len + bytes[split.head] = AbiEncoder.encode(offset.uint64) + bytes.add(split.tail) + bytes + +func append(encoder: var AbiEncoder, bytes: openArray[byte]) = + encoder.stack[^1].append(bytes) + +func postpone(encoder: var AbiEncoder, bytes: seq[byte]) = + if encoder.stack.len > 1: + encoder.stack[^1].postpone(bytes) + else: + encoder.stack[0].append(bytes) + +func setDynamic(encoder: var AbiEncoder) = + encoder.stack[^1].dynamic = true + +func startTuple*(encoder: var AbiEncoder) = + encoder.stack.add(Tuple()) + +func encode(encoder: var AbiEncoder, tupl: Tuple) = + if tupl.dynamic: + encoder.postpone(tupl.finish()) + encoder.setDynamic() + else: + encoder.append(tupl.finish()) + +func finishTuple*(encoder: var AbiEncoder) = + encoder.encode(encoder.stack.pop()) + +func pad(encoder: var AbiEncoder, len: int) = + let padlen = (32 - len mod 32) mod 32 + for _ in 0..