diff --git a/packages/block/src/block.ts b/packages/block/src/block.ts index 4d69ee21b6..880451b410 100644 --- a/packages/block/src/block.ts +++ b/packages/block/src/block.ts @@ -10,6 +10,7 @@ import { bufferToHex, intToHex, isHexPrefixed, + toBuffer, } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak' import { ethers } from 'ethers' @@ -17,7 +18,14 @@ import { ethers } from 'ethers' import { blockFromRpc } from './from-rpc' import { BlockHeader } from './header' -import type { BlockBuffer, BlockData, BlockOptions, JsonBlock, JsonRpcBlock } from './types' +import type { + BlockBuffer, + BlockData, + BlockOptions, + JsonBlock, + JsonRpcBlock, + Withdrawal, +} from './types' import type { Common } from '@ethereumjs/common' import type { FeeMarketEIP1559Transaction, @@ -25,6 +33,7 @@ import type { TxOptions, TypedTransaction, } from '@ethereumjs/tx' +import type { Address } from '@ethereumjs/util' /** * An object that represents the block. @@ -33,6 +42,7 @@ export class Block { public readonly header: BlockHeader public readonly transactions: TypedTransaction[] = [] public readonly uncleHeaders: BlockHeader[] = [] + public readonly withdrawals?: Withdrawal[] public readonly txTrie = new Trie() public readonly _common: Common @@ -43,7 +53,12 @@ export class Block { * @param opts */ public static fromBlockData(blockData: BlockData = {}, opts?: BlockOptions) { - const { header: headerData, transactions: txsData, uncleHeaders: uhsData } = blockData + const { + header: headerData, + transactions: txsData, + uncleHeaders: uhsData, + withdrawals, + } = blockData const header = BlockHeader.fromHeaderData(headerData, opts) // parse transactions @@ -78,7 +93,7 @@ export class Block { uncleHeaders.push(uh) } - return new Block(header, transactions, uncleHeaders, opts) + return new Block(header, transactions, uncleHeaders, opts, withdrawals) } /** @@ -104,11 +119,11 @@ export class Block { * @param opts */ public static fromValuesArray(values: BlockBuffer, opts?: BlockOptions) { - if (values.length > 3) { + if (values.length > 4) { throw new Error('invalid block. More values than expected were received') } - const [headerData, txsData, uhsData] = values + const [headerData, txsData, uhsData, withdrawalsData] = values const header = BlockHeader.fromValuesArray(headerData, opts) @@ -144,7 +159,16 @@ export class Block { uncleHeaders.push(BlockHeader.fromValuesArray(uncleHeaderData, uncleOpts)) } - return new Block(header, transactions, uncleHeaders, opts) + let withdrawals + if (withdrawalsData) { + withdrawals = [] + for (const withdrawal of withdrawalsData) { + const [index, validatorIndex, address, amount] = withdrawal + withdrawals.push({ index, validatorIndex, address, amount }) + } + } + + return new Block(header, transactions, uncleHeaders, opts, withdrawals) } /** @@ -212,7 +236,8 @@ export class Block { header?: BlockHeader, transactions: TypedTransaction[] = [], uncleHeaders: BlockHeader[] = [], - opts: BlockOptions = {} + opts: BlockOptions = {}, + withdrawals?: Withdrawal[] ) { this.header = header ?? BlockHeader.fromHeaderData({}, opts) this.transactions = transactions @@ -234,23 +259,55 @@ export class Block { } } + if (this._common.isActivatedEIP(4895) && withdrawals === undefined) { + throw new Error('Need a withdrawals field if EIP 4895 is active') + } else if (!this._common.isActivatedEIP(4895) && withdrawals !== undefined) { + throw new Error('Cannot have a withdrawals field if EIP 4895 is not active') + } + + this.withdrawals = withdrawals + const freeze = opts?.freeze ?? true if (freeze) { Object.freeze(this) } } + /** + * Convert a withdrawal to a buffer array + * @param withdrawal the withdrawal to convert + * @returns buffer array of the withdrawal + */ + private withdrawalToBufferArray(withdrawal: Withdrawal): [Buffer, Buffer, Buffer, Buffer] { + const { index, validatorIndex, address, amount } = withdrawal + let addressBuffer: Buffer + if (typeof address === 'string') { + addressBuffer = Buffer.from(address.slice(2)) + } else if (Buffer.isBuffer(address)) { + addressBuffer = address + } else { + addressBuffer = (
address).buf + } + return [toBuffer(index), toBuffer(validatorIndex), addressBuffer, toBuffer(amount)] + } + /** * Returns a Buffer Array of the raw Buffers of this block, in order. */ raw(): BlockBuffer { - return [ + const bufferArray = [ this.header.raw(), this.transactions.map((tx) => tx.supports(Capability.EIP2718TypedTransaction) ? tx.serialize() : tx.raw() ) as Buffer[], this.uncleHeaders.map((uh) => uh.raw()), ] + if (this.withdrawals) { + for (const withdrawal of this.withdrawals) { + bufferArray.push(this.withdrawalToBufferArray(withdrawal)) + } + } + return bufferArray } /** @@ -369,6 +426,11 @@ export class Block { const msg = this._errorMsg('invalid uncle hash') throw new Error(msg) } + + if (this._common.isActivatedEIP(4895) && !(await this.validateWithdrawalsTrie())) { + const msg = this._errorMsg('invalid withdrawals trie') + throw new Error(msg) + } } /** @@ -380,6 +442,23 @@ export class Block { return Buffer.from(keccak256(raw)).equals(this.header.uncleHash) } + /** + * Validates the withdrawal root + */ + async validateWithdrawalsTrie(): Promise { + if (!this._common.isActivatedEIP(4895)) { + throw new Error('EIP 4895 is not activated') + } + const trie = new Trie() + let index = 0 + for (const withdrawal of this.withdrawals!) { + const withdrawalRLP = RLP.encode(this.withdrawalToBufferArray(withdrawal)) + await trie.put(Buffer.from('0x' + index.toString(16)), arrToBufArr(withdrawalRLP)) + index++ + } + return trie.root().equals(this.header.withdrawalsRoot!) + } + /** * Consistency checks for uncles included in the block, if any. * diff --git a/packages/block/src/header.ts b/packages/block/src/header.ts index f99e584208..b96969ceb1 100644 --- a/packages/block/src/header.ts +++ b/packages/block/src/header.ts @@ -51,6 +51,7 @@ export class BlockHeader { public readonly mixHash: Buffer public readonly nonce: Buffer public readonly baseFeePerGas?: bigint + public readonly withdrawalsRoot?: Buffer public readonly _common: Common @@ -154,6 +155,7 @@ export class BlockHeader { mixHash: zeros(32), nonce: zeros(8), baseFeePerGas: undefined, + withdrawalsRoot: undefined, } const parentHash = toType(headerData.parentHash, TypeOutput.Buffer) ?? defaults.parentHash @@ -176,6 +178,8 @@ export class BlockHeader { const nonce = toType(headerData.nonce, TypeOutput.Buffer) ?? defaults.nonce let baseFeePerGas = toType(headerData.baseFeePerGas, TypeOutput.BigInt) ?? defaults.baseFeePerGas + const withdrawalsRoot = + toType(headerData.withdrawalsRoot, TypeOutput.Buffer) ?? defaults.withdrawalsRoot const hardforkByBlockNumber = options.hardforkByBlockNumber ?? false if (hardforkByBlockNumber || options.hardforkByTTD !== undefined) { @@ -198,6 +202,18 @@ export class BlockHeader { } } + if (this._common.isActivatedEIP(4895)) { + if (withdrawalsRoot === defaults.withdrawalsRoot) { + throw new Error('invalid header. withdrawalsRoot should be provided') + } + } else { + if (withdrawalsRoot !== undefined) { + throw new Error( + 'A withdrawalsRoot for a header can only be provied with EIP4895 being activated' + ) + } + } + this.parentHash = parentHash this.uncleHash = uncleHash this.coinbase = coinbase @@ -214,6 +230,7 @@ export class BlockHeader { this.mixHash = mixHash this.nonce = nonce this.baseFeePerGas = baseFeePerGas + this.withdrawalsRoot = withdrawalsRoot this._genericFormatValidation() this._validateDAOExtraData() @@ -310,6 +327,19 @@ export class BlockHeader { } } } + + if (this._common.isActivatedEIP(4895) === true) { + if (this.withdrawalsRoot === undefined) { + const msg = this._errorMsg('EIP4895 block has no withdrawalsRoot field') + throw new Error(msg) + } + if (this.withdrawalsRoot?.length !== 32) { + const msg = this._errorMsg( + `withdrawalsRoot must be 32 bytes, received ${this.withdrawalsRoot!.length} bytes` + ) + throw new Error(msg) + } + } } /** diff --git a/packages/block/src/helpers.ts b/packages/block/src/helpers.ts index 1c05c39494..e37a821a58 100644 --- a/packages/block/src/helpers.ts +++ b/packages/block/src/helpers.ts @@ -37,9 +37,10 @@ export function valuesArrayToHeaderData(values: BlockHeaderBuffer): HeaderData { mixHash, nonce, baseFeePerGas, + withdrawalsRoot, ] = values - if (values.length > 16) { + if (values.length > 17) { throw new Error('invalid header. More values than expected were received') } if (values.length < 15) { @@ -63,6 +64,7 @@ export function valuesArrayToHeaderData(values: BlockHeaderBuffer): HeaderData { mixHash, nonce, baseFeePerGas, + withdrawalsRoot, } } diff --git a/packages/block/src/types.ts b/packages/block/src/types.ts index 56c67d3759..f00628d547 100644 --- a/packages/block/src/types.ts +++ b/packages/block/src/types.ts @@ -95,6 +95,14 @@ export interface HeaderData { mixHash?: BufferLike nonce?: BufferLike baseFeePerGas?: BigIntLike + withdrawalsRoot?: BufferLike +} + +export type Withdrawal = { + index: BigIntLike + validatorIndex: BigIntLike + address: AddressLike + amount: BigIntLike } /** @@ -107,16 +115,20 @@ export interface BlockData { header?: HeaderData transactions?: Array uncleHeaders?: Array + withdrawals?: Array } -export type BlockBuffer = [BlockHeaderBuffer, TransactionsBuffer, UncleHeadersBuffer] +export type BlockBuffer = + | [BlockHeaderBuffer, TransactionsBuffer, UncleHeadersBuffer] + | [BlockHeaderBuffer, TransactionsBuffer, UncleHeadersBuffer, WithdrawalBuffer] export type BlockHeaderBuffer = Buffer[] -export type BlockBodyBuffer = [TransactionsBuffer, UncleHeadersBuffer] +export type BlockBodyBuffer = [TransactionsBuffer, UncleHeadersBuffer, WithdrawalBuffer?] /** * TransactionsBuffer can be an array of serialized txs for Typed Transactions or an array of Buffer Arrays for legacy transactions. */ export type TransactionsBuffer = Buffer[][] | Buffer[] export type UncleHeadersBuffer = Buffer[][] +export type WithdrawalBuffer = Buffer[][] /** * An object with the block's data represented as strings. @@ -128,6 +140,7 @@ export interface JsonBlock { header?: JsonHeader transactions?: JsonTx[] uncleHeaders?: JsonHeader[] + withdrawals?: JsonRpcWithdrawal[] } /** @@ -150,6 +163,14 @@ export interface JsonHeader { mixHash?: string nonce?: string baseFeePerGas?: string + withdrawalsRoot?: string +} + +export interface JsonRpcWithdrawal { + index: string // QUANTITY - bigint 8 bytes + validatorIndex: string // QUANTITY - bigint 8 bytes + address: string // DATA, 20 Bytes address to withdraw to + amount: string // QUANTITY - bigint amount in wei 32 bytes } /* @@ -177,4 +198,5 @@ export interface JsonRpcBlock { transactions: Array // Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. uncles: string[] // Array of uncle hashes baseFeePerGas?: string // If EIP-1559 is enabled for this block, returns the base fee per gas + withdrawals?: Array // If EIP-4895 is enabled for this block, array of withdrawals } diff --git a/packages/block/test/eip4895block.spec.ts b/packages/block/test/eip4895block.spec.ts new file mode 100644 index 0000000000..34be593738 --- /dev/null +++ b/packages/block/test/eip4895block.spec.ts @@ -0,0 +1,182 @@ +import { Chain, Common, Hardfork } from '@ethereumjs/common' +import { Address, KECCAK256_RLP } from '@ethereumjs/util' +import * as tape from 'tape' + +import { Block } from '../src/block' +import { BlockHeader } from '../src/header' + +import type { Withdrawal } from '../src' + +const common = new Common({ + eips: [4895], + chain: Chain.Mainnet, + hardfork: Hardfork.Merge, +}) + +// Small hack to hack in the activation block number +// (Otherwise there would be need for a custom chain only for testing purposes) +common.hardforkBlock = function (hardfork: string | undefined) { + if (hardfork === 'london') { + return BigInt(1) + } else if (hardfork === 'dao') { + // Avoid DAO HF side-effects + return BigInt(99) + } + return BigInt(0) +} + +tape('EIP1559 tests', function (t) { + t.test('Header tests', function (st) { + const earlyCommon = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Istanbul }) + st.throws(() => { + BlockHeader.fromHeaderData( + { + withdrawalsRoot: Buffer.from('00'.repeat(32), 'hex'), + }, + { + common: earlyCommon, + } + ) + }, 'should throw when setting withdrawalsRoot with EIP4895 not being activated') + st.throws(() => { + BlockHeader.fromHeaderData( + {}, + { + common, + } + ) + }, 'should throw when withdrawalsRoot is undefined with EIP4895 being activated') + st.doesNotThrow(() => { + BlockHeader.fromHeaderData( + { + withdrawalsRoot: Buffer.from('00'.repeat(32), 'hex'), + }, + { + common, + } + ) + }, 'correctly instantiates an EIP4895 block header') + st.end() + }) + t.test('Block tests', async function (st) { + const earlyCommon = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Istanbul }) + st.throws(() => { + Block.fromBlockData( + { + withdrawals: [], + }, + { + common: earlyCommon, + } + ) + }, 'should throw when setting withdrawals with EIP4895 not being activated') + st.throws(() => { + Block.fromBlockData( + {}, + { + common, + } + ) + }, 'should throw when withdrawals is undefined with EIP4895 being activated') + st.doesNotThrow(() => { + Block.fromBlockData( + { + header: { + withdrawalsRoot: Buffer.from('00'.repeat(32), 'hex'), + }, + withdrawals: [], + }, + { + common, + } + ) + }) + const block = Block.fromBlockData( + { + header: { + withdrawalsRoot: Buffer.from('00'.repeat(32), 'hex'), + }, + withdrawals: [], + }, + { + common, + } + ) + st.notOk(await block.validateWithdrawalsTrie(), 'should invalidate the withdrawals root') + const validHeader = BlockHeader.fromHeaderData( + { + withdrawalsRoot: KECCAK256_RLP, + }, + { common } + ) + const validBlock = Block.fromBlockData( + { + header: validHeader, + withdrawals: [], + }, + { + common, + } + ) + st.ok(await validBlock.validateWithdrawalsTrie(), 'should validate withdrawals root') + + const withdrawal = { + index: BigInt(0), + validatorIndex: BigInt(0), + address: new Address(Buffer.from('20'.repeat(20), 'hex')), + amount: BigInt(1000), + } + + const validBlockWithWithdrawal = Block.fromBlockData( + { + header: { + withdrawalsRoot: Buffer.from( + '69f28913c562b0d38f8dc81e72eb0d99052444d301bf8158dc1f3f94a4526357', + 'hex' + ), + }, + withdrawals: [withdrawal], + }, + { + common, + } + ) + st.ok( + await validBlockWithWithdrawal.validateWithdrawalsTrie(), + 'should validate withdrawals root' + ) + + const withdrawal2 = { + index: BigInt(1), + validatorIndex: BigInt(11), + address: new Address(Buffer.from('30'.repeat(20), 'hex')), + amount: BigInt(2000), + } + + const validBlockWithWithdrawal2 = Block.fromBlockData( + { + header: { + withdrawalsRoot: Buffer.from( + 'cb1accdf466a644291e7b5f0374a3d490d7c5545f9a346f8652f65b3960e720e', + 'hex' + ), + }, + withdrawals: [withdrawal, withdrawal2], + }, + { + common, + } + ) + st.ok( + await validBlockWithWithdrawal2.validateWithdrawalsTrie(), + 'should validate withdrawals root' + ) + st.doesNotThrow(() => { + validBlockWithWithdrawal.hash() + }, 'hashed block with withdrawals') + st.doesNotThrow(() => { + validBlockWithWithdrawal2.hash() + }, 'hashed block with withdrawals') + st.end() + }) +}) diff --git a/packages/block/test/header.spec.ts b/packages/block/test/header.spec.ts index 97e98f7617..e72992d62e 100644 --- a/packages/block/test/header.spec.ts +++ b/packages/block/test/header.spec.ts @@ -144,7 +144,7 @@ tape('[Block]: Header functions', function (t) { }) t.test('Initialization -> fromValuesArray() -> error cases', function (st) { - const headerArray = Array(17).fill(Buffer.alloc(0)) + const headerArray = Array(18).fill(Buffer.alloc(0)) // mock header data (if set to zeros(0) header throws) headerArray[0] = zeros(32) //parentHash diff --git a/packages/common/src/eips/4895.json b/packages/common/src/eips/4895.json new file mode 100644 index 0000000000..0f90d586dd --- /dev/null +++ b/packages/common/src/eips/4895.json @@ -0,0 +1,13 @@ +{ + "name": "EIP-4895", + "number": 4895, + "comment": "Beacon chain push withdrawals as operations", + "url": "https://eips.ethereum.org/EIPS/eip-4895", + "status": "Draft", + "minimumHardfork": "merge", + "requiredEIPs": [], + "gasConfig": {}, + "gasPrices": {}, + "vm": {}, + "pow": {} +} diff --git a/packages/common/src/eips/index.ts b/packages/common/src/eips/index.ts index 99c1906f59..6033203882 100644 --- a/packages/common/src/eips/index.ts +++ b/packages/common/src/eips/index.ts @@ -21,5 +21,6 @@ export const EIPs: { [key: number]: any } = { 3860: require('./3860.json'), 4345: require('./4345.json'), 4399: require('./4399.json'), + 4895: require('./4895.json'), 5133: require('./5133.json'), } diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index f476e976d7..8b6b50b01b 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -79,6 +79,7 @@ export interface EVMOpts { * - [EIP-3855](https://eips.ethereum.org/EIPS/eip-3855) - PUSH0 instruction (`experimental`) * - [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860) - Limit and meter initcode (`experimental`) * - [EIP-4399](https://eips.ethereum.org/EIPS/eip-4399) - Supplant DIFFICULTY opcode with PREVRANDAO (Merge) + * [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895) - Beacon chain push withdrawals as operations (`experimental`) * - [EIP-5133](https://eips.ethereum.org/EIPS/eip-5133) - Delaying Difficulty Bomb to mid-September 2022 * * *Annotations:* @@ -236,7 +237,7 @@ export class EVM implements EVMInterface { // Supported EIPs const supportedEIPs = [ 1153, 1559, 2315, 2537, 2565, 2718, 2929, 2930, 3074, 3198, 3529, 3540, 3541, 3607, 3651, - 3670, 3855, 3860, 4399, 5133, + 3670, 3855, 3860, 4399, 4895, 5133, ] for (const eip of this._common.eips()) { diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 4c4437b9e6..2390eaf2b0 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -2,7 +2,15 @@ import { Block } from '@ethereumjs/block' import { ConsensusType, Hardfork } from '@ethereumjs/common' import { RLP } from '@ethereumjs/rlp' import { Trie } from '@ethereumjs/trie' -import { Account, Address, bigIntToBuffer, bufArrToArr, intToBuffer, short } from '@ethereumjs/util' +import { + Account, + Address, + bigIntToBuffer, + bufArrToArr, + intToBuffer, + short, + toBuffer, +} from '@ethereumjs/util' import { debug as createDebugLogger } from 'debug' import { Bloom } from './bloom' @@ -237,6 +245,9 @@ async function applyBlock(this: VM, block: Block, opts: RunBlockOpts) { debug(`Apply transactions`) } const blockResults = await applyTransactions.bind(this)(block, opts) + if (this._common.isActivatedEIP(4895)) { + await assignWithdrawals.bind(this)(block) + } // Pay ommers and miners if (block._common.consensusType() === ConsensusType.ProofOfWork) { await assignBlockRewards.bind(this)(block) @@ -316,6 +327,19 @@ async function applyTransactions(this: VM, block: Block, opts: RunBlockOpts) { } } +async function assignWithdrawals(this: VM, block: Block): Promise { + const state = this.eei + const withdrawals = block.withdrawals! + for (const withdrawal of withdrawals) { + const { address: addressData, amount: amountData } = withdrawal + const address = new Address(toBuffer(addressData)) + const amount = Buffer.isBuffer(amountData) + ? BigInt('0x' + amountData.toString('hex')) + : BigInt(amountData) + await rewardAccount(state, address, amount) + } +} + /** * Calculates block rewards for miner and ommers and puts * the updated balances of their accounts to state. diff --git a/packages/vm/test/api/EIPs/eip-4895-BeaconChainWithdrawals.spec.ts b/packages/vm/test/api/EIPs/eip-4895-BeaconChainWithdrawals.spec.ts new file mode 100644 index 0000000000..6968e6d2ed --- /dev/null +++ b/packages/vm/test/api/EIPs/eip-4895-BeaconChainWithdrawals.spec.ts @@ -0,0 +1,112 @@ +import { Block } from '@ethereumjs/block' +import { Chain, Common, Hardfork } from '@ethereumjs/common' +import { FeeMarketEIP1559Transaction } from '@ethereumjs/tx' +import { Address, zeros } from '@ethereumjs/util' +import * as tape from 'tape' + +import { VM } from '../../../src/vm' + +import type { Withdrawal } from '@ethereumjs/block' + +const common = new Common({ + chain: Chain.Mainnet, + hardfork: Hardfork.Merge, +}) + +const pkey = Buffer.from('20'.repeat(32), 'hex') + +tape('EIP4895 tests', (t) => { + t.test('EIP4895: withdrawals execute as expected', async (st) => { + const vm = await VM.create({ common }) + vm._common.setEIPs([4895]) + const withdrawals = [] + const addresses = ['20'.repeat(20), '30'.repeat(20), '40'.repeat(20)] + const amounts = [BigInt(1000), BigInt(3000), BigInt(5000)] + + /* + Setup a contract at the second withdrawal address with code: + PUSH 2 + PUSH 0 + SSTORE + If code is ran, this stores "2" at slot "0". Check if withdrawal operations do not invoke this code + */ + const withdrawalCheckAddress = new Address(Buffer.from('fe'.repeat(20), 'hex')) + const withdrawalCode = Buffer.from('6002600055') + + await vm.stateManager.putContractCode(withdrawalCheckAddress, withdrawalCode) + + const contractAddress = new Address(Buffer.from('ff'.repeat(20), 'hex')) + + /* + PUSH + BALANCE // Retrieve balance of addresses[0] + PUSH 0 + MSTORE // Store balance in memory at pos 0 + PUSH 20 + PUSH 0 + RETURN // Return the balance + */ + const contract = '73' + addresses[0] + '3160005260206000F3' + await vm.stateManager.putContractCode(contractAddress, Buffer.from(contract, 'hex')) + + const transaction = FeeMarketEIP1559Transaction.fromTxData({ + to: contractAddress, + maxFeePerGas: BigInt(7), + maxPriorityFeePerGas: BigInt(0), + gasLimit: BigInt(50000), + }).sign(pkey) + + const account = await vm.stateManager.getAccount(transaction.getSenderAddress()) + account.balance = BigInt(1000000) + await vm.stateManager.putAccount(transaction.getSenderAddress(), account) + + let index = 0 + for (let i = 0; i < addresses.length; i++) { + // Just assign any number to validatorIndex as its just for CL convinience + withdrawals.push({ + index, + validatorIndex: index, + address: new Address(Buffer.from(addresses[i], 'hex')), + amount: amounts[i], + }) + index++ + } + const block = Block.fromBlockData( + { + header: { + baseFeePerGas: BigInt(7), + withdrawalsRoot: Buffer.from( + 'c6595e35232ab8ccf2d9af2a1223446c2e60a01667f348ee156608c8dab7795d', + 'hex' + ), + transactionsTrie: Buffer.from( + '9a744e8acc2886e5809ff013e3b71bf8ec97f9941cafbd7730834fc8f76391ba', + 'hex' + ), + }, + transactions: [transaction], + withdrawals, + }, + { common: vm._common } + ) + + let result: Buffer + vm.events.on('afterTx', (e) => { + result = e.execResult.returnValue + }) + + await vm.runBlock({ block, generate: true }) + + for (let i = 0; i < addresses.length; i++) { + const address = new Address(Buffer.from(addresses[i], 'hex')) + const amount = amounts[i] + const balance = (await vm.stateManager.getAccount(address)).balance + st.equals(BigInt(amount), balance, 'balance ok') + } + + st.ok(zeros(32).equals(result!), 'withdrawals happen after transactions') + + const slotValue = await vm.stateManager.getContractStorage(withdrawalCheckAddress, zeros(32)) + st.ok(zeros(0).equals(slotValue), 'withdrawals do not invoke code') + }) +}) diff --git a/packages/vm/test/api/types.spec.ts b/packages/vm/test/api/types.spec.ts index 4f5fcc5cba..e443cb9efc 100644 --- a/packages/vm/test/api/types.spec.ts +++ b/packages/vm/test/api/types.spec.ts @@ -21,7 +21,7 @@ tape('[Types]', function (t) { const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Berlin }) // Block - const block: Required = Block.fromBlockData({}, { common }) + const block: Omit, 'withdrawals'> = Block.fromBlockData({}, { common }) st.ok(block, 'block') // Transactions