From ad3d2f7a4baadfbf6bd5047c7454cf8c9c513bac Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Wed, 19 Jan 2022 09:18:46 +0100 Subject: [PATCH] Support calculation of solidity function selectors --- contractabi.nim | 2 + contractabi.nimble | 1 + contractabi/selector.nim | 66 ++++++++++++++++++++++++++++++ tests/contractabi/testSelector.nim | 31 ++++++++++++++ tests/testAll.nim | 1 + 5 files changed, 101 insertions(+) create mode 100644 contractabi/selector.nim create mode 100644 tests/contractabi/testSelector.nim diff --git a/contractabi.nim b/contractabi.nim index 0b87083..d8eb40f 100644 --- a/contractabi.nim +++ b/contractabi.nim @@ -1,5 +1,7 @@ import contractabi/encoding import contractabi/decoding +import contractabi/selector export encoding export decoding +export selector diff --git a/contractabi.nimble b/contractabi.nimble index f51ebcd..f2ffb25 100644 --- a/contractabi.nimble +++ b/contractabi.nimble @@ -5,5 +5,6 @@ license = "MIT" requires "stint" requires "stew" +requires "nimcrypto >= 0.5.4 & < 0.6.0" requires "questionable >= 0.10.0 & < 0.11.0" requires "upraises >= 0.1.0 & < 0.2.0" diff --git a/contractabi/selector.nim b/contractabi/selector.nim new file mode 100644 index 0000000..fd46540 --- /dev/null +++ b/contractabi/selector.nim @@ -0,0 +1,66 @@ +import std/strutils +import pkg/nimcrypto +import pkg/stint +import pkg/stew/byteutils +import ./address + +export address +export stint + +type FunctionSelector* = distinct array[4, byte] + +proc toArray*(selector: FunctionSelector): array[4, byte] = + array[4, byte](selector) + +proc `$`*(selector: FunctionSelector): string = + "0x" & selector.toArray.toHex + +template solidityType(T: type, s: string) = + func solidityType*(_: type T): string = s + +solidityType uint8, "uint8" +solidityType uint16, "uint16" +solidityType uint32, "uint32" +solidityType uint64, "uint64" +solidityType UInt128, "uint128" +solidityType UInt256, "uint256" +solidityType int8, "int8" +solidityType int16, "int16" +solidityType int32, "int32" +solidityType int64, "int64" +solidityType Int128, "int128" +solidityType Int256, "int256" +solidityType bool, "bool" +solidityType string, "string" +solidityType Address, "address" + +func solidityType*[N: static int](_: type array[N, byte]): string = + "bytes" & $N + +func solidityType*(_: type seq[byte]): string = + "bytes" + +func solidityType*[T, N](_: type array[N, T]): string = + solidityType(T) & "[" & $array[N, T].default.len & "]" + +func solidityType*[T](_: type seq[T]): string = + solidityType(T) & "[]" + +func solidityType*(Tuple: type tuple): string = + var names: seq[string] + for parameter in Tuple.default.fields: + names.add(solidityType(typeof parameter)) + "(" & names.join(",") & ")" + +func signature*(function: string, Parameters: type tuple = ()): string = + function & solidityType(Parameters) + +func hash(s: string): array[32, byte] = + keccak256.digest(s.toBytes).data + +func selector*(function: string, parameters: type tuple): FunctionSelector = + let signature = signature(function, parameters) + let hash = hash(signature) + var selector: array[4, byte] + selector[0..<4] = hash[0..<4] + FunctionSelector(selector) diff --git a/tests/contractabi/testSelector.nim b/tests/contractabi/testSelector.nim new file mode 100644 index 0000000..9438dd9 --- /dev/null +++ b/tests/contractabi/testSelector.nim @@ -0,0 +1,31 @@ +import std/unittest +import pkg/contractabi/selector + +suite "function selector": + + test "translates nim types into solidity types": + check solidityType(uint8) == "uint8" + check solidityType(uint16) == "uint16" + check solidityType(uint32) == "uint32" + check solidityType(uint64) == "uint64" + check solidityType(UInt128) == "uint128" + check solidityType(UInt256) == "uint256" + check solidityType(int8) == "int8" + check solidityType(int16) == "int16" + check solidityType(int32) == "int32" + check solidityType(int64) == "int64" + check solidityType(Int128) == "int128" + check solidityType(Int256) == "int256" + check solidityType(bool) == "bool" + check solidityType(string) == "string" + check solidityType(Address) == "address" + check solidityType(array[4, byte]) == "bytes4" + check solidityType(array[16, byte]) == "bytes16" + check solidityType(seq[byte]) == "bytes" + check solidityType(array[4, string]) == "string[4]" + check solidityType(seq[string]) == "string[]" + check solidityType((Address, string, bool)) == "(address,string,bool)" + + test "calculates solidity function selector": + check $selector("transfer", (Address, UInt256)) == "0xa9059cbb" + check $selector("transferFrom", (Address, Address, UInt256)) == "0x23b872dd" diff --git a/tests/testAll.nim b/tests/testAll.nim index 14847a9..9969939 100644 --- a/tests/testAll.nim +++ b/tests/testAll.nim @@ -2,5 +2,6 @@ import ./contractabi/testAddress import ./contractabi/testEncoding import ./contractabi/testDecoding import ./contractabi/testCustomTypes +import ./contractabi/testSelector {.warning[UnusedImport]:off.}