From b3cb348d63efdd9108bf6a45359007ad972b5bf1 Mon Sep 17 00:00:00 2001 From: Jochem Brouwer Date: Mon, 10 Jul 2023 19:18:33 +0200 Subject: [PATCH] Implement EIP4788: Beacon block root in EVM (#2810) * common: add 4788 * evm: add beaconroot * block/header: add 4788 * vm: add 4788 * common/evm/vm: 4788 spec updates * evm: fix build [no ci] * block: add 4788 tests * vm: update 4788 + tests * Update packages/block/src/header.ts [no ci] Co-authored-by: g11tech * block/vm: update beaconRoot property * block: fix test * add validation in from values array --------- Co-authored-by: g11tech --- packages/block/src/header.ts | 35 +++- packages/block/src/helpers.ts | 4 +- packages/block/src/types.ts | 2 + packages/block/test/eip4788block.spec.ts | 70 +++++++ packages/block/test/header.spec.ts | 2 +- packages/common/src/eips/4788.json | 23 +++ packages/common/src/eips/index.ts | 2 + packages/evm/src/evm.ts | 4 +- packages/evm/src/precompiles/0b-beaconroot.ts | 76 +++++++ packages/evm/src/precompiles/index.ts | 12 +- packages/evm/src/precompiles/types.ts | 3 +- packages/vm/src/runBlock.ts | 35 ++++ .../test/api/EIPs/eip-4788-beaconroot.spec.ts | 191 ++++++++++++++++++ 13 files changed, 452 insertions(+), 7 deletions(-) create mode 100644 packages/block/test/eip4788block.spec.ts create mode 100644 packages/common/src/eips/4788.json create mode 100644 packages/evm/src/precompiles/0b-beaconroot.ts create mode 100644 packages/vm/test/api/EIPs/eip-4788-beaconroot.spec.ts diff --git a/packages/block/src/header.ts b/packages/block/src/header.ts index f70ef99d55..0033aca0c5 100644 --- a/packages/block/src/header.ts +++ b/packages/block/src/header.ts @@ -56,6 +56,7 @@ export class BlockHeader { public readonly withdrawalsRoot?: Uint8Array public readonly dataGasUsed?: bigint public readonly excessDataGas?: bigint + public readonly parentBeaconBlockRoot?: Uint8Array public readonly common: Common @@ -108,7 +109,7 @@ export class BlockHeader { */ public static fromValuesArray(values: BlockHeaderBytes, opts: BlockOptions = {}) { const headerData = valuesArrayToHeaderData(values) - const { number, baseFeePerGas, excessDataGas, dataGasUsed } = headerData + const { number, baseFeePerGas, excessDataGas, dataGasUsed, parentBeaconBlockRoot } = headerData const header = BlockHeader.fromHeaderData(headerData, opts) // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (header.common.isActivatedEIP(1559) && baseFeePerGas === undefined) { @@ -126,6 +127,9 @@ export class BlockHeader { throw new Error('invalid header. dataGasUsed should be provided') } } + if (header.common.isActivatedEIP(4788) && parentBeaconBlockRoot === undefined) { + throw new Error('invalid header. parentBeaconBlockRoot should be provided') + } return header } /** @@ -208,6 +212,7 @@ export class BlockHeader { withdrawalsRoot: this.common.isActivatedEIP(4895) ? KECCAK256_RLP : undefined, dataGasUsed: this.common.isActivatedEIP(4844) ? BigInt(0) : undefined, excessDataGas: this.common.isActivatedEIP(4844) ? BigInt(0) : undefined, + parentBeaconBlockRoot: this.common.isActivatedEIP(4788) ? KECCAK256_RLP : undefined, } const baseFeePerGas = @@ -218,6 +223,9 @@ export class BlockHeader { toType(headerData.dataGasUsed, TypeOutput.BigInt) ?? hardforkDefaults.dataGasUsed const excessDataGas = toType(headerData.excessDataGas, TypeOutput.BigInt) ?? hardforkDefaults.excessDataGas + const parentBeaconBlockRoot = + toType(headerData.parentBeaconBlockRoot, TypeOutput.Uint8Array) ?? + hardforkDefaults.parentBeaconBlockRoot if (!this.common.isActivatedEIP(1559) && baseFeePerGas !== undefined) { throw new Error('A base fee for a block can only be set with EIP1559 being activated') @@ -239,6 +247,12 @@ export class BlockHeader { } } + if (!this.common.isActivatedEIP(4788) && parentBeaconBlockRoot !== undefined) { + throw new Error( + 'A parentBeaconBlockRoot for a header can only be provided with EIP4788 being activated' + ) + } + this.parentHash = parentHash this.uncleHash = uncleHash this.coinbase = coinbase @@ -258,6 +272,7 @@ export class BlockHeader { this.withdrawalsRoot = withdrawalsRoot this.dataGasUsed = dataGasUsed this.excessDataGas = excessDataGas + this.parentBeaconBlockRoot = parentBeaconBlockRoot this._genericFormatValidation() this._validateDAOExtraData() @@ -366,6 +381,21 @@ export class BlockHeader { throw new Error(msg) } } + + if (this.common.isActivatedEIP(4788) === true) { + if (this.parentBeaconBlockRoot === undefined) { + const msg = this._errorMsg('EIP4788 block has no parentBeaconBlockRoot field') + throw new Error(msg) + } + if (this.parentBeaconBlockRoot?.length !== 32) { + const msg = this._errorMsg( + `parentBeaconBlockRoot must be 32 bytes, received ${ + this.parentBeaconBlockRoot!.length + } bytes` + ) + throw new Error(msg) + } + } } /** @@ -892,6 +922,9 @@ export class BlockHeader { jsonDict.dataGasUsed = bigIntToHex(this.dataGasUsed!) jsonDict.excessDataGas = bigIntToHex(this.excessDataGas!) } + if (this.common.isActivatedEIP(4788) === true) { + jsonDict.parentBeaconBlockRoot = bytesToHex(this.parentBeaconBlockRoot!) + } return jsonDict } diff --git a/packages/block/src/helpers.ts b/packages/block/src/helpers.ts index 022bac6f26..1856846cce 100644 --- a/packages/block/src/helpers.ts +++ b/packages/block/src/helpers.ts @@ -42,9 +42,10 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { withdrawalsRoot, dataGasUsed, excessDataGas, + parentBeaconBlockRoot, ] = values - if (values.length > 19) { + if (values.length > 20) { throw new Error('invalid header. More values than expected were received') } if (values.length < 15) { @@ -71,6 +72,7 @@ export function valuesArrayToHeaderData(values: BlockHeaderBytes): HeaderData { withdrawalsRoot, dataGasUsed, excessDataGas, + parentBeaconBlockRoot, } } diff --git a/packages/block/src/types.ts b/packages/block/src/types.ts index 3bc6b7d7af..9e5628dcfe 100644 --- a/packages/block/src/types.ts +++ b/packages/block/src/types.ts @@ -94,6 +94,7 @@ export interface HeaderData { withdrawalsRoot?: BytesLike dataGasUsed?: BigIntLike excessDataGas?: BigIntLike + parentBeaconBlockRoot?: BytesLike } /** @@ -158,6 +159,7 @@ export interface JsonHeader { withdrawalsRoot?: string dataGasUsed?: string excessDataGas?: string + parentBeaconBlockRoot?: string } /* diff --git a/packages/block/test/eip4788block.spec.ts b/packages/block/test/eip4788block.spec.ts new file mode 100644 index 0000000000..d49733b2dc --- /dev/null +++ b/packages/block/test/eip4788block.spec.ts @@ -0,0 +1,70 @@ +import { Chain, Common, Hardfork } from '@ethereumjs/common' +import { KECCAK256_RLP, bytesToHex, zeros } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { BlockHeader } from '../src/header.js' +import { Block } from '../src/index.js' + +describe('EIP4788 header tests', () => { + it('should work', () => { + const earlyCommon = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Istanbul }) + const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Cancun, eips: [4788] }) + + assert.throws( + () => { + BlockHeader.fromHeaderData( + { + parentBeaconBlockRoot: zeros(32), + }, + { + common: earlyCommon, + } + ) + }, + 'A parentBeaconBlockRoot for a header can only be provided with EIP4788 being activated', + undefined, + 'should throw when setting parentBeaconBlockRoot with EIP4788 not being activated' + ) + + assert.throws( + () => { + BlockHeader.fromHeaderData( + { + dataGasUsed: 1n, + }, + { + common: earlyCommon, + } + ) + }, + 'data gas used can only be provided with EIP4844 activated', + undefined, + 'should throw when setting dataGasUsed with EIP4844 not being activated' + ) + assert.doesNotThrow(() => { + BlockHeader.fromHeaderData( + { + excessDataGas: 0n, + dataGasUsed: 0n, + parentBeaconBlockRoot: zeros(32), + }, + { + common, + skipConsensusFormatValidation: true, + } + ) + }, 'correctly instantiates an EIP4788 block header') + + const block = Block.fromBlockData( + { + header: BlockHeader.fromHeaderData({}, { common }), + }, + { common, skipConsensusFormatValidation: true } + ) + assert.equal( + block.toJSON().header?.parentBeaconBlockRoot, + bytesToHex(KECCAK256_RLP), + 'JSON output includes excessDataGas' + ) + }) +}) diff --git a/packages/block/test/header.spec.ts b/packages/block/test/header.spec.ts index a98440b6dc..c3bcf5c86e 100644 --- a/packages/block/test/header.spec.ts +++ b/packages/block/test/header.spec.ts @@ -162,7 +162,7 @@ describe('[Block]: Header functions', () => { }) it('Initialization -> fromValuesArray() -> error cases', () => { - const headerArray = Array(20).fill(new Uint8Array(0)) + const headerArray = Array(21).fill(new Uint8Array(0)) // mock header data (if set to zeros(0) header throws) headerArray[0] = zeros(32) //parentHash diff --git a/packages/common/src/eips/4788.json b/packages/common/src/eips/4788.json new file mode 100644 index 0000000000..b3bc50376c --- /dev/null +++ b/packages/common/src/eips/4788.json @@ -0,0 +1,23 @@ +{ + "name": "EIP-4788", + "number": 4788, + "comment": "Beacon block root in the EVM", + "url": "https://eips.ethereum.org/EIPS/eip-4788", + "status": "Draft", + "minimumHardfork": "cancun", + "requiredEIPs": [], + "gasConfig": {}, + "gasPrices": { + "beaconrootCost": { + "v": 4200, + "d": "Gas cost when calling the beaconroot stateful precompile" + } + }, + "vm": { + "historicalRootsLength": { + "v": 98304, + "d": "The modulo parameter of the beaconroot ring buffer in the beaconroot statefull precompile" + } + }, + "pow": {} +} diff --git a/packages/common/src/eips/index.ts b/packages/common/src/eips/index.ts index d5e0c084fd..1fb8d9a9ab 100644 --- a/packages/common/src/eips/index.ts +++ b/packages/common/src/eips/index.ts @@ -19,6 +19,7 @@ import * as eip3855 from './3855.json' import * as eip3860 from './3860.json' import * as eip4345 from './4345.json' import * as eip4399 from './4399.json' +import * as eip4788 from './4788.json' import * as eip4844 from './4844.json' import * as eip4895 from './4895.json' import * as eip5133 from './5133.json' @@ -47,6 +48,7 @@ export const EIPs: { [key: number]: any } = { 3860: eip3860, 4345: eip4345, 4399: eip4399, + 4788: eip4788, 4844: eip4844, 4895: eip4895, 5133: eip5133, diff --git a/packages/evm/src/evm.ts b/packages/evm/src/evm.ts index c0032f9c9a..45be3833a4 100644 --- a/packages/evm/src/evm.ts +++ b/packages/evm/src/evm.ts @@ -72,6 +72,7 @@ export interface EVMOpts { * - [EIP-3855](https://eips.ethereum.org/EIPS/eip-3855) - PUSH0 instruction * - [EIP-3860](https://eips.ethereum.org/EIPS/eip-3860) - Limit and meter initcode * - [EIP-4399](https://eips.ethereum.org/EIPS/eip-4399) - Supplant DIFFICULTY opcode with PREVRANDAO (Merge) + * - [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) - Beacon block root in the EVM (`experimental`) * - [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) - Shard Blob Transactions (`experimental`) * - [EIP-4895](https://eips.ethereum.org/EIPS/eip-4895) - Beacon chain push withdrawals as operations * - [EIP-5133](https://eips.ethereum.org/EIPS/eip-5133) - Delaying Difficulty Bomb to mid-September 2022 @@ -243,7 +244,7 @@ export class EVM { // Supported EIPs const supportedEIPs = [ 1153, 1559, 2315, 2565, 2718, 2929, 2930, 3074, 3198, 3529, 3540, 3541, 3607, 3651, 3670, - 3855, 3860, 4399, 4895, 4844, 5133, 5656, 6780, + 3855, 3860, 4399, 4895, 4788, 4844, 5133, 5656, 6780, ] for (const eip of this.common.eips()) { @@ -877,6 +878,7 @@ export class EVM { common: this.common, _EVM: this, _debug: this.DEBUG ? debugPrecompiles : undefined, + stateManager: this.stateManager, } return code(opts) diff --git a/packages/evm/src/precompiles/0b-beaconroot.ts b/packages/evm/src/precompiles/0b-beaconroot.ts new file mode 100644 index 0000000000..e3f332b87c --- /dev/null +++ b/packages/evm/src/precompiles/0b-beaconroot.ts @@ -0,0 +1,76 @@ +import { + Address, + bigIntToBytes, + bytesToBigInt, + setLengthLeft, + short, + zeros, +} from '@ethereumjs/util' + +import { type ExecResult, OOGResult } from '../evm.js' +import { ERROR, EvmError } from '../exceptions.js' + +import type { PrecompileInput } from './types.js' + +const address = Address.fromString('0x000000000000000000000000000000000000000b') + +export async function precompile0b(opts: PrecompileInput): Promise { + const data = opts.data + + const gasUsed = opts.common.param('gasPrices', 'beaconrootCost') + if (opts._debug !== undefined) { + opts._debug( + `Run BEACONROOT (0x0B) precompile data=${short(opts.data)} length=${ + opts.data.length + } gasLimit=${opts.gasLimit} gasUsed=${gasUsed}` + ) + } + + if (opts.gasLimit < gasUsed) { + if (opts._debug !== undefined) { + opts._debug(`BEACONROOT (0x0B) failed: OOG`) + } + return OOGResult(opts.gasLimit) + } + + if (data.length < 32) { + return { + returnValue: new Uint8Array(0), + executionGasUsed: gasUsed, + exceptionError: new EvmError(ERROR.INVALID_INPUT_LENGTH), + } + } + + const timestampInput = bytesToBigInt(data.slice(0, 32)) + const historicalRootsLength = BigInt(opts.common.param('vm', 'historicalRootsLength')) + + const timestampIndex = timestampInput % historicalRootsLength + const recordedTimestamp = await opts.stateManager.getContractStorage( + address, + setLengthLeft(bigIntToBytes(timestampIndex), 32) + ) + + if (bytesToBigInt(recordedTimestamp) !== timestampInput) { + return { + executionGasUsed: gasUsed, + returnValue: zeros(32), + } + } + const timestampExtended = timestampIndex + historicalRootsLength + const returnData = setLengthLeft( + await opts.stateManager.getContractStorage( + address, + setLengthLeft(bigIntToBytes(timestampExtended), 32) + ), + 32 + ) + + if (opts._debug !== undefined) { + opts._debug(`BEACONROOT (0x0B) return data=${short(returnData)}`) + } + + return { + executionGasUsed: gasUsed, + returnValue: returnData, + } +} diff --git a/packages/evm/src/precompiles/index.ts b/packages/evm/src/precompiles/index.ts index 1937472744..04314688f9 100644 --- a/packages/evm/src/precompiles/index.ts +++ b/packages/evm/src/precompiles/index.ts @@ -11,6 +11,7 @@ import { precompile07 } from './07-ecmul.js' import { precompile08 } from './08-ecpairing.js' import { precompile09 } from './09-blake2f.js' import { precompile0a } from './0a-kzg-point-evaluation.js' +import { precompile0b } from './0b-beaconroot.js' import type { PrecompileFunc, PrecompileInput } from './types.js' import type { Common } from '@ethereumjs/common' @@ -127,7 +128,14 @@ const precompileEntries: PrecompileEntry[] = [ }, precompile: precompile0a, }, - // 0x00..0b: beacon block root, see PR 2810 + { + address: '000000000000000000000000000000000000000b', + check: { + type: PrecompileAvailabilityCheck.EIP, + param: 4788, + }, + precompile: precompile0b, + }, ] const precompiles: Precompiles = { @@ -141,7 +149,7 @@ const precompiles: Precompiles = { '0000000000000000000000000000000000000008': precompile08, '0000000000000000000000000000000000000009': precompile09, '000000000000000000000000000000000000000a': precompile0a, - // 0b: beacon block root see PR 2810 + '000000000000000000000000000000000000000b': precompile0b, } type DeletePrecompile = { diff --git a/packages/evm/src/precompiles/types.ts b/packages/evm/src/precompiles/types.ts index 5319ad5d36..a4393d1200 100644 --- a/packages/evm/src/precompiles/types.ts +++ b/packages/evm/src/precompiles/types.ts @@ -1,5 +1,5 @@ import type { EVM, ExecResult } from '../evm.js' -import type { Common } from '@ethereumjs/common' +import type { Common, StateManagerInterface } from '@ethereumjs/common' import type { debug } from 'debug' export interface PrecompileFunc { @@ -12,4 +12,5 @@ export interface PrecompileInput { common: Common _EVM: EVM _debug?: debug.Debugger + stateManager: StateManagerInterface } diff --git a/packages/vm/src/runBlock.ts b/packages/vm/src/runBlock.ts index 4d2d99f72d..20dc841bac 100644 --- a/packages/vm/src/runBlock.ts +++ b/packages/vm/src/runBlock.ts @@ -13,6 +13,7 @@ import { equalsBytes, hexToBytes, intToBytes, + setLengthLeft, short, unprefixedHexToBytes, } from '@ethereumjs/util' @@ -39,6 +40,10 @@ const debug = createDebugLogger('vm:block') const DAOAccountList = DAOConfig.DAOAccounts const DAORefundContract = DAOConfig.DAORefundContract +const parentBeaconBlockRootAddress = Address.fromString( + '0x000000000000000000000000000000000000000b' +) + /** * @ignore */ @@ -255,6 +260,36 @@ async function applyBlock(this: VM, block: Block, opts: RunBlockOpts) { await block.validateData() } } + if (this.common.isActivatedEIP(4788)) { + // Save the parentBeaconBlockRoot to the beaconroot stateful precompile ring buffers + const root = block.header.parentBeaconBlockRoot! + const timestamp = block.header.timestamp + const historicalRootsLength = BigInt(this.common.param('vm', 'historicalRootsLength')) + const timestampIndex = timestamp % historicalRootsLength + const timestampExtended = timestampIndex + historicalRootsLength + + /** + * Note: (by Jochem) + * If we don't do this (put account if undefined / non-existant), block runner crashes because the beacon root address does not exist + * This is hence (for me) again a reason why it should /not/ throw if the address does not exist + * All ethereum accounts have empty storage by default + */ + + if ((await this.stateManager.getAccount(parentBeaconBlockRootAddress)) === undefined) { + await this.stateManager.putAccount(parentBeaconBlockRootAddress, new Account()) + } + + await this.stateManager.putContractStorage( + parentBeaconBlockRootAddress, + setLengthLeft(bigIntToBytes(timestampIndex), 32), + bigIntToBytes(block.header.timestamp) + ) + await this.stateManager.putContractStorage( + parentBeaconBlockRootAddress, + setLengthLeft(bigIntToBytes(timestampExtended), 32), + root + ) + } // Apply transactions if (this.DEBUG) { debug(`Apply transactions`) diff --git a/packages/vm/test/api/EIPs/eip-4788-beaconroot.spec.ts b/packages/vm/test/api/EIPs/eip-4788-beaconroot.spec.ts new file mode 100644 index 0000000000..2fbf495c66 --- /dev/null +++ b/packages/vm/test/api/EIPs/eip-4788-beaconroot.spec.ts @@ -0,0 +1,191 @@ +/** + * EIP 4788 beaconroot specs + * + * Test cases: + * - Beaconroot precompile call, timestamp matches + * - Beaconroot precompile call, timestamp does not match + * - Beaconroot precompile call, timestamp matches: + * - Input length > 32 bytes + * - Input length < 32 bytes (reverts) + */ + +import { Block, BlockHeader } from '@ethereumjs/block' +import { Chain, Common, Hardfork } from '@ethereumjs/common' +import { TransactionFactory } from '@ethereumjs/tx' +import { + Address, + bigIntToBytes, + bytesToBigInt, + hexToBytes, + setLengthLeft, + setLengthRight, + zeros, +} from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { VM } from '../../../src' + +import type { TransactionType, TxData } from '@ethereumjs/tx' +import type { BigIntLike } from '@ethereumjs/util' + +const common = new Common({ + chain: Chain.Mainnet, + hardfork: Hardfork.Cancun, + eips: [4788], +}) + +const pkey = hexToBytes('0x' + '20'.repeat(32)) +const contractAddress = Address.fromString('0x' + 'c0de'.repeat(10)) + +function beaconrootBlock( + blockroot: bigint, + timestamp: BigIntLike, + transactions: Array +) { + const newTxData = [] + + for (const txData of transactions) { + const tx = TransactionFactory.fromTxData({ + gasPrice: 7, + gasLimit: 100000, + ...txData, + type: 0, + to: contractAddress, + }) + newTxData.push(tx.sign(pkey)) + } + + const root = setLengthLeft(bigIntToBytes(blockroot), 32) + const header = BlockHeader.fromHeaderData( + { + parentBeaconBlockRoot: root, + timestamp, + }, + { common, freeze: false } + ) + const block = Block.fromBlockData( + { + header, + transactions: newTxData, + }, + { + common, + freeze: false, + } + ) + return block +} + +/** + * This code: + * CALLDATACOPYs the calldata into memory + * CALLS with this calldata into 0x0B (beaconroot precompile) + * Stores the CALL-return field (either 0 or 1 depending if it reverts or not) at storage slot 0 + * Then it returns the data the precompile returns + */ + +const CODE = '0x365F5F375F5F365F5F600B5AF15F553D5F5F3E3D5FF3' + +/** + * Run a block inside a 4788 VM + * @param block Block to run + * @returns Two fields: block return status, and callStatus (field saved in the contract) + */ +async function runBlock(block: Block) { + const vm = await VM.create({ + common, + }) + + await vm.stateManager.putContractCode(contractAddress, hexToBytes(CODE)) + return { + vmResult: await vm.runBlock({ + block, + skipBalance: true, + skipBlockValidation: true, + generate: true, + }), + callStatus: await getCallStatus(vm), + } +} + +/** + * Get call status saved in the contract + */ +async function getCallStatus(vm: VM) { + const stat = await vm.stateManager.getContractStorage(contractAddress, zeros(32)) + return bytesToBigInt(stat) +} + +/** + * Run block test + * @param input + */ +async function runBlockTest(input: { + timestamp: bigint // Timestamp as input to our contract which calls into the precompile + timestampBlock: bigint // Timestamp of the block (this is saved in the precompile) + blockRoot: bigint // Blockroot of the block (also saved in the precompile) + extLeft?: number // Extend length left of the input (defaults to 32) + extRight?: number // Extend lenght right of the input (defaults to 32) - happens after extendLeft + expRet: bigint // Expected return value + expCallStatus: bigint // Expected call status (either 0 or 1) +}) { + const { timestamp, blockRoot, timestampBlock, expRet, expCallStatus } = input + + const data = setLengthRight( + setLengthLeft(bigIntToBytes(timestamp), input.extLeft ?? 32), + input.extRight ?? 32 + ) + const block = beaconrootBlock(blockRoot, timestampBlock, [ + { + data, + }, + ]) + + const ret = await runBlock(block) + const bigIntReturn = bytesToBigInt(ret.vmResult.results[0].execResult.returnValue) + assert.equal(bigIntReturn, expRet, 'blockRoot ok') + assert.equal(ret.callStatus, expCallStatus, 'call status ok') +} + +describe('should run beaconroot precompile correctly', async () => { + it('should run precompile with known timestamp', async () => { + await runBlockTest({ + timestamp: BigInt(12), + timestampBlock: BigInt(12), + blockRoot: BigInt(1), + expRet: BigInt(1), + expCallStatus: BigInt(1), + }) + }) + it('should run precompile with unknown timestamp', async () => { + await runBlockTest({ + timestamp: BigInt(12), + timestampBlock: BigInt(11), + blockRoot: BigInt(1), + expRet: BigInt(0), + expCallStatus: BigInt(1), + }) + }) + it('should run precompile with known timestamp, input length > 32 bytes', async () => { + await runBlockTest({ + timestamp: BigInt(12), + timestampBlock: BigInt(12), + blockRoot: BigInt(1), + extLeft: 32, + extRight: 320, + expRet: BigInt(1), + expCallStatus: BigInt(1), + }) + }) + it('should run precompile with known timestamp, input length < 32 bytes', async () => { + await runBlockTest({ + timestamp: BigInt(12), + timestampBlock: BigInt(12), + blockRoot: BigInt(1), + extLeft: 31, + extRight: 31, + expRet: BigInt(0), + expCallStatus: BigInt(0), + }) + }) +})