diff --git a/packages/ovm/src/app/utils.ts b/packages/ovm/src/app/utils.ts index 8e5f675f0e49a..5d0cfdf58c875 100644 --- a/packages/ovm/src/app/utils.ts +++ b/packages/ovm/src/app/utils.ts @@ -63,6 +63,9 @@ export const convertInternalLogsToOvmLogs = ( numberOfEMLogs++ prevEMLogIndex = log.logIndex const executionManagerLog = executionManagerInterface.parseLog(log) + stringsToDebugLog.push( + `Parsed internal EM log with topic: ${executionManagerLog.name}` + ) if (!executionManagerLog) { stringsToDebugLog.push( `Execution manager emitted log with topics: ${log.topics}. These were unrecognized by the interface parser-but definitely not an ActiveContract event, ignoring...` @@ -140,6 +143,10 @@ export const getSuccessfulOvmTransactionMetadata = ( metadata.revertMessage = revertMessagePrefix } else { // decode revert message from event + logger.debug(`here is the thing we fail to decode:`) + logger.debug( + ethers.utils.hexDataSlice(revertEvents[0].values['_revertMessage'], 4) + ) const msgBuf: any = abi.decode( ['bytes'], // Remove the first 4 bytes of the revert message that is a sighash diff --git a/packages/ovm/test/contracts/execution-manager.call-opcodes.spec.ts b/packages/ovm/test/contracts/execution-manager.call-opcodes.spec.ts index d54fd01de1673..1cacf51e717cd 100644 --- a/packages/ovm/test/contracts/execution-manager.call-opcodes.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.call-opcodes.spec.ts @@ -18,6 +18,7 @@ import { import { Address, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' @@ -88,7 +89,12 @@ describe('Execution Manager -- Call opcodes', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT, } @@ -451,6 +457,7 @@ describe('Execution Manager -- Call opcodes', () => { callBytes, ZERO_ADDRESS, ZERO_ADDRESS, + GAS_LIMIT, true, ]) @@ -478,6 +485,7 @@ describe('Execution Manager -- Call opcodes', () => { callBytes, ZERO_ADDRESS, ZERO_ADDRESS, + GAS_LIMIT, true, ]) return executionManager.provider.call({ diff --git a/packages/ovm/test/contracts/execution-manager.code-opcodes.spec.ts b/packages/ovm/test/contracts/execution-manager.code-opcodes.spec.ts index 1f62cd2ea074f..bb3bc0b979d2c 100644 --- a/packages/ovm/test/contracts/execution-manager.code-opcodes.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.code-opcodes.spec.ts @@ -4,6 +4,7 @@ import '../setup' import { Address, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' @@ -57,7 +58,12 @@ describe('Execution Manager -- Code-related opcodes', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) diff --git a/packages/ovm/test/contracts/execution-manager.context-opcodes.spec.ts b/packages/ovm/test/contracts/execution-manager.context-opcodes.spec.ts index c352ff24d2bca..217a1110af3c3 100644 --- a/packages/ovm/test/contracts/execution-manager.context-opcodes.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.context-opcodes.spec.ts @@ -13,6 +13,7 @@ import { import { Address, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' @@ -72,7 +73,12 @@ describe('Execution Manager -- Context opcodes', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) @@ -236,6 +242,7 @@ describe('Execution Manager -- Context opcodes', () => { callBytes, ZERO_ADDRESS, ZERO_ADDRESS, + GAS_LIMIT, true, ]) return executionManager.provider.call({ diff --git a/packages/ovm/test/contracts/execution-manager.create-opcodes.spec.ts b/packages/ovm/test/contracts/execution-manager.create-opcodes.spec.ts index 3915bbca9a600..5eee116b28864 100644 --- a/packages/ovm/test/contracts/execution-manager.create-opcodes.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.create-opcodes.spec.ts @@ -10,6 +10,7 @@ import { import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' @@ -52,7 +53,12 @@ describe('ExecutionManager -- Create opcodes', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) @@ -64,7 +70,12 @@ describe('ExecutionManager -- Create opcodes', () => { safetyCheckedExecutionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, false], // Note: this is false, so it's safety checked. + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + false, + ], // Note: this is false, so it's safety checked. { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) diff --git a/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts b/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts index c8b4429a31352..01c6b64ab18b8 100644 --- a/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts @@ -4,6 +4,7 @@ import '../setup' import { Address, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, CHAIN_ID, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, @@ -42,7 +43,7 @@ const unsignedCallMethodId: string = ethereumjsAbi .methodID('executeTransaction', []) .toString('hex') -describe('Execution Manager -- Call opcodes', () => { +describe('Execution Manager -- Call execution', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) // Create pointers to our execution manager & simple copier contract @@ -58,7 +59,12 @@ describe('Execution Manager -- Call opcodes', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) // Set the state manager as well @@ -120,6 +126,7 @@ describe('Execution Manager -- Call opcodes', () => { transaction.nonce, transaction.to, transaction.data, + transaction.gasLimit, padToLength(v, 4), padToLength(r, 64), padToLength(s, 64) @@ -158,6 +165,7 @@ describe('Execution Manager -- Call opcodes', () => { transaction.data, wallet.address, ZERO_ADDRESS, + GAS_LIMIT, true ) await provider.waitForTransaction(tx.hash) @@ -195,6 +203,7 @@ describe('Execution Manager -- Call opcodes', () => { transaction.nonce, transaction.to, transaction.data, + transaction.gasLimit, padToLength(v, 4), padToLength(r, 64), padToLength(s, 64) @@ -232,6 +241,7 @@ describe('Execution Manager -- Call opcodes', () => { transaction.nonce, transaction.to, transaction.data, + transaction.gasLimit, v, r, s @@ -270,6 +280,7 @@ describe('Execution Manager -- Call opcodes', () => { transaction.nonce, transaction.to, transaction.data, + transaction.gasLimit, padToLength(v, 4), padToLength(r, 64), padToLength(s, 64) @@ -295,6 +306,7 @@ describe('Execution Manager -- Call opcodes', () => { internalCalldata, wallet.address, ZERO_ADDRESS, + GAS_LIMIT, true, ] ) @@ -337,6 +349,7 @@ describe('Execution Manager -- Call opcodes', () => { internalCalldata, wallet.address, ZERO_ADDRESS, + GAS_LIMIT, true, ] ) diff --git a/packages/ovm/test/contracts/execution-manager.gas-metering.spec.ts b/packages/ovm/test/contracts/execution-manager.gas-metering.spec.ts new file mode 100644 index 0000000000000..62fde79ef8ae4 --- /dev/null +++ b/packages/ovm/test/contracts/execution-manager.gas-metering.spec.ts @@ -0,0 +1,317 @@ +import '../setup' + +/* External Imports */ +import { + Address, + DEFAULT_OPCODE_WHITELIST_MASK, + DEFAULT_ETHNODE_GAS_LIMIT, + getUnsignedTransactionCalldata, +} from '@eth-optimism/rollup-core' +import { + getLogger, + ZERO_ADDRESS, + TestUtils, + hexStrToNumber, +} from '@eth-optimism/core-utils' + +import { + ExecutionManagerContractDefinition as ExecutionManager, + FullStateManagerContractDefinition as StateManager, + TestDummyContractDefinition as DummyContract, + TestSimpleConsumeGasConractDefinition as SimpleGas, +} from '@eth-optimism/rollup-contracts' + +import { Contract, ContractFactory, ethers } from 'ethers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' + +/* Internal Imports */ +import { manuallyDeployOvmContract, ZERO_UINT, numberToBuf } from '../helpers' + +export const abi = new ethers.utils.AbiCoder() + +const log = getLogger('execution-manager-gas-metering', true) + +/********************* + * Testing Constants * + *********************/ + +const OVM_TX_FLAT_GAS_FEE = 30_000 +const OVM_TX_MAX_GAS = 1_500_000 +const GAS_RATE_LIMIT_EPOCH_LENGTH = 60_000 +const MAX_GAS_PER_EPOCH = 2_000_000 + +const SEQUENCER_ORIGIN = 0 +const QUEUED_ORIGIN = 1 + +/********* + * TESTS * + *********/ + +describe('Execution Manager -- Gas Metering', () => { + const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) + const [wallet] = getWallets(provider) + // Create pointers to our execution manager & simple copier contract + let executionManager: Contract + let stateManager: Contract + let gasConsumerContract: ContractFactory + let gasConsumerAddress: Address + + const assertOvmTxRevertedWithMessage = async ( + tx: any, + msg: string, + _wallet: any + ) => { + const reciept = await _wallet.provider.getTransactionReceipt(tx.hash) + const revertTopic = ethers.utils.id('EOACallRevert(bytes)') + const revertEvent = reciept.logs.find((logged) => { + return logged.topics.includes(revertTopic) + }) + revertEvent.should.not.equal(undefined) + revertEvent.data.should.equal(abi.encode(['bytes'], [Buffer.from(msg)])) + return + } + + const assertOvmTxDidNotRevert = async (tx: any, _wallet: any) => { + const reciept = await _wallet.provider.getTransactionReceipt(tx.hash) + const revertTopic = ethers.utils.id('EOACallRevert(bytes)') + const revertEvent = reciept.logs.find((logged) => { + return logged.topics.includes(revertTopic) + }) + const didNotRevert: boolean = !revertEvent + const msg = didNotRevert + ? '' + : `Expected not to find an EOACallRevert but one was found with data: ${revertEvent.data}` + didNotRevert.should.eq(true, msg) + } + + const getConsumeGasTx = ( + timestamp: number, + queueOrigin: number, + gasToConsume: number + ): Promise => { + const internalCalldata = getUnsignedTransactionCalldata( + gasConsumerContract, + 'consumeGasExceeding', + [gasToConsume] + ) + // overall tx gas padding to account for executeTransaction and SimpleGas return overhead + const gasPad: number = 50_000 + const ovmTxGasLimit: number = gasToConsume + OVM_TX_FLAT_GAS_FEE + gasPad + return executionManager.executeTransaction( + timestamp, + queueOrigin, + gasConsumerAddress, + internalCalldata, + wallet.address, + ZERO_ADDRESS, + ovmTxGasLimit, + false + ) + } + + const getCumulativeQueuedGas = async (): Promise => { + return hexStrToNumber( + (await executionManager.getCumulativeQueuedGas())._hex + ) + } + + const getCumulativeSequencedGas = async (): Promise => { + return hexStrToNumber( + (await executionManager.getCumulativeSequencedGas())._hex + ) + } + + const getChangeInCumulativeGas = async ( + call: Promise + ): Promise<{ sequenced: number; queued: number }> => { + // record value before + const queuedBefore: number = await getCumulativeQueuedGas() + const sequencedBefore: number = await getCumulativeSequencedGas() + await call + const queuedAfter: number = await getCumulativeQueuedGas() + const sequencedAfter: number = await getCumulativeSequencedGas() + + return { + sequenced: sequencedAfter - sequencedBefore, + queued: queuedAfter - queuedBefore, + } + } + + beforeEach(async () => { + // Before each test let's deploy a fresh ExecutionManager and GasConsumer + // Deploy ExecutionManager the normal way + executionManager = await deployContract( + wallet, + ExecutionManager, + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + [ + OVM_TX_FLAT_GAS_FEE, + OVM_TX_MAX_GAS, + GAS_RATE_LIMIT_EPOCH_LENGTH, + MAX_GAS_PER_EPOCH, + MAX_GAS_PER_EPOCH, + ], + true, + ], + { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } + ) + // Set the state manager as well + stateManager = new Contract( + await executionManager.getStateManagerAddress(), + StateManager.abi, + wallet + ) + // Deploy SimpleCopier with the ExecutionManager + gasConsumerAddress = await manuallyDeployOvmContract( + wallet, + provider, + executionManager, + SimpleGas, + [], + 1 + ) + log.debug(`Gas consumer contract address: [${gasConsumerAddress}]`) + + // Also set our simple copier Ethers contract so we can generate unsigned transactions + gasConsumerContract = new ContractFactory( + SimpleGas.abi as any, + SimpleGas.bytecode + ) + }) + + const dummyCalldata = '0x123412341234' + describe('Per-transaction gas limit', async () => { + it('Should emit EOACallRevert event if the gas limit is higher than the max allowed', async () => { + const gasToConsume = OVM_TX_MAX_GAS + 1 + const timestamp = 1 + + const doTx = await getConsumeGasTx( + timestamp, + SEQUENCER_ORIGIN, + gasToConsume + ) + const tx = await doTx + await assertOvmTxRevertedWithMessage( + tx, + 'Transaction gas limit exceeds max OVM tx gas limit', + wallet + ) + }) + }) + describe('Cumulative gas tracking', async () => { + const gasToConsume: number = 500_000 + const timestamp = 1 + it('Should properly track sequenced consumed gas', async () => { + const consumeTx = getConsumeGasTx( + timestamp, + SEQUENCER_ORIGIN, + gasToConsume + ) + const change = await getChangeInCumulativeGas(consumeTx) + + change.queued.should.equal(0) + // TODO get the SimpleGas consuming the exact gas amount input so we can check an equality + change.sequenced.should.be.gt(gasToConsume) + }) + it('Should properly track queued consumed gas', async () => { + const consumeTx = getConsumeGasTx(timestamp, QUEUED_ORIGIN, gasToConsume) + const change = await getChangeInCumulativeGas(consumeTx) + + change.sequenced.should.equal(0) + // TODO get the SimpleGas consuming the exact gas amount input so we can check an equality + change.queued.should.be.gt(gasToConsume) + }) + it('Should properly track both queue and sequencer consumed gas', async () => { + const queuedBefore: number = await getCumulativeQueuedGas() + const sequencedBefore: number = await getCumulativeSequencedGas() + + const sequencerGasToConsume = 100_000 + const queueGasToConsume = 200_000 + + const consumeQueueGasTx = await getConsumeGasTx( + timestamp, + QUEUED_ORIGIN, + queueGasToConsume + ) + await consumeQueueGasTx + const consumeSequencerGasTx = await getConsumeGasTx( + timestamp, + SEQUENCER_ORIGIN, + sequencerGasToConsume + ) + await consumeSequencerGasTx + + const queuedAfter: number = await getCumulativeQueuedGas() + const sequencedAfter: number = await getCumulativeSequencedGas() + const change = { + sequenced: sequencedAfter - sequencedBefore, + queued: queuedAfter - queuedBefore, + } + + // TODO get the SimpleGas consuming the exact gas amount input so we can check an equality + change.sequenced.should.not.equal(0) + change.queued.should.not.equal(0) + change.queued.should.be.gt(change.sequenced) + }) + describe('Gas rate limiting over multiple transactions', async () => { + // start in a new epoch since the deployment takes some gas + const startTimestamp = 1 + GAS_RATE_LIMIT_EPOCH_LENGTH + const moreThanHalfGas: number = MAX_GAS_PER_EPOCH / 2 + 1000 + for (const [queueToFill, otherQueue] of [ + [QUEUED_ORIGIN, SEQUENCER_ORIGIN], + [SEQUENCER_ORIGIN, QUEUED_ORIGIN], + ]) { + it('Should revert like-kind transactions in a full epoch, still allowing gas through the other queue', async () => { + // Get us close to the limit + const firstTx = await getConsumeGasTx( + startTimestamp, + queueToFill, + moreThanHalfGas + ) + await firstTx + // Now try a tx which goes over the limit + const secondTx = await getConsumeGasTx( + startTimestamp, + queueToFill, + moreThanHalfGas + ) + const failedTx = await secondTx + await assertOvmTxRevertedWithMessage( + failedTx, + 'Transaction gas limit exceeds remaining gas for this epoch and queue origin.', + wallet + ) + const otherQueueTx = await getConsumeGasTx( + startTimestamp, + otherQueue, + moreThanHalfGas + ) + const successTx = await otherQueueTx + await assertOvmTxDidNotRevert(successTx, wallet) + }).timeout(30000) + it('Should allow gas back in at the start of a new epoch', async () => { + // Get us close to the limit + const firstTx = await getConsumeGasTx( + startTimestamp, + queueToFill, + moreThanHalfGas + ) + await firstTx + + // Now consume more than half gas again, but in the next epoch + const nextEpochTimestamp = + startTimestamp + GAS_RATE_LIMIT_EPOCH_LENGTH + 1 + const secondEpochTx = await getConsumeGasTx( + nextEpochTimestamp, + queueToFill, + moreThanHalfGas + ) + const successTx = await secondEpochTx + await assertOvmTxDidNotRevert(successTx, wallet) + }).timeout(30000) + } + }) + }) +}) diff --git a/packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts b/packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts index ddb518186a861..b37fb58e633f3 100644 --- a/packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts @@ -4,6 +4,7 @@ import '../setup' import { Address, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_OPCODE_WHITELIST_MASK, L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS, DEFAULT_ETHNODE_GAS_LIMIT, @@ -104,7 +105,12 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT, } @@ -143,6 +149,7 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { callBytes, ZERO_ADDRESS, ZERO_ADDRESS, + GAS_LIMIT, true, ]) @@ -191,6 +198,7 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { getL1MessageSenderMethodId, ZERO_ADDRESS, testL1MsgSenderAddress, + GAS_LIMIT, true, ], ['address'] @@ -217,6 +225,7 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { getL1MessageSenderMethodId, '0x' + '66'.repeat(20), testL1MsgSenderAddress, + GAS_LIMIT, true, ], ['address'] @@ -244,6 +253,7 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { getL1MessageSenderMethodId, '0x' + '66'.repeat(20), ZERO_ADDRESS, + GAS_LIMIT, true, ], ['address'] diff --git a/packages/ovm/test/contracts/execution-manager.purity-checking.spec.ts b/packages/ovm/test/contracts/execution-manager.purity-checking.spec.ts index f462c71a8b3d1..2a8a828c85fb2 100644 --- a/packages/ovm/test/contracts/execution-manager.purity-checking.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.purity-checking.spec.ts @@ -5,6 +5,7 @@ import { getLogger } from '@eth-optimism/core-utils' import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' @@ -41,7 +42,12 @@ describe('Execution Manager -- Safety Checking', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, false], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + false, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) }) diff --git a/packages/ovm/test/contracts/execution-manager.recover-eoa-address.spec.ts b/packages/ovm/test/contracts/execution-manager.recover-eoa-address.spec.ts index dedce1adfc983..16982a6f1c9d9 100644 --- a/packages/ovm/test/contracts/execution-manager.recover-eoa-address.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.recover-eoa-address.spec.ts @@ -6,6 +6,7 @@ import { CHAIN_ID, DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' import { ExecutionManagerContractDefinition as ExecutionManager } from '@eth-optimism/rollup-contracts' @@ -36,7 +37,12 @@ describe('Execution Manager -- Recover EOA Address', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) }) diff --git a/packages/ovm/test/contracts/execution-manager.storage-opcodes.spec.ts b/packages/ovm/test/contracts/execution-manager.storage-opcodes.spec.ts index 54d3655af7d8d..ee2e385e432c1 100644 --- a/packages/ovm/test/contracts/execution-manager.storage-opcodes.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.storage-opcodes.spec.ts @@ -5,6 +5,7 @@ import { abi, getLogger, add0x } from '@eth-optimism/core-utils' import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' import { ExecutionManagerContractDefinition as ExecutionManager } from '@eth-optimism/rollup-contracts' @@ -42,7 +43,12 @@ describe('ExecutionManager -- Storage opcodes', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) }) diff --git a/packages/ovm/test/contracts/l2-execution-manager.spec.ts b/packages/ovm/test/contracts/l2-execution-manager.spec.ts index 2a386ef0ccbc0..22f533b2f484d 100644 --- a/packages/ovm/test/contracts/l2-execution-manager.spec.ts +++ b/packages/ovm/test/contracts/l2-execution-manager.spec.ts @@ -6,6 +6,7 @@ import { L2ExecutionManagerContractDefinition as L2ExecutionManager } from '@eth import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' @@ -38,7 +39,12 @@ describe('L2 Execution Manager', () => { l2ExecutionManager = await deployContract( wallet, L2ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) }) diff --git a/packages/ovm/test/contracts/simple-storage.spec.ts b/packages/ovm/test/contracts/simple-storage.spec.ts index 0d6adc51b3a3e..32a46ac613a4f 100644 --- a/packages/ovm/test/contracts/simple-storage.spec.ts +++ b/packages/ovm/test/contracts/simple-storage.spec.ts @@ -11,6 +11,7 @@ import { Address, CHAIN_ID, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, getUnsignedTransactionCalldata, @@ -50,7 +51,12 @@ describe('SimpleStorage', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) // Set the state manager as well @@ -133,6 +139,7 @@ describe('SimpleStorage', () => { transaction.nonce, transaction.to, transaction.data, + transaction.gasLimit, v, r, s, diff --git a/packages/ovm/test/contracts/state-manager.spec.ts b/packages/ovm/test/contracts/state-manager.spec.ts index 9eebe448f46df..5ef589b9c6b07 100644 --- a/packages/ovm/test/contracts/state-manager.spec.ts +++ b/packages/ovm/test/contracts/state-manager.spec.ts @@ -5,6 +5,7 @@ import { getLogger } from '@eth-optimism/core-utils' import { ExecutionManagerContractDefinition as ExecutionManager } from '@eth-optimism/rollup-contracts' import { GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, } from '@eth-optimism/rollup-core' @@ -26,7 +27,12 @@ describe('ExecutionManager', () => { executionManager = await deployContract( wallet1, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) }) diff --git a/packages/ovm/test/contracts/tx-origin.spec.ts b/packages/ovm/test/contracts/tx-origin.spec.ts index 747ac75b05ed4..ca5edb0bd08b7 100644 --- a/packages/ovm/test/contracts/tx-origin.spec.ts +++ b/packages/ovm/test/contracts/tx-origin.spec.ts @@ -6,6 +6,7 @@ import { Address, CHAIN_ID, GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, getUnsignedTransactionCalldata, @@ -48,7 +49,12 @@ describe('SimpleTxOrigin', () => { executionManager = await deployContract( wallet, ExecutionManager, - [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], + [ + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) stateManager = new Contract( @@ -99,6 +105,7 @@ describe('SimpleTxOrigin', () => { transaction.nonce, transaction.to, transaction.data, + transaction.gasLimit, v, r, s, diff --git a/packages/ovm/test/govm/simple-storage.spec.ts b/packages/ovm/test/govm/simple-storage.spec.ts index cd9ed3c3eb103..b808b3ec5ebca 100644 --- a/packages/ovm/test/govm/simple-storage.spec.ts +++ b/packages/ovm/test/govm/simple-storage.spec.ts @@ -113,7 +113,17 @@ describe('SimpleStorage', () => { const callData = getUnsignedTransactionCalldata( executionManager, 'executeEOACall', - [0, 0, transaction.nonce, transaction.to, transaction.data, v, r, s] + [ + 0, + 0, + transaction.nonce, + transaction.to, + transaction.data, + transaction.gasLimit, + v, + r, + s, + ] ) const result = await executionManager.provider.call({ diff --git a/packages/ovm/test/helpers.ts b/packages/ovm/test/helpers.ts index 0fd9241a0a2b1..298db00ce7137 100644 --- a/packages/ovm/test/helpers.ts +++ b/packages/ovm/test/helpers.ts @@ -13,7 +13,7 @@ import { bufToHexString, bufferUtils, } from '@eth-optimism/core-utils' -import { Contract, ContractFactory, Wallet, ethers } from 'ethers' +import { Contract, ContractFactory, Wallet, ethers, providers } from 'ethers' import { Provider, TransactionReceipt, @@ -27,6 +27,7 @@ import * as ethereumjsAbi from 'ethereumjs-abi' import { internalTxReceiptToOvmTxReceipt } from '../src/app' import { OvmTransactionReceipt } from '../src/types' +import { timeStamp } from 'console' type Signature = [string, string, string] @@ -60,7 +61,8 @@ export const manuallyDeployOvmContractReturnReceipt = async ( provider: Provider, executionManager: Contract, contractDefinition, - constructorArguments: any[] + constructorArguments: any[], + timestamp: number = getCurrentTime() ): Promise => { const initCode = new ContractFactory( contractDefinition.abi, @@ -72,7 +74,8 @@ export const manuallyDeployOvmContractReturnReceipt = async ( wallet, undefined, initCode, - false + false, + timestamp ) return internalTxReceiptToOvmTxReceipt(receipt, executionManager.address) @@ -86,14 +89,16 @@ export const manuallyDeployOvmContract = async ( provider: Provider, executionManager: Contract, contractDefinition, - constructorArguments: any[] + constructorArguments: any[], + timestamp: number = getCurrentTime() ): Promise
=> { const receipt = await manuallyDeployOvmContractReturnReceipt( wallet, provider, executionManager, contractDefinition, - constructorArguments + constructorArguments, + timestamp ) return receipt.contractAddress } @@ -103,7 +108,8 @@ export const executeTransaction = async ( wallet: Wallet, to: Address, data: string, - allowRevert: boolean + allowRevert: boolean, + timestamp: number = getCurrentTime() ): Promise => { // Verify that the transaction is not accidentally sending to the ZERO_ADDRESS if (to === ZERO_ADDRESS) { @@ -113,14 +119,24 @@ export const executeTransaction = async ( // Get the `to` field -- NOTE: We have to set `to` to equal ZERO_ADDRESS if this is a contract create const ovmTo = to === null || to === undefined ? ZERO_ADDRESS : to + // get the max gas limit allowed by the EM + const getMaxGasLimitCalldata = + executionManager.interface.functions['ovmBlockGasLimit'].sighash + const maxTxGasLimit = await wallet.provider.call({ + to: executionManager.address, + data: getMaxGasLimitCalldata, + gasLimit: GAS_LIMIT, + }) + // Actually make the call const tx = await executionManager.executeTransaction( - getCurrentTime(), + timestamp, 0, ovmTo, data, wallet.address, ZERO_ADDRESS, + maxTxGasLimit, allowRevert ) // Return the parsed transaction values @@ -165,6 +181,7 @@ export const executeEOACall = async ( ovmTx.nonce, ovmTo, ovmTx.data, + ovmTx.gasLimit, ovmTx.v, ovmTx.r, ovmTx.s diff --git a/packages/rollup-contracts/contracts/DataTypes.sol b/packages/rollup-contracts/contracts/DataTypes.sol index 353e91858d19c..2b5f436bac8ff 100644 --- a/packages/rollup-contracts/contracts/DataTypes.sol +++ b/packages/rollup-contracts/contracts/DataTypes.sol @@ -26,14 +26,22 @@ contract DataTypes { bool inStaticContext; uint chainId; uint timestamp; + uint txGasLimit; uint queueOrigin; - uint gasLimit; address ovmActiveContract; address ovmMsgSender; address ovmTxOrigin; address l1MessageSender; } + struct ChainParams { + uint OvmTxFlatGasFee; // The flat gas fee imposed on all transactions + uint OvmTxMaxGas; // Max gas a single transaction is allowed + uint GasRateLimitEpochLength; // The frequency with which we reset the gas rate limit, expressed in same units as ETH timestamp + uint MaxSequencedGasPerEpoch; // The max gas which sequenced tansactions consume per rate limit epoch + uint MaxQueuedGasPerEpoch; // The max gas which queued tansactions consume per rate limit epoch + } + struct TxElementInclusionProof { uint batchIndex; TxChainBatchHeader batchHeader; diff --git a/packages/rollup-contracts/contracts/ExecutionManager.sol b/packages/rollup-contracts/contracts/ExecutionManager.sol index f2037296eb2ca..e92686c113225 100644 --- a/packages/rollup-contracts/contracts/ExecutionManager.sol +++ b/packages/rollup-contracts/contracts/ExecutionManager.sol @@ -28,6 +28,21 @@ contract ExecutionManager { address constant l2ToL1MessagePasserOvmAddress = 0x4200000000000000000000000000000000000000; address constant l1MsgSenderAddress = 0x4200000000000000000000000000000000000001; + // Gas rate limiting parameters: + // OVM address where we handle some persistent state that is used directly by the EM. There will never be code deployed here, we just use it to persist this chain-related metadata. + address constant METADATA_STORAGE_ADDRESS = ZERO_ADDRESS; + // Storage keys which the EM will directly use to persist the different pieces of metadata: + // Storage slot where we will store the cumulative sequencer tx gas spent + bytes32 constant CUMULATIVE_SEQUENCED_GAS_STORAGE_KEY = 0x0000000000000000000000000000000000000000000000000000000000000001; + // Storage slot where we will store the cumulative queued tx gas spent + bytes32 constant CUMULATIVE_QUEUED_GAS_STORAGE_KEY = 0x0000000000000000000000000000000000000000000000000000000000000002; + // Storage slot where we will store the start of the current gas rate limit epoch + bytes32 constant GAS_RATE_LMIT_EPOCH_START_STORAGE_KEY = 0x0000000000000000000000000000000000000000000000000000000000000003; + // Storage slot where we will store what the cumulative sequencer gas was at the start of the last epoch + bytes32 constant CUMULATIVE_SEQUENCED_GAS_AT_EPOCH_START_STORAGE_KEY = 0x0000000000000000000000000000000000000000000000000000000000000004; + // Storage slot where we will store what the cumulative queued gas was at the start of the last epoch + bytes32 constant CUMULATIVE_QUEUED_GAS_AT_EPOCH_START_STORAGE_KEY = 0x0000000000000000000000000000000000000000000000000000000000000005; + /************************ * Contract Dependencies * ************************/ @@ -38,6 +53,7 @@ contract ExecutionManager { /*************** * Other Fields * ***************/ + dt.ChainParams chainParams; dt.ExecutionContext executionContext; /********* @@ -69,9 +85,15 @@ contract ExecutionManager { * @notice Construct a new ExecutionManager with a specified safety checker & owner. * @param _opcodeWhitelistMask A bit mask representing which opcodes are whitelisted or not for our safety checker * @param _owner The owner of this contract. - * @param _blockGasLimit The block gas limit for OVM blocks + * @param _chainParams The various gas-related parameters of the instantiated chain. + * @param _overrideSafetyChecker Whether to safety check. */ - constructor(uint256 _opcodeWhitelistMask, address _owner, uint _blockGasLimit, bool _overrideSafetyChecker) public { + constructor( + uint256 _opcodeWhitelistMask, + address _owner, + dt.ChainParams memory _chainParams, + bool _overrideSafetyChecker + ) public { rlp = new RLPEncode(); // Initialize new contract address generator contractAddressGenerator = new ContractAddressGenerator(); @@ -83,28 +105,23 @@ contract ExecutionManager { stateManager.associateCodeContract(address(i), address(i)); } + // Store Gas Metering Params + chainParams = _chainParams; + // Deploy custom precompiles L2ToL1MessagePasser l1ToL2MessagePasser = new L2ToL1MessagePasser(address(this)); stateManager.associateCodeContract(l2ToL1MessagePasserOvmAddress, address(l1ToL2MessagePasser)); L1MessageSender l1MessageSender = new L1MessageSender(address(this)); stateManager.associateCodeContract(l1MsgSenderAddress, address(l1MessageSender)); - executionContext.gasLimit = _blockGasLimit; executionContext.chainId = 108; + // TODO start off the initial gas rate limit epoch once we configure a start time + // Set our owner // TODO } - /** - * @notice Increments the provided address's nonce. - * This is only used by the sequencer to correct nonces when transactions fail. - * @param addr The address of the nonce to increment. - */ - function incrementNonce(address addr) external { - stateManager.incrementOvmContractNonce(addr); - } - /******************** * Execute EOA Calls * ********************/ @@ -118,6 +135,7 @@ contract ExecutionManager { * @param _nonce The current nonce of the EOA. * @param _ovmEntrypoint The contract which this transaction should be executed against. * @param _callBytes The calldata for this ovm transaction. + * @param _ovmTxGasLimit The max gas this OVM transaction has been allotted. * @param _v The v value of the ECDSA signature + CHAIN_ID. * @param _r The r value of the ECDSA signature. * @param _s The s value of the ECDSA signature. @@ -128,6 +146,7 @@ contract ExecutionManager { uint _nonce, address _ovmEntrypoint, bytes memory _callBytes, + uint _ovmTxGasLimit, uint8 _v, bytes32 _r, bytes32 _s @@ -143,7 +162,7 @@ contract ExecutionManager { _ovmEntrypoint ); // Make the EOA call for the account - executeTransaction(_timestamp, _queueOrigin, _ovmEntrypoint, _callBytes, eoaAddress, ZERO_ADDRESS, false); + executeTransaction(_timestamp, _queueOrigin, _ovmEntrypoint, _callBytes, eoaAddress, ZERO_ADDRESS, _ovmTxGasLimit, false); } /** @@ -154,6 +173,7 @@ contract ExecutionManager { * @param _ovmEntrypoint The contract which this transaction should be executed against. * @param _callBytes The calldata for this ovm transaction. * @param _fromAddress The address which this call should originate from--the msg.sender. + * @param _ovmTxGasLimit The max gas this OVM transaction has been allotted. * @param _allowRevert Flag which controls whether or not to revert in the case of failure. */ function executeTransaction( @@ -163,16 +183,68 @@ contract ExecutionManager { bytes memory _callBytes, address _fromAddress, address _l1MsgSenderAddress, + uint _ovmTxGasLimit, bool _allowRevert ) public { require(_timestamp > 0, "Timestamp must be greater than 0"); - uint _nonce = stateManager.getOvmContractNonce(_fromAddress); // Initialize our context - initializeContext(_timestamp, _queueOrigin, _fromAddress, _l1MsgSenderAddress); + initializeContext(_timestamp, _queueOrigin, _fromAddress, _l1MsgSenderAddress, _ovmTxGasLimit); // Set the active contract to be our EOA address switchActiveContract(_fromAddress); + // Check for individual tx gas limit violation + if (_ovmTxGasLimit > chainParams.OvmTxMaxGas) { + // todo handle _allowRevert=true or ideally remove it altogether as it should probably always be false for Fraud Verification purposes. + emit EOACallRevert("Transaction gas limit exceeds max OVM tx gas limit"); + assembly { + return(0,0) + } + } + // emit EOACallRevert(abi.encodePacked(stateManager.getStorage(METADATA_STORAGE_ADDRESS, GAS_RATE_LMIT_EPOCH_START_STORAGE_KEY)));//,chainParams.GasRateLimitEpochLength, getGasRateLimitEpochStart())); + + // If we are at the start of a new epoch, the current tiime is the new start and curent cumulative gas is the new cumulative gas at stat! + if (_timestamp >= chainParams.GasRateLimitEpochLength + getGasRateLimitEpochStart()) { + setGasRateLimitEpochStart(_timestamp); + setCumulativeSequencedGasAtEpochStart( + getCumulativeSequencedGas() + ); + setCumulativeQueuedGasAtEpochStart( + getCumulativeQueuedGas() + ); + } + + // check for gas rate limit + // TODO: split up EM somehow? We are at max stack depth so can't use vars here yet + // TODO: make queue origin an enum? or just configure better? + if (_queueOrigin == 0) { + if ( + getCumulativeSequencedGas() + - getCumulativeSequencedGasAtEpochStart() + + _ovmTxGasLimit + > + chainParams.MaxSequencedGasPerEpoch + ) { + emit EOACallRevert("Transaction gas limit exceeds remaining gas for this epoch and queue origin."); + assembly { + return(0,0) + } + } + } else { + if ( + getCumulativeQueuedGas() + - getCumulativeQueuedGasAtEpochStart() + + _ovmTxGasLimit + > + chainParams.MaxQueuedGasPerEpoch + ) { + emit EOACallRevert("Transaction gas limit exceeds remaining gas for this epoch and queue origin."); + assembly { + return(0,0) + } + } + } + // Set methodId based on whether we're creating a contract bytes32 methodId; uint256 callSize; @@ -182,7 +254,10 @@ contract ExecutionManager { methodId = ovmCreateMethodId; callSize = _callBytes.length + 4; // Emit event that we are creating a contract with an EOA - address _newOvmContractAddress = contractAddressGenerator.getAddressFromCREATE(_fromAddress, _nonce); + address _newOvmContractAddress = contractAddressGenerator.getAddressFromCREATE( + _fromAddress, + stateManager.getOvmContractNonce(_fromAddress) + ); emit EOACreatedContract(_newOvmContractAddress); } else { methodId = ovmCallMethodId; @@ -206,23 +281,60 @@ contract ExecutionManager { mstore8(add(_callBytes, 3), methodId) } + // record the current gas so we can measure how much execution took + // note that later we subtract the post-call gas(), would call this a different var but we're out of stack! + uint gasConsumedByExecution; + assembly { + gasConsumedByExecution := gas() + } + // subtract the flat gas fee off the tx gas limit which we will pass as gas + _ovmTxGasLimit -= chainParams.OvmTxFlatGasFee; + bool success = false; address addr = address(this); bytes memory result; + uint ovmCallReturnDataSize; assembly { - success := call(gas, addr, 0, _callBytes, callSize, 0, 0) + success := call( + _ovmTxGasLimit, + addr, 0, _callBytes, callSize, 0, 0 + ) + ovmCallReturnDataSize := returndatasize result := mload(0x40) let resultData := add(result, 0x20) - returndatacopy(resultData, 0, returndatasize) + returndatacopy(resultData, 0, ovmCallReturnDataSize) + mstore(result, ovmCallReturnDataSize) + mstore(0x40, add(resultData, ovmCallReturnDataSize)) + } + + // get the consumed by execution itself + assembly { + gasConsumedByExecution := sub(gasConsumedByExecution, gas()) + } + + // set the new cumulative gas + if (_queueOrigin == 0) { + setCumulativeSequencedGas( + getCumulativeSequencedGas() + + chainParams.OvmTxFlatGasFee + + gasConsumedByExecution + ); + } else { + setCumulativeQueuedGas( + getCumulativeQueuedGas() + + chainParams.OvmTxFlatGasFee + + gasConsumedByExecution + ); + } + assembly { + let resultData := add(result, 0x20) if eq(success, 1) { - return(resultData, returndatasize) + return(resultData, ovmCallReturnDataSize) } if eq(_allowRevert, 1) { - revert(resultData, returndatasize) + revert(resultData, ovmCallReturnDataSize) } - mstore(result, returndatasize) - mstore(0x40, add(resultData, returndatasize)) } if (!success) { @@ -256,7 +368,7 @@ contract ExecutionManager { bytes[] memory message = new bytes[](9); message[0] = rlp.encodeUint(_nonce); // Nonce message[1] = rlp.encodeUint(0); // Gas price - message[2] = rlp.encodeUint(executionContext.gasLimit); // Gas limit + message[2] = rlp.encodeUint(chainParams.OvmTxMaxGas); // Gas limit // To -- Special rlp encoding handling if _to is the ZERO_ADDRESS if (_to == ZERO_ADDRESS) { message[3] = rlp.encodeUint(0); @@ -358,7 +470,7 @@ contract ExecutionManager { * returndata: uint256 representing the current gas limit. */ function ovmGASLIMIT() public view { - uint g = executionContext.gasLimit; + uint g = executionContext.txGasLimit; assembly { let gasLimitMemory := mload(0x40) @@ -376,7 +488,7 @@ contract ExecutionManager { * returndata: uint256 representing the fraud proof gas limit. */ function ovmBlockGasLimit() public view { - uint g = executionContext.gasLimit; + uint g = chainParams.OvmTxMaxGas; assembly { let gasLimitMemory := mload(0x40) @@ -939,6 +1051,29 @@ contract ExecutionManager { } } + /***************************** + * OVM (non-EVM-equivalent) State Access * + *****************************/ + + /** + * @notice Getter for the execution context's L1MessageSender. Used by the L1MessageSender precompile. + * @return The L1MessageSender in our current execution context. + */ + function getL1MessageSender() public returns(address) { + require(executionContext.ovmActiveContract == l1MsgSenderAddress, "Only the L1MessageSender precompile is allowed to call getL1MessageSender(...)!"); + require(executionContext.l1MessageSender != ZERO_ADDRESS, "L1MessageSender not set!"); + require(executionContext.ovmMsgSender == ZERO_ADDRESS, "L1MessageSender only accessible in entrypoint contract!"); + return executionContext.l1MessageSender; + } + + function getCumulativeSequencedGas() public view returns(uint) { + return uint(stateManager.getStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_SEQUENCED_GAS_STORAGE_KEY)); + } + + function getCumulativeQueuedGas() public view returns(uint) { + return uint(stateManager.getStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_QUEUED_GAS_STORAGE_KEY)); + } + /******** * Utils * ********/ @@ -951,7 +1086,7 @@ contract ExecutionManager { * @param _queueOrigin The queue which this context's transaction was sent from. * @param _ovmTxOrigin The tx.origin for the currently executing transaction. It will be ZERO_ADDRESS if it's not an EOA call. */ - function initializeContext(uint _timestamp, uint _queueOrigin, address _ovmTxOrigin, address _l1MsgSender) internal { + function initializeContext(uint _timestamp, uint _queueOrigin, address _ovmTxOrigin, address _l1MsgSender, uint _gasLimit) internal { // First zero out the context for good measure (Note ZERO_ADDRESS is reserved for the genesis contract & initial msgSender) restoreContractContext(ZERO_ADDRESS, ZERO_ADDRESS); // And finally set the timestamp, queue origin, tx origin, and l1MessageSender @@ -959,6 +1094,7 @@ contract ExecutionManager { executionContext.queueOrigin = _queueOrigin; executionContext.ovmTxOrigin = _ovmTxOrigin; executionContext.l1MessageSender = _l1MsgSender; + executionContext.txGasLimit = _gasLimit; } /** @@ -991,17 +1127,74 @@ contract ExecutionManager { } /** - * @notice Getter for the execution context's L1MessageSender. Used by the L1MessageSender precompile. - * @return The L1MessageSender in our current execution context. + * @notice Increments the provided address's nonce. + * This is only used by the sequencer to correct nonces when transactions fail. + * @param addr The address of the nonce to increment. */ - function getL1MessageSender() public returns(address) { - require(executionContext.ovmActiveContract == l1MsgSenderAddress, "Only the L1MessageSender precompile is allowed to call getL1MessageSender(...)!"); - require(executionContext.l1MessageSender != ZERO_ADDRESS, "L1MessageSender not set!"); - require(executionContext.ovmMsgSender == ZERO_ADDRESS, "L1MessageSender only accessible in entrypoint contract!"); - return executionContext.l1MessageSender; + function incrementNonce(address addr) external { + stateManager.incrementOvmContractNonce(addr); } + /** + * @notice Gets the state manager address for this EM. + */ function getStateManagerAddress() public view returns (address){ return address(stateManager); } + + /** + * @notice Sets the new cumulative sequenced gas as a result of tx execution. + */ + function setCumulativeSequencedGas(uint _value) internal { + stateManager.setStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_SEQUENCED_GAS_STORAGE_KEY, bytes32(_value)); + } + + /** + * @notice Sets the new cumulative queued gas as a result of this new tx. + */ + function setCumulativeQueuedGas(uint _value) internal { + stateManager.setStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_QUEUED_GAS_STORAGE_KEY, bytes32(_value)); + } + + /** + * @notice Gets what the cumulative sequenced gas was at the start of this gas rate limit epoch. + */ + function getGasRateLimitEpochStart() internal view returns (uint) { + return uint(stateManager.getStorage(METADATA_STORAGE_ADDRESS, GAS_RATE_LMIT_EPOCH_START_STORAGE_KEY)); + } + + /** + * @notice Used to store the current time at the start of a new gas rate limit epoch. + */ + function setGasRateLimitEpochStart(uint _value) internal { + stateManager.setStorage(METADATA_STORAGE_ADDRESS, GAS_RATE_LMIT_EPOCH_START_STORAGE_KEY, bytes32(_value)); + } + + /** + * @notice Sets the cumulative sequenced gas at the start of a new gas rate limit epoch. + */ + function setCumulativeSequencedGasAtEpochStart(uint _value) internal { + stateManager.setStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_SEQUENCED_GAS_AT_EPOCH_START_STORAGE_KEY, bytes32(_value)); + } + + /** + * @notice Gets what the cumulative sequenced gas was at the start of this gas rate limit epoch. + */ + function getCumulativeSequencedGasAtEpochStart() internal view returns (uint) { + return uint(stateManager.getStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_SEQUENCED_GAS_AT_EPOCH_START_STORAGE_KEY)); + } + + /** + * @notice Sets what the cumulative queued gas is at the start of a new gas rate limit epoch. + */ + function setCumulativeQueuedGasAtEpochStart(uint _value) internal { + stateManager.setStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_QUEUED_GAS_AT_EPOCH_START_STORAGE_KEY, bytes32(_value)); + } + + /** + * @notice Gets the cumulative queued gas was at the start of this gas rate limit epoch. + */ + function getCumulativeQueuedGasAtEpochStart() internal view returns (uint) { + return uint(stateManager.getStorage(METADATA_STORAGE_ADDRESS, CUMULATIVE_QUEUED_GAS_AT_EPOCH_START_STORAGE_KEY)); + } } diff --git a/packages/rollup-contracts/contracts/L2ExecutionManager.sol b/packages/rollup-contracts/contracts/L2ExecutionManager.sol index 4c6ebf5cc0adf..8d9b206249ff9 100644 --- a/packages/rollup-contracts/contracts/L2ExecutionManager.sol +++ b/packages/rollup-contracts/contracts/L2ExecutionManager.sol @@ -3,6 +3,7 @@ pragma experimental ABIEncoderV2; /* Internal Imports */ import {ExecutionManager} from "./ExecutionManager.sol"; +import {DataTypes as dt} from "./DataTypes.sol"; /** * @title L2ExecutionManager @@ -17,9 +18,9 @@ contract L2ExecutionManager is ExecutionManager { constructor( uint256 _opcodeWhitelistMask, address _owner, - uint _gasLimit, + dt.ChainParams memory _chainParams, bool _overrideSafetyChecker - ) ExecutionManager(_opcodeWhitelistMask, _owner, _gasLimit, _overrideSafetyChecker) public {} + ) ExecutionManager(_opcodeWhitelistMask, _owner, _chainParams, _overrideSafetyChecker) public {} /** @notice Stores the provided OVM transaction, mapping its hash to its value and its hash to the EVM tx hash diff --git a/packages/rollup-contracts/contracts/testing-contracts/SimpleGas.sol b/packages/rollup-contracts/contracts/testing-contracts/SimpleGas.sol new file mode 100644 index 0000000000000..3a23e2b860bff --- /dev/null +++ b/packages/rollup-contracts/contracts/testing-contracts/SimpleGas.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.5.0; + +contract SimpleGas { + function consumeGasExceeding(uint _amount) public view { + uint startGas = gasleft(); + while (true) { + if (startGas - gasleft() > _amount) { + return; + } + } + } +} diff --git a/packages/rollup-contracts/src/test-contracts.ts b/packages/rollup-contracts/src/test-contracts.ts index 2250904fe8a7c..a9a7237f9def1 100644 --- a/packages/rollup-contracts/src/test-contracts.ts +++ b/packages/rollup-contracts/src/test-contracts.ts @@ -7,6 +7,7 @@ import * as SimpleCall from '../build/SimpleCall.json' import * as SimpleStorage from '../build/SimpleStorage.json' import * as SimpleStorageArgsFromCalldata from '../build/SimpleStorageArgsFromCalldata.json' import * as SimpleTxOrigin from '../build/SimpleTxOrigin.json' +import * as SimpleGas from '../build/SimpleGas.json' export const TestAddThreeContractDefinition = AddThree export const TestContextContractDefinition = ContextContract @@ -17,3 +18,4 @@ export const TestSimpleCallContractDefinition = SimpleCall export const TestSimpleStorageContractDefinition = SimpleStorage export const TestSimpleStorageArgsFromCalldataDefinition = SimpleStorageArgsFromCalldata export const TestSimpleTxOriginContractDefinition = SimpleTxOrigin +export const TestSimpleConsumeGasConractDefinition = SimpleGas diff --git a/packages/rollup-core/src/app/constants.ts b/packages/rollup-core/src/app/constants.ts index 9a27376866f93..120245a977a15 100644 --- a/packages/rollup-core/src/app/constants.ts +++ b/packages/rollup-core/src/app/constants.ts @@ -5,8 +5,20 @@ export const L1ToL2TransactionEventName = 'L1ToL2Transaction' export const L1ToL2TransactionBatchEventName = 'NewTransactionBatchAdded' export const CREATOR_CONTRACT_ADDRESS = ZERO_ADDRESS + +export const TX_FLAT_GAS_FEE = 30_000 export const GAS_LIMIT = 1_000_000_000 -export const DEFAULT_ETHNODE_GAS_LIMIT = 10_000_000 +export const GAS_RATE_LIMIT_EPOCH_LENGTH = 0 +export const MAX_SEQUENCED_GAS_PER_EPOCH = 2_000_000_000 +export const MAX_QUEUED_GAS_PER_EPOCH = 2_000_000_000 +export const DEFAULT_CHAIN_PARAMS = [ + TX_FLAT_GAS_FEE, + GAS_LIMIT, + GAS_RATE_LIMIT_EPOCH_LENGTH, + MAX_SEQUENCED_GAS_PER_EPOCH, + MAX_QUEUED_GAS_PER_EPOCH, +] +export const DEFAULT_ETHNODE_GAS_LIMIT = 12_000_000 export const CHAIN_ID = 108 diff --git a/packages/rollup-core/src/app/util/l2-node.ts b/packages/rollup-core/src/app/util/l2-node.ts index 7efff4ac303a9..1b32b95b467b7 100644 --- a/packages/rollup-core/src/app/util/l2-node.ts +++ b/packages/rollup-core/src/app/util/l2-node.ts @@ -18,7 +18,12 @@ import { createMockProvider, getWallets } from 'ethereum-waffle' /* Internal Imports */ import { Address, L2NodeContext } from '../../types' import { Environment } from './environment' -import { CHAIN_ID, deployContract, GAS_LIMIT } from '../index' +import { + CHAIN_ID, + deployContract, + GAS_LIMIT, + DEFAULT_CHAIN_PARAMS, +} from '../index' import * as fs from 'fs' const log = getLogger('l2-node') @@ -205,7 +210,12 @@ async function deployExecutionManager(wallet: Wallet): Promise { const executionManager: Contract = await deployContract( wallet, L2ExecutionManagerContractDefinition, - [Environment.opcodeWhitelistMask(), wallet.address, GAS_LIMIT, true], + [ + Environment.opcodeWhitelistMask(), + wallet.address, + DEFAULT_CHAIN_PARAMS, + true, + ], { gasLimit: GAS_LIMIT } ) diff --git a/packages/rollup-core/test/app/block-batch-processor.spec.ts b/packages/rollup-core/test/app/block-batch-processor.spec.ts index 6262a0808d6c8..d4543d5641e02 100644 --- a/packages/rollup-core/test/app/block-batch-processor.spec.ts +++ b/packages/rollup-core/test/app/block-batch-processor.spec.ts @@ -9,6 +9,7 @@ import { sleep, TestUtils, ZERO_ADDRESS, + numberToHexString, } from '@eth-optimism/core-utils' import { Wallet } from 'ethers' @@ -28,7 +29,7 @@ import { BatchLogParserContext, L1Batch, } from '../../src/types' -import { CHAIN_ID, BlockBatchProcessor } from '../../src/app' +import { CHAIN_ID, BlockBatchProcessor, GAS_LIMIT } from '../../src/app' class DummyListener implements BlockBatchListener { public readonly receivedBlockBatches: BlockBatches[] = [] diff --git a/packages/rollup-core/test/app/block-batch-submitter.spec.ts b/packages/rollup-core/test/app/block-batch-submitter.spec.ts index 482a834084202..d12501c076bfb 100644 --- a/packages/rollup-core/test/app/block-batch-submitter.spec.ts +++ b/packages/rollup-core/test/app/block-batch-submitter.spec.ts @@ -1,14 +1,19 @@ import '../setup' /* External Imports */ -import { hexStrToNumber, keccak256, TestUtils } from '@eth-optimism/core-utils' +import { + hexStrToNumber, + keccak256, + TestUtils, + numberToHexString, +} from '@eth-optimism/core-utils' import { Wallet } from 'ethers' import { JsonRpcProvider } from 'ethers/providers' /* Internal Imports */ import { BlockBatches, RollupTransaction } from '../../src/types' -import { BlockBatchSubmitter } from '../../src/app' +import { BlockBatchSubmitter, GAS_LIMIT } from '../../src/app' import { verifyMessage } from 'ethers/utils' interface Payload { diff --git a/packages/rollup-full-node/src/app/web3-rpc-handler.ts b/packages/rollup-full-node/src/app/web3-rpc-handler.ts index 5bbf00ce822f4..f17515de062a8 100644 --- a/packages/rollup-full-node/src/app/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/web3-rpc-handler.ts @@ -262,6 +262,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { txObject['data'], txObject['from'], ZERO_ADDRESS, + numberToHexString(GAS_LIMIT), true ) @@ -278,6 +279,8 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { }, defaultBlock, ]) + // todo delete this log + log.debug(`internal eth_call response is: ${JSON.stringify(response)}`) } catch (e) { log.debug( `Internal error executing call: ${JSON.stringify( @@ -319,6 +322,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { txObject['data'], txObject['from'], ZERO_ADDRESS, + numberToHexString(GAS_LIMIT), true ) @@ -827,6 +831,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { transaction.calldata, ZERO_ADDRESS, transaction.sender, + transaction.gasLimit, false, ]) @@ -1049,6 +1054,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { ovmTx.data, ovmFrom, ZERO_ADDRESS, + ovmTx.gasLimit, true ) @@ -1092,6 +1098,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { callBytes: string, fromAddress: string, l1TxSenderAddress: string, + gasLimit: string, allowRevert: boolean ): string { // Update the ovmEntrypoint to be the ZERO_ADDRESS if this is a contract creation @@ -1107,6 +1114,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { callBytes, fromAddress, l1TxSenderAddress, + gasLimit, allowRevert, ]) } diff --git a/packages/rollup-full-node/test/app/test-web-rpc-handler.spec.ts b/packages/rollup-full-node/test/app/test-web-rpc-handler.spec.ts index ecb3857a98d22..d380a754167b9 100644 --- a/packages/rollup-full-node/test/app/test-web-rpc-handler.spec.ts +++ b/packages/rollup-full-node/test/app/test-web-rpc-handler.spec.ts @@ -98,6 +98,8 @@ describe('TestHandler', () => { const simpleStorage = await factory.deploy() + log.debug(`deployed`) + const deploymentTxReceipt = await wallet.provider.getTransactionReceipt( simpleStorage.deployTransaction.hash ) @@ -121,6 +123,7 @@ describe('TestHandler', () => { const receipt = await httpProvider.getTransactionReceipt(tx.hash) const receipt2 = await httpProvider.getTransactionReceipt(tx2.hash) const response2 = await httpProvider.send('evm_revert', [snapShotId]) + const res = await simpleStorage.getStorage( executionManagerAddress, storageKey diff --git a/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts b/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts index cf4f31557ee3b..4d671ca930fbd 100644 --- a/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts +++ b/packages/rollup-full-node/test/app/web-rpc-handler.spec.ts @@ -7,8 +7,9 @@ import { keccak256, JSONRPC_ERRORS, hexStrToBuf, + numberToHexString, } from '@eth-optimism/core-utils' -import { CHAIN_ID } from '@eth-optimism/rollup-core' +import { CHAIN_ID, GAS_LIMIT } from '@eth-optimism/rollup-core' import { ethers, ContractFactory, Wallet, Contract, utils } from 'ethers' import { resolve } from 'path' @@ -232,7 +233,7 @@ describe('Web3Handler', () => { const revertingTx = { nonce: await wallet.getTransactionCount(), gasPrice: 0, - gasLimit: 9999999999, + gasLimit: 9999999, to: simpleReversion.address, chainId: CHAIN_ID, data: simpleReversion.interface.functions['doRevert'].encode([]), diff --git a/packages/rollup-full-node/test/integration/message-passing-integration.spec.ts b/packages/rollup-full-node/test/integration/message-passing-integration.spec.ts index 33703837e95d4..92fb8345a84a4 100644 --- a/packages/rollup-full-node/test/integration/message-passing-integration.spec.ts +++ b/packages/rollup-full-node/test/integration/message-passing-integration.spec.ts @@ -42,7 +42,7 @@ describe('Message Passing Integration Tests', () => { }) describe('L1 Message Passing Tests', () => { - it('Should not queue any messages if ', async () => { + it('Should not queue any messages if none are sent', async () => { const simpleStorage = await deployContract(wallet, SimpleStorage, [], []) const simpleCaller = await deployContract(wallet, SimpleCaller, [], [])