nim-eth/eth/common/addresses.nim

112 lines
3.8 KiB
Nim

# 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()