Skip to content

Commit 674a8a3

Browse files
EIP-7702 devnet-3 readiness (#3581)
* tx: implement strict 7702 validation * vm: update 7702 tx validation * evm: update 7702 [no ci] * tx: add / fix 7702 tests * vm: fix test encoding of authorization lists [no ci] * vm: correctly put authority nonce * vm: add test 7702 extcodehash/extcodesize evm: fix extcodehash/extcodesize for delegated accounts * vm: expand extcode* tests 7702 [no ci] * tx/vm: update tests [no ci] * evm/vm: update opcodes and fix tests 7702 * fix cspell [no ci] * vm: get params from tx for 7702 [no ci] * vm: 7702 correctly apply the refund [no ci] * vm: 7702: correctly handle self-sponsored txs [no ci] * tx: throw if authorization list is empty * vm: requests do not throw if code is non-existant * evm: ensure correct extcodehash reporting if account is delegated to a non-existing account * vm: 7702 ensure delegated accounts are not deleted [no ci] * evm: 7702 correctly check for gas on delegated code * evm: add verkle gas logic for 7702 * vm/tx: fix 7702 tests * tx: throw if 7702-tx has no `to` field * vm/tx: fix 7702 tests * VM: exit early on non-existing system contracts * 7702: add delegated account to warm address * vm: requests do restore system account * 7702: continue processing once auth ecrecover is invalid * evm/vm: add 7702 delegation constant * vm: fix requests * vm: unduplify 3607 error msg * fix example --------- Co-authored-by: acolytec3 <[email protected]>
1 parent 22766f2 commit 674a8a3

File tree

15 files changed

+356
-137
lines changed

15 files changed

+356
-137
lines changed

packages/evm/src/evm.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,28 @@ import { getOpcodesForHF } from './opcodes/index.js'
3030
import { paramsEVM } from './params.js'
3131
import { NobleBLS, getActivePrecompiles, getPrecompileName } from './precompiles/index.js'
3232
import { TransientStorage } from './transientStorage.js'
33+
import {
34+
type Block,
35+
type CustomOpcode,
36+
DELEGATION_7702_FLAG,
37+
type EVMBLSInterface,
38+
type EVMBN254Interface,
39+
type EVMEvents,
40+
type EVMInterface,
41+
type EVMMockBlockchainInterface,
42+
type EVMOpts,
43+
type EVMResult,
44+
type EVMRunCallOpts,
45+
type EVMRunCodeOpts,
46+
type ExecResult,
47+
} from './types.js'
3348

3449
import type { InterpreterOpts } from './interpreter.js'
3550
import type { Timer } from './logger.js'
3651
import type { MessageWithTo } from './message.js'
3752
import type { AsyncDynamicGasHandler, SyncDynamicGasHandler } from './opcodes/gas.js'
3853
import type { OpHandler, OpcodeList, OpcodeMap } from './opcodes/index.js'
3954
import type { CustomPrecompile, PrecompileFunc } from './precompiles/index.js'
40-
import type {
41-
Block,
42-
CustomOpcode,
43-
EVMBLSInterface,
44-
EVMBN254Interface,
45-
EVMEvents,
46-
EVMInterface,
47-
EVMMockBlockchainInterface,
48-
EVMOpts,
49-
EVMResult,
50-
EVMRunCallOpts,
51-
EVMRunCodeOpts,
52-
ExecResult,
53-
} from './types.js'
5455
import type { Common, StateManagerInterface } from '@ethereumjs/common'
5556

5657
const debug = debugDefault('evm:evm')
@@ -1016,6 +1017,19 @@ export class EVM implements EVMInterface {
10161017
message.isCompiled = true
10171018
} else {
10181019
message.code = await this.stateManager.getCode(message.codeAddress)
1020+
1021+
// EIP-7702 delegation check
1022+
if (
1023+
this.common.isActivatedEIP(7702) &&
1024+
equalsBytes(message.code.slice(0, 3), DELEGATION_7702_FLAG)
1025+
) {
1026+
const address = new Address(message.code.slice(3, 24))
1027+
message.code = await this.stateManager.getCode(address)
1028+
if (message.depth === 0) {
1029+
this.journal.addAlwaysWarmAddress(address.toString())
1030+
}
1031+
}
1032+
10191033
message.isCompiled = false
10201034
message.chargeCodeAccesses = true
10211035
}

packages/evm/src/opcodes/functions.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ import {
2626
getVerkleTreeIndicesForStorageSlot,
2727
setLengthLeft,
2828
} from '@ethereumjs/util'
29+
import { equalBytes } from '@noble/curves/abstract/utils'
2930
import { keccak256 } from 'ethereum-cryptography/keccak.js'
3031

3132
import { EOFContainer, EOFContainerMode } from '../eof/container.js'
3233
import { EOFError } from '../eof/errors.js'
3334
import { EOFBYTES, EOFHASH, isEOF } from '../eof/util.js'
3435
import { ERROR } from '../exceptions.js'
36+
import { DELEGATION_7702_FLAG } from '../types.js'
3537

3638
import {
3739
createAddressFromStackBigInt,
@@ -59,6 +61,21 @@ export interface AsyncOpHandler {
5961

6062
export type OpHandler = SyncOpHandler | AsyncOpHandler
6163

64+
function getEIP7702DelegatedAddress(code: Uint8Array) {
65+
if (equalBytes(code.slice(0, 3), DELEGATION_7702_FLAG)) {
66+
return new Address(code.slice(3, 24))
67+
}
68+
}
69+
70+
async function eip7702CodeCheck(runState: RunState, code: Uint8Array) {
71+
const address = getEIP7702DelegatedAddress(code)
72+
if (address !== undefined) {
73+
return runState.stateManager.getCode(address)
74+
}
75+
76+
return code
77+
}
78+
6279
// the opcode functions
6380
export const handlers: Map<number, OpHandler> = new Map([
6481
// 0x00: STOP
@@ -511,36 +528,39 @@ export const handlers: Map<number, OpHandler> = new Map([
511528
// 0x3b: EXTCODESIZE
512529
[
513530
0x3b,
514-
async function (runState) {
531+
async function (runState, common) {
515532
const addressBigInt = runState.stack.pop()
516533
const address = createAddressFromStackBigInt(addressBigInt)
517534
// EOF check
518-
const code = await runState.stateManager.getCode(address)
535+
let code = await runState.stateManager.getCode(address)
519536
if (isEOF(code)) {
520537
// In legacy code, the target code is treated as to be "EOFBYTES" code
521538
runState.stack.push(BigInt(EOFBYTES.length))
522539
return
540+
} else if (common.isActivatedEIP(7702)) {
541+
code = await eip7702CodeCheck(runState, code)
523542
}
524543

525-
const size = BigInt(
526-
await runState.stateManager.getCodeSize(createAddressFromStackBigInt(addressBigInt)),
527-
)
544+
const size = BigInt(code.length)
528545

529546
runState.stack.push(size)
530547
},
531548
],
532549
// 0x3c: EXTCODECOPY
533550
[
534551
0x3c,
535-
async function (runState) {
552+
async function (runState, common) {
536553
const [addressBigInt, memOffset, codeOffset, dataLength] = runState.stack.popN(4)
537554

538555
if (dataLength !== BIGINT_0) {
539-
let code = await runState.stateManager.getCode(createAddressFromStackBigInt(addressBigInt))
556+
const address = createAddressFromStackBigInt(addressBigInt)
557+
let code = await runState.stateManager.getCode(address)
540558

541559
if (isEOF(code)) {
542560
// In legacy code, the target code is treated as to be "EOFBYTES" code
543561
code = EOFBYTES
562+
} else if (common.isActivatedEIP(7702)) {
563+
code = await eip7702CodeCheck(runState, code)
544564
}
545565

546566
const data = getDataSlice(code, codeOffset, dataLength)
@@ -553,7 +573,7 @@ export const handlers: Map<number, OpHandler> = new Map([
553573
// 0x3f: EXTCODEHASH
554574
[
555575
0x3f,
556-
async function (runState) {
576+
async function (runState, common) {
557577
const addressBigInt = runState.stack.pop()
558578
const address = createAddressFromStackBigInt(addressBigInt)
559579

@@ -564,6 +584,21 @@ export const handlers: Map<number, OpHandler> = new Map([
564584
// Therefore, push the hash of EOFBYTES to the stack
565585
runState.stack.push(bytesToBigInt(EOFHASH))
566586
return
587+
} else if (common.isActivatedEIP(7702)) {
588+
const possibleDelegatedAddress = getEIP7702DelegatedAddress(code)
589+
if (possibleDelegatedAddress !== undefined) {
590+
const account = await runState.stateManager.getAccount(possibleDelegatedAddress)
591+
if (!account || account.isEmpty()) {
592+
runState.stack.push(BIGINT_0)
593+
return
594+
}
595+
596+
runState.stack.push(BigInt(bytesToHex(account.codeHash)))
597+
return
598+
} else {
599+
runState.stack.push(bytesToBigInt(keccak256(code)))
600+
return
601+
}
567602
}
568603

569604
const account = await runState.stateManager.getAccount(address)

packages/evm/src/opcodes/gas.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import {
99
VERKLE_BASIC_DATA_LEAF_KEY,
1010
VERKLE_CODE_HASH_LEAF_KEY,
1111
bigIntToBytes,
12+
equalsBytes,
1213
getVerkleTreeIndicesForStorageSlot,
1314
setLengthLeft,
1415
} from '@ethereumjs/util'
1516

1617
import { EOFError } from '../eof/errors.js'
1718
import { ERROR } from '../exceptions.js'
19+
import { DELEGATION_7702_FLAG } from '../types.js'
1820

1921
import { updateSstoreGasEIP1283 } from './EIP1283.js'
2022
import { updateSstoreGasEIP2200 } from './EIP2200.js'
@@ -31,9 +33,23 @@ import {
3133

3234
import type { RunState } from '../interpreter.js'
3335
import type { Common } from '@ethereumjs/common'
36+
import type { Address } from '@ethereumjs/util'
3437

3538
const EXTCALL_TARGET_MAX = BigInt(2) ** BigInt(8 * 20) - BigInt(1)
3639

40+
async function eip7702GasCost(
41+
runState: RunState,
42+
common: Common,
43+
address: Address,
44+
charge2929Gas: boolean,
45+
) {
46+
const code = await runState.stateManager.getCode(address)
47+
if (equalsBytes(code.slice(0, 3), DELEGATION_7702_FLAG)) {
48+
return accessAddressEIP2929(runState, code.slice(3, 24), common, charge2929Gas)
49+
}
50+
return BIGINT_0
51+
}
52+
3753
/**
3854
* This file returns the dynamic parts of opcodes which have dynamic gas
3955
* These are not pure functions: some edit the size of the memory
@@ -175,6 +191,10 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
175191
gas += accessAddressEIP2929(runState, address.bytes, common, charge2929Gas)
176192
}
177193

194+
if (common.isActivatedEIP(7702)) {
195+
gas += await eip7702GasCost(runState, common, address, charge2929Gas)
196+
}
197+
178198
return gas
179199
},
180200
],
@@ -208,6 +228,10 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
208228
gas += accessAddressEIP2929(runState, address.bytes, common, charge2929Gas)
209229
}
210230

231+
if (common.isActivatedEIP(7702)) {
232+
gas += await eip7702GasCost(runState, common, address, charge2929Gas)
233+
}
234+
211235
if (dataLength !== BIGINT_0) {
212236
gas += common.param('copyGas') * divCeil(dataLength, BIGINT_32)
213237

@@ -273,6 +297,10 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
273297
gas += accessAddressEIP2929(runState, address.bytes, common, charge2929Gas)
274298
}
275299

300+
if (common.isActivatedEIP(7702)) {
301+
gas += await eip7702GasCost(runState, common, address, charge2929Gas)
302+
}
303+
276304
return gas
277305
},
278306
],
@@ -573,6 +601,10 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
573601
gas += accessAddressEIP2929(runState, toAddress.bytes, common, charge2929Gas)
574602
}
575603

604+
if (common.isActivatedEIP(7702)) {
605+
gas += await eip7702GasCost(runState, common, toAddress, charge2929Gas)
606+
}
607+
576608
if (value !== BIGINT_0 && !common.isActivatedEIP(6800)) {
577609
gas += common.param('callValueTransferGas')
578610
}
@@ -647,6 +679,10 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
647679
)
648680
}
649681

682+
if (common.isActivatedEIP(7702)) {
683+
gas += await eip7702GasCost(runState, common, toAddress, charge2929Gas)
684+
}
685+
650686
if (value !== BIGINT_0) {
651687
gas += common.param('callValueTransferGas')
652688
}
@@ -708,6 +744,10 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
708744
)
709745
}
710746

747+
if (common.isActivatedEIP(7702)) {
748+
gas += await eip7702GasCost(runState, common, toAddress, charge2929Gas)
749+
}
750+
711751
const gasLimit = maxCallGas(
712752
currentGasLimit,
713753
runState.interpreter.getGasLeft() - gas,
@@ -907,6 +947,15 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
907947
)
908948
}
909949

950+
if (common.isActivatedEIP(7702)) {
951+
gas += await eip7702GasCost(
952+
runState,
953+
common,
954+
createAddressFromStackBigInt(toAddr),
955+
charge2929Gas,
956+
)
957+
}
958+
910959
const gasLimit = maxCallGas(
911960
currentGasLimit,
912961
runState.interpreter.getGasLeft() - gas,

packages/evm/src/params.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -409,12 +409,4 @@ export const paramsEVM: ParamsDict = {
409409
eofcreateGas: 32000, // Base fee of the EOFCREATE opcode (Same as CREATE/CREATE2)
410410
returncontractGas: 0, // Base fee of the RETURNCONTRACT opcode
411411
},
412-
/**
413-
. * Set EOA account code for one transaction
414-
. */
415-
7702: {
416-
// TODO: Set correct minimum hardfork
417-
// gasPrices
418-
perAuthBaseGas: 2500, // Gas cost of each authority item
419-
},
420412
}

packages/evm/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,3 +507,6 @@ export type EOFEnv = {
507507
returnStack: number[]
508508
}
509509
}
510+
511+
// EIP-7702 flag: if contract code starts with these 3 bytes, it is a 7702-delegated EOA
512+
export const DELEGATION_7702_FLAG = new Uint8Array([0xef, 0x01, 0x00])

packages/tx/examples/EOACodeTx.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
22
import { createEOACode7702Tx } from '@ethereumjs/tx'
3-
4-
import type { PrefixedHexString } from '@ethereumjs/util'
3+
import { type PrefixedHexString, createAddressFromPrivateKey, randomBytes } from '@ethereumjs/util'
54

65
const ones32 = `0x${'01'.repeat(32)}` as PrefixedHexString
76

@@ -12,12 +11,13 @@ const tx = createEOACode7702Tx(
1211
{
1312
chainId: '0x2',
1413
address: `0x${'20'.repeat(20)}`,
15-
nonce: ['0x1'],
14+
nonce: '0x1',
1615
yParity: '0x1',
1716
r: ones32,
1817
s: ones32,
1918
},
2019
],
20+
to: createAddressFromPrivateKey(randomBytes(32)),
2121
},
2222
{ common },
2323
)

packages/tx/src/7702/tx.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ export class EOACode7702Transaction extends BaseTransaction<TransactionType.EOAC
117117
EIP2718.validateYParity(this)
118118
Legacy.validateHighS(this)
119119

120+
if (this.to === undefined) {
121+
const msg = this._errorMsg(
122+
`tx should have a "to" field and cannot be used to create contracts`,
123+
)
124+
throw new Error(msg)
125+
}
126+
120127
const freeze = opts?.freeze ?? true
121128
if (freeze) {
122129
Object.freeze(this)

packages/tx/src/capabilities/eip7702.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { EIP7702CompatibleTx } from '../types.js'
1010
export function getDataGas(tx: EIP7702CompatibleTx): bigint {
1111
const eip2930Cost = BigInt(AccessLists.getDataGasEIP2930(tx.accessList, tx.common))
1212
const eip7702Cost = BigInt(
13-
tx.authorizationList.length * Number(tx.common.param('perAuthBaseGas')),
13+
tx.authorizationList.length * Number(tx.common.param('perEmptyAccountCost')),
1414
)
1515
return Legacy.getDataGas(tx, eip2930Cost + eip7702Cost)
1616
}

packages/tx/src/params.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,13 @@ export const paramsTx: ParamsDict = {
4343
4844: {
4444
blobCommitmentVersionKzg: 1, // The number indicated a versioned hash is a KZG commitment
4545
},
46+
/**
47+
. * Set EOA account code for one transaction
48+
. */
49+
7702: {
50+
// TODO: Set correct minimum hardfork
51+
// gasPrices
52+
perAuthBaseGas: 2500, // Gas cost of each authority item, provided the authority exists in the trie
53+
perEmptyAccountCost: 25000, // Gas cost of each authority item, in case the authority does not exist in the trie
54+
},
4655
}

packages/tx/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ export type AccessList = AccessListItem[]
600600
export type AuthorizationListItem = {
601601
chainId: PrefixedHexString
602602
address: PrefixedHexString
603-
nonce: PrefixedHexString[]
603+
nonce: PrefixedHexString
604604
yParity: PrefixedHexString
605605
r: PrefixedHexString
606606
s: PrefixedHexString
@@ -610,7 +610,7 @@ export type AuthorizationListItem = {
610610
export type AuthorizationListBytesItem = [
611611
Uint8Array,
612612
Uint8Array,
613-
Uint8Array[],
613+
Uint8Array,
614614
Uint8Array,
615615
Uint8Array,
616616
Uint8Array,

0 commit comments

Comments
 (0)