mirror of https://github.com/status-im/nim-eth.git
186 lines
6.4 KiB
Nim
186 lines
6.4 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: [].}
|
||
|
|
||
|
## Base primitive types used in ethereum, as specified in the execution specs:
|
||
|
## https://github.com/ethereum/execution-specs/
|
||
|
##
|
||
|
## For all of `UInt` and `UIntXX`, we use native `uintXX` types and/or `stint`.
|
||
|
##
|
||
|
## In the specification `UInt` is often used to denote an unsigned
|
||
|
## arbitrary-precision integers - in actual code we opt for a bounded type
|
||
|
## instead depending on "reasonable bounds", ie bounds that are unlikely to be
|
||
|
## exceeded in the foreseeable future.
|
||
|
|
||
|
import
|
||
|
std/[hashes, macros, typetraits],
|
||
|
stint,
|
||
|
results,
|
||
|
stew/[assign2, byteutils, endians2, staticfor]
|
||
|
|
||
|
export stint, hashes, results
|
||
|
|
||
|
type
|
||
|
FixedBytes*[N: static int] = distinct array[N, byte]
|
||
|
## Fixed-length byte sequence holding arbitrary data
|
||
|
## https://github.com/ethereum/execution-specs/blob/51fac24740e662844446439ceeb96a460aae0ba0/src/ethereum/base_types.py
|
||
|
##
|
||
|
## This type is specialized to `Bytes4`, `Bytes8` etc below.
|
||
|
# A distinct array is used to avoid copying on trivial type conversions
|
||
|
# to and from other array-based types
|
||
|
|
||
|
ChainId* = distinct uint64
|
||
|
## Chain identifier used for transaction signing to guard against replay
|
||
|
## attacks between networks
|
||
|
##
|
||
|
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
|
||
|
|
||
|
NetworkId* = distinct uint64
|
||
|
## Network identifier - similar to chain id but used for network
|
||
|
## communication to ensure connectivity with peers on the same network.
|
||
|
## Often has the same value as ChainId, but not always
|
||
|
|
||
|
GasInt* = uint64
|
||
|
## Integer used to comput gas usage in individual blocks / transactions -
|
||
|
## here, a smaller type is convenient since gas computations are expensive.
|
||
|
##
|
||
|
## Care must be taken since the sum of gas usage across many blocks may
|
||
|
## exceed the uint64 range.
|
||
|
##
|
||
|
## See also:
|
||
|
## * https://github.com/status-im/nimbus-eth1/issues/35
|
||
|
|
||
|
template to*[N: static int](v: array[N, byte], T: type FixedBytes[N]): T =
|
||
|
T(v)
|
||
|
|
||
|
template data*[N: static int](v: FixedBytes[N]): array[N, byte] =
|
||
|
distinctBase(v)
|
||
|
|
||
|
template `data=`*[N: static int](a: FixedBytes[N], b: array[N, byte]) =
|
||
|
assign(distinctBase(a), b)
|
||
|
|
||
|
func copyFrom*[N: static int](T: type FixedBytes[N], 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.
|
||
|
if v.len > start:
|
||
|
let n = min(N, v.len - start)
|
||
|
assign(result.data.toOpenArray(0, n - 1), v.toOpenArray(start, start + n - 1))
|
||
|
|
||
|
template default*[N](T: type FixedBytes[N]): T =
|
||
|
# Avoid bad codegen where fixed bytes are zeroed byte-by-byte at call site
|
||
|
const def = system.default(T)
|
||
|
def
|
||
|
|
||
|
func `==`*(a, b: FixedBytes): bool {.inline.} =
|
||
|
equalMem(addr a.data[0], addr b.data[0], a.N)
|
||
|
|
||
|
func hash*[N: static int](v: FixedBytes[N]): Hash {.inline.} =
|
||
|
copyMem(addr result, addr v.data[0], min(N, sizeof(Hash)))
|
||
|
|
||
|
when N > sizeof(Hash):
|
||
|
var tmp: Hash
|
||
|
staticFor i, 1 ..< N div sizeof(Hash):
|
||
|
copyMem(addr tmp, addr v.data[i * sizeof(Hash)], sizeof(Hash))
|
||
|
result = result !& tmp
|
||
|
const last = N mod sizeof(Hash)
|
||
|
when last > 0:
|
||
|
copyMem(addr tmp, addr v.data[N - last], last)
|
||
|
result !& tmp
|
||
|
|
||
|
func toHex*(v: FixedBytes): string =
|
||
|
## Convert to lowercase hex without 0x prefix
|
||
|
toHex(v.data)
|
||
|
|
||
|
func to0xHex*(v: FixedBytes): string =
|
||
|
## Convert to lowercase hex with 0x prefix
|
||
|
to0xHex(v.data)
|
||
|
|
||
|
func `$`*(v: FixedBytes): string =
|
||
|
## Convert the given value to a string representation suitable for presentation
|
||
|
## To convert to a specific string encoding, use `toHex`, `to0xHex` etc
|
||
|
to0xHex(v)
|
||
|
|
||
|
func fromHex*(T: type FixedBytes, c: openArray[char]): T {.raises: [ValueError].} =
|
||
|
## Parse a string as hex after optionally stripping "0x", raising ValueError if:
|
||
|
## * the string is too long or to short
|
||
|
## * the string can't be parsed as hex
|
||
|
T(hexToByteArrayStrict(c, T.N))
|
||
|
|
||
|
template makeFixedBytesN(N: static int) =
|
||
|
# Create specific numbered instantiations along with helpers
|
||
|
type `Bytes N`* = FixedBytes[N]
|
||
|
|
||
|
const `zeroBytes N`* = system.default(`Bytes N`)
|
||
|
template default*(T: type `Bytes N`): `Bytes N` =
|
||
|
# reuse single constant for precomputed N
|
||
|
`zeroBytes N`
|
||
|
|
||
|
template `bytes N`*(s: static string): `Bytes N` =
|
||
|
`Bytes N`.fromHex(s)
|
||
|
|
||
|
makeFixedBytesN(4)
|
||
|
makeFixedBytesN(8)
|
||
|
makeFixedBytesN(20)
|
||
|
makeFixedBytesN(32)
|
||
|
makeFixedBytesN(48)
|
||
|
makeFixedBytesN(64)
|
||
|
makeFixedBytesN(96)
|
||
|
makeFixedBytesN(256)
|
||
|
|
||
|
# Ethereum keeps integers as big-endian
|
||
|
template to*(v: uint32, T: type Bytes4): T =
|
||
|
T v.toBytesBE()
|
||
|
|
||
|
template to*(v: Bytes4, T: type uint32): T =
|
||
|
T.fromBytesBE(v)
|
||
|
|
||
|
template to*(v: uint64, T: type Bytes8): T =
|
||
|
T v.toBytesBE()
|
||
|
|
||
|
template to*(v: Bytes8, T: type uint64): T =
|
||
|
T.fromBytesBE(v)
|
||
|
|
||
|
template to*[M, N: static int](v: FixedBytes[M], T: type StUint[N]): T =
|
||
|
static:
|
||
|
assert N == M * 8
|
||
|
T.fromBytesBE(v.data)
|
||
|
|
||
|
template to*[M, N: static int](v: StUint[M], T: type FixedBytes[N]): T =
|
||
|
static:
|
||
|
assert N * 8 == M
|
||
|
T v.toBytesBE()
|
||
|
|
||
|
type
|
||
|
# Aliases commonly found in the spec - reasons to use an alias instead of the
|
||
|
# underlying type include:
|
||
|
# * the spec says `UInt` and we use a bounded type instead (uint64 or UInt256)
|
||
|
# * the spec consistently uses the alias and we're translating from there
|
||
|
# directly
|
||
|
# * we have distinct behaviour attached to it that shouldn't "pollute" other
|
||
|
# usages of the same type - `$` is the canonical example
|
||
|
# * the algorithm is specific to ethereum and not of "general interest"
|
||
|
#
|
||
|
# In most other cases, code is easier to read and more flexible when it
|
||
|
# doesn't use these aliases.
|
||
|
AccountNonce* = uint64
|
||
|
BlockNumber* = uint64
|
||
|
Bloom* = Bytes256
|
||
|
Bytes* = seq[byte] # TODO distinct?
|
||
|
KzgCommitment* = Bytes48
|
||
|
KzgProof* = Bytes48
|
||
|
|
||
|
ForkID* = tuple[crc: uint32, nextFork: uint64] ## EIP 2364/2124
|
||
|
|
||
|
func `==`*(a, b: NetworkId): bool {.borrow.}
|
||
|
func `$`*(x: NetworkId): string {.borrow.}
|
||
|
func `==`*(a, b: ChainId): bool {.borrow.}
|
||
|
func `$`*(x: ChainId): string {.borrow.}
|