# eth # Copyright (c) 2024 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. {.push raises: [].} ## 20-byte ethereum account address, as derived from the keypair controlling it ## https://ethereum.org/en/developers/docs/accounts/#account-creation import std/[typetraits, hashes as std_hashes], "."/[base, hashes], stew/assign2 export hashes type Address* = distinct Bytes20 ## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/paris/fork_types.py#L28 const zeroAddress* = system.default(Address) ## Address consisting of all zeroes. ## Transactions to zeroAddress are legitimate transfers to that account, not ## contract creations. They are used to "burn" Eth. People also send Eth to ## address zero by accident, unrecoverably, due to poor user interface issues. template to*(v: array[20, byte], _: type Address): Address = Address(v) func to*(s: Bytes32 | Hash32, _: type Address): Address = ## Take the last 20 bytes of the given hash to form an Address - this is a ## lossy conversion which discards the first 12 bytes assign(result.data, s.data.toOpenArray(12, 31)) func to*(a: Address, _: type Bytes32): Bytes32 = ## Place the address in the last 20 bytes of the result, filling the rest with 0 assign(result.data.toOpenArray(12, 31), a.data) template data*(v: Address): array[20, byte] = distinctBase(v) template `data=`*[N: static int](a: FixedBytes[N], b: array[N, byte]) = assign(distinctBase(a), b) template copyFrom*(T: type Address, v: openArray[byte], start = 0): T = ## Copy up to N bytes from the given openArray, starting at `start` and ## filling any missing bytes with zero. ## ## This is a lenient function in that `v` may contain both fewer and more ## bytes than N and start might be out of bounds. Address(Bytes20.copyFrom(v, start)) template default*(_: type Address): Address = # Avoid bad codegen where fixed bytes are zeroed byte-by-byte at call site zeroAddress func `==`*(a, b: Address): bool {.borrow.} func hash*(a: Address): Hash {.inline.} = # Addresses are more or less random so we should not need a fancy mixing # function var a0 {.noinit.}, a1 {.noinit.}: uint64 var a2 {.noinit.}: uint32 copyMem(addr a0, unsafeAddr a.data[0], sizeof(a0)) copyMem(addr a1, unsafeAddr a.data[8], sizeof(a1)) copyMem(addr a2, unsafeAddr a.data[16], sizeof(a2)) cast[Hash](a0 + a1 + uint64(a2)) func toHex*(a: Address): string {.borrow.} func to0xHex*(a: Address): string {.borrow.} func `$`*(a: Address): string {.borrow.} func fromHex*(_: type Address, s: openArray[char]): Address {.raises: [ValueError].} = Address(Bytes20.fromHex(s)) template to*(s: static string, _: type Address): Address = const hash = Address.fromHex(s) hash template address*(s: static string): Address = s.to(Address) func toChecksum0xHex*(a: Address): string = ## Convert the address to 0x-prefixed mixed-case EIP-55 format let # TODO avoid memory allocations here hhash1 = a.toHex() hhash2 = keccak256(hhash1).toHex() result = newStringOfCap(hhash2.len + 2) result.add "0x" for i, c in hhash1: if hhash2[i] >= '0' and hhash2[i] <= '7': result.add c else: if c >= '0' and c <= '9': result.add c else: result.add chr(ord(c) - ord('a') + ord('A')) func hasValidChecksum*(_: type Address, a: string): bool = ## Validate checksumable mixed-case address (EIP-55). let address = try: Address.fromHex(a) except ValueError: return false a == address.toChecksum0xHex()