Skip to content

Commit

Permalink
Implement EIP-7702: Set EOA account code (#2631)
Browse files Browse the repository at this point in the history
* Implement EIP-7702 part 1: Behavior

* Implement EIP-7702 part 2: Tx validation

* Implement EIP-7702 part 3: Delegation Designation and Gas Costs
  • Loading branch information
jangko authored Nov 25, 2024
1 parent 78c5770 commit fbfc161
Show file tree
Hide file tree
Showing 18 changed files with 382 additions and 59 deletions.
30 changes: 27 additions & 3 deletions fluffy/tools/eth_data_exporter/parser.nim
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,27 @@ proc fromJson*[T: Bytes32 | Hash32](n: JsonNode, name: string, x: var seq[T]) =
hexToByteArray(v.getStr(), h.data)
x.add h

proc fromJson*(n: JsonNode, name: string, x: var ChainId) =
let node = n[name]
if node.kind == JInt:
x = ChainId(node.getInt)
else:
x = hexToInt(node.getStr(), int).ChainId

proc parseAuth(n: JsonNode): Authorization =
n.fromJson("chainId", result.chainId)
n.fromJson("address", result.address)
n.fromJson("nonce", result.nonce)
n.fromJson("v", result.v)
n.fromJson("r", result.r)
n.fromJson("s", result.s)

proc fromJson*(n: JsonNode, name: string, x: var seq[Authorization]) =
let node = n[name]
for v in node:
x = newSeqOfCap[Authorization](node.len)
x.add parseAuth(node)

proc parseBlockHeader*(n: JsonNode): Header =
n.fromJson "parentHash", result.parentHash
n.fromJson "sha3Uncles", result.ommersHash
Expand Down Expand Up @@ -184,11 +205,14 @@ proc parseTransaction*(n: JsonNode): Transaction =
for acn in accessList:
tx.accessList.add parseAccessPair(acn)

if tx.txType >= TxEip4844:
if tx.txType == TxEip4844:
n.fromJson "maxFeePerBlobGas", tx.maxFeePerBlobGas

if n.hasKey("versionedHashes") and n["versionedHashes"].kind != JNull:
n.fromJson "versionedHashes", tx.versionedHashes
if n.hasKey("versionedHashes") and n["versionedHashes"].kind != JNull:
n.fromJson "versionedHashes", tx.versionedHashes

if tx.txType == TxEip7702:
n.fromJson "authorizationList", tx.authorizationList

tx

Expand Down
64 changes: 64 additions & 0 deletions nimbus/core/eip7702.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Nimbus
# Copyright (c) 2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

{.push raises: [].}

import
../evm/code_bytes,
results,
stew/assign2,
eth/common/eth_types,
eth/common/eth_types_rlp,
eth/common/keys

const
DelegationPrefix = [0xef.byte, 0x01, 0x00]

const
PER_AUTH_BASE_COST* = 2500
PER_EMPTY_ACCOUNT_COST* = 25000

func authority*(auth: Authorization): Opt[Address] =
let sigHash = rlpHashForSigning(auth)

var bytes: array[65, byte]
assign(bytes.toOpenArray(0, 31), auth.r.toBytesBE())
assign(bytes.toOpenArray(32, 63), auth.s.toBytesBE())
bytes[64] = auth.v.byte

let sig = Signature.fromRaw(bytes).valueOr:
return Opt.none(Address)

let pubkey = recover(sig, SkMessage(sigHash.data)).valueOr:
return Opt.none(Address)

ok(pubkey.toCanonicalAddress())

func parseDelegation*(code: CodeBytesRef): bool =
if code.len != 23:
return false

if not code.hasPrefix(DelegationPrefix):
return false

true

func addressToDelegation*(auth: Address): array[23, byte] =
assign(result.toOpenArray(0, 2), DelegationPrefix)
assign(result.toOpenArray(3, 22), auth.data)

func parseDelegationAddress*(code: CodeBytesRef): Opt[Address] =
if code.len != 23:
return Opt.none(Address)

if not code.hasPrefix(DelegationPrefix):
return Opt.none(Address)

Opt.some(Address(slice[20](code, 3, 22)))
2 changes: 1 addition & 1 deletion nimbus/core/tx_pool/tx_tasks/tx_classify.nim
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ proc txFeesCovered(xp: TxPoolRef; item: TxItemRef): bool =
baseFee = xp.baseFee
return false

if item.tx.txType >= TxEip4844:
if item.tx.txType == TxEip4844:
let
excessBlobGas = xp.excessBlobGas
blobGasPrice = getBlobBaseFee(excessBlobGas)
Expand Down
20 changes: 18 additions & 2 deletions nimbus/core/validate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ proc validateTxBasic*(
if tx.txType == TxEip4844 and fork < FkCancun:
return err("invalid tx: Eip4844 Tx type detected before Cancun")

if tx.txType == TxEip7702 and fork < FkPrague:
return err("invalid tx: Eip7702 Tx type detected before Prague")

if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
return err("invalid tx: initcode size exceeds maximum")

Expand Down Expand Up @@ -252,7 +255,7 @@ proc validateTxBasic*(
if not validateEip2930SignatureForm(tx):
return err("invalid tx: invalid post EIP-2930 signature form")

if tx.txType >= TxEip4844:
if tx.txType == TxEip4844:
if tx.to.isNone:
return err("invalid tx: destination must be not empty")

Expand All @@ -267,6 +270,19 @@ proc validateTxBasic*(
return err("invalid tx: one of blobVersionedHash has invalid version. " &
&"get={bv.data[0].int}, expect={VERSIONED_HASH_VERSION_KZG.int}")

if tx.txType == TxEip7702:
if tx.authorizationList.len == 0:
return err("invalid tx: authorization list must not empty")

const SECP256K1halfN = SECPK1_N div 2

for auth in tx.authorizationList:
if auth.v > 1'u64:
return err("invalid tx: auth.v must be 0 or 1")

if auth.s > SECP256K1halfN:
return err("invalid tx: auth.s must be <= SECP256K1N/2")

ok()

proc validateTransaction*(
Expand Down Expand Up @@ -330,7 +346,7 @@ proc validateTransaction*(
if codeHash != EMPTY_CODE_HASH:
return err(&"invalid tx: sender is not an EOA. sender={sender.toHex}, codeHash={codeHash.data.toHex}")

if tx.txType >= TxEip4844:
if tx.txType == TxEip4844:
# ensure that the user was willing to at least pay the current data gasprice
let blobGasPrice = getBlobBaseFee(excessBlobGas)
if tx.maxFeePerBlobGas < blobGasPrice:
Expand Down
36 changes: 33 additions & 3 deletions nimbus/db/ledger.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import
../utils/mergeutils,
../evm/code_bytes,
../stateless/multi_keys,
../core/eip7702,
"/.."/[constants, utils/utils],
./access_list as ac_access_list,
"."/[core_db, storage_types, transient_storage],
Expand Down Expand Up @@ -449,11 +450,16 @@ proc getNonce*(ac: LedgerRef, address: Address): AccountNonce =
if acc.isNil: emptyEthAccount.nonce
else: acc.statement.nonce

proc getCode*(ac: LedgerRef, address: Address): CodeBytesRef =
proc getCode*(ac: LedgerRef,
address: Address,
returnHash: static[bool] = false): auto =
# Always returns non-nil!
let acc = ac.getAccount(address, false)
if acc.isNil:
return CodeBytesRef()
when returnHash:
return (EMPTY_CODE_HASH, CodeBytesRef())
else:
return CodeBytesRef()

if acc.code == nil:
acc.code =
Expand All @@ -470,7 +476,10 @@ proc getCode*(ac: LedgerRef, address: Address): CodeBytesRef =
else:
CodeBytesRef()

acc.code
when returnHash:
(acc.statement.codeHash, acc.code)
else:
acc.code

proc getCodeSize*(ac: LedgerRef, address: Address): int =
let acc = ac.getAccount(address, false)
Expand All @@ -494,6 +503,24 @@ proc getCodeSize*(ac: LedgerRef, address: Address): int =

acc.code.len()

proc resolveCodeHash*(ac: LedgerRef, address: Address): Hash32 =
let (codeHash, code) = ac.getCode(address, true)
let delegateTo = parseDelegationAddress(code).valueOr:
return codeHash
ac.getCodeHash(delegateTo)

proc resolveCode*(ac: LedgerRef, address: Address): CodeBytesRef =
let code = ac.getCode(address)
let delegateTo = parseDelegationAddress(code).valueOr:
return code
ac.getCode(delegateTo)

proc resolveCodeSize*(ac: LedgerRef, address: Address): int =
let code = ac.getCode(address)
let delegateTo = parseDelegationAddress(code).valueOr:
return code.len
ac.getCodeSize(delegateTo)

proc getCommittedStorage*(ac: LedgerRef, address: Address, slot: UInt256): UInt256 =
let acc = ac.getAccount(address, false)
if acc.isNil:
Expand Down Expand Up @@ -923,6 +950,9 @@ proc getTransientStorage*(db: ReadOnlyStateDB,
address: Address, slot: UInt256): UInt256 = getTransientStorage(distinctBase db, address, slot)
proc getAccountProof*(db: ReadOnlyStateDB, address: Address): seq[seq[byte]] = getAccountProof(distinctBase db, address)
proc getStorageProof*(db: ReadOnlyStateDB, address: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] = getStorageProof(distinctBase db, address, slots)
proc resolveCodeHash*(db: ReadOnlyStateDB, address: Address): Hash32 = resolveCodeHash(distinctBase db, address)
proc resolveCode*(db: ReadOnlyStateDB, address: Address): CodeBytesRef = resolveCode(distinctBase db, address)
proc resolveCodeSize*(db: ReadOnlyStateDB, address: Address): int = resolveCodeSize(distinctBase db, address)

# ------------------------------------------------------------------------------
# End
Expand Down
17 changes: 16 additions & 1 deletion nimbus/evm/code_bytes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import stew/byteutils, results, ./interpreter/op_codes
import
stew/byteutils,
stew/assign2,
results,
./interpreter/op_codes

export results

Expand Down Expand Up @@ -77,3 +81,14 @@ func isValidOpcode*(c: CodeBytesRef, position: int): bool =

func `==`*(a: CodeBytesRef, b: openArray[byte]): bool =
a.bytes == b

func hasPrefix*(a: CodeBytesRef, b: openArray[byte]): bool =
if b.len > a.bytes.len:
return false
for i in 0..<b.len:
if a.bytes[i] != b[i]:
return false
true

func slice*[N: static[int]](a: CodeBytesRef, b, c: int): array[N, byte] =
assign(result, a.bytes.toOpenArray(b, c))
42 changes: 37 additions & 5 deletions nimbus/evm/computation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import
../common/common,
eth/common/eth_types_rlp,
chronicles, chronos,
sets
sets,
stew/assign2

export
common
Expand Down Expand Up @@ -224,8 +225,32 @@ template getTransientStorage*(c: Computation, slot: UInt256): UInt256 =
c.vmState.readOnlyStateDB.
getTransientStorage(c.msg.contractAddress, slot)

template resolveCodeSize*(c: Computation, address: Address): uint =
when evmc_enabled:
c.host.getCodeSize(address)
else:
uint(c.vmState.readOnlyStateDB.resolveCodeSize(address))

template resolveCodeHash*(c: Computation, address: Address): Hash32=
when evmc_enabled:
c.host.getCodeHash(address)
else:
let
db = c.vmState.readOnlyStateDB
if not db.accountExists(address) or db.isEmptyAccount(address):
default(Hash32)
else:
db.resolveCodeHash(address)

template resolveCode*(c: Computation, address: Address): CodeBytesRef =
when evmc_enabled:
CodeBytesRef.init(c.host.copyCode(address))
else:
c.vmState.readOnlyStateDB.resolveCode(address)

proc newComputation*(vmState: BaseVMState, sysCall: bool, message: Message,
salt: ContractSalt = ZERO_CONTRACTSALT): Computation =
salt: ContractSalt = ZERO_CONTRACTSALT,
authorizationList: openArray[Authorization] = []): Computation =
new result
result.vmState = vmState
result.msg = message
Expand All @@ -240,11 +265,17 @@ proc newComputation*(vmState: BaseVMState, sysCall: bool, message: Message,
result.code = CodeStream.init(message.data)
message.data = @[]
else:
result.code = CodeStream.init(
vmState.readOnlyStateDB.getCode(message.codeAddress))
if vmState.fork >= FkPrague:
result.code = CodeStream.init(
vmState.readOnlyStateDB.resolveCode(message.codeAddress))
else:
result.code = CodeStream.init(
vmState.readOnlyStateDB.getCode(message.codeAddress))
assign(result.authorizationList, authorizationList)

func newComputation*(vmState: BaseVMState, sysCall: bool,
message: Message, code: CodeBytesRef): Computation =
message: Message, code: CodeBytesRef,
authorizationList: openArray[Authorization] = []): Computation =
new result
result.vmState = vmState
result.msg = message
Expand All @@ -254,6 +285,7 @@ func newComputation*(vmState: BaseVMState, sysCall: bool,
result.gasMeter.init(message.gas)
result.code = CodeStream.init(code)
result.sysCall = sysCall
assign(result.authorizationList, authorizationList)

template gasCosts*(c: Computation): untyped =
c.vmState.gasCosts
Expand Down
10 changes: 5 additions & 5 deletions nimbus/evm/interpreter/gas_costs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type
kind*: Op
isNewAccount*: bool
gasLeft*: GasInt
gasCallEIP2929*: GasInt
gasCallEIPs*: GasInt
contractGas*: UInt256
currentMemSize*: GasNatural
memOffset*: GasNatural
Expand Down Expand Up @@ -375,9 +375,9 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
# Both gasCost and childGasLimit are always on positive side

var gasLeft = params.gasLeft
if gasLeft < params.gasCallEIP2929:
if gasLeft < params.gasCallEIPs:
return err(opErr(OutOfGas))
gasLeft -= params.gasCallEIP2929
gasLeft -= params.gasCallEIPs

var gasCost: GasInt = `prefix gasMemoryExpansion`(
params.currentMemSize,
Expand Down Expand Up @@ -422,11 +422,11 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
return err(gasErr(GasIntOverflow))
childGasLimit = params.contractGas.truncate(GasInt)

if gasCost.u256 + childGasLimit.u256 + params.gasCallEIP2929.u256 > high(GasInt).u256:
if gasCost.u256 + childGasLimit.u256 + params.gasCallEIPs.u256 > high(GasInt).u256:
return err(gasErr(GasIntOverflow))

gasCost += childGasLimit
gasCost += params.gasCallEIP2929
gasCost += params.gasCallEIPs

# Ccallgas - Gas sent to the child message
if not value.isZero and params.kind in {Call, CallCode}:
Expand Down
Loading

0 comments on commit fbfc161

Please sign in to comment.