Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement pectra devnet-5 spec #3807

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/block/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ export function genRequestsRoot(

let flatRequests = new Uint8Array()
for (const req of requests) {
flatRequests = concatBytes(flatRequests, sha256Function(req.bytes))
if (req.bytes.length > 1) {
// Only append requests if they have content
flatRequests = concatBytes(flatRequests, sha256Function(req.bytes))
}
}

return sha256Function(flatRequests)
Expand Down
42 changes: 31 additions & 11 deletions packages/evm/src/opcodes/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
concatBytes,
equalsBytes,
getVerkleTreeIndicesForStorageSlot,
hexToBytes,
setLengthLeft,
} from '@ethereumjs/util'
import { keccak256 } from 'ethereum-cryptography/keccak.js'
Expand Down Expand Up @@ -61,16 +62,40 @@

export type OpHandler = SyncOpHandler | AsyncOpHandler

// TODO: verify that this is the correct designator
// The PR https://github.com/ethereum/EIPs/pull/8969 has two definitions of the
// designator: the original (0xef0100) and the designator added in the changes (0xef01)
const eip7702Designator = hexToBytes('0xef01')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming my thinking. These only get computed once at run time, right? They aren't recomputed every single time we access this code are they?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only computed once! Can also move it somewhere else like types.ts.

const eip7702HashBigInt = bytesToBigInt(keccak256(eip7702Designator))

function getEIP7702DelegatedAddress(code: Uint8Array) {
if (equalsBytes(code.slice(0, 3), DELEGATION_7702_FLAG)) {
return new Address(code.slice(3, 24))
}
}

async function eip7702CodeCheck(runState: RunState, code: Uint8Array) {
/**
* This method performs checks to transform the code which the EVM observes regarding EIP-7702.
* If the code is 7702-delegated code, it will retrieve the code of the designated address
* in case of an executable operation (`isReadOperation` == false), or the 7702 designator
* code in case of a read operation
* @param runState
* @param code
* @param isReadOperation Boolean to determine if the target code is meant to be read or executed (default: `false`)
* @returns
*/
async function eip7702CodeCheck(
runState: RunState,
code: Uint8Array,
isReadOperation: boolean = false,
) {
const address = getEIP7702DelegatedAddress(code)
if (address !== undefined) {
return runState.stateManager.getCode(address)
if (isReadOperation) {
return eip7702Designator
} else {
return runState.stateManager.getCode(address)
}

Check warning on line 98 in packages/evm/src/opcodes/functions.ts

View check run for this annotation

Codecov / codecov/patch

packages/evm/src/opcodes/functions.ts#L97-L98

Added lines #L97 - L98 were not covered by tests
}

return code
Expand Down Expand Up @@ -538,7 +563,7 @@
runState.stack.push(BigInt(EOFBYTES.length))
return
} else if (common.isActivatedEIP(7702)) {
code = await eip7702CodeCheck(runState, code)
code = await eip7702CodeCheck(runState, code, true)
}

const size = BigInt(code.length)
Expand All @@ -560,7 +585,7 @@
// In legacy code, the target code is treated as to be "EOFBYTES" code
code = EOFBYTES
} else if (common.isActivatedEIP(7702)) {
code = await eip7702CodeCheck(runState, code)
code = await eip7702CodeCheck(runState, code, true)
}

const data = getDataSlice(code, codeOffset, dataLength)
Expand All @@ -587,13 +612,8 @@
} else if (common.isActivatedEIP(7702)) {
const possibleDelegatedAddress = getEIP7702DelegatedAddress(code)
if (possibleDelegatedAddress !== undefined) {
const account = await runState.stateManager.getAccount(possibleDelegatedAddress)
if (!account || account.isEmpty()) {
runState.stack.push(BIGINT_0)
return
}

runState.stack.push(BigInt(bytesToHex(account.codeHash)))
// The account is delegated by an EIP-7702 tx. Push the EIP-7702 designator hash to the stack
runState.stack.push(eip7702HashBigInt)
return
} else {
runState.stack.push(bytesToBigInt(keccak256(code)))
Expand Down
99 changes: 99 additions & 0 deletions packages/evm/test/eips/eip-7702.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import {
Address,
bytesToBigInt,
equalsBytes,
hexToBytes,
setLengthRight,
unprefixedHexToBytes,
} from '@ethereumjs/util'
import { keccak256 } from 'ethereum-cryptography/keccak'
import { assert, describe, it } from 'vitest'

import { createEVM } from '../../src/index.js'

const eip7702Designator = hexToBytes('0xef01')
const addressHex = '01'.repeat(20) // Address as unprefixed hex string
const address = new Address(unprefixedHexToBytes(addressHex))

const delegationCode = hexToBytes('0xef0100' + addressHex) // This code will delegate to `address`

// Helpers for unprefixed hex strings of opcodes
const EXTCODESIZE = '3B'
const EXTCODECOPY = '3C'
const EXTCODEHASH = '3F'

// This code stores the topmost stack item in slot 0
const STORE_TOP_STACK_CODE = '5F55'

// This code pushes the `address` to the stack
const PUSH_Address = '73' + addressHex // PUSH20 <address> as unprefixed hex string

const testCodeAddress = new Address(unprefixedHexToBytes('02'.repeat(20)))

// Setups EVM with an account at `address` which is delegated to itself
async function getEVM() {
const common = new Common({
chain: Mainnet,
hardfork: Hardfork.Prague,
})
const evm = await createEVM({
common,
})
await evm.stateManager.putCode(address, delegationCode)
return evm
}

describe('EIP 7702 tests', () => {
it('EXTCODESIZE', async () => {
const evm = await getEVM()
const code = unprefixedHexToBytes(PUSH_Address + EXTCODESIZE + STORE_TOP_STACK_CODE)
await evm.stateManager.putCode(testCodeAddress, code)

await evm.runCall({
to: testCodeAddress,
gasLimit: BigInt(100_000),
})

const result = await evm.stateManager.getStorage(testCodeAddress, new Uint8Array(32))
const expected = BigInt(eip7702Designator.length)

assert.equal(bytesToBigInt(result), expected)
})

it('EXTCODEHASH', async () => {
const evm = await getEVM()
const code = unprefixedHexToBytes(PUSH_Address + EXTCODEHASH + STORE_TOP_STACK_CODE)
await evm.stateManager.putCode(testCodeAddress, code)

await evm.runCall({
to: testCodeAddress,
gasLimit: BigInt(100_000),
})

const result = await evm.stateManager.getStorage(testCodeAddress, new Uint8Array(32))
const expected = keccak256(eip7702Designator)

assert.ok(equalsBytes(result, expected))
})

it('EXTCODECOPY', async () => {
const evm = await getEVM()
// This code does some extra logic than other tests, because it has to setup EXTCODECOPY (4 stack items instead of 1)
// It EXTCODECOPYs 32 bytes of the delegated address in memory at key 0, and then MLOADs key 0 before storing the result
const code = unprefixedHexToBytes(
'60205F5F' + PUSH_Address + EXTCODECOPY + '5F51' + STORE_TOP_STACK_CODE,
)
await evm.stateManager.putCode(testCodeAddress, code)

await evm.runCall({
to: testCodeAddress,
gasLimit: BigInt(100_000),
})

const result = await evm.stateManager.getStorage(testCodeAddress, new Uint8Array(32))
const expected = setLengthRight(eip7702Designator, 32)

assert.ok(equalsBytes(result, expected))
})
})
5 changes: 4 additions & 1 deletion packages/vm/test/api/EIPs/eip-7685.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createBlock, genRequestsRoot } from '@ethereumjs/block'
import { createBlockchain } from '@ethereumjs/blockchain'
import { Common, Hardfork, Mainnet } from '@ethereumjs/common'
import { createCLRequest, hexToBytes } from '@ethereumjs/util'
import { createCLRequest, equalsBytes, hexToBytes } from '@ethereumjs/util'
import { sha256 } from 'ethereum-cryptography/sha256'
import { assert, describe, expect, it } from 'vitest'

Expand Down Expand Up @@ -31,6 +31,9 @@ describe('EIP-7685 runBlock tests', () => {
generate: true,
})
assert.equal(res.gasUsed, 0n)
// Verify that if the requests are empty, the byte-types are not appended to the to-be-hashed flat array
// I.e. the flat array to-be-hashed is not `0x 00 01 02`, but is now the empty bytes array, `0x`
assert.ok(equalsBytes(res.requestsHash!, sha256(new Uint8Array())))
})
it('should error when an invalid requestsHash is provided', async () => {
const vm = await setupVM({ common })
Expand Down
Loading