diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol index 46bbba956fb47..175b6039f5888 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /* Contract Imports */ @@ -31,20 +32,20 @@ contract PartialStateManager is ContractResolver { StateTransitioner private stateTransitioner; - mapping(address => mapping(bytes32 => bytes32)) private ovmContractStorage; - mapping(address => uint) private ovmContractNonces; - mapping(address => address) private ovmAddressToCodeContractAddress; + mapping(address => mapping(bytes32 => bytes32)) public ovmContractStorage; + mapping(address => uint) public ovmContractNonces; + mapping(address => address) public ovmAddressToCodeContractAddress; bool public existsInvalidStateAccessFlag; mapping(address => mapping(bytes32 => bool)) public isVerifiedStorage; mapping(address => bool) public isVerifiedContract; - mapping(uint => bytes32) private updatedStorageSlotContract; - mapping(uint => bytes32) private updatedStorageSlotKey; - mapping(address => mapping(bytes32 => bool)) private storageSlotTouched; + mapping(uint => bytes32) public updatedStorageSlotContract; + mapping(uint => bytes32) public updatedStorageSlotKey; + mapping(address => mapping(bytes32 => bool)) public storageSlotTouched; uint public updatedStorageSlotCounter; - mapping(uint => address) private updatedContracts; - mapping(address => bool) private contractTouched; + mapping(uint => address) public updatedContracts; + mapping(address => bool) public contractTouched; uint public updatedContractsCounter; diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol index 8ed1e85ccdd12..0c5d34e0283d1 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol @@ -332,11 +332,11 @@ contract StateTransitioner is IStateTransitioner, ContractResolver { { require( stateManager.updatedStorageSlotCounter() == 0, - "There's still updated storage to account for!" + "All storage updates must be proven to complete the transition." ); require( - stateManager.updatedStorageSlotCounter() == 0, - "There's still updated contracts to account for!" + stateManager.updatedContractsCounter() == 0, + "All contract updates must be proven to complete the transition." ); currentTransitionPhase = TransitionPhases.Complete; diff --git a/packages/contracts/contracts/test-helpers/ContextContract.sol b/packages/contracts/contracts/test-helpers/ContextContract.sol index 53eeef34e9673..6e515a20915b1 100644 --- a/packages/contracts/contracts/test-helpers/ContextContract.sol +++ b/packages/contracts/contracts/test-helpers/ContextContract.sol @@ -40,6 +40,33 @@ contract ContextContract { } } + function staticCallThroughExecutionManager() public { + // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes + bytes32 methodId = keccak256("ovmSTATICCALL()") >> 224; + address addr = executionManagerAddress; + + assembly { + let callBytes := mload(0x40) + calldatacopy(callBytes, 0, calldatasize) + + // replace the first 4 bytes with the right methodID + mstore8(callBytes, shr(24, methodId)) + mstore8(add(callBytes, 1), shr(16, methodId)) + mstore8(add(callBytes, 2), shr(8, methodId)) + mstore8(add(callBytes, 3), methodId) + + // overwrite call params + let result := mload(0x40) + let success := call(gas, addr, 0, callBytes, calldatasize, result, 32) + + if eq(success, 0) { + revert(0, 0) + } + + return(result, returndatasize) + } + } + function getCALLER() public { // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes bytes32 methodId = keccak256("ovmCALLER()") >> 224; @@ -228,4 +255,85 @@ contract ContextContract { return(result, returndatasize) } } + + function ovmBlockGasLimit() public { + // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes + bytes32 methodId = keccak256("ovmBlockGasLimit()") >> 224; + address addr = executionManagerAddress; + + assembly { + let callBytes := mload(0x40) + calldatacopy(callBytes, 0, calldatasize) + + // replace the first 4 bytes with the right methodID + mstore8(callBytes, shr(24, methodId)) + mstore8(add(callBytes, 1), shr(16, methodId)) + mstore8(add(callBytes, 2), shr(8, methodId)) + mstore8(add(callBytes, 3), methodId) + + // overwrite call params + let result := mload(0x40) + let success := call(gas, addr, 0, callBytes, calldatasize, result, 32) + + if eq(success, 0) { + revert(0, 0) + } + + return(result, returndatasize) + } + } + + function isStaticContext() public { + // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes + bytes32 methodId = keccak256("isStaticContext()") >> 224; + address addr = executionManagerAddress; + + assembly { + let callBytes := mload(0x40) + calldatacopy(callBytes, 0, calldatasize) + + // replace the first 4 bytes with the right methodID + mstore8(callBytes, shr(24, methodId)) + mstore8(add(callBytes, 1), shr(16, methodId)) + mstore8(add(callBytes, 2), shr(8, methodId)) + mstore8(add(callBytes, 3), methodId) + + // overwrite call params + let result := mload(0x40) + let success := call(gas, addr, 0, callBytes, calldatasize, result, 32) + + if eq(success, 0) { + revert(0, 0) + } + + return(result, returndatasize) + } + } + + function ovmORIGIN() public { + // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes + bytes32 methodId = keccak256("ovmORIGIN()") >> 224; + address addr = executionManagerAddress; + + assembly { + let callBytes := mload(0x40) + calldatacopy(callBytes, 0, calldatasize) + + // replace the first 4 bytes with the right methodID + mstore8(callBytes, shr(24, methodId)) + mstore8(add(callBytes, 1), shr(16, methodId)) + mstore8(add(callBytes, 2), shr(8, methodId)) + mstore8(add(callBytes, 3), methodId) + + // overwrite call params + let result := mload(0x40) + let success := call(gas, addr, 0, callBytes, calldatasize, result, 32) + + if eq(success, 0) { + revert(0, 0) + } + + return(result, returndatasize) + } + } } diff --git a/packages/contracts/contracts/test-helpers/FraudTester.sol b/packages/contracts/contracts/test-helpers/FraudTester.sol index 62b1dcd7220a0..a1d1b12e16f97 100644 --- a/packages/contracts/contracts/test-helpers/FraudTester.sol +++ b/packages/contracts/contracts/test-helpers/FraudTester.sol @@ -3,6 +3,10 @@ pragma solidity ^0.5.0; contract BaseFraudTester { mapping (bytes32 => bytes32) public builtInStorage; + function doRevert() public { + revert("Performing a revert for testing."); + } + function setStorage(bytes32 key, bytes32 value) public { builtInStorage[key] = value; } diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 4074b0d379dae..3d7c592393c43 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -38,6 +38,7 @@ "dependencies": { "@eth-optimism/core-db": "^0.0.1-alpha.25", "@eth-optimism/core-utils": "^0.0.1-alpha.25", + "@eth-optimism/rollup-core": "^0.0.1-alpha.28", "@eth-optimism/solc-transpiler": "^0.0.1-alpha.27", "@nomiclabs/buidler": "^1.3.8", "@nomiclabs/buidler-ethers": "^2.0.0", diff --git a/packages/contracts/src/deployment/index.ts b/packages/contracts/src/deployment/index.ts index 086de396b196b..2b2879f29e041 100644 --- a/packages/contracts/src/deployment/index.ts +++ b/packages/contracts/src/deployment/index.ts @@ -1 +1,2 @@ export * from './contract-deploy' +export { AddressResolverMapping, RollupDeployConfig } from './types' diff --git a/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts b/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts index ad79b75f49240..f53c7847d959f 100644 --- a/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts +++ b/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts @@ -7,12 +7,18 @@ import { Contract, Signer, ContractFactory } from 'ethers' /* Internal Imports */ import { + DEFAULT_FORCE_INCLUSION_PERIOD, makeRandomBatchOfSize, TxQueueBatch, TxChainBatch, makeAddressResolver, deployAndRegister, AddressResolverMapping, + appendSequencerBatch, + appendAndGenerateSequencerBatch, + createTxChainBatch, + enqueueAndGenerateSafetyBatch, + enqueueAndGenerateL1ToL2Batch, } from '../../test-helpers' /* Logging */ @@ -21,7 +27,6 @@ const log = getLogger('canonical-tx-chain', true) /* Tests */ describe('CanonicalTransactionChain', () => { const provider = ethers.provider - const FORCE_INCLUSION_PERIOD = 600 //600 seconds = 10 minutes const DEFAULT_BATCH = ['0x1234', '0x5678'] const DEFAULT_TX = '0x1234' @@ -38,82 +43,6 @@ describe('CanonicalTransactionChain', () => { ] = await ethers.getSigners() }) - let canonicalTxChain: Contract - let l1ToL2Queue: Contract - let safetyQueue: Contract - - const appendSequencerBatch = async (batch: string[]): Promise => { - const timestamp = Math.floor(Date.now() / 1000) - // Submit the rollup batch on-chain - await canonicalTxChain - .connect(sequencer) - .appendSequencerBatch(batch, timestamp) - return timestamp - } - - const appendAndGenerateSequencerBatch = async ( - batch: string[], - batchIndex: number = 0, - cumulativePrevElements: number = 0 - ): Promise => { - const timestamp = await appendSequencerBatch(batch) - return createTxChainBatch( - batch, - timestamp, - false, - batchIndex, - cumulativePrevElements - ) - } - - const createTxChainBatch = async ( - batch: string[], - timestamp: number, - isL1ToL2Tx: boolean, - batchIndex: number = 0, - cumulativePrevElements: number = 0 - ): Promise => { - const localBatch = new TxChainBatch( - timestamp, - isL1ToL2Tx, - batchIndex, - cumulativePrevElements, - batch - ) - await localBatch.generateTree() - return localBatch - } - - const enqueueAndGenerateL1ToL2Batch = async ( - _tx: string - ): Promise => { - // Submit the rollup batch on-chain - const enqueueTx = await l1ToL2Queue - .connect(l1ToL2TransactionPasser) - .enqueueTx(_tx) - const localBatch = await generateQueueBatch(_tx, enqueueTx.hash) - return localBatch - } - const enqueueAndGenerateSafetyBatch = async ( - _tx: string - ): Promise => { - const enqueueTx = await safetyQueue.connect(randomWallet).enqueueTx(_tx) - const localBatch = await generateQueueBatch(_tx, enqueueTx.hash) - return localBatch - } - - const generateQueueBatch = async ( - _tx: string, - _txHash: string - ): Promise => { - const txReceipt = await provider.getTransactionReceipt(_txHash) - const timestamp = (await provider.getBlock(txReceipt.blockNumber)).timestamp - // Generate a local version of the rollup batch - const localBatch = new TxQueueBatch(_tx, timestamp) - await localBatch.generateTree() - return localBatch - } - let resolver: AddressResolverMapping before(async () => { resolver = await makeAddressResolver(wallet) @@ -134,6 +63,9 @@ describe('CanonicalTransactionChain', () => { ) }) + let canonicalTxChain: Contract + let l1ToL2Queue: Contract + let safetyQueue: Contract beforeEach(async () => { canonicalTxChain = await deployAndRegister( resolver.addressResolver, @@ -145,7 +77,7 @@ describe('CanonicalTransactionChain', () => { resolver.addressResolver.address, await sequencer.getAddress(), await l1ToL2TransactionPasser.getAddress(), - FORCE_INCLUSION_PERIOD, + DEFAULT_FORCE_INCLUSION_PERIOD, ], } ) @@ -174,9 +106,50 @@ describe('CanonicalTransactionChain', () => { ) }) + describe('hashBatchHeader(...)', async () => { + it('should correctly compute the hash of a given batch', async () => { + const localBatch = await createTxChainBatch( + DEFAULT_BATCH, + Math.floor(Date.now() / 1000), + false, + 0, + 0 + ) + + const expectedBatchHeaderHash = await localBatch.hashBatchHeader() + const calculatedBatchHeaderHash = await canonicalTxChain.hashBatchHeader({ + timestamp: localBatch.timestamp, + isL1ToL2Tx: localBatch.isL1ToL2Tx, + elementsMerkleRoot: localBatch.elementsMerkleTree.getRootHash(), + numElementsInBatch: localBatch.elements.length, + cumulativePrevElements: localBatch.cumulativePrevElements, + }) + + calculatedBatchHeaderHash.should.equal(expectedBatchHeaderHash) + }) + }) + + describe('authenticateAppend(...)', async () => { + it('should return true when the sender is the sequencer', async () => { + const result = await canonicalTxChain.authenticateAppend( + await sequencer.getAddress() + ) + + result.should.equal(true) + }) + + it('should return false when the sender is not the sequencer', async () => { + const result = await canonicalTxChain.authenticateAppend( + await randomWallet.getAddress() + ) + + result.should.equal(false) + }) + }) + describe('appendSequencerBatch()', async () => { it('should not throw when appending a batch from the sequencer', async () => { - await appendSequencerBatch(DEFAULT_BATCH) + await appendSequencerBatch(canonicalTxChain, sequencer, DEFAULT_BATCH) }) it('should throw if submitting an empty batch', async () => { @@ -184,14 +157,14 @@ describe('CanonicalTransactionChain', () => { await TestUtils.assertRevertsAsync( 'Cannot submit an empty batch', async () => { - await appendSequencerBatch(emptyBatch) + await appendSequencerBatch(canonicalTxChain, sequencer, emptyBatch) } ) }) it('should revert if submitting a batch older than the inclusion period', async () => { const timestamp = Math.floor(Date.now() / 1000) - const oldTimestamp = timestamp - (FORCE_INCLUSION_PERIOD + 1) + const oldTimestamp = timestamp - (DEFAULT_FORCE_INCLUSION_PERIOD + 1) await TestUtils.assertRevertsAsync( 'Cannot submit a batch with a timestamp older than the sequencer inclusion period', async () => { @@ -204,7 +177,7 @@ describe('CanonicalTransactionChain', () => { it('should not revert if submitting a 5 minute old batch', async () => { const timestamp = Math.floor(Date.now() / 1000) - const oldTimestamp = timestamp - FORCE_INCLUSION_PERIOD / 2 + const oldTimestamp = timestamp - DEFAULT_FORCE_INCLUSION_PERIOD / 2 await canonicalTxChain .connect(sequencer) .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) @@ -224,7 +197,11 @@ describe('CanonicalTransactionChain', () => { }) it('should revert if submitting a new batch with a timestamp older than last batch timestamp', async () => { - const timestamp = await appendSequencerBatch(DEFAULT_BATCH) + const timestamp = await appendSequencerBatch( + canonicalTxChain, + sequencer, + DEFAULT_BATCH + ) const oldTimestamp = timestamp - 1 await TestUtils.assertRevertsAsync( 'Timestamps must monotonically increase', @@ -237,13 +214,13 @@ describe('CanonicalTransactionChain', () => { }) it('should add to batches array', async () => { - await appendSequencerBatch(DEFAULT_BATCH) + await appendSequencerBatch(canonicalTxChain, sequencer, DEFAULT_BATCH) const batchesLength = await canonicalTxChain.getBatchesLength() batchesLength.toNumber().should.equal(1) }) it('should update cumulativeNumElements correctly', async () => { - await appendSequencerBatch(DEFAULT_BATCH) + await appendSequencerBatch(canonicalTxChain, sequencer, DEFAULT_BATCH) const cumulativeNumElements = await canonicalTxChain.cumulativeNumElements.call() cumulativeNumElements.toNumber().should.equal(DEFAULT_BATCH.length) }) @@ -259,7 +236,11 @@ describe('CanonicalTransactionChain', () => { }) it('should calculate batchHeaderHash correctly', async () => { - const localBatch = await appendAndGenerateSequencerBatch(DEFAULT_BATCH) + const localBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, + DEFAULT_BATCH + ) const expectedBatchHeaderHash = await localBatch.hashBatchHeader() const calculatedBatchHeaderHash = await canonicalTxChain.batches(0) calculatedBatchHeaderHash.should.equal(expectedBatchHeaderHash) @@ -272,6 +253,8 @@ describe('CanonicalTransactionChain', () => { const batch = makeRandomBatchOfSize(batchIndex + 1) const cumulativePrevElements = expectedNumElements const localBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, batch, batchIndex, cumulativePrevElements @@ -292,7 +275,12 @@ describe('CanonicalTransactionChain', () => { describe('when there is a batch in the L1toL2Queue', async () => { let localBatch beforeEach(async () => { - localBatch = await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + localBatch = await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) }) it('should successfully append a batch with an older timestamp', async () => { @@ -310,7 +298,9 @@ describe('CanonicalTransactionChain', () => { it('should revert when there is an older batch in the L1ToL2Queue', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD]) + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD, + ]) const newTimestamp = localBatch.timestamp + 60 await TestUtils.assertRevertsAsync( 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', @@ -327,7 +317,12 @@ describe('CanonicalTransactionChain', () => { describe('when there is a batch in the SafetyQueue', async () => { let localBatch beforeEach(async () => { - localBatch = await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + localBatch = await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) }) it('should successfully append a batch with an older timestamp', async () => { @@ -345,7 +340,9 @@ describe('CanonicalTransactionChain', () => { it('should revert when there is an older batch in the SafetyQueue', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD]) + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD, + ]) const newTimestamp = localBatch.timestamp + 60 await TestUtils.assertRevertsAsync( 'Must process older SafetyQueue batches first to enforce timestamp monotonicity', @@ -363,11 +360,23 @@ describe('CanonicalTransactionChain', () => { let l1ToL2Timestamp let snapshotID beforeEach(async () => { - const localSafetyBatch = await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + const localSafetyBatch = await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) safetyTimestamp = localSafetyBatch.timestamp snapshotID = await provider.send('evm_snapshot', []) - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 2]) - const localL1ToL2Batch = await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD / 2, + ]) + const localL1ToL2Batch = await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) l1ToL2Timestamp = localL1ToL2Batch.timestamp }) afterEach(async () => { @@ -400,7 +409,9 @@ describe('CanonicalTransactionChain', () => { }) it('should revert when appending a batch with a timestamp newer than both batches', async () => { - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 10]) // increase time by 60 seconds + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD / 10, + ]) // increase time by 60 seconds const oldTimestamp = l1ToL2Timestamp + 1 await TestUtils.assertRevertsAsync( 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', @@ -418,11 +429,23 @@ describe('CanonicalTransactionChain', () => { let safetyTimestamp let snapshotID beforeEach(async () => { - const localL1ToL2Batch = await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + const localL1ToL2Batch = await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) l1ToL2Timestamp = localL1ToL2Batch.timestamp snapshotID = await provider.send('evm_snapshot', []) - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 2]) - const localSafetyBatch = await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD / 2, + ]) + const localSafetyBatch = await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) safetyTimestamp = localSafetyBatch.timestamp }) afterEach(async () => { @@ -455,7 +478,9 @@ describe('CanonicalTransactionChain', () => { }) it('should revert when appending a batch with a timestamp newer than both batches', async () => { - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 10]) // increase time by 60 seconds + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD / 10, + ]) // increase time by 60 seconds const newTimestamp = safetyTimestamp + 1 await TestUtils.assertRevertsAsync( 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', @@ -472,7 +497,12 @@ describe('CanonicalTransactionChain', () => { describe('appendL1ToL2Batch()', async () => { describe('when there is a batch in the L1toL2Queue', async () => { beforeEach(async () => { - await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) }) it('should successfully dequeue a L1ToL2Batch', async () => { @@ -513,7 +543,9 @@ describe('CanonicalTransactionChain', () => { it('should allow non-sequencer to appendL1ToL2Batch after inclusion period has elapsed', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD]) + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD, + ]) await canonicalTxChain.appendL1ToL2Batch() await provider.send('evm_revert', [snapshotID]) }) @@ -522,9 +554,19 @@ describe('CanonicalTransactionChain', () => { describe('when there is a batch in both the SafetyQueue and L1toL2Queue', async () => { it('should revert when the SafetyQueue batch is older', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) await provider.send('evm_increaseTime', [10]) - await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) await TestUtils.assertRevertsAsync( 'Must process older SafetyQueue batches first to enforce timestamp monotonicity', async () => { @@ -536,9 +578,19 @@ describe('CanonicalTransactionChain', () => { it('should succeed when the L1ToL2Queue batch is older', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) await provider.send('evm_increaseTime', [10]) - await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) await canonicalTxChain.connect(sequencer).appendL1ToL2Batch() await provider.send('evm_revert', [snapshotID]) }) @@ -557,7 +609,12 @@ describe('CanonicalTransactionChain', () => { describe('appendSafetyBatch()', async () => { describe('when there is a batch in the SafetyQueue', async () => { beforeEach(async () => { - await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) }) it('should successfully dequeue a SafetyBatch', async () => { @@ -598,7 +655,9 @@ describe('CanonicalTransactionChain', () => { it('should allow non-sequencer to appendSafetyBatch after force inclusion period has elapsed', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD]) + await provider.send('evm_increaseTime', [ + DEFAULT_FORCE_INCLUSION_PERIOD, + ]) await canonicalTxChain.appendSafetyBatch() await provider.send('evm_revert', [snapshotID]) }) @@ -606,9 +665,19 @@ describe('CanonicalTransactionChain', () => { it('should revert when trying to appendSafetyBatch when there is an older batch in the L1ToL2Queue ', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) await provider.send('evm_increaseTime', [10]) - await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) await TestUtils.assertRevertsAsync( 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', async () => { @@ -620,9 +689,19 @@ describe('CanonicalTransactionChain', () => { it('should succeed when there are only newer batches in the L1ToL2Queue ', async () => { const snapshotID = await provider.send('evm_snapshot', []) - await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) await provider.send('evm_increaseTime', [10]) - await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) await canonicalTxChain.connect(sequencer).appendSafetyBatch() await provider.send('evm_revert', [snapshotID]) }) @@ -645,6 +724,8 @@ describe('CanonicalTransactionChain', () => { const batchSize = batchIndex * batchIndex + 1 // 1, 2, 5, 10 const batch = makeRandomBatchOfSize(batchSize) const localBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, batch, batchIndex, cumulativePrevElements @@ -671,7 +752,12 @@ describe('CanonicalTransactionChain', () => { }) it('should return true for valid element from a l1ToL2Batch', async () => { - const l1ToL2Batch = await enqueueAndGenerateL1ToL2Batch(DEFAULT_TX) + const l1ToL2Batch = await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, + DEFAULT_TX + ) await canonicalTxChain.connect(sequencer).appendL1ToL2Batch() const localBatch = new TxChainBatch( l1ToL2Batch.timestamp, //timestamp @@ -695,7 +781,12 @@ describe('CanonicalTransactionChain', () => { }) it('should return true for valid element from a SafetyBatch', async () => { - const safetyBatch = await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + const safetyBatch = await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) await canonicalTxChain.connect(sequencer).appendSafetyBatch() const localBatch = new TxChainBatch( safetyBatch.timestamp, //timestamp @@ -720,7 +811,11 @@ describe('CanonicalTransactionChain', () => { it('should return false for wrong position with wrong indexInBatch', async () => { const batch = ['0x1234', '0x4567', '0x890a', '0x4567', '0x890a', '0xabcd'] - const localBatch = await appendAndGenerateSequencerBatch(batch) + const localBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, + batch + ) const elementIndex = 1 const element = batch[elementIndex] const position = localBatch.getPosition(elementIndex) @@ -739,7 +834,11 @@ describe('CanonicalTransactionChain', () => { it('should return false for wrong position and matching indexInBatch', async () => { const batch = ['0x1234', '0x4567', '0x890a', '0x4567', '0x890a', '0xabcd'] - const localBatch = await appendAndGenerateSequencerBatch(batch) + const localBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, + batch + ) const elementIndex = 1 const element = batch[elementIndex] const position = localBatch.getPosition(elementIndex) @@ -752,9 +851,54 @@ describe('CanonicalTransactionChain', () => { elementInclusionProof.indexInBatch++ const isIncluded = await canonicalTxChain.verifyElement( element, - wrongPosition, + position, + elementInclusionProof + ) + isIncluded.should.equal(false) + }) + + it('should return false when element data is invalid', async () => { + const batch = ['0x1234', '0x4567', '0x890a', '0x4567', '0x890a', '0xabcd'] + const localBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, + batch + ) + const elementIndex = 1 + const position = localBatch.getPosition(elementIndex) + const elementInclusionProof = await localBatch.getElementInclusionProof( + elementIndex + ) + + const isIncluded = await canonicalTxChain.verifyElement( + batch[elementIndex + 1], // Wrong element + position, + elementInclusionProof + ) + + isIncluded.should.equal(false) + }) + + it('should return false when siblings are invalid', async () => { + const batch = ['0x1234', '0x4567', '0x890a', '0x4567', '0x890a', '0xabcd'] + const localBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, + batch + ) + const elementIndex = 1 + const element = batch[elementIndex] + const position = localBatch.getPosition(elementIndex) + const elementInclusionProof = await localBatch.getElementInclusionProof( + elementIndex + 1 // Proof for the wrong thing + ) + + const isIncluded = await canonicalTxChain.verifyElement( + element, + position, elementInclusionProof ) + isIncluded.should.equal(false) }) }) @@ -769,6 +913,8 @@ describe('CanonicalTransactionChain', () => { } ) const localBatch: TxChainBatch = await appendAndGenerateSequencerBatch( + canonicalTxChain, + sequencer, DEFAULT_BATCH ) @@ -789,7 +935,12 @@ describe('CanonicalTransactionChain', () => { txEnqueued = true }) - await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) await sleep(5_000) @@ -803,6 +954,9 @@ describe('CanonicalTransactionChain', () => { }) const localBatch: TxQueueBatch = await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, DEFAULT_TX ) @@ -827,6 +981,9 @@ describe('CanonicalTransactionChain', () => { ) const localBatch: TxQueueBatch = await enqueueAndGenerateL1ToL2Batch( + provider, + l1ToL2Queue, + l1ToL2TransactionPasser, DEFAULT_TX ) await canonicalTxChain.connect(sequencer).appendL1ToL2Batch() @@ -858,7 +1015,12 @@ describe('CanonicalTransactionChain', () => { } ) - const localBatch = await enqueueAndGenerateSafetyBatch(DEFAULT_TX) + const localBatch = await enqueueAndGenerateSafetyBatch( + provider, + safetyQueue, + randomWallet, + DEFAULT_TX + ) await canonicalTxChain.connect(sequencer).appendSafetyBatch() const front = await safetyQueue.front() diff --git a/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts b/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts index bb3ea66d781cd..9223bd3bd365d 100644 --- a/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts +++ b/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts @@ -7,11 +7,12 @@ import { Contract, Signer, ContractFactory } from 'ethers' /* Internal Imports */ import { - StateChainBatch, - TxChainBatch, + DEFAULT_FORCE_INCLUSION_PERIOD, makeAddressResolver, AddressResolverMapping, deployAndRegister, + generateTxBatch, + generateStateBatch, } from '../../test-helpers' /* Logging */ @@ -21,7 +22,6 @@ const log = getLogger('batch-submitter', true) describe('SequencerBatchSubmitter', () => { const DEFAULT_STATE_BATCH = ['0x1234', '0x5678'] const DEFAULT_TX_BATCH = ['0xabcd', '0xef12'] - const FORCE_INCLUSION_PERIOD = 600 let wallet: Signer let sequencer: Signer @@ -38,41 +38,6 @@ describe('SequencerBatchSubmitter', () => { ] = await ethers.getSigners() }) - let stateChain: Contract - let canonicalTxChain: Contract - let sequencerBatchSubmitter: Contract - - const generateStateBatch = async ( - batch: string[], - batchIndex: number = 0, - cumulativePrevElements: number = 0 - ): Promise => { - const localBatch = new StateChainBatch( - batchIndex, - cumulativePrevElements, - batch - ) - await localBatch.generateTree() - return localBatch - } - - const generateTxBatch = async ( - batch: string[], - timestamp: number, - batchIndex: number = 0, - cumulativePrevElements: number = 0 - ): Promise => { - const localBatch = new TxChainBatch( - timestamp, - false, - batchIndex, - cumulativePrevElements, - batch - ) - await localBatch.generateTree() - return localBatch - } - let resolver: AddressResolverMapping before(async () => { resolver = await makeAddressResolver(wallet) @@ -98,6 +63,9 @@ describe('SequencerBatchSubmitter', () => { ) }) + let stateChain: Contract + let canonicalTxChain: Contract + let sequencerBatchSubmitter: Contract beforeEach(async () => { sequencerBatchSubmitter = await deployAndRegister( resolver.addressResolver, @@ -122,7 +90,7 @@ describe('SequencerBatchSubmitter', () => { resolver.addressResolver.address, sequencerBatchSubmitter.address, await l1ToL2TransactionPasser.getAddress(), - FORCE_INCLUSION_PERIOD, + DEFAULT_FORCE_INCLUSION_PERIOD, ], } ) diff --git a/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts b/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts index 7e3edcff79a74..984ced05eaf1d 100644 --- a/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts +++ b/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts @@ -7,11 +7,13 @@ import { Contract, Signer, ContractFactory } from 'ethers' /* Internal Imports */ import { + DEFAULT_FORCE_INCLUSION_PERIOD, makeRandomBatchOfSize, - StateChainBatch, makeAddressResolver, deployAndRegister, AddressResolverMapping, + appendAndGenerateStateBatch, + appendTxBatch, } from '../../test-helpers' /* Logging */ @@ -32,7 +34,6 @@ describe('StateCommitmentChain', () => { '0x1234', '0x5678', ] - const FORCE_INCLUSION_PERIOD = 600 let wallet: Signer let sequencer: Signer @@ -49,33 +50,6 @@ describe('StateCommitmentChain', () => { ] = await ethers.getSigners() }) - let stateChain: Contract - let canonicalTxChain: Contract - - const appendAndGenerateStateBatch = async ( - batch: string[], - batchIndex: number = 0, - cumulativePrevElements: number = 0 - ): Promise => { - await stateChain.appendStateBatch(batch) - // Generate a local version of the rollup batch - const localBatch = new StateChainBatch( - batchIndex, - cumulativePrevElements, - batch - ) - await localBatch.generateTree() - return localBatch - } - - const appendTxBatch = async (batch: string[]): Promise => { - const timestamp = Math.floor(Date.now() / 1000) - // Submit the rollup batch on-chain - await canonicalTxChain - .connect(sequencer) - .appendSequencerBatch(batch, timestamp) - } - let resolver: AddressResolverMapping before(async () => { resolver = await makeAddressResolver(wallet) @@ -92,6 +66,8 @@ describe('StateCommitmentChain', () => { ) }) + let stateChain: Contract + let canonicalTxChain: Contract before(async () => { canonicalTxChain = await deployAndRegister( resolver.addressResolver, @@ -103,12 +79,13 @@ describe('StateCommitmentChain', () => { resolver.addressResolver.address, await sequencer.getAddress(), await l1ToL2TransactionPasser.getAddress(), - FORCE_INCLUSION_PERIOD, + DEFAULT_FORCE_INCLUSION_PERIOD, ], } ) - await appendTxBatch(DEFAULT_TX_BATCH) + await appendTxBatch(canonicalTxChain, sequencer, DEFAULT_TX_BATCH) + await resolver.addressResolver.setAddress( 'FraudVerifier', await fraudVerifier.getAddress() @@ -157,7 +134,10 @@ describe('StateCommitmentChain', () => { }) it('should calculate batchHeaderHash correctly', async () => { - const localBatch = await appendAndGenerateStateBatch(DEFAULT_STATE_BATCH) + const localBatch = await appendAndGenerateStateBatch( + stateChain, + DEFAULT_STATE_BATCH + ) const expectedBatchHeaderHash = await localBatch.hashBatchHeader() const calculatedBatchHeaderHash = await stateChain.batches(0) calculatedBatchHeaderHash.should.equal(expectedBatchHeaderHash) @@ -170,6 +150,7 @@ describe('StateCommitmentChain', () => { const batch = makeRandomBatchOfSize(batchIndex + 1) const cumulativePrevElements = expectedNumElements const localBatch = await appendAndGenerateStateBatch( + stateChain, batch, batchIndex, cumulativePrevElements @@ -202,7 +183,7 @@ describe('StateCommitmentChain', () => { describe('verifyElement() ', async () => { it('should return true for valid elements for different batches and elements', async () => { // add enough transaction batches so # txs > # state roots - await appendTxBatch(DEFAULT_TX_BATCH) + await appendTxBatch(canonicalTxChain, sequencer, DEFAULT_TX_BATCH) const numBatches = 4 let cumulativePrevElements = 0 @@ -210,6 +191,7 @@ describe('StateCommitmentChain', () => { const batchSize = batchIndex * batchIndex + 1 // 1, 2, 5, 10 const batch = makeRandomBatchOfSize(batchSize) const localBatch = await appendAndGenerateStateBatch( + stateChain, batch, batchIndex, cumulativePrevElements @@ -237,7 +219,7 @@ describe('StateCommitmentChain', () => { it('should return false for wrong position with wrong indexInBatch', async () => { const batch = ['0x1234', '0x4567', '0x890a', '0x4567', '0x890a', '0xabcd'] - const localBatch = await appendAndGenerateStateBatch(batch) + const localBatch = await appendAndGenerateStateBatch(stateChain, batch) const elementIndex = 1 const element = batch[elementIndex] const position = localBatch.getPosition(elementIndex) @@ -256,7 +238,7 @@ describe('StateCommitmentChain', () => { it('should return false for wrong position and matching indexInBatch', async () => { const batch = ['0x1234', '0x4567', '0x890a', '0x4567', '0x890a', '0xabcd'] - const localBatch = await appendAndGenerateStateBatch(batch) + const localBatch = await appendAndGenerateStateBatch(stateChain, batch) const elementIndex = 1 const element = batch[elementIndex] const position = localBatch.getPosition(elementIndex) @@ -280,7 +262,10 @@ describe('StateCommitmentChain', () => { it('should not allow deletion from address other than fraud verifier', async () => { const cumulativePrevElements = 0 const batchIndex = 0 - const localBatch = await appendAndGenerateStateBatch(DEFAULT_STATE_BATCH) + const localBatch = await appendAndGenerateStateBatch( + stateChain, + DEFAULT_STATE_BATCH + ) const batchHeader = { elementsMerkleRoot: await localBatch.elementsMerkleTree.getRootHash(), numElementsInBatch: DEFAULT_STATE_BATCH.length, @@ -301,6 +286,7 @@ describe('StateCommitmentChain', () => { const cumulativePrevElements = 0 const batchIndex = 0 const localBatch = await appendAndGenerateStateBatch( + stateChain, DEFAULT_STATE_BATCH ) const batchHeader = { @@ -321,6 +307,7 @@ describe('StateCommitmentChain', () => { it('should successfully append a batch after deletion', async () => { const localBatch = await appendAndGenerateStateBatch( + stateChain, DEFAULT_STATE_BATCH ) const expectedBatchHeaderHash = await localBatch.hashBatchHeader() @@ -335,6 +322,7 @@ describe('StateCommitmentChain', () => { for (let batchIndex = 0; batchIndex < 5; batchIndex++) { const cumulativePrevElements = batchIndex * DEFAULT_STATE_BATCH.length const localBatch = await appendAndGenerateStateBatch( + stateChain, DEFAULT_STATE_BATCH, batchIndex, cumulativePrevElements @@ -358,7 +346,10 @@ describe('StateCommitmentChain', () => { it('should revert if batchHeader is incorrect', async () => { const cumulativePrevElements = 0 const batchIndex = 0 - const localBatch = await appendAndGenerateStateBatch(DEFAULT_STATE_BATCH) + const localBatch = await appendAndGenerateStateBatch( + stateChain, + DEFAULT_STATE_BATCH + ) const batchHeader = { elementsMerkleRoot: await localBatch.elementsMerkleTree.getRootHash(), numElementsInBatch: DEFAULT_STATE_BATCH.length + 1, // increment to make header incorrect @@ -378,7 +369,10 @@ describe('StateCommitmentChain', () => { it('should revert if trying to delete a batch outside of valid range', async () => { const cumulativePrevElements = 0 const batchIndex = 1 // outside of range - const localBatch = await appendAndGenerateStateBatch(DEFAULT_STATE_BATCH) + const localBatch = await appendAndGenerateStateBatch( + stateChain, + DEFAULT_STATE_BATCH + ) const batchHeader = { elementsMerkleRoot: await localBatch.elementsMerkleTree.getRootHash(), numElementsInBatch: DEFAULT_STATE_BATCH.length + 1, // increment to make header incorrect @@ -401,7 +395,10 @@ describe('StateCommitmentChain', () => { stateChain.on(stateChain.filters['StateBatchAppended'](), (...data) => { receivedBatchHeaderHash = data[0] }) - const localBatch = await appendAndGenerateStateBatch(DEFAULT_STATE_BATCH) + const localBatch = await appendAndGenerateStateBatch( + stateChain, + DEFAULT_STATE_BATCH + ) await sleep(5_000) diff --git a/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts b/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts index e89df5e29c48a..d63cc5b65a3ac 100644 --- a/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts +++ b/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts @@ -1,7 +1,6 @@ import { expect } from '../../setup' /* External Imports */ -import * as rlp from 'rlp' import { ethers } from '@nomiclabs/buidler' import { Contract, ContractFactory, Signer } from 'ethers' import { TestUtils } from '@eth-optimism/core-utils' @@ -10,127 +9,32 @@ import { TestUtils } from '@eth-optimism/core-utils' import { TxChainBatch, StateChainBatch, - toHexString, makeAddressResolver, deployAndRegister, AddressResolverMapping, + makeDummyOvmTransaction, + encodeOvmTransaction, + appendAndGenerateTransactionBatch, + appendAndGenerateStateBatch, } from '../../test-helpers' -interface OVMTransactionData { - timestamp: number - queueOrigin: number - ovmEntrypoint: string - callBytes: string - fromAddress: string - l1MsgSenderAddress: string - allowRevert: boolean -} - -const NULL_ADDRESS = '0x' + '00'.repeat(20) -const FORCE_INCLUSION_PERIOD = 600 - -const makeDummyTransaction = (calldata: string): OVMTransactionData => { - return { - timestamp: Math.floor(Date.now() / 1000), - queueOrigin: 0, - ovmEntrypoint: NULL_ADDRESS, - callBytes: calldata, - fromAddress: NULL_ADDRESS, - l1MsgSenderAddress: NULL_ADDRESS, - allowRevert: false, - } -} - -const encodeTransaction = (transaction: OVMTransactionData): string => { - return toHexString( - rlp.encode([ - transaction.timestamp, - transaction.queueOrigin, - transaction.ovmEntrypoint, - transaction.callBytes, - transaction.fromAddress, - transaction.l1MsgSenderAddress, - transaction.allowRevert ? 1 : 0, - ]) - ) -} - -const appendTransactionBatch = async ( - canonicalTransactionChain: Contract, - sequencer: Signer, - batch: string[] -): Promise => { - const timestamp = Math.floor(Date.now() / 1000) - - await canonicalTransactionChain - .connect(sequencer) - .appendSequencerBatch(batch, timestamp) - - return timestamp -} - -const appendAndGenerateTransactionBatch = async ( - canonicalTransactionChain: Contract, - sequencer: Signer, - batch: string[], - batchIndex: number = 0, - cumulativePrevElements: number = 0 -): Promise => { - const timestamp = await appendTransactionBatch( - canonicalTransactionChain, - sequencer, - batch - ) - - const localBatch = new TxChainBatch( - timestamp, - false, - batchIndex, - cumulativePrevElements, - batch - ) - - await localBatch.generateTree() - - return localBatch -} - -const appendAndGenerateStateBatch = async ( - stateCommitmentChain: Contract, - batch: string[], - batchIndex: number = 0, - cumulativePrevElements: number = 0 -): Promise => { - await stateCommitmentChain.appendStateBatch(batch) - - const localBatch = new StateChainBatch( - batchIndex, - cumulativePrevElements, - batch - ) - - await localBatch.generateTree() - - return localBatch -} - -const DUMMY_STATE_BATCH = [ - '0x' + '01'.repeat(32), - '0x' + '02'.repeat(32), - '0x' + '03'.repeat(32), - '0x' + '04'.repeat(32), -] - /* Tests */ describe('FraudVerifier', () => { + const DUMMY_STATE_BATCH = [ + '0x' + '01'.repeat(32), + '0x' + '02'.repeat(32), + '0x' + '03'.repeat(32), + '0x' + '04'.repeat(32), + ] + // Must create these when the tests are executed or the timestamp will be // invalid when we have a lot of tests to run. const DUMMY_TRANSACTION_BATCH = DUMMY_STATE_BATCH.map((element) => { - return makeDummyTransaction(element) + return makeDummyOvmTransaction(element) }) const ENCODED_DUMMY_TRANSACTION_BATCH = DUMMY_TRANSACTION_BATCH.map( (transaction) => { - return encodeTransaction(transaction) + return encodeOvmTransaction(transaction) } ) diff --git a/packages/contracts/test/contracts/ovm/L2ExecutionManager.spec.ts b/packages/contracts/test/contracts/ovm/L2ExecutionManager.spec.ts index a89dd6c7ca8d1..7e5b3b145c398 100644 --- a/packages/contracts/test/contracts/ovm/L2ExecutionManager.spec.ts +++ b/packages/contracts/test/contracts/ovm/L2ExecutionManager.spec.ts @@ -2,25 +2,29 @@ import '../../setup' /* External Imports */ import { ethers } from '@nomiclabs/buidler' -import { add0x, getLogger } from '@eth-optimism/core-utils' +import { add0x, getLogger, NULL_ADDRESS } from '@eth-optimism/core-utils' import { Contract, Signer, ContractFactory } from 'ethers' /* Internal Imports */ import { - DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, makeAddressResolver, - deployAndRegister, AddressResolverMapping, + fillHexBytes, } from '../../test-helpers' /* Logging */ const log = getLogger('l2-execution-manager-calls', true) -export const abi = new ethers.utils.AbiCoder() -const zero32: string = add0x('00'.repeat(32)) -const key: string = add0x('01'.repeat(32)) -const value: string = add0x('02'.repeat(32)) +const zero32: string = fillHexBytes('00') +const key: string = fillHexBytes('01') +const value: string = fillHexBytes('02') + +const fakeSignedTx = add0x( + Buffer.from('derp') + .toString('hex') + .repeat(20) +) describe('L2 Execution Manager', () => { let wallet: Signer @@ -42,22 +46,18 @@ describe('L2 Execution Manager', () => { beforeEach(async () => { l2ExecutionManager = await L2ExecutionManager.deploy( resolver.addressResolver.address, - '0x' + '00'.repeat(20), + NULL_ADDRESS, GAS_LIMIT ) }) - describe('Store OVM transactions', async () => { - const fakeSignedTx = add0x( - Buffer.from('derp') - .toString('hex') - .repeat(20) - ) - + describe('storeOvmTransaction(...)', async () => { it('properly maps OVM tx hash to internal tx hash', async () => { await l2ExecutionManager.storeOvmTransaction(key, value, fakeSignedTx) }) + }) + describe('getInternalTransactionHash(...)', async () => { it('properly reads non-existent mapping', async () => { const result = await l2ExecutionManager.getInternalTransactionHash(key) result.should.equal(zero32, 'Incorrect unpopulated result!') @@ -68,13 +68,17 @@ describe('L2 Execution Manager', () => { const result = await l2ExecutionManager.getInternalTransactionHash(key) result.should.equal(value, 'Incorrect hash mapped!') }) + }) + describe('getOvmTransactionHash(...)', async () => { it('properly reads existing internal tx hash -> OVM tx hash mapping', async () => { await l2ExecutionManager.storeOvmTransaction(key, value, fakeSignedTx) const result = await l2ExecutionManager.getOvmTransactionHash(value) result.should.equal(key, 'Incorrect hash mapped!') }) + }) + describe('getOvmTransaction(...)', async () => { it('properly reads existing OVM tx hash -> OVM tx mapping', async () => { await l2ExecutionManager.storeOvmTransaction(key, value, fakeSignedTx) const result = await l2ExecutionManager.getOvmTransaction(key) diff --git a/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts b/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts index db94a37a523ee..6f4a49d04c65b 100644 --- a/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts +++ b/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts @@ -2,24 +2,49 @@ import '../../setup' /* External Imports */ import { ethers } from '@nomiclabs/buidler' -import { getLogger } from '@eth-optimism/core-utils' +import { getLogger, TestUtils, NULL_ADDRESS } from '@eth-optimism/core-utils' import { Contract, ContractFactory, Signer } from 'ethers' /* Internal Imports */ -import { - makeAddressResolver, - deployAndRegister, - AddressResolverMapping, -} from '../../test-helpers' +import { makeAddressResolver, AddressResolverMapping } from '../../test-helpers' /* Logging */ const log = getLogger('partial-state-manager', true) /* Begin tests */ describe('PartialStateManager', () => { + const DUMMY_CONTRACTS = [ + '0x' + '01'.repeat(20), + '0x' + '02'.repeat(20), + '0x' + '03'.repeat(20), + ] + + const DUMMY_CODE_CONTRACTS = [ + '0x' + '10'.repeat(20), + '0x' + '20'.repeat(20), + '0x' + '30'.repeat(20), + ] + + const DUMMY_SLOTS = [ + '0x' + '04'.repeat(32), + '0x' + '05'.repeat(32), + '0x' + '06'.repeat(32), + ] + + const DUMMY_VALUES = [ + '0x' + '07'.repeat(32), + '0x' + '08'.repeat(32), + '0x' + '09'.repeat(32), + ] + + const NULL_BYTES32 = '0x' + '00'.repeat(32) + const RLP_NULL_HASH = + '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' + let wallet: Signer + let randomWallet: Signer before(async () => { - ;[wallet] = await ethers.getSigners() + ;[wallet, randomWallet] = await ethers.getSigners() }) let resolver: AddressResolverMapping @@ -45,39 +70,650 @@ describe('PartialStateManager', () => { ) }) - describe('Pre-Execution', async () => { - describe('Storage Verification', async () => { - it('does not set existsInvalidStateAccessFlag=true if getStorage(contract, key) is called with a verified value', async () => { - const address = '0x' + '01'.repeat(20) - const key = '0x' + '01'.repeat(32) - const value = '0x' + '01'.repeat(32) + describe('initNewTransactionExecution()', async () => { + it('should set the initial state', async () => { + await partialStateManager.initNewTransactionExecution() + + const existsInvalidStateAccessFlag = await partialStateManager.existsInvalidStateAccessFlag() + const updatedStorageSlotCounter = await partialStateManager.updatedStorageSlotCounter() + const updatedContractsCounter = await partialStateManager.updatedContractsCounter() - // First verify the value - await partialStateManager.insertVerifiedStorage(address, key, value) - // Then access - await partialStateManager.getStorage(address, key) + existsInvalidStateAccessFlag.should.equal(false) + updatedStorageSlotCounter.should.equal(0) + updatedContractsCounter.should.equal(0) + }) - const existsInvalidStateAccessFlag = await partialStateManager.existsInvalidStateAccessFlag() - existsInvalidStateAccessFlag.should.equal(false) + it('should fail if not called by the state transitioner', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.initNewTransactionExecution({ + from: await randomWallet.getAddress(), + }) }) + }) + }) - it('sets existsInvalidStateAccessFlag=true if getStorage(contract, key) is called without being verified', async () => { - const address = '0x' + '01'.repeat(20) - const key = '0x' + '01'.repeat(32) + describe('insertVerifiedStorage(...)', async () => { + it('should mark a storage slot as verified', async () => { + await partialStateManager.insertVerifiedStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0] + ) - // Attempt to get unverified storage! - await partialStateManager.getStorage(address, key) + const isVerifiedStorage = await partialStateManager.isVerifiedStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) + const ovmContractStorage = await partialStateManager.ovmContractStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) - const existsInvalidStateAccessFlag = await partialStateManager.existsInvalidStateAccessFlag() - existsInvalidStateAccessFlag.should.equal(true) + isVerifiedStorage.should.equal(true) + ovmContractStorage.should.equal(DUMMY_VALUES[0]) + }) + + it('should fail if not called by the state transitioner', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.insertVerifiedStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0], + { + from: await randomWallet.getAddress(), + } + ) }) }) + }) - describe('Contract Verification', async () => { - // TODO + describe('insertVerifiedContract(...)', async () => { + it('should mark a contract as verified', async () => { + const nonce = 1234 + await partialStateManager.insertVerifiedContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0], + nonce + ) + + const isVerifiedContract = await partialStateManager.isVerifiedContract( + DUMMY_CONTRACTS[0] + ) + const ovmContractNonce = await partialStateManager.ovmContractNonces( + DUMMY_CONTRACTS[0] + ) + const codeContractAddress = await partialStateManager.ovmAddressToCodeContractAddress( + DUMMY_CONTRACTS[0] + ) + + isVerifiedContract.should.equal(true) + ovmContractNonce.should.equal(nonce) + codeContractAddress.should.equal(DUMMY_CODE_CONTRACTS[0]) + }) + + it('should fail if not called by the state transitioner', async () => { + await TestUtils.assertThrowsAsync(async () => { + const nonce = 1234 + await partialStateManager.insertVerifiedContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0], + nonce, + { + from: await randomWallet.getAddress(), + } + ) + }) }) }) - describe('Post-Execution', async () => { - // TODO + + describe('setStorage(...)', async () => { + it('should set the storage slot for a given address', async () => { + await partialStateManager.setStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0] + ) + + const slotContract = await partialStateManager.updatedStorageSlotContract( + 0 + ) + const slotKey = await partialStateManager.updatedStorageSlotKey(0) + const slotTouched = await partialStateManager.storageSlotTouched( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) + const slotValue = await partialStateManager.ovmContractStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) + const counter = await partialStateManager.updatedStorageSlotCounter() + + slotContract.should.equal(DUMMY_CONTRACTS[0] + '00'.repeat(12)) + slotKey.should.equal(DUMMY_SLOTS[0]) + slotTouched.should.equal(true) + slotValue.should.equal(DUMMY_VALUES[0]) + counter.should.equal(1) + }) + + it('should not change the counter if the slot has already been touched', async () => { + await partialStateManager.setStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0] + ) + + await partialStateManager.setStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[1] + ) + + const slotContract = await partialStateManager.updatedStorageSlotContract( + 0 + ) + const slotKey = await partialStateManager.updatedStorageSlotKey(0) + const slotTouched = await partialStateManager.storageSlotTouched( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) + const slotValue = await partialStateManager.ovmContractStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) + const counter = await partialStateManager.updatedStorageSlotCounter() + + slotContract.should.equal(DUMMY_CONTRACTS[0] + '00'.repeat(12)) + slotKey.should.equal(DUMMY_SLOTS[0]) + slotTouched.should.equal(true) + slotValue.should.equal(DUMMY_VALUES[1]) + counter.should.equal(1) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.setStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0], + { + from: await randomWallet.getAddress(), + } + ) + }) + }) + }) + + describe('getStorageView(...)', async () => { + it('should return the value of a storage slot for a given address', async () => { + await partialStateManager.setStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0] + ) + + const value = await partialStateManager.getStorageView( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) + + value.should.equal(DUMMY_VALUES[0]) + }) + + it('should return null bytes if the storage slot is not set', async () => { + const value = await partialStateManager.getStorageView( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0] + ) + + value.should.equal(NULL_BYTES32) + }) + }) + + describe('getStorage(...)', async () => { + it('should return the value of a storage slot when it exists', async () => { + await partialStateManager.insertVerifiedStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0] + ) + + await partialStateManager.getStorage(DUMMY_CONTRACTS[0], DUMMY_SLOTS[0]) + const flagged = await partialStateManager.existsInvalidStateAccessFlag() + + flagged.should.equal(false) + }) + + it('should return null bytes and flag if the storage slot is not set', async () => { + await partialStateManager.getStorage(DUMMY_CONTRACTS[0], DUMMY_SLOTS[0]) + const flagged = await partialStateManager.existsInvalidStateAccessFlag() + + flagged.should.equal(true) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.getStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + { + from: await randomWallet.getAddress(), + } + ) + }) + }) + }) + + describe('setOvmContractNonce(...)', async () => { + it('should set the nonce for an address', async () => { + const nonce = 1234 + await partialStateManager.setOvmContractNonce(DUMMY_CONTRACTS[0], nonce) + + const contract = await partialStateManager.updatedContracts(0) + const counter = await partialStateManager.updatedContractsCounter() + const touched = await partialStateManager.contractTouched( + DUMMY_CONTRACTS[0] + ) + const updatedNonce = await partialStateManager.ovmContractNonces( + DUMMY_CONTRACTS[0] + ) + + contract.should.equal(DUMMY_CONTRACTS[0]) + counter.should.equal(1) + touched.should.equal(true) + updatedNonce.should.equal(nonce) + }) + + it('should not change the counter if the address has already been touched', async () => { + const nonce = 1234 + await partialStateManager.setOvmContractNonce(DUMMY_CONTRACTS[0], nonce) + + const newNonce = 5678 + await partialStateManager.setOvmContractNonce( + DUMMY_CONTRACTS[0], + newNonce + ) + + const contract = await partialStateManager.updatedContracts(0) + const counter = await partialStateManager.updatedContractsCounter() + const touched = await partialStateManager.contractTouched( + DUMMY_CONTRACTS[0] + ) + const updatedNonce = await partialStateManager.ovmContractNonces( + DUMMY_CONTRACTS[0] + ) + + contract.should.equal(DUMMY_CONTRACTS[0]) + counter.should.equal(1) + touched.should.equal(true) + updatedNonce.should.equal(newNonce) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + const nonce = 1234 + await partialStateManager.setOvmContractNonce( + DUMMY_CONTRACTS[0], + nonce, + { + from: await randomWallet.getAddress(), + } + ) + }) + }) + }) + + describe('getOvmContractNonceView(...)', async () => { + it('should get the nonce for a given address', async () => { + const nonce = 1234 + await partialStateManager.setOvmContractNonce(DUMMY_CONTRACTS[0], nonce) + + const result = await partialStateManager.getOvmContractNonceView( + DUMMY_CONTRACTS[0] + ) + + result.should.equal(nonce) + }) + + it('should return zero if the address has not been set', async () => { + const result = await partialStateManager.getOvmContractNonceView( + DUMMY_CONTRACTS[0] + ) + + result.should.equal(0) + }) + }) + + describe('getOvmContractNonce(...)', async () => { + it('should get the nonce for a given address', async () => { + const nonce = 1234 + await partialStateManager.insertVerifiedContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0], + nonce + ) + + await partialStateManager.getOvmContractNonce(DUMMY_CONTRACTS[0]) + const flagged = await partialStateManager.existsInvalidStateAccessFlag() + + flagged.should.equal(false) + }) + + it('should return zero and flag if the address has not been set', async () => { + await partialStateManager.getOvmContractNonce(DUMMY_CONTRACTS[0]) + const flagged = await partialStateManager.existsInvalidStateAccessFlag() + + flagged.should.equal(true) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.getOvmContractNonce(DUMMY_CONTRACTS[0], { + from: await randomWallet.getAddress(), + }) + }) + }) + }) + + describe('incrementOvmContractNonce(...)', async () => { + it('should increase the contract nonce by one', async () => { + const nonce = 1234 + await partialStateManager.setOvmContractNonce(DUMMY_CONTRACTS[0], nonce) + await partialStateManager.incrementOvmContractNonce(DUMMY_CONTRACTS[0]) + + const contract = await partialStateManager.updatedContracts(0) + const counter = await partialStateManager.updatedContractsCounter() + const touched = await partialStateManager.contractTouched( + DUMMY_CONTRACTS[0] + ) + const result = await partialStateManager.ovmContractNonces( + DUMMY_CONTRACTS[0] + ) + + contract.should.equal(DUMMY_CONTRACTS[0]) + counter.should.equal(1) + touched.should.equal(true) + result.should.equal(nonce + 1) + }) + + it('should not change the counter if the address has already been touched', async () => { + const nonce = 1234 + await partialStateManager.setOvmContractNonce(DUMMY_CONTRACTS[0], nonce) + await partialStateManager.incrementOvmContractNonce(DUMMY_CONTRACTS[0]) + await partialStateManager.incrementOvmContractNonce(DUMMY_CONTRACTS[0]) + + const contract = await partialStateManager.updatedContracts(0) + const counter = await partialStateManager.updatedContractsCounter() + const touched = await partialStateManager.contractTouched( + DUMMY_CONTRACTS[0] + ) + const result = await partialStateManager.ovmContractNonces( + DUMMY_CONTRACTS[0] + ) + + contract.should.equal(DUMMY_CONTRACTS[0]) + counter.should.equal(1) + touched.should.equal(true) + result.should.equal(nonce + 2) + }) + + it('should flag if not verified', async () => { + await partialStateManager.incrementOvmContractNonce(DUMMY_CONTRACTS[0]) + + const flagged = await partialStateManager.existsInvalidStateAccessFlag() + + flagged.should.equal(true) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.incrementOvmContractNonce( + DUMMY_CONTRACTS[0], + { + from: await randomWallet.getAddress(), + } + ) + }) + }) + }) + + describe('associateCodeContract(...)', async () => { + it('should set the code contract address for a given ovm contract', async () => { + await partialStateManager.associateCodeContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0] + ) + + const associated = await partialStateManager.ovmAddressToCodeContractAddress( + DUMMY_CONTRACTS[0] + ) + + associated.should.equal(DUMMY_CODE_CONTRACTS[0]) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.associateCodeContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0], + { + from: await randomWallet.getAddress(), + } + ) + }) + }) + }) + + describe('registerCreatedContract(...)', async () => { + it('should mark the contract as verified and set its nonce to zero', async () => { + await partialStateManager.registerCreatedContract(DUMMY_CONTRACTS[0]) + + const isVerifiedContract = await partialStateManager.isVerifiedContract( + DUMMY_CONTRACTS[0] + ) + const nonce = await partialStateManager.getOvmContractNonceView( + DUMMY_CONTRACTS[0] + ) + + isVerifiedContract.should.equal(true) + nonce.should.equal(0) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.registerCreatedContract(DUMMY_CONTRACTS[0], { + from: await randomWallet.getAddress(), + }) + }) + }) + }) + + describe('peekUpdatedStorageSlot()', async () => { + it('should return the last storage slot on the queue', async () => { + await partialStateManager.setStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0] + ) + + const [ + updatedContract, + updatedSlot, + updatedValue, + ] = await partialStateManager.peekUpdatedStorageSlot() + + updatedContract.should.equal(DUMMY_CONTRACTS[0]) + updatedSlot.should.equal(DUMMY_SLOTS[0]) + updatedValue.should.equal(DUMMY_VALUES[0]) + }) + + it('should fail if there are no storage slots to be updated', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.peekUpdatedStorageSlot() + }) + }) + }) + + describe('popUpdatedStorageSlot()', async () => { + it('should return the last storage slot on the queue and remove it', async () => { + await partialStateManager.setStorage( + DUMMY_CONTRACTS[0], + DUMMY_SLOTS[0], + DUMMY_VALUES[0] + ) + + await partialStateManager.popUpdatedStorageSlot() + const counter = await partialStateManager.updatedStorageSlotCounter() + + counter.should.equal(0) + }) + + it('should fail if there are no storage slots to be updated', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.popUpdatedStorageSlot() + }) + }) + + it('should fail if not called by the state transitioner', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.popUpdatedStorageSlot({ + from: await randomWallet.getAddress(), + }) + }) + }) + }) + + describe('peekUpdatedContract()', async () => { + it('should return the last contract on the queue', async () => { + await partialStateManager.registerCreatedContract(DUMMY_CONTRACTS[0]) + await partialStateManager.associateCodeContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0] + ) + + const [ + updatedContract, + updatedNonce, + updatedCodeHash, + ] = await partialStateManager.peekUpdatedContract() + + updatedContract.should.equal(DUMMY_CONTRACTS[0]) + updatedNonce.should.equal(0) + updatedCodeHash.should.equal(NULL_BYTES32) + }) + + it('should fail if there are no contracts to be updated', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.peekUpdatedContract() + }) + }) + }) + + describe('popUpdatedContract()', async () => { + it('should return the last contract on the queue and remove it', async () => { + await partialStateManager.registerCreatedContract(DUMMY_CONTRACTS[0]) + await partialStateManager.associateCodeContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0] + ) + + await partialStateManager.popUpdatedContract() + const counter = await partialStateManager.updatedContractsCounter() + + counter.should.equal(0) + }) + + it('should fail if there are no contracts to be updated', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.popUpdatedContract() + }) + }) + + it('should fail if not called by the state transitioner', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.popUpdatedContract({ + from: await randomWallet.getAddress(), + }) + }) + }) + }) + + describe('getCodeContractAddressView(...)', async () => { + it('should return the code contract for a given ovm contract', async () => { + await partialStateManager.associateCodeContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0] + ) + + const codeAddress = await partialStateManager.getCodeContractAddressView( + DUMMY_CONTRACTS[0] + ) + + codeAddress.should.equal(DUMMY_CODE_CONTRACTS[0]) + }) + + it('should return null bytes if the contract is not associated', async () => { + const codeAddress = await partialStateManager.getCodeContractAddressView( + DUMMY_CONTRACTS[0] + ) + + codeAddress.should.equal(NULL_ADDRESS) + }) + }) + + describe('getCodeContractAddressFromOvmAddress(...)', async () => { + it('should return the code contract address when it exists', async () => { + await partialStateManager.insertVerifiedContract( + DUMMY_CONTRACTS[0], + DUMMY_CODE_CONTRACTS[0], + 0 + ) + + await partialStateManager.getCodeContractAddressFromOvmAddress( + DUMMY_CONTRACTS[0] + ) + const flagged = await partialStateManager.existsInvalidStateAccessFlag() + + flagged.should.equal(false) + }) + + it('should return null bytes and flag when the contract does not exist', async () => { + await partialStateManager.getCodeContractAddressFromOvmAddress( + DUMMY_CONTRACTS[0] + ) + const flagged = await partialStateManager.existsInvalidStateAccessFlag() + + flagged.should.equal(true) + }) + + it('should fail if not called by the execution manager', async () => { + await TestUtils.assertThrowsAsync(async () => { + await partialStateManager.getCodeContractAddressFromOvmAddress( + DUMMY_CONTRACTS[0], + { + from: await randomWallet.getAddress(), + } + ) + }) + }) + }) + + describe('getCodeContractBytecode(...)', async () => { + it('should get the bytecode of a contract at the given address', async () => { + const bytecode = await partialStateManager.getCodeContractBytecode( + DUMMY_CONTRACTS[0] + ) + + bytecode.should.equal('0x') + }) + }) + + describe('getCodeContractHash(...)', async () => { + it('should get the hash of the bytecode at the given address', async () => { + const codehash = await partialStateManager.getCodeContractHash( + DUMMY_CONTRACTS[0] + ) + + codehash.should.equal(RLP_NULL_HASH) + }) }) }) diff --git a/packages/contracts/test/contracts/ovm/SafetyChecker.spec.ts b/packages/contracts/test/contracts/ovm/SafetyChecker.spec.ts index 4a6040e96a764..aa16759c8a03e 100644 --- a/packages/contracts/test/contracts/ovm/SafetyChecker.spec.ts +++ b/packages/contracts/test/contracts/ovm/SafetyChecker.spec.ts @@ -2,15 +2,18 @@ import '../../setup' /* External Imports */ import { ethers } from '@nomiclabs/buidler' -import { getLogger, add0x, remove0x } from '@eth-optimism/core-utils' import { Contract, ContractFactory, Signer } from 'ethers' +import { getLogger, add0x, remove0x } from '@eth-optimism/core-utils' +import { EVMOpcode, Opcode } from '@eth-optimism/rollup-core' /* Internal Imports */ import { DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_UNSAFE_OPCODES, - EVMOpcode, - Opcode, + WHITELISTED_NOT_HALTING_OR_CALL, + HALTING_OPCODES, + HALTING_OPCODES_NO_JUMP, + JUMP_OPCODES, makeAddressResolver, deployAndRegister, AddressResolverMapping, @@ -19,22 +22,10 @@ import { /* Logging */ const log = getLogger('safety-checker', true) -/* Helpers */ -const executionManagerAddress = add0x('12'.repeat(20)) // Test Execution Manager address 0x121...212 -const haltingOpcodes: EVMOpcode[] = Opcode.HALTING_OP_CODES -const haltingOpcodesNoJump: EVMOpcode[] = haltingOpcodes.filter( - (x) => x.name !== 'JUMP' -) -const jumps: EVMOpcode[] = [Opcode.JUMP, Opcode.JUMPI] -const whitelistedNotHaltingOrCALL: EVMOpcode[] = Opcode.ALL_OP_CODES.filter( - (x) => - DEFAULT_UNSAFE_OPCODES.indexOf(x) < 0 && - haltingOpcodes.indexOf(x) < 0 && - x.name !== 'CALL' -) - /* Tests */ describe('Safety Checker', () => { + const executionManagerAddress = add0x('12'.repeat(20)) // Test Execution Manager address 0x121...212 + let wallet: Signer before(async () => { ;[wallet] = await ethers.getSigners() @@ -93,7 +84,7 @@ describe('Safety Checker', () => { }) it('should correctly classify whitelisted', async () => { - for (const opcode of whitelistedNotHaltingOrCALL) { + for (const opcode of WHITELISTED_NOT_HALTING_OR_CALL) { if (!opcode.name.startsWith('PUSH')) { const res: boolean = await safetyChecker.isBytecodeSafe( `0x${opcode.code.toString('hex')}` @@ -157,7 +148,7 @@ describe('Safety Checker', () => { 'hex' ) - for (const opcode of whitelistedNotHaltingOrCALL) { + for (const opcode of WHITELISTED_NOT_HALTING_OR_CALL) { bytecode += `${opcode.code.toString('hex')}${invalidOpcode.repeat( opcode.programBytesConsumed )}` @@ -173,7 +164,7 @@ describe('Safety Checker', () => { 'hex' ) - for (const opcode of whitelistedNotHaltingOrCALL) { + for (const opcode of WHITELISTED_NOT_HALTING_OR_CALL) { bytecode += `${opcode.code.toString('hex')}${invalidOpcode.repeat( opcode.programBytesConsumed )}` @@ -192,7 +183,7 @@ describe('Safety Checker', () => { describe('handles unreachable code', async () => { it(`skips unreachable bytecode after a halting opcode`, async () => { - for (const haltingOp of haltingOpcodes) { + for (const haltingOp of HALTING_OPCODES) { let bytecode: string = '0x' bytecode += haltingOp.code.toString('hex') for (const opcode of DEFAULT_UNSAFE_OPCODES) { @@ -206,7 +197,7 @@ describe('Safety Checker', () => { } }) it('skips bytecode after an unreachable JUMPDEST', async () => { - for (const haltingOp of haltingOpcodesNoJump) { + for (const haltingOp of HALTING_OPCODES_NO_JUMP) { let bytecode: string = '0x' bytecode += haltingOp.code.toString('hex') bytecode += Opcode.JUMPDEST.code.toString('hex') @@ -222,8 +213,8 @@ describe('Safety Checker', () => { }) it('parses opcodes after a reachable JUMPDEST', async () => { - for (const haltingOp of haltingOpcodesNoJump) { - for (const jump of jumps) { + for (const haltingOp of HALTING_OPCODES_NO_JUMP) { + for (const jump of JUMP_OPCODES) { let bytecode: string = '0x' bytecode += jump.code.toString('hex') bytecode += Opcode.JUMPDEST.code.toString('hex') // JUMPDEST here so that the haltingOp is reachable @@ -269,8 +260,8 @@ describe('Safety Checker', () => { }) it('should correctly handle alternating reachable/uncreachable code ending in reachable, valid code', async () => { - for (const haltingOp of haltingOpcodesNoJump) { - for (const jump of jumps) { + for (const haltingOp of HALTING_OPCODES_NO_JUMP) { + for (const jump of JUMP_OPCODES) { let bytecode: string = '0x' bytecode += jump.code.toString('hex') // JUMPDEST here so that the haltingOp is reachable @@ -283,7 +274,7 @@ describe('Safety Checker', () => { } bytecode += Opcode.JUMPDEST.code.toString('hex') // Reachable, valid code - for (const opcode of whitelistedNotHaltingOrCALL) { + for (const opcode of WHITELISTED_NOT_HALTING_OR_CALL) { bytecode += opcode.code.toString('hex') } } @@ -297,8 +288,8 @@ describe('Safety Checker', () => { }).timeout(30_000) it('should correctly handle alternating reachable/uncreachable code ending in reachable, invalid code', async () => { - for (const haltingOp of haltingOpcodesNoJump) { - for (const jump of jumps) { + for (const haltingOp of HALTING_OPCODES_NO_JUMP) { + for (const jump of JUMP_OPCODES) { let bytecode: string = '0x' bytecode += jump.code.toString('hex') // JUMPDEST here so that the haltingOp is reachable @@ -311,7 +302,7 @@ describe('Safety Checker', () => { } bytecode += Opcode.JUMPDEST.code.toString('hex') // Reachable, valid code - for (const opcode of whitelistedNotHaltingOrCALL) { + for (const opcode of WHITELISTED_NOT_HALTING_OR_CALL) { bytecode += opcode.code.toString('hex') } } @@ -381,7 +372,7 @@ describe('Safety Checker', () => { } }) it(`rejects invalid CALLs using opcodes other than PUSH or DUP to set gas`, async () => { - const invalidGasSetters: EVMOpcode[] = whitelistedNotHaltingOrCALL.filter( + const invalidGasSetters: EVMOpcode[] = WHITELISTED_NOT_HALTING_OR_CALL.filter( (x) => !x.name.startsWith('PUSH') && !x.name.startsWith('DUP') ) log.debug(`Invalid Gas Setters ${invalidGasSetters.map((x) => x.name)}`) @@ -406,7 +397,7 @@ describe('Safety Checker', () => { } }) it(`rejects invalid CALLs using opcodes other than PUSH1 to set value`, async () => { - const invalidValueSetters: EVMOpcode[] = whitelistedNotHaltingOrCALL.filter( + const invalidValueSetters: EVMOpcode[] = WHITELISTED_NOT_HALTING_OR_CALL.filter( (x) => x.name !== 'PUSH1' ) log.debug( @@ -436,7 +427,7 @@ describe('Safety Checker', () => { } }).timeout(20_000) it(`rejects invalid CALLs using opcodes other than PUSH20 to set address`, async () => { - const invalidAddressSetters: EVMOpcode[] = whitelistedNotHaltingOrCALL.filter( + const invalidAddressSetters: EVMOpcode[] = WHITELISTED_NOT_HALTING_OR_CALL.filter( (x) => x.name !== 'PUSH20' ) log.debug( diff --git a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts index 12713135b778b..ae61483c04719 100644 --- a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts +++ b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts @@ -2,7 +2,6 @@ import { expect } from '../../setup' /* External Imports */ import * as path from 'path' -import * as rlp from 'rlp' import { ethers } from '@nomiclabs/buidler' import { getLogger, TestUtils, remove0x } from '@eth-optimism/core-utils' import * as solc from '@eth-optimism/solc-transpiler' @@ -22,15 +21,17 @@ import { compile, makeStateTrieUpdateTest, StateTrieUpdateTest, - toHexString, makeAddressResolver, AddressResolverMapping, + makeDummyOvmTransaction, + encodeOvmTransaction, + OVMTransactionData, + getCodeHash, } from '../../test-helpers' /* Logging */ const log = getLogger('state-transitioner', true) -const NULL_ADDRESS = '0x' + '00'.repeat(20) const DUMMY_ACCOUNT_ADDRESSES = [ '0x548855F6073c3430285c61Ed0ABf62F12084aA41', '0xD80e66Cbc34F06d24a0a4fDdD6f2aDB41ac1517D', @@ -38,28 +39,6 @@ const DUMMY_ACCOUNT_ADDRESSES = [ '0x808E5eCe9a8EA2cdce515764139Ee24bEF7098b4', ] -interface OVMTransactionData { - timestamp: number - queueOrigin: number - ovmEntrypoint: string - callBytes: string - fromAddress: string - l1MsgSenderAddress: string - allowRevert: boolean -} - -const makeDummyTransaction = (calldata: string): OVMTransactionData => { - return { - timestamp: Math.floor(Date.now() / 1000), - queueOrigin: 0, - ovmEntrypoint: NULL_ADDRESS, - callBytes: calldata, - fromAddress: NULL_ADDRESS, - l1MsgSenderAddress: NULL_ADDRESS, - allowRevert: false, - } -} - const EMPTY_ACCOUNT_STATE = (): StateTrieNode => { return cloneDeep({ nonce: 0, @@ -107,20 +86,6 @@ const DUMMY_STATE_TRIE = { }, } -const encodeTransaction = (transaction: OVMTransactionData): string => { - return toHexString( - rlp.encode([ - transaction.timestamp, - transaction.queueOrigin, - transaction.ovmEntrypoint, - transaction.callBytes, - transaction.fromAddress, - transaction.l1MsgSenderAddress, - transaction.allowRevert ? 1 : 0, - ]) - ) -} - const makeStateTrie = (account: string, state: any, storage: any[]): any => { return { [account]: { @@ -131,16 +96,13 @@ const makeStateTrie = (account: string, state: any, storage: any[]): any => { } } -const getCodeHash = async (provider: any, address: string): Promise => { - return keccak256(await provider.getCode(address)) -} - const makeTransactionData = async ( TargetFactory: ContractFactory, target: Contract, wallet: Signer, functionName: string, - functionArgs: any[] + functionArgs: any[], + eoa?: boolean ): Promise => { const calldata = TargetFactory.interface.encodeFunctionData( functionName, @@ -152,7 +114,7 @@ const makeTransactionData = async ( queueOrigin: 1, ovmEntrypoint: target.address, callBytes: calldata, - fromAddress: target.address, + fromAddress: eoa ? await wallet.getAddress() : target.address, l1MsgSenderAddress: await wallet.getAddress(), allowRevert: false, } @@ -162,7 +124,10 @@ const proveAllStorageUpdates = async ( stateTransitioner: Contract, stateManager: Contract, stateTrie: StateTrieMap -): Promise => { +): Promise<{ + trie: StateTrieMap + newStateTrieRoot: string +}> => { let updateTest: AccountStorageUpdateTest let trie = cloneDeep(stateTrie) @@ -198,14 +163,20 @@ const proveAllStorageUpdates = async ( ]) } - return updateTest.newStateTrieRoot + return { + trie, + newStateTrieRoot: updateTest.newStateTrieRoot, + } } const proveAllContractUpdates = async ( stateTransitioner: Contract, stateManager: Contract, stateTrie: StateTrieMap -): Promise => { +): Promise<{ + trie: StateTrieMap + newStateTrieRoot: string +}> => { let updateTest: StateTrieUpdateTest let trie = cloneDeep(stateTrie) @@ -245,7 +216,10 @@ const proveAllContractUpdates = async ( ]) } - return updateTest.newStateTrieRoot + return { + trie, + newStateTrieRoot: updateTest.newStateTrieRoot, + } } const getMappingStorageSlot = (key: string, index: number): string => { @@ -267,7 +241,7 @@ const initStateTransitioner = async ( addressResolver.address, 10, stateTrieRoot, - keccak256(encodeTransaction(transactionData)) + keccak256(encodeOvmTransaction(transactionData)) ) const stateManager = StateManager.attach( await stateTransitioner.stateManager() @@ -387,23 +361,53 @@ describe('StateTransitioner', () => { let stateTrie: any let test: AccountStorageProofTest + let eoaTest: AccountStorageProofTest + let wrongTest: AccountStorageProofTest before(async () => { - stateTrie = makeStateTrie( - fraudTester.address, - { - nonce: 0, - balance: 0, - storageRoot: null, - codeHash: await getCodeHash(ethers.provider, fraudTester.address), + stateTrie = { + ...makeStateTrie( + fraudTester.address, + { + nonce: 0, + balance: 0, + storageRoot: null, + codeHash: await getCodeHash(ethers.provider, fraudTester.address), + }, + DUMMY_ACCOUNT_STORAGE() + ), + ...{ + [await wallet.getAddress()]: { + state: { + nonce: 0, + balance: 0, + storageRoot: null, + codeHash: await getCodeHash( + ethers.provider, + await wallet.getAddress() + ), + }, + storage: DUMMY_ACCOUNT_STORAGE(), + }, }, - DUMMY_ACCOUNT_STORAGE() - ) + } test = await makeAccountStorageProofTest( stateTrie, fraudTester.address, DUMMY_ACCOUNT_STORAGE()[0].key ) + + eoaTest = await makeAccountStorageProofTest( + stateTrie, + await wallet.getAddress(), + DUMMY_ACCOUNT_STORAGE()[0].key + ) + + wrongTest = await makeAccountStorageProofTest( + stateTrie, + DUMMY_ACCOUNT_ADDRESSES[0], + DUMMY_ACCOUNT_STORAGE()[1].key + ) }) let stateTransitioner: Contract @@ -415,7 +419,7 @@ describe('StateTransitioner', () => { StateManager, resolver.addressResolver, test.stateTrieRoot, - makeDummyTransaction('0x00') + makeDummyOvmTransaction('0x00') ) }) @@ -457,6 +461,40 @@ describe('StateTransitioner', () => { await stateManager.isVerifiedContract(fraudTester.address) ).to.equal(false) }) + + it('should fail if the code contract is invalid', async () => { + try { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + await wallet.getAddress(), // Wrong code contract address. + 0, + test.stateTrieWitness + ) + } catch (e) { + expect(e.toString()).to.contain('Invalid account state provided.') + } + + expect( + await stateManager.isVerifiedContract(fraudTester.address) + ).to.equal(false) + }) + + it('should fail if the state trie witness is invalid', async () => { + try { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + wrongTest.stateTrieWitness // Wrong witness + ) + } catch (e) { + expect(e.toString()).to.contain('Invalid large internal hash') + } + + expect( + await stateManager.isVerifiedContract(fraudTester.address) + ).to.equal(false) + }) }) describe('proveStorageSlotInclusion(...)', async () => { @@ -511,6 +549,87 @@ describe('StateTransitioner', () => { ) ).to.equal(false) }) + + it('should fail if the provided contract has not been verified', async () => { + // Not proving contract inclusion first. + + try { + await stateTransitioner.proveStorageSlotInclusion( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key, + DUMMY_ACCOUNT_STORAGE()[0].val, + test.stateTrieWitness, + test.storageTrieWitness + ) + } catch (e) { + expect(e.toString()).to.contain( + 'Contract must be verified before proving storage!' + ) + } + + expect( + await stateManager.isVerifiedStorage( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key + ) + ).to.equal(false) + }) + + it('should fail if the state trie witness is invalid', async () => { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + try { + await stateTransitioner.proveStorageSlotInclusion( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key, + DUMMY_ACCOUNT_STORAGE()[0].val, + wrongTest.stateTrieWitness, // Wrong state trie witness + test.storageTrieWitness + ) + } catch (e) { + expect(e.toString()).to.contain('Invalid large internal hash') + } + + expect( + await stateManager.isVerifiedStorage( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key + ) + ).to.equal(false) + }) + + it('should fail if the storage trie witness is invalid', async () => { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + try { + await stateTransitioner.proveStorageSlotInclusion( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key, + DUMMY_ACCOUNT_STORAGE()[0].val, + test.stateTrieWitness, + wrongTest.storageTrieWitness // Wrong storage trie witness + ) + } catch (e) { + expect(e.toString()).to.contain('Invalid large internal hash') + } + + expect( + await stateManager.isVerifiedStorage( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key + ) + ).to.equal(false) + }) }) }) @@ -710,6 +829,75 @@ describe('StateTransitioner', () => { STATE_TRANSITIONER_PHASES.PRE_EXECUTION ) }) + + it('should succeed even if the underlying transaction reverts', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'doRevert', + [] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.POST_EXECUTION + ) + }) + + it('should fail if the provided transaction data does not match the original data', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'doRevert', + [] + ) + ) + + await TestUtils.assertRevertsAsync( + 'Provided transaction does not match the original transaction.', + async () => { + await stateTransitioner.applyTransaction({ + ...transactionData, + ...{ + timestamp: 12345, // Wrong timestamp. + }, + }) + } + ) + + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.PRE_EXECUTION + ) + }) }) describe('Post-Execution', async () => { @@ -744,7 +932,7 @@ describe('StateTransitioner', () => { expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) - const newStateTrieRoot = await proveAllStorageUpdates( + const { trie, newStateTrieRoot } = await proveAllStorageUpdates( stateTransitioner, stateManager, stateTrie @@ -788,7 +976,7 @@ describe('StateTransitioner', () => { expect(await stateManager.updatedStorageSlotCounter()).to.equal(3) - const newStateTrieRoot = await proveAllStorageUpdates( + const { trie, newStateTrieRoot } = await proveAllStorageUpdates( stateTransitioner, stateManager, stateTrie @@ -832,7 +1020,7 @@ describe('StateTransitioner', () => { expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) - const newStateTrieRoot = await proveAllStorageUpdates( + const { trie, newStateTrieRoot } = await proveAllStorageUpdates( stateTransitioner, stateManager, stateTrie @@ -841,6 +1029,124 @@ describe('StateTransitioner', () => { expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) }) + + it('should revert if the provided state trie witness is invalid', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + + const [ + storageSlotContract, + storageSlotKey, + storageSlotValue, + ] = await stateManager.peekUpdatedStorageSlot() + + const updateTest = await makeAccountStorageUpdateTest( + stateTrie, + storageSlotContract, + storageSlotKey, + storageSlotValue + ) + + const oldStateRoot = await stateTransitioner.stateRoot() + + await TestUtils.assertRevertsAsync( + 'Invalid large internal hash', + async () => { + await stateTransitioner.proveUpdatedStorageSlot( + wrongTest.stateTrieWitness, // Wrong state trie witness + updateTest.storageTrieWitness + ) + } + ) + + expect(await stateTransitioner.stateRoot()).to.equal(oldStateRoot) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + }) + + it('should revert if the provided storage trie witness is invalid', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + + const [ + storageSlotContract, + storageSlotKey, + storageSlotValue, + ] = await stateManager.peekUpdatedStorageSlot() + + const updateTest = await makeAccountStorageUpdateTest( + stateTrie, + storageSlotContract, + storageSlotKey, + storageSlotValue + ) + + const oldStateRoot = await stateTransitioner.stateRoot() + + await TestUtils.assertRevertsAsync( + 'Invalid large internal hash', + async () => { + await stateTransitioner.proveUpdatedStorageSlot( + updateTest.stateTrieWitness, + wrongTest.storageTrieWitness // Wrong storage trie witness + ) + } + ) + + expect(await stateTransitioner.stateRoot()).to.equal(oldStateRoot) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + }) }) describe('proveUpdatedContract(...)', async () => { @@ -875,7 +1181,7 @@ describe('StateTransitioner', () => { // One update for each new contract, plus one nonce update for the creating contract. expect(await stateManager.updatedContractsCounter()).to.equal(2) - const newStateTrieRoot = await proveAllContractUpdates( + const { trie, newStateTrieRoot } = await proveAllContractUpdates( stateTransitioner, stateManager, stateTrie @@ -919,7 +1225,7 @@ describe('StateTransitioner', () => { // One update for each new contract, plus one nonce update for the creating contract. expect(await stateManager.updatedContractsCounter()).to.equal(4) - const newStateTrieRoot = await proveAllContractUpdates( + const { trie, newStateTrieRoot } = await proveAllContractUpdates( stateTransitioner, stateManager, stateTrie @@ -928,6 +1234,100 @@ describe('StateTransitioner', () => { expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) expect(await stateManager.updatedContractsCounter()).to.equal(0) }) + + it('should update when an externally owned account has been modified', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'createContract', + ['0x' + MicroFraudTesterJson.evm.bytecode.object], + true // EOA transaction. + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.proveContractInclusion( + await wallet.getAddress(), + await wallet.getAddress(), + 0, + eoaTest.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + expect(await stateManager.updatedContractsCounter()).to.equal(3) + + const { trie, newStateTrieRoot } = await proveAllContractUpdates( + stateTransitioner, + stateManager, + stateTrie + ) + + expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) + expect(await stateManager.updatedContractsCounter()).to.equal(0) + }) + + it('should revert if the provided state trie witness is invalid', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'createContract', + ['0x' + MicroFraudTesterJson.evm.bytecode.object] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + // One update for each new contract, plus one nonce update for the creating contract. + expect(await stateManager.updatedContractsCounter()).to.equal(2) + + const oldStateRoot = await stateTransitioner.stateRoot() + + await TestUtils.assertRevertsAsync( + 'Invalid large internal hash', + async () => { + await stateTransitioner.proveUpdatedContract( + wrongTest.stateTrieWitness + ) // Wrong state trie witness + } + ) + + expect(await stateTransitioner.stateRoot()).to.equal(oldStateRoot) + expect(await stateManager.updatedContractsCounter()).to.equal(2) + }) }) describe('completeTransition(...)', async () => { @@ -988,11 +1388,13 @@ describe('StateTransitioner', () => { await stateTransitioner.applyTransaction(transactionData) expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) + expect(await stateManager.updatedContractsCounter()).to.equal(1) + + await proveAllContractUpdates(stateTransitioner, stateManager, trie) + expect(await stateManager.updatedContractsCounter()).to.equal(0) await stateTransitioner.completeTransition() - expect(await stateTransitioner.currentTransitionPhase()).to.equal( - STATE_TRANSITIONER_PHASES.COMPLETE - ) + expect(await stateTransitioner.isComplete()).to.equal(true) }) it('should correctly finalize when storage slots are changed', async () => { @@ -1023,14 +1425,143 @@ describe('StateTransitioner', () => { await stateTransitioner.applyTransaction(transactionData) expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + expect(await stateManager.updatedContractsCounter()).to.equal(1) - await proveAllStorageUpdates(stateTransitioner, stateManager, stateTrie) + const { trie, newStateTrieRoot } = await proveAllContractUpdates( + stateTransitioner, + stateManager, + stateTrie + ) + expect(await stateManager.updatedContractsCounter()).to.equal(0) + + await proveAllStorageUpdates(stateTransitioner, stateManager, trie) expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) await stateTransitioner.completeTransition() - expect(await stateTransitioner.currentTransitionPhase()).to.equal( - STATE_TRANSITIONER_PHASES.COMPLETE + expect(await stateTransitioner.isComplete()).to.equal(true) + }) + + it('should not finalize if the transaction has not been executed yet', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness ) + + // Don't apply the transaction + + await TestUtils.assertRevertsAsync( + 'Must be called during the correct phase.', + async () => { + await stateTransitioner.completeTransition() + } + ) + + expect(await stateTransitioner.isComplete()).to.equal(false) + }) + + it('should not finalize if there are still storage slots to be updated', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + + // Don't prove the storage slot updates. + + await TestUtils.assertRevertsAsync( + 'All storage updates must be proven to complete the transition.', + async () => { + await stateTransitioner.completeTransition() + } + ) + + expect(await stateTransitioner.isComplete()).to.equal(false) + }) + + it('should not finalize if there are still contracts to be updated', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + resolver.addressResolver, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + + await proveAllStorageUpdates(stateTransitioner, stateManager, stateTrie) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) + + // Don't prove the contract updates. + + await TestUtils.assertRevertsAsync( + 'All contract updates must be proven to complete the transition.', + async () => { + await stateTransitioner.completeTransition() + } + ) + + expect(await stateTransitioner.isComplete()).to.equal(false) }) }) }) diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts index 30c4d8ad98ac8..1ee712fcd7137 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts @@ -5,48 +5,30 @@ import { ethers } from '@nomiclabs/buidler' import { getLogger, remove0x, - add0x, TestUtils, - getCurrentTime, ZERO_ADDRESS, NULL_ADDRESS, } from '@eth-optimism/core-utils' import { Contract, ContractFactory, Signer } from 'ethers' -import { fromPairs } from 'lodash' /* Internal Imports */ import { GAS_LIMIT, - DEFAULT_OPCODE_WHITELIST_MASK, + OVM_METHOD_IDS, Address, manuallyDeployOvmContract, addressToBytes32Address, didCreateSucceed, - encodeMethodId, - encodeRawArguments, makeAddressResolver, deployAndRegister, AddressResolverMapping, + executeTestTransaction, + executePersistedTestTransaction, } from '../../../test-helpers' /* Logging */ const log = getLogger('execution-manager-calls', true) -export const abi = new ethers.utils.AbiCoder() - -const methodIds = fromPairs( - [ - 'makeCall', - 'makeStaticCall', - 'makeStaticCallThenCall', - 'staticFriendlySLOAD', - 'notStaticFriendlySSTORE', - 'notStaticFriendlyCREATE', - 'notStaticFriendlyCREATE2', - 'makeDelegateCall', - ].map((methodId) => [methodId, encodeMethodId(methodId)]) -) - const sloadKey: string = '11'.repeat(32) const unpopultedSLOADResult: string = '00'.repeat(32) const populatedSLOADResult: string = '22'.repeat(32) @@ -131,9 +113,10 @@ describe('Execution Manager -- Call opcodes', () => { describe('ovmCALL', async () => { it('properly executes ovmCALL to SLOAD', async () => { - const result: string = await executeTransaction( + const result: string = await executeTestTransaction( + executionManager, callContractAddress, - methodIds.staticFriendlySLOAD, + 'staticFriendlySLOAD', [sloadKey] ) log.debug(`Result: [${result}]`) @@ -142,20 +125,23 @@ describe('Execution Manager -- Call opcodes', () => { }) it('properly executes ovmCALL to SSTORE', async () => { - await executePersistedTransaction( + await executePersistedTestTransaction( + executionManager, + wallet, callContractAddress, - methodIds.makeCall, + 'makeCall', [ addressToBytes32Address(callContract2Address), - methodIds.notStaticFriendlySSTORE, + OVM_METHOD_IDS.notStaticFriendlySSTORE, sloadKey, populatedSLOADResult, ] ) - const result: string = await executeTransaction( + const result: string = await executeTestTransaction( + executionManager, callContract2Address, - methodIds.staticFriendlySLOAD, + 'staticFriendlySLOAD', [sloadKey] ) @@ -166,9 +152,10 @@ describe('Execution Manager -- Call opcodes', () => { }) it('properly executes ovmCALL to CREATE', async () => { - const result: string = await executeTransaction( + const result: string = await executeTestTransaction( + executionManager, callContract2Address, - methodIds.notStaticFriendlyCREATE, + 'notStaticFriendlyCREATE', [deployTx.data] ) @@ -183,9 +170,10 @@ describe('Execution Manager -- Call opcodes', () => { }) it('properly executes ovmCALL to CREATE2', async () => { - const result: string = await executeTransaction( + const result: string = await executeTestTransaction( + executionManager, callContract2Address, - methodIds.notStaticFriendlyCREATE2, + 'notStaticFriendlyCREATE2', [0, deployTx.data] ) @@ -202,21 +190,24 @@ describe('Execution Manager -- Call opcodes', () => { describe('ovmDELEGATECALL', async () => { it('properly executes ovmDELEGATECALL to SSTORE', async () => { - await executePersistedTransaction( + await executePersistedTestTransaction( + executionManager, + wallet, callContractAddress, - methodIds.makeDelegateCall, + 'makeDelegateCall', [ addressToBytes32Address(callContract2Address), - methodIds.notStaticFriendlySSTORE, + OVM_METHOD_IDS.notStaticFriendlySSTORE, sloadKey, populatedSLOADResult, ] ) // Stored in contract 2 via delegate call but accessed via contract 1 - const result: string = await executeTransaction( + const result: string = await executeTestTransaction( + executionManager, callContractAddress, - methodIds.staticFriendlySLOAD, + 'staticFriendlySLOAD', [sloadKey] ) @@ -227,9 +218,10 @@ describe('Execution Manager -- Call opcodes', () => { 'SLOAD should yield stored result!' ) - const contract2Result: string = await executeTransaction( + const contract2Result: string = await executeTestTransaction( + executionManager, callContract2Address, - methodIds.staticFriendlySLOAD, + 'staticFriendlySLOAD', [sloadKey] ) @@ -244,22 +236,25 @@ describe('Execution Manager -- Call opcodes', () => { it('properly executes nested ovmDELEGATECALLs to SSTORE', async () => { // contract 1 delegate calls contract 2 delegate calls contract 3 - const result = await executePersistedTransaction( + const result = await executePersistedTestTransaction( + executionManager, + wallet, callContractAddress, - methodIds.makeDelegateCall, + 'makeDelegateCall', [ addressToBytes32Address(callContract2Address), - methodIds.makeDelegateCall, + OVM_METHOD_IDS.makeDelegateCall, addressToBytes32Address(callContract3Address), - methodIds.notStaticFriendlySSTORE, + OVM_METHOD_IDS.notStaticFriendlySSTORE, sloadKey, populatedSLOADResult, ] ) - const contract1Result: string = await executeTransaction( + const contract1Result: string = await executeTestTransaction( + executionManager, callContractAddress, - methodIds.staticFriendlySLOAD, + 'staticFriendlySLOAD', [sloadKey] ) @@ -271,9 +266,10 @@ describe('Execution Manager -- Call opcodes', () => { 'SLOAD should yield stored data!' ) - const contract2Result: string = await executeTransaction( + const contract2Result: string = await executeTestTransaction( + executionManager, callContract2Address, - methodIds.staticFriendlySLOAD, + 'staticFriendlySLOAD', [sloadKey] ) @@ -285,9 +281,10 @@ describe('Execution Manager -- Call opcodes', () => { 'SLOAD should not yield any data (0 x 32 bytes)!' ) - const contract3Result: string = await executeTransaction( + const contract3Result: string = await executeTestTransaction( + executionManager, callContract3Address, - methodIds.staticFriendlySLOAD, + 'staticFriendlySLOAD', [sloadKey] ) @@ -303,12 +300,13 @@ describe('Execution Manager -- Call opcodes', () => { describe('ovmSTATICCALL', async () => { it('properly executes ovmSTATICCALL to SLOAD', async () => { - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContract2Address), - methodIds.staticFriendlySLOAD, + OVM_METHOD_IDS.staticFriendlySLOAD, sloadKey, ] ) @@ -319,14 +317,15 @@ describe('Execution Manager -- Call opcodes', () => { }) it('properly executes nested ovmSTATICCALL to SLOAD', async () => { - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContract2Address), - methodIds.makeStaticCall, + OVM_METHOD_IDS.makeStaticCall, addressToBytes32Address(callContract2Address), - methodIds.staticFriendlySLOAD, + OVM_METHOD_IDS.staticFriendlySLOAD, sloadKey, ] ) @@ -338,21 +337,24 @@ describe('Execution Manager -- Call opcodes', () => { it('successfully makes static call then call', async () => { // Should not throw - await executeTransaction( + await executeTestTransaction( + executionManager, callContractAddress, - methodIds.makeStaticCallThenCall, + 'makeStaticCallThenCall', [addressToBytes32Address(callContractAddress)] ) }) it('remains in static context when exiting nested static context', async () => { await TestUtils.assertThrowsAsync(async () => { - await executePersistedTransaction( + await executePersistedTestTransaction( + executionManager, + wallet, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContractAddress), - methodIds.makeStaticCallThenCall, + OVM_METHOD_IDS.makeStaticCallThenCall, addressToBytes32Address(callContractAddress), ] ) @@ -361,12 +363,14 @@ describe('Execution Manager -- Call opcodes', () => { it('fails on ovmSTATICCALL to SSTORE', async () => { await TestUtils.assertThrowsAsync(async () => { - await executePersistedTransaction( + await executePersistedTestTransaction( + executionManager, + wallet, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContractAddress), - methodIds.notStaticFriendlySSTORE, + OVM_METHOD_IDS.notStaticFriendlySSTORE, sloadKey, populatedSLOADResult, ] @@ -375,12 +379,14 @@ describe('Execution Manager -- Call opcodes', () => { }) it('Fails to create on ovmSTATICCALL to CREATE -- tx', async () => { - const hash = await executePersistedTransaction( + const hash = await executePersistedTestTransaction( + executionManager, + wallet, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContractAddress), - methodIds.notStaticFriendlyCREATE, + OVM_METHOD_IDS.notStaticFriendlyCREATE, deployTx.data, ] ) @@ -390,12 +396,13 @@ describe('Execution Manager -- Call opcodes', () => { }) it('Fails to create on ovmSTATICCALL to CREATE -- call', async () => { - const address = await executeTransaction( + const address = await executeTestTransaction( + executionManager, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContractAddress), - methodIds.notStaticFriendlyCREATE, + OVM_METHOD_IDS.notStaticFriendlyCREATE, deployTx.data, ] ) @@ -407,12 +414,14 @@ describe('Execution Manager -- Call opcodes', () => { }) it('fails on ovmSTATICCALL to CREATE2 -- tx', async () => { - const hash = await executePersistedTransaction( + const hash = await executePersistedTestTransaction( + executionManager, + wallet, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContractAddress), - methodIds.notStaticFriendlyCREATE2, + OVM_METHOD_IDS.notStaticFriendlyCREATE2, 0, deployTx.data, ] @@ -423,12 +432,13 @@ describe('Execution Manager -- Call opcodes', () => { }) it('fails on ovmSTATICCALL to CREATE2 -- call', async () => { - const res = await executeTransaction( + const res = await executeTestTransaction( + executionManager, callContractAddress, - methodIds.makeStaticCall, + 'makeStaticCall', [ addressToBytes32Address(callContractAddress), - methodIds.notStaticFriendlyCREATE2, + OVM_METHOD_IDS.notStaticFriendlyCREATE2, 0, deployTx.data, ] @@ -440,57 +450,4 @@ describe('Execution Manager -- Call opcodes', () => { ) }) }) - - const executePersistedTransaction = async ( - contractAddress: string, - methodId: string, - args: any[] - ): Promise => { - const callBytes = add0x(methodId + encodeRawArguments(args)) - const data = executionManager.interface.encodeFunctionData( - 'executeTransaction', - [ - getCurrentTime(), - 0, - callContractAddress, - callBytes, - ZERO_ADDRESS, - ZERO_ADDRESS, - true, - ] - ) - - const receipt = await wallet.sendTransaction({ - to: executionManager.address, - data: add0x(data), - gasLimit: GAS_LIMIT, - }) - - return receipt.hash - } - - const executeTransaction = async ( - contractAddress: string, - methodId: string, - args: any[] - ): Promise => { - const callBytes = add0x(methodId + encodeRawArguments(args)) - const data = executionManager.interface.encodeFunctionData( - 'executeTransaction', - [ - getCurrentTime(), - 0, - contractAddress, - callBytes, - ZERO_ADDRESS, - ZERO_ADDRESS, - true, - ] - ) - return executionManager.provider.call({ - to: executionManager.address, - data, - gasLimit: GAS_LIMIT, - }) - } }) diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.code-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.code-opcodes.spec.ts index 1ada856fc5f4b..09ee14898c7aa 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.code-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.code-opcodes.spec.ts @@ -15,7 +15,6 @@ import { Contract, ContractFactory, Signer } from 'ethers' /* Internal Imports */ import { GAS_LIMIT, - DEFAULT_OPCODE_WHITELIST_MASK, Address, manuallyDeployOvmContract, executeOVMCall, @@ -28,8 +27,6 @@ import { /* Logging */ const log = getLogger('execution-manager-code-opcodes', true) -export const abi = new ethers.utils.AbiCoder() - /* Tests */ describe('Execution Manager -- Code-related opcodes', () => { const provider = ethers.provider diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts index 5a961c5591e08..e1031c8e58041 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts @@ -7,47 +7,29 @@ import { hexStrToNumber, remove0x, add0x, - ZERO_ADDRESS, TestUtils, getCurrentTime, NULL_ADDRESS, + ZERO_ADDRESS, } from '@eth-optimism/core-utils' -import { Contract, ContractFactory, Signer } from 'ethers' -import { fromPairs } from 'lodash' +import { Contract, ContractFactory, Signer, BigNumber } from 'ethers' /* Internal Imports */ import { + OVM_METHOD_IDS, GAS_LIMIT, - DEFAULT_OPCODE_WHITELIST_MASK, Address, manuallyDeployOvmContract, addressToBytes32Address, - encodeRawArguments, - encodeMethodId, makeAddressResolver, deployAndRegister, AddressResolverMapping, + executeTestTransaction, } from '../../../test-helpers' /* Logging */ const log = getLogger('execution-manager-context', true) -export const abi = new ethers.utils.AbiCoder() - -const methodIds = fromPairs( - [ - 'callThroughExecutionManager', - 'getADDRESS', - 'getCALLER', - 'getGASLIMIT', - 'getQueueOrigin', - 'getTIMESTAMP', - 'getCHAINID', - 'ovmADDRESS', - 'ovmCALLER', - ].map((methodId) => [methodId, encodeMethodId(methodId)]) -) - /* Tests */ describe('Execution Manager -- Context opcodes', () => { const provider = ethers.provider @@ -115,15 +97,21 @@ describe('Execution Manager -- Context opcodes', () => { describe('ovmCALLER', async () => { it('reverts when CALLER is not set', async () => { await TestUtils.assertThrowsAsync(async () => { - await executeTransaction(contractAddress, methodIds.ovmCALLER, []) + await executeTestTransaction( + executionManager, + contractAddress, + 'ovmCALLER', + [] + ) }) }) it('properly retrieves CALLER when caller is set', async () => { - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, contractAddress, - methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getCALLER] + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.getCALLER] ) log.debug(`CALLER result: ${result}`) @@ -135,15 +123,21 @@ describe('Execution Manager -- Context opcodes', () => { describe('ovmADDRESS', async () => { it('reverts when ADDRESS is not set', async () => { await TestUtils.assertThrowsAsync(async () => { - await executeTransaction(contractAddress, methodIds.ovmADDRESS, []) + await executeTestTransaction( + executionManager, + contractAddress, + 'ovmADDRESS', + [] + ) }) }) it('properly retrieves ADDRESS when address is set', async () => { - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, contractAddress, - methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getADDRESS] + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.getADDRESS] ) log.debug(`ADDRESS result: ${result}`) @@ -156,10 +150,11 @@ describe('Execution Manager -- Context opcodes', () => { describe('ovmTIMESTAMP', async () => { it('properly retrieves TIMESTAMP', async () => { const timestamp: number = getCurrentTime() - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, contractAddress, - methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getTIMESTAMP] + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.getTIMESTAMP] ) log.debug(`TIMESTAMP result: ${result}`) @@ -175,10 +170,11 @@ describe('Execution Manager -- Context opcodes', () => { describe('ovmCHAINID', async () => { it('properly retrieves CHAINID', async () => { const chainId: number = 108 - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, contractAddress, - methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getCHAINID] + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.getCHAINID] ) log.debug(`CHAINID result: ${result}`) @@ -190,10 +186,11 @@ describe('Execution Manager -- Context opcodes', () => { describe('ovmGASLIMIT', async () => { it('properly retrieves GASLIMIT', async () => { - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, contractAddress, - methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getGASLIMIT] + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.getGASLIMIT] ) log.debug(`GASLIMIT result: ${result}`) @@ -206,10 +203,11 @@ describe('Execution Manager -- Context opcodes', () => { describe('ovmQueueOrigin', async () => { it('gets Queue Origin when it is 0', async () => { const queueOrigin: string = '00'.repeat(32) - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, contractAddress, - methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getQueueOrigin] + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.getQueueOrigin] ) log.debug(`QUEUE ORIGIN result: ${result}`) @@ -220,10 +218,11 @@ describe('Execution Manager -- Context opcodes', () => { it('properly retrieves Queue Origin when queue origin is set', async () => { const queueOrigin: string = '00'.repeat(30) + '1111' - const result = await executeTransaction( + const result = await executeTestTransaction( + executionManager, contractAddress, - methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getQueueOrigin], + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.getQueueOrigin], add0x(queueOrigin) ) @@ -234,29 +233,75 @@ describe('Execution Manager -- Context opcodes', () => { }) }) - const executeTransaction = async ( - address: string, - methodId: string, - args: any[], - queueOrigin = ZERO_ADDRESS - ): Promise => { - const callBytes = add0x(methodId + encodeRawArguments(args)) - const data = executionManager.interface.encodeFunctionData( - 'executeTransaction', - [ - getCurrentTime(), - queueOrigin, - address, - callBytes, - ZERO_ADDRESS, + describe('ovmBlockGasLimit', async () => { + it('should retrieve the block gas limit', async () => { + const result = await executeTestTransaction( + executionManager, + contractAddress, + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.ovmBlockGasLimit] + ) + + const resultNum = BigNumber.from(result).toNumber() + resultNum.should.equal(GAS_LIMIT, 'Block gas limit was incorrect') + }) + }) + + describe('isStaticContext', async () => { + it('should be true when inside a static context', async () => { + const result = await executeTestTransaction( + executionManager, + contractAddress, + 'staticCallThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.isStaticContext] + ) + + const resultNum = BigNumber.from(result).toNumber() + resultNum.should.equal(1, 'Context is not static but should be') + }) + + it('should be false when not in a static context', async () => { + const result = await executeTestTransaction( + executionManager, + contractAddress, + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.isStaticContext] + ) + + const resultNum = BigNumber.from(result).toNumber() + resultNum.should.equal(0, 'Context is static but should not be') + }) + }) + + describe('ovmORIGIN', async () => { + it('should give us the origin of the transaction', async () => { + const origin = await wallet.getAddress() + const result = await executeTestTransaction( + executionManager, + contractAddress, + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.ovmORIGIN], ZERO_ADDRESS, - true, - ] - ) - return executionManager.provider.call({ - to: executionManager.address, - data, - gasLimit: GAS_LIMIT, + origin + ) + + result.should.equal( + addressToBytes32Address(origin), + 'Returned origin is incorrect' + ) }) - } + + it('should revert if the transaction has no origin', async () => { + await TestUtils.assertThrowsAsync(async () => { + await executeTestTransaction( + executionManager, + contractAddress, + 'callThroughExecutionManager', + [contract2Address32, OVM_METHOD_IDS.ovmORIGIN], + ZERO_ADDRESS, + ZERO_ADDRESS + ) + }) + }) + }) }) diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.create-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.create-opcodes.spec.ts index 305267d023000..45aa926bb0d84 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.create-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.create-opcodes.spec.ts @@ -5,35 +5,25 @@ import { ethers } from '@nomiclabs/buidler' import { getLogger, remove0x, - add0x, TestUtils, NULL_ADDRESS, } from '@eth-optimism/core-utils' import { Contract, ContractFactory, Signer } from 'ethers' -import { fromPairs } from 'lodash' /* Internal Imports */ import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, executeOVMCall, - encodeMethodId, - encodeRawArguments, makeAddressResolver, deployAndRegister, AddressResolverMapping, + encodeFunctionData, } from '../../../test-helpers' /* Logging */ const log = getLogger('execution-manager-create', true) -const methodIds = fromPairs( - ['ovmCREATE', 'ovmCREATE2'].map((methodId) => [ - methodId, - encodeMethodId(methodId), - ]) -) - /* Tests */ describe('ExecutionManager -- Create opcodes', () => { let wallet: Signer @@ -113,9 +103,8 @@ describe('ExecutionManager -- Create opcodes', () => { safetyChecker.address ) - const data = add0x( - methodIds.ovmCREATE + encodeRawArguments([deployInvalidTx.data]) - ) + const data = encodeFunctionData('ovmCREATE', [deployInvalidTx.data]) + await TestUtils.assertRevertsAsync( 'Contract init (creation) code is not safe', async () => { @@ -136,9 +125,7 @@ describe('ExecutionManager -- Create opcodes', () => { stubSafetyChecker.address ) - const data = add0x( - methodIds.ovmCREATE2 + encodeRawArguments([0, deployTx.data]) - ) + const data = encodeFunctionData('ovmCREATE2', [0, deployTx.data]) // Now actually apply it to our execution manager const result = await executionManager.provider.call({ @@ -160,9 +147,7 @@ describe('ExecutionManager -- Create opcodes', () => { safetyChecker.address ) - const data = add0x( - methodIds.ovmCREATE2 + encodeRawArguments([0, deployInvalidTx.data]) - ) + const data = encodeFunctionData('ovmCREATE2', [0, deployInvalidTx.data]) await TestUtils.assertRevertsAsync( 'Contract init (creation) code is not safe', diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts index 6de8b64c83c5e..e2fa3dd0bfee0 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts @@ -30,13 +30,11 @@ import { /* Logging */ const log = getLogger('execution-manager-calls', true) -export const abi = new ethers.utils.AbiCoder() - /* Tests */ describe('Execution Manager -- Call opcodes', () => { const provider = ethers.provider - const [wallet] = getWallets() + const [wallet] = getWallets(provider) let resolver: AddressResolverMapping before(async () => { diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts index 36043d1efa5f1..10531cd7349c5 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts @@ -9,89 +9,28 @@ import { bufToHexString, ZERO_ADDRESS, NULL_ADDRESS, + abi, } from '@eth-optimism/core-utils' import { Contract, Signer, ContractFactory } from 'ethers' import * as ethereumjsAbi from 'ethereumjs-abi' -import { cloneDeep, fromPairs } from 'lodash' /* Internal Imports */ import { GAS_LIMIT, - DEFAULT_OPCODE_WHITELIST_MASK, L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS, Address, manuallyDeployOvmContract, addressToBytes32Address, - encodeMethodId, - encodeRawArguments, makeAddressResolver, deployAndRegister, AddressResolverMapping, + callExecutionManagerExecuteTransaction, + encodeFunctionData, } from '../../../test-helpers' -/* Contract Imports */ -import * as ExecutionManagerJson from '../../../../artifacts/ExecutionManager.json' - /* Logging */ const log = getLogger('l2-to-l1-messaging', true) -export const abi = new ethers.utils.AbiCoder() - -const methodIds = fromPairs( - ['makeCall'].map((methodId) => [methodId, encodeMethodId(methodId)]) -) - -/*********** - * HELPERS * - **********/ - -/** - * Override the ABI description of a particular function, changing it's `constant` & `outputs` values. - * @param {Array} an abi object. - * @param {string} the name of the function we would like to change. - * @param {Object} an object containing the new `constant` & `outputs` values. - */ -function overrideAbiFunctionData( - abiDefinition: any, - functionName: string, - functionData: { constant: boolean; outputs: any[]; stateMutability: string } -): void { - for (const functionDefinition of abiDefinition) { - if (functionDefinition.name === functionName) { - functionDefinition.constant = functionData.constant - functionDefinition.outputs = functionData.outputs.map((output) => { - return { internalType: output, name: '', type: output } - }) - functionDefinition.stateMutability = functionData.stateMutability - } - } -} - -/** - * Use executeTransaction with `eth_call`. - * @param {ethers.Contract} an ExecutionManager contract instance used for it's address & provider. - * @param {Array} an array of parameters which should be fed into `executeTransaction(...)`. - * @param {OutputTypes} an array ABI types which should be used to decode the output of the call. - */ -function callExecutionManagerExecuteTransaction( - executionManager: Contract, - parameters: any[], - outputTypes: any[] -): Promise { - const modifiedAbi = cloneDeep(ExecutionManagerJson.abi) - overrideAbiFunctionData(modifiedAbi, 'executeTransaction', { - constant: true, - outputs: outputTypes, - stateMutability: 'view', - }) - const callableExecutionManager = new Contract( - executionManager.address, - modifiedAbi, - executionManager.provider - ) - return callableExecutionManager.executeTransaction.apply(null, parameters) -} - /* Tests */ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { const provider = ethers.provider @@ -140,17 +79,12 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { describe('OVM L2 -> L1 message passer', () => { it(`Should emit the right msg.sender and calldata when an L2->L1 call is made`, async () => { const bytesToSendToL1 = '0x123412341234deadbeef' - const callBytes = add0x( - methodIds.makeCall + - encodeRawArguments([ - addressToBytes32Address(L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS), - ethereumjsAbi.methodID('passMessageToL1', ['bytes']), - abi.encode(['bytes'], [bytesToSendToL1]), - ]) - ) - const passMessageToL1MethodId = bufToHexString( - ethereumjsAbi.methodID('passMessageToL1', ['bytes']) - ) + const callBytes = encodeFunctionData('makeCall', [ + addressToBytes32Address(L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS), + ethereumjsAbi.methodID('passMessageToL1', ['bytes']), + abi.encode(['bytes'], [bytesToSendToL1]), + ]) + const data = executionManager.interface.encodeFunctionData( 'executeTransaction', [ diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.recover-eoa-address.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.recover-eoa-address.spec.ts index 8718cf2eb7a14..1c9cf584213ec 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.recover-eoa-address.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.recover-eoa-address.spec.ts @@ -8,7 +8,6 @@ import { Contract, ContractFactory } from 'ethers' /* Internal Imports */ import { CHAIN_ID, - DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, signTransaction, getSignedComponents, @@ -21,11 +20,9 @@ import { /* Logging */ const log = getLogger('execution-manager-recover-eoa-address', true) -export const abi = new ethers.utils.AbiCoder() - /* Tests */ describe('Execution Manager -- Recover EOA Address', () => { - const [wallet] = getWallets() + const [wallet] = getWallets(ethers.provider) let resolver: AddressResolverMapping before(async () => { diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.storage-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.storage-opcodes.spec.ts index a4dc971b59153..afe0ef14796c6 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.storage-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.storage-opcodes.spec.ts @@ -2,18 +2,17 @@ import '../../../setup' /* External Imports */ import { ethers } from '@nomiclabs/buidler' -import { abi, getLogger, add0x, NULL_ADDRESS } from '@eth-optimism/core-utils' +import { abi, getLogger, NULL_ADDRESS } from '@eth-optimism/core-utils' import { Contract, Signer, ContractFactory } from 'ethers' /* Internal Imports */ import { - DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, - encodeMethodId, - encodeRawArguments, + encodeFunctionData, makeAddressResolver, deployAndRegister, AddressResolverMapping, + fillHexBytes, } from '../../../test-helpers' /* Logging */ @@ -22,8 +21,8 @@ const log = getLogger('execution-manager-storage', true) /* Tests */ describe('ExecutionManager -- Storage opcodes', () => { const provider = ethers.provider - const ONE_FILLED_BYTES_32 = '0x' + '11'.repeat(32) - const TWO_FILLED_BYTES_32 = '0x' + '22'.repeat(32) + const ONE_FILLED_BYTES_32 = fillHexBytes('11') + const TWO_FILLED_BYTES_32 = fillHexBytes('22') let wallet: Signer before(async () => { @@ -54,10 +53,11 @@ describe('ExecutionManager -- Storage opcodes', () => { }) const sstore = async (): Promise => { - const data = add0x( - encodeMethodId('ovmSSTORE') + - encodeRawArguments([ONE_FILLED_BYTES_32, TWO_FILLED_BYTES_32]) - ) + const data = encodeFunctionData('ovmSSTORE', [ + ONE_FILLED_BYTES_32, + TWO_FILLED_BYTES_32, + ]) + // Now actually apply it to our execution manager const tx = await wallet.sendTransaction({ to: executionManager.address, @@ -94,10 +94,10 @@ describe('ExecutionManager -- Storage opcodes', () => { it('loads a value immediately after it is stored', async () => { await sstore() - const data = add0x( - encodeMethodId('ovmSLOAD') + - encodeRawArguments([ONE_FILLED_BYTES_32, TWO_FILLED_BYTES_32]) - ) + const data = encodeFunctionData('ovmSLOAD', [ + ONE_FILLED_BYTES_32, + TWO_FILLED_BYTES_32, + ]) // Now actually apply it to our execution manager const result = await executionManager.provider.call({ diff --git a/packages/contracts/test/test-helpers/batch-helpers.ts b/packages/contracts/test/test-helpers/batch-helpers.ts index f1b547fdeedfc..7e347bb7eb1cd 100644 --- a/packages/contracts/test/test-helpers/batch-helpers.ts +++ b/packages/contracts/test/test-helpers/batch-helpers.ts @@ -1,3 +1,6 @@ +import { Contract, Signer } from 'ethers' +import { TxChainBatch, TxQueueBatch, StateChainBatch } from './types' + export function makeRepeatedBytes(value: string, length: number): string { const repeated = value.repeat((length * 2) / value.length + 1) return '0x' + repeated.slice(0, length * 2) @@ -14,3 +17,182 @@ export function makeRandomBlockOfSize(blockSize: number): string[] { export function makeRandomBatchOfSize(batchSize: number): string[] { return makeRandomBlockOfSize(batchSize) } + +export const appendSequencerBatch = async ( + canonicalTransactionChain: Contract, + sequencer: Signer, + batch: string[] +): Promise => { + const timestamp = Math.floor(Date.now() / 1000) + // Submit the rollup batch on-chain + await canonicalTransactionChain + .connect(sequencer) + .appendSequencerBatch(batch, timestamp) + return timestamp +} + +export const appendAndGenerateSequencerBatch = async ( + canonicalTransactionChain: Contract, + sequencer: Signer, + batch: string[], + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + const timestamp = await appendSequencerBatch( + canonicalTransactionChain, + sequencer, + batch + ) + + return createTxChainBatch( + batch, + timestamp, + false, + batchIndex, + cumulativePrevElements + ) +} + +export const createTxChainBatch = async ( + batch: string[], + timestamp: number, + isL1ToL2Tx: boolean, + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + const localBatch = new TxChainBatch( + timestamp, + isL1ToL2Tx, + batchIndex, + cumulativePrevElements, + batch + ) + await localBatch.generateTree() + return localBatch +} + +export const enqueueAndGenerateL1ToL2Batch = async ( + provider: any, + l1ToL2Queue: Contract, + l1ToL2TransactionPasser: Signer, + tx: string +): Promise => { + // Submit the rollup batch on-chain + const enqueueTx = await l1ToL2Queue + .connect(l1ToL2TransactionPasser) + .enqueueTx(tx) + const localBatch = await generateQueueBatch(provider, tx, enqueueTx.hash) + return localBatch +} + +export const enqueueAndGenerateSafetyBatch = async ( + provider: any, + safetyQueue: Contract, + randomWallet: Signer, + tx: string +): Promise => { + const enqueueTx = await safetyQueue.connect(randomWallet).enqueueTx(tx) + const localBatch = await generateQueueBatch(provider, tx, enqueueTx.hash) + return localBatch +} + +export const generateQueueBatch = async ( + provider: any, + tx: string, + txHash: string +): Promise => { + const txReceipt = await provider.getTransactionReceipt(txHash) + const timestamp = (await provider.getBlock(txReceipt.blockNumber)).timestamp + // Generate a local version of the rollup batch + const localBatch = new TxQueueBatch(tx, timestamp) + await localBatch.generateTree() + return localBatch +} + +export const generateStateBatch = async ( + batch: string[], + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + const localBatch = new StateChainBatch( + batchIndex, + cumulativePrevElements, + batch + ) + await localBatch.generateTree() + return localBatch +} + +export const generateTxBatch = async ( + batch: string[], + timestamp: number, + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + const localBatch = new TxChainBatch( + timestamp, + false, + batchIndex, + cumulativePrevElements, + batch + ) + await localBatch.generateTree() + return localBatch +} + +export const appendAndGenerateStateBatch = async ( + stateChain: Contract, + batch: string[], + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + await stateChain.appendStateBatch(batch) + // Generate a local version of the rollup batch + const localBatch = new StateChainBatch( + batchIndex, + cumulativePrevElements, + batch + ) + await localBatch.generateTree() + return localBatch +} + +export const appendTxBatch = async ( + canonicalTxChain: Contract, + sequencer: Signer, + batch: string[] +): Promise => { + const timestamp = Math.floor(Date.now() / 1000) + // Submit the rollup batch on-chain + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch(batch, timestamp) + + return timestamp +} + +export const appendAndGenerateTransactionBatch = async ( + canonicalTransactionChain: Contract, + sequencer: Signer, + batch: string[], + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + const timestamp = await appendTxBatch( + canonicalTransactionChain, + sequencer, + batch + ) + + const localBatch = new TxChainBatch( + timestamp, + false, + batchIndex, + cumulativePrevElements, + batch + ) + + await localBatch.generateTree() + + return localBatch +} diff --git a/packages/contracts/test/test-helpers/constants.ts b/packages/contracts/test/test-helpers/constants.ts index ff913374b3a55..3169009bab2b4 100644 --- a/packages/contracts/test/test-helpers/constants.ts +++ b/packages/contracts/test/test-helpers/constants.ts @@ -1,10 +1,12 @@ /* External Imports */ import { ethers } from 'ethers' import { defaultAccounts } from 'ethereum-waffle' +import { + Opcode, + DEFAULT_UNSAFE_OPCODES as UNSAFE_OPCODES, +} from '@eth-optimism/rollup-core' /* Internal Imports */ -import { EVMOpcode, Opcode } from './types' - export { ZERO_ADDRESS } from '@eth-optimism/core-utils' export const DEFAULT_ACCOUNTS = defaultAccounts @@ -15,33 +17,6 @@ export const DEFAULT_ACCOUNTS_BUIDLER = defaultAccounts.map((account) => { } }) -export const DEFAULT_UNSAFE_OPCODES: EVMOpcode[] = [ - Opcode.ADDRESS, - Opcode.BALANCE, - Opcode.BLOCKHASH, - Opcode.CALLCODE, - Opcode.CALLER, - Opcode.CHAINID, - Opcode.COINBASE, - Opcode.CREATE, - Opcode.CREATE2, - Opcode.DELEGATECALL, - Opcode.DIFFICULTY, - Opcode.EXTCODESIZE, - Opcode.EXTCODECOPY, - Opcode.EXTCODEHASH, - Opcode.GASLIMIT, - Opcode.GASPRICE, - Opcode.NUMBER, - Opcode.ORIGIN, - Opcode.SELFBALANCE, - Opcode.SELFDESTRUCT, - Opcode.SLOAD, - Opcode.SSTORE, - Opcode.STATICCALL, - Opcode.TIMESTAMP, -] - export const GAS_LIMIT = 1_000_000_000 export const DEFAULT_OPCODE_WHITELIST_MASK = '0x600a0000000000000000001fffffffffffffffff0fcf000063f000013fff0fff' @@ -52,3 +27,21 @@ export const L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS = export const CHAIN_ID = 108 export const ZERO_UINT = '00'.repeat(32) export const DEFAULT_FORCE_INCLUSION_PERIOD = 600 + +export const DEFAULT_UNSAFE_OPCODES = UNSAFE_OPCODES.concat([Opcode.CHAINID]) + +export const HALTING_OPCODES = Opcode.HALTING_OP_CODES +export const HALTING_OPCODES_NO_JUMP = HALTING_OPCODES.filter( + (x) => x.name !== 'JUMP' +) +export const JUMP_OPCODES = [Opcode.JUMP, Opcode.JUMPI] +export const WHITELISTED_NOT_HALTING_OR_CALL = Opcode.ALL_OP_CODES.filter( + (x) => + DEFAULT_UNSAFE_OPCODES.indexOf(x) < 0 && + HALTING_OPCODES.indexOf(x) < 0 && + x.name !== 'CALL' +) + +export const fillHexBytes = (byte: string): string => { + return '0x' + byte.repeat(32) +} diff --git a/packages/contracts/test/test-helpers/deployment-helpers.ts b/packages/contracts/test/test-helpers/deployment-helpers.ts new file mode 100644 index 0000000000000..34d45c84fd47c --- /dev/null +++ b/packages/contracts/test/test-helpers/deployment-helpers.ts @@ -0,0 +1,48 @@ +/* External Imports */ +import { ethers } from '@nomiclabs/buidler' +import { Contract, Signer, Wallet, ContractFactory } from 'ethers' + +/* Internal Imports */ +import { + RollupDeployConfig, + AddressResolverMapping, + deployAllContracts, + deployAndRegister as originalDeployAndRegister, +} from '../../src' +import { GAS_LIMIT, DEFAULT_FORCE_INCLUSION_PERIOD } from './constants' + +export const makeAddressResolver = async ( + wallet: Signer | Wallet +): Promise => { + const [owner, sequencer, l1ToL2TransactionPasser] = await ethers.getSigners() + + const config: RollupDeployConfig = { + signer: wallet, + rollupOptions: { + gasLimit: GAS_LIMIT, + forceInclusionPeriod: DEFAULT_FORCE_INCLUSION_PERIOD, + owner: wallet, + sequencer, + l1ToL2TransactionPasser, + }, + } + + return deployAllContracts(config) +} + +export const deployAndRegister = async ( + addressResolver: Contract, + signer: Signer, + name: string, + deployConfig: { + factory: ContractFactory + params: any[] + } +): Promise => { + return originalDeployAndRegister(addressResolver, name, { + ...deployConfig, + ...{ + signer, + }, + }) +} diff --git a/packages/contracts/test/test-helpers/ethereum-helpers.ts b/packages/contracts/test/test-helpers/ethereum-helpers.ts index c2f4a0b9ef0bb..4d7870edb33e5 100644 --- a/packages/contracts/test/test-helpers/ethereum-helpers.ts +++ b/packages/contracts/test/test-helpers/ethereum-helpers.ts @@ -1,12 +1,11 @@ import * as path from 'path' import * as fs from 'fs' -import { Transaction } from 'ethers/utils' +import { Transaction, keccak256 } from 'ethers/utils' import { Log } from 'ethers/providers' import * as ethereumjsAbi from 'ethereumjs-abi' import { add0x, remove0x, - keccak256, abi, strToHexStr, bufferUtils, @@ -26,9 +25,9 @@ export const buildCreate2Address = ( saltHex: string, byteCode: string ): string => { - const preimage: string = `ff${remove0x(creatorAddress)}${remove0x( + const preimage: string = `0xff${remove0x(creatorAddress)}${remove0x( saltHex - )}${keccak256(byteCode)}` + )}${remove0x(keccak256(byteCode))}` return add0x( keccak256(preimage) .slice(-40) @@ -140,3 +139,19 @@ export const compile = ( } return JSON.parse(compiler.compile(JSON.stringify(input))) } + +export const encodeFunctionData = ( + functionName: string, + functionParams: any[] = [] +): string => { + return add0x( + encodeMethodId(functionName) + encodeRawArguments(functionParams) + ) +} + +export const getCodeHash = async ( + provider: any, + address: string +): Promise => { + return keccak256(await provider.getCode(address)) +} diff --git a/packages/contracts/test/test-helpers/execution-manager-helpers.ts b/packages/contracts/test/test-helpers/execution-manager-helpers.ts new file mode 100644 index 0000000000000..65453bf1c0eb6 --- /dev/null +++ b/packages/contracts/test/test-helpers/execution-manager-helpers.ts @@ -0,0 +1,147 @@ +/* External Imports */ +import { Contract, Signer } from 'ethers' +import { fromPairs, cloneDeep } from 'lodash' +import { getCurrentTime, ZERO_ADDRESS, add0x } from '@eth-optimism/core-utils' + +/* Internal Imports */ +import { GAS_LIMIT } from './constants' +import { encodeMethodId, encodeFunctionData } from './ethereum-helpers' + +/* Contract Imports */ +import * as ExecutionManagerJson from '../../artifacts/ExecutionManager.json' + +export const OVM_METHOD_IDS = fromPairs( + [ + 'makeCall', + 'makeDelegateCall', + 'makeStaticCall', + 'makeStaticCallThenCall', + 'callThroughExecutionManager', + 'staticCallThroughExecutionManager', + 'staticFriendlySLOAD', + 'notStaticFriendlySSTORE', + 'notStaticFriendlyCREATE', + 'notStaticFriendlyCREATE2', + 'getADDRESS', + 'getCALLER', + 'getGASLIMIT', + 'getQueueOrigin', + 'getTIMESTAMP', + 'getCHAINID', + 'ovmADDRESS', + 'ovmCALLER', + 'ovmCREATE', + 'ovmCREATE2', + 'ovmORIGIN', + 'ovmBlockGasLimit', + 'isStaticContext', + ].map((methodId) => [methodId, encodeMethodId(methodId)]) +) + +/** + * Override the ABI description of a particular function, changing it's `constant` & `outputs` values. + * @param {Array} an abi object. + * @param {string} the name of the function we would like to change. + * @param {Object} an object containing the new `constant` & `outputs` values. + */ +export function overrideAbiFunctionData( + abiDefinition: any, + functionName: string, + functionData: { constant: boolean; outputs: any[]; stateMutability: string } +): void { + for (const functionDefinition of abiDefinition) { + if (functionDefinition.name === functionName) { + functionDefinition.constant = functionData.constant + functionDefinition.outputs = functionData.outputs.map((output) => { + return { internalType: output, name: '', type: output } + }) + functionDefinition.stateMutability = functionData.stateMutability + } + } +} + +/** + * Use executeTransaction with `eth_call`. + * @param {ethers.Contract} an ExecutionManager contract instance used for it's address & provider. + * @param {Array} an array of parameters which should be fed into `executeTransaction(...)`. + * @param {OutputTypes} an array ABI types which should be used to decode the output of the call. + */ +export function callExecutionManagerExecuteTransaction( + executionManager: Contract, + parameters: any[], + outputTypes: any[] +): Promise { + const modifiedAbi = cloneDeep(ExecutionManagerJson.abi) + overrideAbiFunctionData(modifiedAbi, 'executeTransaction', { + constant: true, + outputs: outputTypes, + stateMutability: 'view', + }) + const callableExecutionManager = new Contract( + executionManager.address, + modifiedAbi, + executionManager.provider + ) + return callableExecutionManager.executeTransaction.apply(null, parameters) +} + +export const executePersistedTestTransaction = async ( + executionManager: Contract, + wallet: Signer, + callContractAddress: string, + methodName: string, + args: any[] +): Promise => { + const callBytes = encodeFunctionData(methodName, args) + + const data = executionManager.interface.encodeFunctionData( + 'executeTransaction', + [ + getCurrentTime(), + 0, + callContractAddress, + callBytes, + ZERO_ADDRESS, + ZERO_ADDRESS, + true, + ] + ) + + const receipt = await wallet.sendTransaction({ + to: executionManager.address, + data: add0x(data), + gasLimit: GAS_LIMIT, + }) + + return receipt.hash +} + +export const executeTestTransaction = async ( + executionManager: Contract, + contractAddress: string, + methodName: string, + args: any[], + queueOrigin = ZERO_ADDRESS, + ovmTxOrign = ZERO_ADDRESS +): Promise => { + const callBytes = encodeFunctionData(methodName, args) + + const data = executionManager.interface.encodeFunctionData( + 'executeTransaction', + [ + getCurrentTime(), + queueOrigin, + contractAddress, + callBytes, + ovmTxOrign, + ZERO_ADDRESS, + true, + ] + ) + + return executionManager.provider.call({ + to: executionManager.address, + data, + gasLimit: GAS_LIMIT, + }) +} diff --git a/packages/contracts/test/test-helpers/index.ts b/packages/contracts/test/test-helpers/index.ts index f8f72c544c3f6..1e4b00a70d16c 100644 --- a/packages/contracts/test/test-helpers/index.ts +++ b/packages/contracts/test/test-helpers/index.ts @@ -6,4 +6,5 @@ export * from './ethereum-helpers' export * from './ovm-helpers' export * from './trie-helpers' export * from './wallet-helpers' -export * from './resolution' +export * from './execution-manager-helpers' +export * from './deployment-helpers' diff --git a/packages/contracts/test/test-helpers/ovm-helpers.ts b/packages/contracts/test/test-helpers/ovm-helpers.ts index 60bb0bac1a4ae..2e2e6ed0f63c1 100644 --- a/packages/contracts/test/test-helpers/ovm-helpers.ts +++ b/packages/contracts/test/test-helpers/ovm-helpers.ts @@ -1,6 +1,7 @@ /* External Imports */ +import * as rlp from 'rlp' import { ethers, Signer, Contract, ContractFactory } from 'ethers' -import { Log, TransactionReceipt, JsonRpcProvider } from 'ethers/providers' +import { Log, TransactionReceipt } from 'ethers/providers' import { abi, add0x, @@ -11,6 +12,7 @@ import { numberToHexString, bufToHexString, getCurrentTime, + NULL_ADDRESS, } from '@eth-optimism/core-utils' /* Internal Imports */ @@ -20,6 +22,7 @@ import { encodeMethodId, encodeRawArguments } from './ethereum-helpers' /* Contract Imports */ import { getContractInterface } from '../../index' +import { toHexString } from './trie-helpers' const ExecutionManagerInterface = getContractInterface('ExecutionManager') const logger = getLogger('contracts:test-helpers', true) @@ -320,3 +323,43 @@ export const executeOVMCall = async ( gasLimit: GAS_LIMIT, }) } + +export interface OVMTransactionData { + timestamp: number + queueOrigin: number + ovmEntrypoint: string + callBytes: string + fromAddress: string + l1MsgSenderAddress: string + allowRevert: boolean +} + +export const makeDummyOvmTransaction = ( + calldata: string +): OVMTransactionData => { + return { + timestamp: Math.floor(Date.now() / 1000), + queueOrigin: 0, + ovmEntrypoint: NULL_ADDRESS, + callBytes: calldata, + fromAddress: NULL_ADDRESS, + l1MsgSenderAddress: NULL_ADDRESS, + allowRevert: false, + } +} + +export const encodeOvmTransaction = ( + transaction: OVMTransactionData +): string => { + return toHexString( + rlp.encode([ + transaction.timestamp, + transaction.queueOrigin, + transaction.ovmEntrypoint, + transaction.callBytes, + transaction.fromAddress, + transaction.l1MsgSenderAddress, + transaction.allowRevert ? 1 : 0, + ]) + ) +} diff --git a/packages/contracts/test/test-helpers/resolution/config.ts b/packages/contracts/test/test-helpers/resolution/config.ts deleted file mode 100644 index 4778be9c4f2b9..0000000000000 --- a/packages/contracts/test/test-helpers/resolution/config.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* External Imports */ -import { ethers } from '@nomiclabs/buidler' -import { Contract } from 'ethers' - -/* Internal Imports */ -import { AddressResolverDeployConfig, AddressResolverConfig } from './types' -import { GAS_LIMIT, DEFAULT_FORCE_INCLUSION_PERIOD } from '../constants' - -/** - * Generates the default deployment configuration. Runs as an async function - * because we need to get the contract factories async via buidler. - * @param addressResolver Address resolver contract to connect to. - * @returns Default address resolver deployment configuration. - */ -export const getDefaultDeployConfig = async ( - addressResolver: Contract -): Promise => { - const [owner, sequencer, l1ToL2TransactionPasser] = await ethers.getSigners() - - return { - L1ToL2TransactionQueue: { - factory: await ethers.getContractFactory('L1ToL2TransactionQueue'), - params: [ - addressResolver.address, - await l1ToL2TransactionPasser.getAddress(), - ], - }, - SafetyTransactionQueue: { - factory: await ethers.getContractFactory('SafetyTransactionQueue'), - params: [addressResolver.address], - }, - CanonicalTransactionChain: { - factory: await ethers.getContractFactory('CanonicalTransactionChain'), - params: [ - addressResolver.address, - await sequencer.getAddress(), - await l1ToL2TransactionPasser.getAddress(), - DEFAULT_FORCE_INCLUSION_PERIOD, - ], - }, - StateCommitmentChain: { - factory: await ethers.getContractFactory('StateCommitmentChain'), - params: [addressResolver.address], - }, - StateManager: { - factory: await ethers.getContractFactory('FullStateManager'), - params: [], - }, - ExecutionManager: { - factory: await ethers.getContractFactory('ExecutionManager'), - params: [addressResolver.address, await owner.getAddress(), GAS_LIMIT], - }, - SafetyChecker: { - factory: await ethers.getContractFactory('StubSafetyChecker'), - params: [], - }, - FraudVerifier: { - factory: await ethers.getContractFactory('FraudVerifier'), - params: [addressResolver.address], - }, - } -} - -/** - * Generates the deployment configuration for various libraries. - * @returns Library deployment configuration. - */ -export const getLibraryDeployConfig = async (): Promise => { - return { - RollupMerkleUtils: { - factory: await ethers.getContractFactory('RollupMerkleUtils'), - params: [], - }, - } -} - -/** - * Given a config, generates the default config and merges the two. - * @param addressResolver Address resolver to connect to the config. - * @param config User-provided configuration. - * @returns Config merged with default config. - */ -export const makeDeployConfig = async ( - addressResolver: Contract, - config: Partial -): Promise => { - const defaultDeployConfig = await getDefaultDeployConfig(addressResolver) - - return { - ...defaultDeployConfig, - ...config.deployConfig, - } -} diff --git a/packages/contracts/test/test-helpers/resolution/index.ts b/packages/contracts/test/test-helpers/resolution/index.ts deleted file mode 100644 index 8fd4eaea78bcc..0000000000000 --- a/packages/contracts/test/test-helpers/resolution/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './resolver' -export { AddressResolverMapping } from './types' diff --git a/packages/contracts/test/test-helpers/resolution/resolver.ts b/packages/contracts/test/test-helpers/resolution/resolver.ts deleted file mode 100644 index 1b0df11b9ab5c..0000000000000 --- a/packages/contracts/test/test-helpers/resolution/resolver.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* External Imports */ -import { ethers } from '@nomiclabs/buidler' -import { Contract, Signer } from 'ethers' - -/* Internal Imports */ -import { - AddressResolverConfig, - ContractDeployConfig, - AddressResolverMapping, - factoryToContractName, -} from './types' -import { getLibraryDeployConfig, makeDeployConfig } from './config' - -/** - * Deploys all necessary libraries. - * @param addressResolver Address resolver to attach libraries to. - * @param signer Signer to deploy libraries from. - */ -const deployLibraries = async ( - addressResolver: Contract, - signer: Signer -): Promise => { - const libraryDeployConfig = await getLibraryDeployConfig() - - for (const name of Object.keys(libraryDeployConfig)) { - await deployAndRegister( - addressResolver, - signer, - name, - libraryDeployConfig[name] - ) - } -} - -/** - * Deploys a contract and registers it with the address resolver. - * @param addressResolver Address resolver to register to. - * @param signer Wallet to deploy the contract from. - * @param name Name of the contract within the resolver. - * @param deployConfig Contract deployment configuration. - * @returns Ethers Contract instance. - */ -export const deployAndRegister = async ( - addressResolver: Contract, - signer: Signer, - name: string, - deployConfig: ContractDeployConfig -): Promise => { - deployConfig.factory.connect(signer) - const deployedContract = await deployConfig.factory.deploy( - ...deployConfig.params - ) - await addressResolver.setAddress(name, deployedContract.address) - return deployedContract -} - -/** - * Creates an address resolver based on some user config. Defaults used for any - * values not provided for the user. - * @param signer Wallet to deploy all contracts from. - * @param config Config used to deploy various contracts. - * @returns Object containing the resolver and any deployed contracts. - */ -export const makeAddressResolver = async ( - signer: Signer, - config: Partial = {} -): Promise => { - const AddressResolver = await ethers.getContractFactory('AddressResolver') - const addressResolver = await AddressResolver.deploy() - - await deployLibraries(addressResolver, signer) - - const deployConfig = await makeDeployConfig(addressResolver, config) - - const contracts: any = {} - for (const name of Object.keys(deployConfig)) { - if ( - config.dependencies === undefined || - config.dependencies.includes(name as any) - ) { - const contractName = factoryToContractName[name] - contracts[contractName] = await deployAndRegister( - addressResolver, - signer, - name, - deployConfig[name] - ) - } - } - - return { - addressResolver, - contracts, - } -} diff --git a/packages/contracts/test/test-helpers/resolution/types.ts b/packages/contracts/test/test-helpers/resolution/types.ts deleted file mode 100644 index 7238a22ad885b..0000000000000 --- a/packages/contracts/test/test-helpers/resolution/types.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* External Imports */ -import { Contract, ContractFactory } from 'ethers' - -export interface ContractDeployConfig { - factory: ContractFactory - params: any[] -} - -type ContractFactoryName = - | 'L1ToL2TransactionQueue' - | 'SafetyTransactionQueue' - | 'CanonicalTransactionChain' - | 'StateCommitmentChain' - | 'StateManager' - | 'ExecutionManager' - | 'SafetyChecker' - | 'FraudVerifier' - -export interface AddressResolverDeployConfig { - L1ToL2TransactionQueue: ContractDeployConfig - SafetyTransactionQueue: ContractDeployConfig - CanonicalTransactionChain: ContractDeployConfig - StateCommitmentChain: ContractDeployConfig - StateManager: ContractDeployConfig - ExecutionManager: ContractDeployConfig - SafetyChecker: ContractDeployConfig - FraudVerifier: ContractDeployConfig -} - -export interface AddressResolverConfig { - deployConfig: AddressResolverDeployConfig - dependencies: ContractFactoryName[] -} - -interface ContractMapping { - l1ToL2TransactionQueue: Contract - safetyTransactionQueue: Contract - canonicalTransactionChain: Contract - stateCommitmentChain: Contract - stateManager: Contract - executionManager: Contract - safetyChecker: Contract - fraudVerifier: Contract -} - -export interface AddressResolverMapping { - addressResolver: Contract - contracts: ContractMapping -} - -export const factoryToContractName = { - L1ToL2TransactionQueue: 'l1ToL2TransactionQueue', - SafetyTransactionQueue: 'safetyTransactionQueue', - CanonicalTransactionChain: 'canonicalTransactionChain', - StateCommitmentChain: 'stateCommitmentChain', - StateManager: 'stateManager', - ExecutionManager: 'executionManager', - SafetyChecker: 'safetyChecker', - FraudVerifier: 'fraudVerifier', -} diff --git a/packages/contracts/test/test-helpers/types/deployment.ts b/packages/contracts/test/test-helpers/types/deployment.ts new file mode 100644 index 0000000000000..042c7ff66f483 --- /dev/null +++ b/packages/contracts/test/test-helpers/types/deployment.ts @@ -0,0 +1 @@ +export { AddressResolverMapping } from '../../../src' diff --git a/packages/contracts/test/test-helpers/types/index.ts b/packages/contracts/test/test-helpers/types/index.ts index dad87e889748c..b54e860661b2d 100644 --- a/packages/contracts/test/test-helpers/types/index.ts +++ b/packages/contracts/test/test-helpers/types/index.ts @@ -1,3 +1,3 @@ export * from './batch' export * from './convenience' -export * from './opcodes' +export * from './deployment' diff --git a/packages/contracts/test/test-helpers/types/opcodes.ts b/packages/contracts/test/test-helpers/types/opcodes.ts deleted file mode 100644 index 25a5a5cceeeaa..0000000000000 --- a/packages/contracts/test/test-helpers/types/opcodes.ts +++ /dev/null @@ -1,990 +0,0 @@ -import { bufToHexString, remove0x } from '@eth-optimism/core-utils' - -export interface EVMOpcode { - name: string - code: Buffer - programBytesConsumed: number -} - -export interface EVMOpcodeAndBytes { - opcode: EVMOpcode - consumedBytes: Buffer - tag?: OpcodeTag -} - -export type EVMBytecode = EVMOpcodeAndBytes[] - -export interface OpcodeTag { - padPUSH: boolean // whether this PUSHN should be turned into a PUSH(N+1) to preempt later changes to consumedBytes in transpilation. - reasonTagged: string | OpcodeTagReason - metadata: any -} - -export enum OpcodeTagReason { - IS_CONSTANT_OFFSET, - IS_DEPLOY_CODECOPY_OFFSET, - IS_DEPLOY_CODE_LENGTH, - IS_CONSTRUCTOR_INPUTS_OFFSET, - IS_PUSH_BINARY_SEARCH_NODE_LOCATION, - IS_BINARY_SEARCH_NODE_JUMPDEST, - IS_PUSH_JUMPDEST_MATCH_SUCCESS_LOCATION, - IS_PUSH_OPCODE_FUNCTION_LOCATION, - IS_JUMP_TO_OPCODE_FUNCTION, - IS_OPCODE_FUNCTION_JUMPDEST, - IS_OPCODE_FUNCTION_RETURN_JUMP, - IS_OPCODE_FUNCTION_RETURN_JUMPDEST, -} - -export class Opcode { - public static readonly STOP: EVMOpcode = { - code: Buffer.from('00', 'hex'), - name: 'STOP', - programBytesConsumed: 0, - } - public static readonly ADD: EVMOpcode = { - code: Buffer.from('01', 'hex'), - name: 'ADD', - programBytesConsumed: 0, - } - public static readonly MUL: EVMOpcode = { - code: Buffer.from('02', 'hex'), - name: 'MUL', - programBytesConsumed: 0, - } - public static readonly SUB: EVMOpcode = { - code: Buffer.from('03', 'hex'), - name: 'SUB', - programBytesConsumed: 0, - } - public static readonly DIV: EVMOpcode = { - code: Buffer.from('04', 'hex'), - name: 'DIV', - programBytesConsumed: 0, - } - public static readonly SDIV: EVMOpcode = { - code: Buffer.from('05', 'hex'), - name: 'SDIV', - programBytesConsumed: 0, - } - public static readonly MOD: EVMOpcode = { - code: Buffer.from('06', 'hex'), - name: 'MOD', - programBytesConsumed: 0, - } - public static readonly SMOD: EVMOpcode = { - code: Buffer.from('07', 'hex'), - name: 'SMOD', - programBytesConsumed: 0, - } - public static readonly ADDMOD: EVMOpcode = { - code: Buffer.from('08', 'hex'), - name: 'ADDMOD', - programBytesConsumed: 0, - } - public static readonly MULMOD: EVMOpcode = { - code: Buffer.from('09', 'hex'), - name: 'MULMOD', - programBytesConsumed: 0, - } - public static readonly EXP: EVMOpcode = { - code: Buffer.from('0a', 'hex'), - name: 'EXP', - programBytesConsumed: 0, - } - public static readonly SIGNEXTEND: EVMOpcode = { - code: Buffer.from('0b', 'hex'), - name: 'SIGNEXTEND', - programBytesConsumed: 0, - } - - // gap - - public static readonly LT: EVMOpcode = { - code: Buffer.from('10', 'hex'), - name: 'LT', - programBytesConsumed: 0, - } - public static readonly GT: EVMOpcode = { - code: Buffer.from('11', 'hex'), - name: 'GT', - programBytesConsumed: 0, - } - public static readonly SLT: EVMOpcode = { - code: Buffer.from('12', 'hex'), - name: 'SLT', - programBytesConsumed: 0, - } - public static readonly SGT: EVMOpcode = { - code: Buffer.from('13', 'hex'), - name: 'SGT', - programBytesConsumed: 0, - } - public static readonly EQ: EVMOpcode = { - code: Buffer.from('14', 'hex'), - name: 'EQ', - programBytesConsumed: 0, - } - public static readonly ISZERO: EVMOpcode = { - code: Buffer.from('15', 'hex'), - name: 'ISZERO', - programBytesConsumed: 0, - } - public static readonly AND: EVMOpcode = { - code: Buffer.from('16', 'hex'), - name: 'AND', - programBytesConsumed: 0, - } - public static readonly OR: EVMOpcode = { - code: Buffer.from('17', 'hex'), - name: 'OR', - programBytesConsumed: 0, - } - public static readonly XOR: EVMOpcode = { - code: Buffer.from('18', 'hex'), - name: 'XOR', - programBytesConsumed: 0, - } - public static readonly NOT: EVMOpcode = { - code: Buffer.from('19', 'hex'), - name: 'NOT', - programBytesConsumed: 0, - } - public static readonly BYTE: EVMOpcode = { - code: Buffer.from('1a', 'hex'), - name: 'BYTE', - programBytesConsumed: 0, - } - public static readonly SHL: EVMOpcode = { - code: Buffer.from('1b', 'hex'), - name: 'SHL', - programBytesConsumed: 0, - } - public static readonly SHR: EVMOpcode = { - code: Buffer.from('1c', 'hex'), - name: 'SHR', - programBytesConsumed: 0, - } - public static readonly SAR: EVMOpcode = { - code: Buffer.from('1d', 'hex'), - name: 'SAR', - programBytesConsumed: 0, - } - - // gap - - public static readonly SHA3: EVMOpcode = { - code: Buffer.from('20', 'hex'), - name: 'SHA3', - programBytesConsumed: 0, - } - - // gap - - public static readonly ADDRESS: EVMOpcode = { - code: Buffer.from('30', 'hex'), - name: 'ADDRESS', - programBytesConsumed: 0, - } - public static readonly BALANCE: EVMOpcode = { - code: Buffer.from('31', 'hex'), - name: 'BALANCE', - programBytesConsumed: 0, - } - public static readonly ORIGIN: EVMOpcode = { - code: Buffer.from('32', 'hex'), - name: 'ORIGIN', - programBytesConsumed: 0, - } - public static readonly CALLER: EVMOpcode = { - code: Buffer.from('33', 'hex'), - name: 'CALLER', - programBytesConsumed: 0, - } - public static readonly CALLVALUE: EVMOpcode = { - code: Buffer.from('34', 'hex'), - name: 'CALLVALUE', - programBytesConsumed: 0, - } - public static readonly CALLDATALOAD: EVMOpcode = { - code: Buffer.from('35', 'hex'), - name: 'CALLDATALOAD', - programBytesConsumed: 0, - } - public static readonly CALLDATASIZE: EVMOpcode = { - code: Buffer.from('36', 'hex'), - name: 'CALLDATASIZE', - programBytesConsumed: 0, - } - public static readonly CALLDATACOPY: EVMOpcode = { - code: Buffer.from('37', 'hex'), - name: 'CALLDATACOPY', - programBytesConsumed: 0, - } - public static readonly CODESIZE: EVMOpcode = { - code: Buffer.from('38', 'hex'), - name: 'CODESIZE', - programBytesConsumed: 0, - } - public static readonly CODECOPY: EVMOpcode = { - code: Buffer.from('39', 'hex'), - name: 'CODECOPY', - programBytesConsumed: 0, - } - public static readonly GASPRICE: EVMOpcode = { - code: Buffer.from('3a', 'hex'), - name: 'GASPRICE', - programBytesConsumed: 0, - } - public static readonly EXTCODESIZE: EVMOpcode = { - code: Buffer.from('3b', 'hex'), - name: 'EXTCODESIZE', - programBytesConsumed: 0, - } - public static readonly EXTCODECOPY: EVMOpcode = { - code: Buffer.from('3c', 'hex'), - name: 'EXTCODECOPY', - programBytesConsumed: 0, - } - public static readonly RETURNDATASIZE: EVMOpcode = { - code: Buffer.from('3d', 'hex'), - name: 'RETURNDATASIZE', - programBytesConsumed: 0, - } - public static readonly RETURNDATACOPY: EVMOpcode = { - code: Buffer.from('3e', 'hex'), - name: 'RETURNDATACOPY', - programBytesConsumed: 0, - } - public static readonly EXTCODEHASH: EVMOpcode = { - code: Buffer.from('3f', 'hex'), - name: 'EXTCODEHASH', - programBytesConsumed: 0, - } - public static readonly BLOCKHASH: EVMOpcode = { - code: Buffer.from('40', 'hex'), - name: 'BLOCKHASH', - programBytesConsumed: 0, - } - public static readonly COINBASE: EVMOpcode = { - code: Buffer.from('41', 'hex'), - name: 'COINBASE', - programBytesConsumed: 0, - } - public static readonly TIMESTAMP: EVMOpcode = { - code: Buffer.from('42', 'hex'), - name: 'TIMESTAMP', - programBytesConsumed: 0, - } - public static readonly NUMBER: EVMOpcode = { - code: Buffer.from('43', 'hex'), - name: 'NUMBER', - programBytesConsumed: 0, - } - public static readonly DIFFICULTY: EVMOpcode = { - code: Buffer.from('44', 'hex'), - name: 'DIFFICULTY', - programBytesConsumed: 0, - } - public static readonly GASLIMIT: EVMOpcode = { - code: Buffer.from('45', 'hex'), - name: 'GASLIMIT', - programBytesConsumed: 0, - } - public static readonly CHAINID: EVMOpcode = { - code: Buffer.from('46', 'hex'), - name: 'CHAINID', - programBytesConsumed: 0, - } - public static readonly SELFBALANCE: EVMOpcode = { - code: Buffer.from('47', 'hex'), - name: 'SELFBALANCE', - programBytesConsumed: 0, - } - - // gap - - public static readonly POP: EVMOpcode = { - code: Buffer.from('50', 'hex'), - name: 'POP', - programBytesConsumed: 0, - } - public static readonly MLOAD: EVMOpcode = { - code: Buffer.from('51', 'hex'), - name: 'MLOAD', - programBytesConsumed: 0, - } - public static readonly MSTORE: EVMOpcode = { - code: Buffer.from('52', 'hex'), - name: 'MSTORE', - programBytesConsumed: 0, - } - public static readonly MSTORE8: EVMOpcode = { - code: Buffer.from('53', 'hex'), - name: 'MSTORE8', - programBytesConsumed: 0, - } - public static readonly SLOAD: EVMOpcode = { - code: Buffer.from('54', 'hex'), - name: 'SLOAD', - programBytesConsumed: 0, - } - public static readonly SSTORE: EVMOpcode = { - code: Buffer.from('55', 'hex'), - name: 'SSTORE', - programBytesConsumed: 0, - } - public static readonly JUMP: EVMOpcode = { - code: Buffer.from('56', 'hex'), - name: 'JUMP', - programBytesConsumed: 0, - } - public static readonly JUMPI: EVMOpcode = { - code: Buffer.from('57', 'hex'), - name: 'JUMPI', - programBytesConsumed: 0, - } - public static readonly PC: EVMOpcode = { - code: Buffer.from('58', 'hex'), - name: 'PC', - programBytesConsumed: 0, - } - public static readonly MSIZE: EVMOpcode = { - code: Buffer.from('59', 'hex'), - name: 'MSIZE', - programBytesConsumed: 0, - } - public static readonly GAS: EVMOpcode = { - code: Buffer.from('5a', 'hex'), - name: 'GAS', - programBytesConsumed: 0, - } - public static readonly JUMPDEST: EVMOpcode = { - code: Buffer.from('5b', 'hex'), - name: 'JUMPDEST', - programBytesConsumed: 0, - } - - // gap - - public static readonly PUSH1: EVMOpcode = { - code: Buffer.from('60', 'hex'), - name: 'PUSH1', - programBytesConsumed: 1, - } - public static readonly PUSH2: EVMOpcode = { - code: Buffer.from('61', 'hex'), - name: 'PUSH2', - programBytesConsumed: 2, - } - public static readonly PUSH3: EVMOpcode = { - code: Buffer.from('62', 'hex'), - name: 'PUSH3', - programBytesConsumed: 3, - } - public static readonly PUSH4: EVMOpcode = { - code: Buffer.from('63', 'hex'), - name: 'PUSH4', - programBytesConsumed: 4, - } - public static readonly PUSH5: EVMOpcode = { - code: Buffer.from('64', 'hex'), - name: 'PUSH5', - programBytesConsumed: 5, - } - public static readonly PUSH6: EVMOpcode = { - code: Buffer.from('65', 'hex'), - name: 'PUSH6', - programBytesConsumed: 6, - } - public static readonly PUSH7: EVMOpcode = { - code: Buffer.from('66', 'hex'), - name: 'PUSH7', - programBytesConsumed: 7, - } - public static readonly PUSH8: EVMOpcode = { - code: Buffer.from('67', 'hex'), - name: 'PUSH8', - programBytesConsumed: 8, - } - public static readonly PUSH9: EVMOpcode = { - code: Buffer.from('68', 'hex'), - name: 'PUSH9', - programBytesConsumed: 9, - } - public static readonly PUSH10: EVMOpcode = { - code: Buffer.from('69', 'hex'), - name: 'PUSH10', - programBytesConsumed: 10, - } - public static readonly PUSH11: EVMOpcode = { - code: Buffer.from('6a', 'hex'), - name: 'PUSH11', - programBytesConsumed: 11, - } - public static readonly PUSH12: EVMOpcode = { - code: Buffer.from('6b', 'hex'), - name: 'PUSH12', - programBytesConsumed: 12, - } - public static readonly PUSH13: EVMOpcode = { - code: Buffer.from('6c', 'hex'), - name: 'PUSH13', - programBytesConsumed: 13, - } - public static readonly PUSH14: EVMOpcode = { - code: Buffer.from('6d', 'hex'), - name: 'PUSH14', - programBytesConsumed: 14, - } - public static readonly PUSH15: EVMOpcode = { - code: Buffer.from('6e', 'hex'), - name: 'PUSH15', - programBytesConsumed: 15, - } - public static readonly PUSH16: EVMOpcode = { - code: Buffer.from('6f', 'hex'), - name: 'PUSH16', - programBytesConsumed: 16, - } - public static readonly PUSH17: EVMOpcode = { - code: Buffer.from('70', 'hex'), - name: 'PUSH17', - programBytesConsumed: 17, - } - public static readonly PUSH18: EVMOpcode = { - code: Buffer.from('71', 'hex'), - name: 'PUSH18', - programBytesConsumed: 18, - } - public static readonly PUSH19: EVMOpcode = { - code: Buffer.from('72', 'hex'), - name: 'PUSH19', - programBytesConsumed: 19, - } - public static readonly PUSH20: EVMOpcode = { - code: Buffer.from('73', 'hex'), - name: 'PUSH20', - programBytesConsumed: 20, - } - public static readonly PUSH21: EVMOpcode = { - code: Buffer.from('74', 'hex'), - name: 'PUSH21', - programBytesConsumed: 21, - } - public static readonly PUSH22: EVMOpcode = { - code: Buffer.from('75', 'hex'), - name: 'PUSH22', - programBytesConsumed: 22, - } - public static readonly PUSH23: EVMOpcode = { - code: Buffer.from('76', 'hex'), - name: 'PUSH23', - programBytesConsumed: 23, - } - public static readonly PUSH24: EVMOpcode = { - code: Buffer.from('77', 'hex'), - name: 'PUSH24', - programBytesConsumed: 24, - } - public static readonly PUSH25: EVMOpcode = { - code: Buffer.from('78', 'hex'), - name: 'PUSH25', - programBytesConsumed: 25, - } - public static readonly PUSH26: EVMOpcode = { - code: Buffer.from('79', 'hex'), - name: 'PUSH26', - programBytesConsumed: 26, - } - public static readonly PUSH27: EVMOpcode = { - code: Buffer.from('7a', 'hex'), - name: 'PUSH27', - programBytesConsumed: 27, - } - public static readonly PUSH28: EVMOpcode = { - code: Buffer.from('7b', 'hex'), - name: 'PUSH28', - programBytesConsumed: 28, - } - public static readonly PUSH29: EVMOpcode = { - code: Buffer.from('7c', 'hex'), - name: 'PUSH29', - programBytesConsumed: 29, - } - public static readonly PUSH30: EVMOpcode = { - code: Buffer.from('7d', 'hex'), - name: 'PUSH30', - programBytesConsumed: 30, - } - public static readonly PUSH31: EVMOpcode = { - code: Buffer.from('7e', 'hex'), - name: 'PUSH31', - programBytesConsumed: 31, - } - public static readonly PUSH32: EVMOpcode = { - code: Buffer.from('7f', 'hex'), - name: 'PUSH32', - programBytesConsumed: 32, - } - - public static readonly DUP1: EVMOpcode = { - code: Buffer.from('80', 'hex'), - name: 'DUP1', - programBytesConsumed: 0, - } - public static readonly DUP2: EVMOpcode = { - code: Buffer.from('81', 'hex'), - name: 'DUP2', - programBytesConsumed: 0, - } - public static readonly DUP3: EVMOpcode = { - code: Buffer.from('82', 'hex'), - name: 'DUP3', - programBytesConsumed: 0, - } - public static readonly DUP4: EVMOpcode = { - code: Buffer.from('83', 'hex'), - name: 'DUP4', - programBytesConsumed: 0, - } - public static readonly DUP5: EVMOpcode = { - code: Buffer.from('84', 'hex'), - name: 'DUP5', - programBytesConsumed: 0, - } - public static readonly DUP6: EVMOpcode = { - code: Buffer.from('85', 'hex'), - name: 'DUP6', - programBytesConsumed: 0, - } - public static readonly DUP7: EVMOpcode = { - code: Buffer.from('86', 'hex'), - name: 'DUP7', - programBytesConsumed: 0, - } - public static readonly DUP8: EVMOpcode = { - code: Buffer.from('87', 'hex'), - name: 'DUP8', - programBytesConsumed: 0, - } - public static readonly DUP9: EVMOpcode = { - code: Buffer.from('88', 'hex'), - name: 'DUP9', - programBytesConsumed: 0, - } - public static readonly DUP10: EVMOpcode = { - code: Buffer.from('89', 'hex'), - name: 'DUP10', - programBytesConsumed: 0, - } - public static readonly DUP11: EVMOpcode = { - code: Buffer.from('8a', 'hex'), - name: 'DUP11', - programBytesConsumed: 0, - } - public static readonly DUP12: EVMOpcode = { - code: Buffer.from('8b', 'hex'), - name: 'DUP12', - programBytesConsumed: 0, - } - public static readonly DUP13: EVMOpcode = { - code: Buffer.from('8c', 'hex'), - name: 'DUP13', - programBytesConsumed: 0, - } - public static readonly DUP14: EVMOpcode = { - code: Buffer.from('8d', 'hex'), - name: 'DUP14', - programBytesConsumed: 0, - } - public static readonly DUP15: EVMOpcode = { - code: Buffer.from('8e', 'hex'), - name: 'DUP15', - programBytesConsumed: 0, - } - public static readonly DUP16: EVMOpcode = { - code: Buffer.from('8f', 'hex'), - name: 'DUP16', - programBytesConsumed: 0, - } - - public static readonly SWAP1: EVMOpcode = { - code: Buffer.from('90', 'hex'), - name: 'SWAP1', - programBytesConsumed: 0, - } - public static readonly SWAP2: EVMOpcode = { - code: Buffer.from('91', 'hex'), - name: 'SWAP2', - programBytesConsumed: 0, - } - public static readonly SWAP3: EVMOpcode = { - code: Buffer.from('92', 'hex'), - name: 'SWAP3', - programBytesConsumed: 0, - } - public static readonly SWAP4: EVMOpcode = { - code: Buffer.from('93', 'hex'), - name: 'SWAP4', - programBytesConsumed: 0, - } - public static readonly SWAP5: EVMOpcode = { - code: Buffer.from('94', 'hex'), - name: 'SWAP5', - programBytesConsumed: 0, - } - public static readonly SWAP6: EVMOpcode = { - code: Buffer.from('95', 'hex'), - name: 'SWAP6', - programBytesConsumed: 0, - } - public static readonly SWAP7: EVMOpcode = { - code: Buffer.from('96', 'hex'), - name: 'SWAP7', - programBytesConsumed: 0, - } - public static readonly SWAP8: EVMOpcode = { - code: Buffer.from('97', 'hex'), - name: 'SWAP8', - programBytesConsumed: 0, - } - public static readonly SWAP9: EVMOpcode = { - code: Buffer.from('98', 'hex'), - name: 'SWAP9', - programBytesConsumed: 0, - } - public static readonly SWAP10: EVMOpcode = { - code: Buffer.from('99', 'hex'), - name: 'SWAP10', - programBytesConsumed: 0, - } - public static readonly SWAP11: EVMOpcode = { - code: Buffer.from('9a', 'hex'), - name: 'SWAP11', - programBytesConsumed: 0, - } - public static readonly SWAP12: EVMOpcode = { - code: Buffer.from('9b', 'hex'), - name: 'SWAP12', - programBytesConsumed: 0, - } - public static readonly SWAP13: EVMOpcode = { - code: Buffer.from('9c', 'hex'), - name: 'SWAP13', - programBytesConsumed: 0, - } - public static readonly SWAP14: EVMOpcode = { - code: Buffer.from('9d', 'hex'), - name: 'SWAP14', - programBytesConsumed: 0, - } - public static readonly SWAP15: EVMOpcode = { - code: Buffer.from('9e', 'hex'), - name: 'SWAP15', - programBytesConsumed: 0, - } - public static readonly SWAP16: EVMOpcode = { - code: Buffer.from('9f', 'hex'), - name: 'SWAP16', - programBytesConsumed: 0, - } - - public static readonly LOG0: EVMOpcode = { - code: Buffer.from('a0', 'hex'), - name: 'LOG0', - programBytesConsumed: 0, - } - public static readonly LOG1: EVMOpcode = { - code: Buffer.from('a1', 'hex'), - name: 'LOG1', - programBytesConsumed: 0, - } - public static readonly LOG2: EVMOpcode = { - code: Buffer.from('a2', 'hex'), - name: 'LOG2', - programBytesConsumed: 0, - } - public static readonly LOG3: EVMOpcode = { - code: Buffer.from('a3', 'hex'), - name: 'LOG3', - programBytesConsumed: 0, - } - public static readonly LOG4: EVMOpcode = { - code: Buffer.from('a4', 'hex'), - name: 'LOG4', - programBytesConsumed: 0, - } - - // gap - - public static readonly CREATE: EVMOpcode = { - code: Buffer.from('f0', 'hex'), - name: 'CREATE', - programBytesConsumed: 0, - } - public static readonly CALL: EVMOpcode = { - code: Buffer.from('f1', 'hex'), - name: 'CALL', - programBytesConsumed: 0, - } - public static readonly CALLCODE: EVMOpcode = { - code: Buffer.from('f2', 'hex'), - name: 'CALLCODE', - programBytesConsumed: 0, - } - public static readonly RETURN: EVMOpcode = { - code: Buffer.from('f3', 'hex'), - name: 'RETURN', - programBytesConsumed: 0, - } - public static readonly DELEGATECALL: EVMOpcode = { - code: Buffer.from('f4', 'hex'), - name: 'DELEGATECALL', - programBytesConsumed: 0, - } - public static readonly CREATE2: EVMOpcode = { - code: Buffer.from('f5', 'hex'), - name: 'CREATE2', - programBytesConsumed: 0, - } - - // gap - - public static readonly STATICCALL: EVMOpcode = { - code: Buffer.from('fa', 'hex'), - name: 'STATICCALL', - programBytesConsumed: 0, - } - - // gap - - public static readonly REVERT: EVMOpcode = { - code: Buffer.from('fd', 'hex'), - name: 'REVERT', - programBytesConsumed: 0, - } - public static readonly INVALID: EVMOpcode = { - code: Buffer.from('fe', 'hex'), - name: 'INVALID', - programBytesConsumed: 0, - } - public static readonly SELFDESTRUCT: EVMOpcode = { - code: Buffer.from('ff', 'hex'), - name: 'SELFDESTRUCT', - programBytesConsumed: 0, - } - - public static readonly ALL_OP_CODES: EVMOpcode[] = [ - Opcode.STOP, - Opcode.ADD, - Opcode.MUL, - Opcode.SUB, - Opcode.DIV, - Opcode.SDIV, - Opcode.MOD, - Opcode.SMOD, - Opcode.ADDMOD, - Opcode.MULMOD, - Opcode.EXP, - Opcode.SIGNEXTEND, - - Opcode.LT, - Opcode.GT, - Opcode.SLT, - Opcode.SGT, - Opcode.EQ, - Opcode.ISZERO, - Opcode.AND, - Opcode.OR, - Opcode.XOR, - Opcode.NOT, - Opcode.BYTE, - Opcode.SHL, - Opcode.SHR, - Opcode.SAR, - - Opcode.SHA3, - - Opcode.ADDRESS, - Opcode.BALANCE, - Opcode.ORIGIN, - Opcode.CALLER, - Opcode.CALLVALUE, - Opcode.CALLDATALOAD, - Opcode.CALLDATASIZE, - Opcode.CALLDATACOPY, - Opcode.CODESIZE, - Opcode.CODECOPY, - Opcode.GASPRICE, - Opcode.EXTCODESIZE, - Opcode.EXTCODECOPY, - Opcode.RETURNDATASIZE, - Opcode.RETURNDATACOPY, - Opcode.EXTCODEHASH, - Opcode.BLOCKHASH, - Opcode.COINBASE, - Opcode.TIMESTAMP, - Opcode.NUMBER, - Opcode.DIFFICULTY, - Opcode.GASLIMIT, - Opcode.CHAINID, - Opcode.SELFBALANCE, - - Opcode.POP, - Opcode.MLOAD, - Opcode.MSTORE, - Opcode.MSTORE8, - Opcode.SLOAD, - Opcode.SSTORE, - Opcode.JUMP, - Opcode.JUMPI, - Opcode.PC, - Opcode.MSIZE, - Opcode.GAS, - Opcode.JUMPDEST, - - Opcode.PUSH1, - Opcode.PUSH2, - Opcode.PUSH3, - Opcode.PUSH4, - Opcode.PUSH5, - Opcode.PUSH6, - Opcode.PUSH7, - Opcode.PUSH8, - Opcode.PUSH9, - Opcode.PUSH10, - Opcode.PUSH11, - Opcode.PUSH12, - Opcode.PUSH13, - Opcode.PUSH14, - Opcode.PUSH15, - Opcode.PUSH16, - Opcode.PUSH17, - Opcode.PUSH18, - Opcode.PUSH19, - Opcode.PUSH20, - Opcode.PUSH21, - Opcode.PUSH22, - Opcode.PUSH23, - Opcode.PUSH24, - Opcode.PUSH25, - Opcode.PUSH26, - Opcode.PUSH27, - Opcode.PUSH28, - Opcode.PUSH29, - Opcode.PUSH30, - Opcode.PUSH31, - Opcode.PUSH32, - - Opcode.DUP1, - Opcode.DUP2, - Opcode.DUP3, - Opcode.DUP4, - Opcode.DUP5, - Opcode.DUP6, - Opcode.DUP7, - Opcode.DUP8, - Opcode.DUP9, - Opcode.DUP10, - Opcode.DUP11, - Opcode.DUP12, - Opcode.DUP13, - Opcode.DUP14, - Opcode.DUP15, - Opcode.DUP16, - - Opcode.SWAP1, - Opcode.SWAP2, - Opcode.SWAP3, - Opcode.SWAP4, - Opcode.SWAP5, - Opcode.SWAP6, - Opcode.SWAP7, - Opcode.SWAP8, - Opcode.SWAP9, - Opcode.SWAP10, - Opcode.SWAP11, - Opcode.SWAP12, - Opcode.SWAP13, - Opcode.SWAP14, - Opcode.SWAP15, - Opcode.SWAP16, - - Opcode.LOG0, - Opcode.LOG1, - Opcode.LOG2, - Opcode.LOG3, - Opcode.LOG4, - - Opcode.CREATE, - Opcode.CALL, - Opcode.CALLCODE, - Opcode.RETURN, - Opcode.DELEGATECALL, - Opcode.CREATE2, - - Opcode.STATICCALL, - - Opcode.REVERT, - Opcode.INVALID, - Opcode.SELFDESTRUCT, - ] - - public static readonly HALTING_OP_CODES: EVMOpcode[] = [ - Opcode.STOP, - Opcode.JUMP, - Opcode.RETURN, - Opcode.REVERT, - Opcode.INVALID, - ] - - public static readonly JUMP_OP_CODES: EVMOpcode[] = [ - Opcode.JUMP, - Opcode.JUMPI, - ] - - private static readonly nameToOpcode: Map = new Map< - string, - EVMOpcode - >(Opcode.ALL_OP_CODES.map((x) => [x.name, x])) - private static readonly codeToOpcode: Map = new Map< - string, - EVMOpcode - >(Opcode.ALL_OP_CODES.map((x) => [x.code.toString('hex'), x])) - - public static parseByName(name: string): EVMOpcode | undefined { - return this.nameToOpcode.get(name) - } - - public static parseByCode(code: Buffer): EVMOpcode | undefined { - if (!code) { - return undefined - } - - return this.codeToOpcode.get(code.toString('hex')) - } - - public static parseByNumber(code: number): EVMOpcode | undefined { - if (code === undefined || code === null) { - return undefined - } - - if (code < 16) { - return this.codeToOpcode.get(`0${code.toString(16)}`) - } - - return this.codeToOpcode.get(code.toString(16)) - } - - public static getCodeNumber(opcode: EVMOpcode): number { - return parseInt(remove0x(bufToHexString(opcode.code)), 16) - } - - public static isPUSHOpcode(opcode: EVMOpcode): boolean { - const num: number = Opcode.getCodeNumber(opcode) - return ( - num >= Opcode.getCodeNumber(Opcode.PUSH1) && - num <= Opcode.getCodeNumber(Opcode.PUSH32) - ) - } -} diff --git a/packages/contracts/test/test-helpers/wallet-helpers.ts b/packages/contracts/test/test-helpers/wallet-helpers.ts index 809fdf14908df..518aad10e4c14 100644 --- a/packages/contracts/test/test-helpers/wallet-helpers.ts +++ b/packages/contracts/test/test-helpers/wallet-helpers.ts @@ -4,9 +4,9 @@ import { ethers, Wallet } from 'ethers' /* Internal Imports */ import { DEFAULT_ACCOUNTS } from './constants' -export const getWallets = (): Wallet[] => { +export const getWallets = (provider?: any): Wallet[] => { return DEFAULT_ACCOUNTS.map((account) => { - return new ethers.Wallet(account.secretKey) + return new ethers.Wallet(account.secretKey, provider) }) } diff --git a/yarn.lock b/yarn.lock index c1f89d9621f36..f21c0e264e85c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,53 @@ resolved "https://registry.yarnpkg.com/@ensdomains/resolver/-/resolver-0.2.4.tgz#c10fe28bf5efbf49bff4666d909aed0265efbc89" integrity sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA== +"@eth-optimism/core-db@^0.0.1-alpha.27": + version "0.0.1-alpha.27" + resolved "https://registry.yarnpkg.com/@eth-optimism/core-db/-/core-db-0.0.1-alpha.27.tgz#c1b5bab856d5bca44d26de9685e2fa9f1b48b579" + integrity sha512-kKkGvss/w7q57NTTFWbY1B3VKI0soClBHDvcP1t5CJrNw7ggkSrlgEbx+LsriTd/vn+fL5iUgwCIcUJn711A0Q== + dependencies: + "@eth-optimism/core-utils" "^0.0.1-alpha.27" + abstract-leveldown "^6.2.2" + async-lock "^1.2.2" + chai "^4.2.0" + chai-as-promised "^7.1.1" + debug "^4.1.1" + ethers "^4.0.39" + level "^6.0.0" + memdown "^4.0.0" + +"@eth-optimism/core-utils@^0.0.1-alpha.27": + version "0.0.1-alpha.27" + resolved "https://registry.yarnpkg.com/@eth-optimism/core-utils/-/core-utils-0.0.1-alpha.27.tgz#b43c8b5cd8ee89de7294c5f93aef5c47ee846cf5" + integrity sha512-OLQONkCevn6xaAyFlxOrL5BkE8BycvicBk0+RDL13g4OXSl/WkLaAjzUJcxYsGDzDUaB7yPUcwLQwUaT6xmcww== + dependencies: + abstract-leveldown "^6.2.2" + async-lock "^1.2.2" + axios "^0.19.0" + bn.js "^4.11.8" + body-parser "^1.19.0" + chai "^4.2.0" + chai-as-promised "^7.1.1" + debug "^4.1.1" + dotenv "^8.2.0" + ethereumjs-util "^6.2.0" + ethers "^4.0.37" + express "^4.17.1" + memdown "^4.0.0" + ts-md5 "^1.2.4" + uuid "^3.3.3" + +"@eth-optimism/rollup-core@^0.0.1-alpha.28": + version "0.0.1-alpha.28" + resolved "https://registry.yarnpkg.com/@eth-optimism/rollup-core/-/rollup-core-0.0.1-alpha.28.tgz#845de41ed266e0f1fd738776cb68bfe1fa391104" + integrity sha512-NYNIkokGCJwsZAe4pWO3FGQV4g/AmTuHZsMiYrpwIJ8+sc3cWNDWl6gyOksaofBMjRES4cGDLoU3xvO1jzI7Tw== + dependencies: + "@eth-optimism/core-db" "^0.0.1-alpha.27" + "@eth-optimism/core-utils" "^0.0.1-alpha.27" + async-lock "^1.2.2" + ethereum-waffle "2.1.0" + ethers "^4.0.39" + "@ethereum-waffle/chai@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.0.2.tgz#5492398abbf2b64ec2524deac78777ee62d02d08"