diff --git a/package.json b/package.json index 63062a7b11c..97e76b80f39 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "release:alpha": "yarn clean && yarn build && lerna version prerelease --yes && lerna publish from-package --yes --force-publish --dist-tag=alpha -m \"chore(@ethereum-optimism) publish %s release\"", "release:beta": "yarn clean && yarn build && lerna version prerelease --yes && lerna publish from-package --yes --force-publish --dist-tag=beta -m \"chore(@ethereum-optimism) publish %s release\"", "patch": "yarn run patch:ganache && yarn run patch:waffle", - "patch:ganache": "sed -ie 's!import { Provider as Web3Provider } from \"web3/providers\";!import { provider as Web3Provider } from \"web3-core\";!' node_modules/ganache-core/typings/index.d.ts", + "patch:ganache": "sed -ie 's!import { Provider as Web3Provider } from \"web3/providers\";!import { AbstractProvider as Web3Provider } from \"web3-core\";!' node_modules/ganache-core/typings/index.d.ts", "patch:waffle": "sed -ie 's!Ganache.GanacheOpts!any!g' node_modules/ethereum-waffle/dist/waffle.d.ts" }, "repository": "git+https://github.com/ethereum-optimism/optimism-monorepo.git", diff --git a/packages/rollup-contracts/.gitattributes b/packages/contracts/.gitattributes similarity index 100% rename from packages/rollup-contracts/.gitattributes rename to packages/contracts/.gitattributes diff --git a/packages/rollup-contracts/.solhint.json b/packages/contracts/.solhint.json similarity index 100% rename from packages/rollup-contracts/.solhint.json rename to packages/contracts/.solhint.json diff --git a/packages/rollup-contracts/README.md b/packages/contracts/README.md similarity index 100% rename from packages/rollup-contracts/README.md rename to packages/contracts/README.md diff --git a/packages/rollup-contracts/config/.env.example b/packages/contracts/config/.env.example similarity index 100% rename from packages/rollup-contracts/config/.env.example rename to packages/contracts/config/.env.example diff --git a/packages/rollup-contracts/contracts/L1ToL2TransactionPasser.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/L1ToL2TransactionPasser.sol similarity index 51% rename from packages/rollup-contracts/contracts/L1ToL2TransactionPasser.sol rename to packages/contracts/contracts/optimistic-ethereum/bridge/L1ToL2TransactionPasser.sol index 20dc0f0f94d..67260ebad54 100644 --- a/packages/rollup-contracts/contracts/L1ToL2TransactionPasser.sol +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/L1ToL2TransactionPasser.sol @@ -1,11 +1,10 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; -/* Internal Imports */ -import {DataTypes as dt} from "./DataTypes.sol"; - contract L1ToL2TransactionPasser { - uint nonce = 0; + /* + * Events + */ event L1ToL2Transaction( uint _nonce, @@ -14,13 +13,30 @@ contract L1ToL2TransactionPasser { bytes _callData ); - function passTransactionToL2(address ovmEntrypoint, bytes memory ovmCalldata) public { - // TODO: Actually create/enqueue a rollup block with this message. We are simply mocking this functionality for now. + + /* + * Contract Variables + */ + + uint nonce; + + + /* + * Public Functions + */ + + function passTransactionToL2( + address _ovmEntrypoint, + bytes memory _ovmCalldata + ) public { + // TODO: Actually create/enqueue a rollup block with this message. + // We are simply mocking this functionality for now. + emit L1ToL2Transaction( nonce++, msg.sender, - ovmEntrypoint, - ovmCalldata + _ovmEntrypoint, + _ovmCalldata ); } } \ No newline at end of file diff --git a/packages/rollup-contracts/contracts/L2ToL1MessageReceiver.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/L2ToL1MessageReceiver.sol similarity index 51% rename from packages/rollup-contracts/contracts/L2ToL1MessageReceiver.sol rename to packages/contracts/contracts/optimistic-ethereum/bridge/L2ToL1MessageReceiver.sol index c864e504b59..d721c23f9b6 100644 --- a/packages/rollup-contracts/contracts/L2ToL1MessageReceiver.sol +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/L2ToL1MessageReceiver.sol @@ -2,56 +2,105 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /* Internal Imports */ -import {DataTypes as dt} from "./DataTypes.sol"; +import { DataTypes } from "../utils/DataTypes.sol"; contract L2ToL1MessageReceiver { + /* + * Events + */ + event L2ToL1MessageEnqueued( address ovmSender, bytes callData, uint nonce ); + + /* + * Structs + */ + struct EnqueuedL2ToL1Message { - dt.L2ToL1Message message; + DataTypes.L2ToL1Message message; uint l1BlockEnqueued; } + + /* + * Contract Variables + */ + address public sequencer; uint public blocksUntilFinal; - uint messageNonce = 0; + uint public messageNonce; mapping (uint => EnqueuedL2ToL1Message) public messages; + + /* + * Constructor + */ + constructor(address _sequencer, uint _blocksUntilFinal) public { sequencer = _sequencer; blocksUntilFinal = _blocksUntilFinal; } - function enqueueL2ToL1Message(dt.L2ToL1Message memory _message) public { - require(msg.sender == sequencer, "For now, only our trusted sequencer can enqueue messages to be verified on L1"); - uint blockNum = block.number; + + /* + * Public Functions + */ + + function enqueueL2ToL1Message( + DataTypes.L2ToL1Message memory _message + ) public { + require( + msg.sender == sequencer, + "For now, only our trusted sequencer can enqueue messages." + ); + + // Enqueue the message. messages[messageNonce] = EnqueuedL2ToL1Message({ message: _message, - l1BlockEnqueued: blockNum + l1BlockEnqueued: block.number }); + + // Let the world know. emit L2ToL1MessageEnqueued( _message.ovmSender, _message.callData, messageNonce ); + + // On to the next one. messageNonce += 1; } - function verifyL2ToL1Message(dt.L2ToL1Message memory _message, uint _nonce) public view returns (bool) { - // The enqueued message for the given nonce must match the _message being verified + function verifyL2ToL1Message( + DataTypes.L2ToL1Message memory _message, + uint _nonce + ) public view returns (bool) { + // The enqueued message for the given nonce must match the _message + // being verified. bytes32 givenMessageHash = getMessageHash(_message); bytes32 storedMessageHash = getMessageHash(messages[_nonce].message); bool messageWasEnqueued = (storedMessageHash == givenMessageHash); - // Message must be finalized on L1 - bool messageIsFinalized = (block.number >= messages[_nonce].l1BlockEnqueued + blocksUntilFinal); + + // Message must be finalized on L1. + bool messageIsFinalized = ( + block.number >= messages[_nonce].l1BlockEnqueued + blocksUntilFinal + ); + return messageWasEnqueued && messageIsFinalized; } - function getMessageHash(dt.L2ToL1Message memory _message) internal pure returns(bytes32) { + + /* + * Internal Functions + */ + + function getMessageHash( + DataTypes.L2ToL1Message memory _message + ) internal pure returns (bytes32) { return keccak256(abi.encode(_message)); } } \ No newline at end of file diff --git a/packages/contracts/contracts/optimistic-ethereum/chain/CanonicalTransactionChain.sol b/packages/contracts/contracts/optimistic-ethereum/chain/CanonicalTransactionChain.sol new file mode 100644 index 00000000000..fdc980f870e --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/chain/CanonicalTransactionChain.sol @@ -0,0 +1,206 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +/* Internal Imports */ +import { DataTypes } from "../utils/DataTypes.sol"; +import { RollupMerkleUtils } from "../utils/RollupMerkleUtils.sol"; +import { L1ToL2TransactionQueue } from "../queue/L1ToL2TransactionQueue.sol"; +import { SafetyTransactionQueue } from "../queue/SafetyTransactionQueue.sol"; + +contract CanonicalTransactionChain { + /* + * Contract Variables + */ + + address public sequencer; + uint public forceInclusionPeriod; + RollupMerkleUtils public merkleUtils; + L1ToL2TransactionQueue public l1ToL2Queue; + SafetyTransactionQueue public safetyQueue; + uint public cumulativeNumElements; + bytes32[] public batches; + uint public lastOVMTimestamp; + + + /* + * Constructor + */ + + constructor( + address _rollupMerkleUtilsAddress, + address _sequencer, + address _l1ToL2TransactionPasserAddress, + uint _forceInclusionPeriod + ) public { + merkleUtils = RollupMerkleUtils(_rollupMerkleUtilsAddress); + sequencer = _sequencer; + forceInclusionPeriod = _forceInclusionPeriod; + lastOVMTimestamp = 0; + + safetyQueue = new SafetyTransactionQueue(address(this)); + l1ToL2Queue = new L1ToL2TransactionQueue( + _l1ToL2TransactionPasserAddress, + address(this) + ); + } + + + /* + * Public Functions + */ + + function getBatchesLength() public view returns (uint) { + return batches.length; + } + + function hashBatchHeader( + DataTypes.TxChainBatchHeader memory _batchHeader + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked( + _batchHeader.timestamp, + _batchHeader.isL1ToL2Tx, + _batchHeader.elementsMerkleRoot, + _batchHeader.numElementsInBatch, + _batchHeader.cumulativePrevElements + )); + } + + function authenticateAppend( + address _sender + ) public view returns (bool) { + return _sender == sequencer; + } + + function appendL1ToL2Batch() public { + DataTypes.TimestampedHash memory l1ToL2Header = l1ToL2Queue.peek(); + + require( + safetyQueue.isEmpty() || l1ToL2Header.timestamp <= safetyQueue.peekTimestamp(), + "Must process older SafetyQueue batches first to enforce timestamp monotonicity" + ); + + _appendQueueBatch(l1ToL2Header, true); + l1ToL2Queue.dequeue(); + } + + function appendSafetyBatch() public { + DataTypes.TimestampedHash memory safetyHeader = safetyQueue.peek(); + + require( + l1ToL2Queue.isEmpty() || safetyHeader.timestamp <= l1ToL2Queue.peekTimestamp(), + "Must process older L1ToL2Queue batches first to enforce timestamp monotonicity" + ); + + _appendQueueBatch(safetyHeader, false); + safetyQueue.dequeue(); + } + + function _appendQueueBatch( + DataTypes.TimestampedHash memory timestampedHash, + bool isL1ToL2Tx + ) internal { + uint timestamp = timestampedHash.timestamp; + + require( + timestamp + forceInclusionPeriod <= now || authenticateAppend(msg.sender), + "Message sender does not have permission to append this batch" + ); + + lastOVMTimestamp = timestamp; + bytes32 elementsMerkleRoot = timestampedHash.txHash; + uint numElementsInBatch = 1; + + bytes32 batchHeaderHash = keccak256(abi.encodePacked( + timestamp, + isL1ToL2Tx, + elementsMerkleRoot, + numElementsInBatch, + cumulativeNumElements // cumulativePrevElements + )); + + batches.push(batchHeaderHash); + cumulativeNumElements += numElementsInBatch; + } + + function appendSequencerBatch( + bytes[] memory _txBatch, + uint _timestamp + ) public { + require( + authenticateAppend(msg.sender), + "Message sender does not have permission to append a batch" + ); + + require( + _txBatch.length > 0, + "Cannot submit an empty batch" + ); + + require( + _timestamp + forceInclusionPeriod > now, + "Cannot submit a batch with a timestamp older than the sequencer inclusion period" + ); + + require( + _timestamp <= now, + "Cannot submit a batch with a timestamp in the future" + ); + + require( + l1ToL2Queue.isEmpty() || _timestamp <= l1ToL2Queue.peekTimestamp(), + "Must process older L1ToL2Queue batches first to enforce timestamp monotonicity" + ); + + require( + safetyQueue.isEmpty() || _timestamp <= safetyQueue.peekTimestamp(), + "Must process older SafetyQueue batches first to enforce timestamp monotonicity" + ); + + require( + _timestamp >= lastOVMTimestamp, + "Timestamps must monotonically increase" + ); + + lastOVMTimestamp = _timestamp; + + bytes32 batchHeaderHash = keccak256(abi.encodePacked( + _timestamp, + false, // isL1ToL2Tx + merkleUtils.getMerkleRoot(_txBatch), // elementsMerkleRoot + _txBatch.length, // numElementsInBatch + cumulativeNumElements // cumulativeNumElements + )); + + batches.push(batchHeaderHash); + cumulativeNumElements += _txBatch.length; + } + + // verifies an element is in the current list at the given position + function verifyElement( + bytes memory _element, // the element of the list being proven + uint _position, // the position in the list of the element being proven + DataTypes.TxElementInclusionProof memory _inclusionProof // inclusion proof in the rollup batch + ) public view returns (bool) { + // For convenience, store the batchHeader + DataTypes.TxChainBatchHeader memory batchHeader = _inclusionProof.batchHeader; + + // make sure absolute position equivalent to relative positions + if (_position != _inclusionProof.indexInBatch + + batchHeader.cumulativePrevElements) { + return false; + } + + // verify elementsMerkleRoot + if (!merkleUtils.verify( + batchHeader.elementsMerkleRoot, + _element, + _inclusionProof.indexInBatch, + _inclusionProof.siblings + )) { + return false; + } + + //compare computed batch header with the batch header in the list. + return hashBatchHeader(batchHeader) == batches[_inclusionProof.batchIndex]; + } +} diff --git a/packages/contracts/contracts/optimistic-ethereum/chain/SequencerBatchSubmitter.sol b/packages/contracts/contracts/optimistic-ethereum/chain/SequencerBatchSubmitter.sol new file mode 100644 index 00000000000..22048ad2ed6 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/chain/SequencerBatchSubmitter.sol @@ -0,0 +1,80 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +/* Internal Imports */ +import { CanonicalTransactionChain } from "./CanonicalTransactionChain.sol"; +import { StateCommitmentChain } from "./StateCommitmentChain.sol"; + +/** + * @title SequencerBatchSubmitter + * @notice Helper contract that allows the sequencer to submit both a state + * commitment batch and tx batch in a single transaction. This ensures + * that # state roots == # of txs, preventing other users from + * submitting state batches to the state chain. + */ +contract SequencerBatchSubmitter { + /* + * Contract Variables + */ + + CanonicalTransactionChain canonicalTransactionChain; + StateCommitmentChain stateCommitmentChain; + address public sequencer; + + /* + * Modifiers + */ + + modifier onlySequencer () { + require( + msg.sender == sequencer, + "Only the sequencer may perform this action" + ); + _; + } + + + /* + * Constructor + */ + + constructor(address _sequencer) public { + sequencer = _sequencer; + } + + + /* + * Public Functions + */ + + function initialize( + address _canonicalTransactionChain, + address _stateCommitmentChain + ) public onlySequencer { + canonicalTransactionChain = CanonicalTransactionChain(_canonicalTransactionChain); + stateCommitmentChain = StateCommitmentChain(_stateCommitmentChain); + } + + /** + * @notice Append equal sized batches of transactions and state roots to + * their respective chains. + * @param _txBatch An array of transactions. + * @param _txBatchTimestamp The timestamp that will be submitted with the + * tx batch - this timestamp will likely lag + * behind the actual time by a few minutes. + * @param _stateBatch An array of 32 byte state roots + */ + function appendTransitionBatch( + bytes[] memory _txBatch, + uint _txBatchTimestamp, + bytes[] memory _stateBatch + ) public onlySequencer { + require( + _stateBatch.length == _txBatch.length, + "Must append the same number of state roots and transactions" + ); + + canonicalTransactionChain.appendSequencerBatch(_txBatch, _txBatchTimestamp); + stateCommitmentChain.appendStateBatch(_stateBatch); + } +} diff --git a/packages/contracts/contracts/optimistic-ethereum/chain/StateCommitmentChain.sol b/packages/contracts/contracts/optimistic-ethereum/chain/StateCommitmentChain.sol new file mode 100644 index 00000000000..773691a2abf --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/chain/StateCommitmentChain.sol @@ -0,0 +1,125 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +/* Internal Imports */ +import { DataTypes } from "../utils/DataTypes.sol"; +import { RollupMerkleUtils } from "../utils/RollupMerkleUtils.sol"; +import { CanonicalTransactionChain } from "./CanonicalTransactionChain.sol"; + +contract StateCommitmentChain { + /* + * Contract Variables + */ + + CanonicalTransactionChain canonicalTransactionChain; + RollupMerkleUtils public merkleUtils; + address public fraudVerifier; + uint public cumulativeNumElements; + bytes32[] public batches; + + + /* + * Constructor + */ + + constructor( + address _rollupMerkleUtilsAddress, + address _canonicalTransactionChain, + address _fraudVerifier + ) public { + merkleUtils = RollupMerkleUtils(_rollupMerkleUtilsAddress); + canonicalTransactionChain = CanonicalTransactionChain(_canonicalTransactionChain); + fraudVerifier = _fraudVerifier; + } + + + /* + * Public Functions + */ + + function getBatchesLength() public view returns (uint) { + return batches.length; + } + + function hashBatchHeader( + DataTypes.StateChainBatchHeader memory _batchHeader + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked( + _batchHeader.elementsMerkleRoot, + _batchHeader.numElementsInBatch, + _batchHeader.cumulativePrevElements + )); + } + + function appendStateBatch( + bytes[] memory _stateBatch + ) public { + require( + cumulativeNumElements + _stateBatch.length <= canonicalTransactionChain.cumulativeNumElements(), + "Cannot append more state commitments than total number of transactions in CanonicalTransactionChain" + ); + + require( + _stateBatch.length > 0, + "Cannot submit an empty state commitment batch" + ); + + bytes32 batchHeaderHash = keccak256(abi.encodePacked( + merkleUtils.getMerkleRoot(_stateBatch), // elementsMerkleRoot + _stateBatch.length, // numElementsInBatch + cumulativeNumElements // cumulativeNumElements + )); + + batches.push(batchHeaderHash); + cumulativeNumElements += _stateBatch.length; + } + + // verifies an element is in the current list at the given position + function verifyElement( + bytes memory _element, // the element of the list being proven + uint _position, // the position in the list of the element being proven + DataTypes.StateElementInclusionProof memory _inclusionProof + ) public view returns (bool) { + DataTypes.StateChainBatchHeader memory batchHeader = _inclusionProof.batchHeader; + if (_position != _inclusionProof.indexInBatch + + batchHeader.cumulativePrevElements) { + return false; + } + + if (!merkleUtils.verify( + batchHeader.elementsMerkleRoot, + _element, + _inclusionProof.indexInBatch, + _inclusionProof.siblings + )) { + return false; + } + + //compare computed batch header with the batch header in the list. + return hashBatchHeader(batchHeader) == batches[_inclusionProof.batchIndex]; + } + + function deleteAfterInclusive( + uint _batchIndex, + DataTypes.StateChainBatchHeader memory _batchHeader + ) public { + require( + msg.sender == fraudVerifier, + "Only FraudVerifier has permission to delete state batches" + ); + + require( + _batchIndex < batches.length, + "Cannot delete batches outside of valid range" + ); + + bytes32 calculatedBatchHeaderHash = hashBatchHeader(_batchHeader); + require( + calculatedBatchHeaderHash == batches[_batchIndex], + "Calculated batch header is different than expected batch header" + ); + + batches.length = _batchIndex; + cumulativeNumElements = _batchHeader.cumulativePrevElements; + } +} diff --git a/packages/rollup-contracts/contracts/ExecutionManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol similarity index 90% rename from packages/rollup-contracts/contracts/ExecutionManager.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol index 6d7214addeb..022e7a6f8a0 100644 --- a/packages/rollup-contracts/contracts/ExecutionManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol @@ -2,24 +2,26 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /* Internal Imports */ -import {DataTypes as dt} from "./DataTypes.sol"; -import {FullStateManager} from "./FullStateManager.sol"; -import {ContractAddressGenerator} from "./ContractAddressGenerator.sol"; -import {StubSafetyChecker} from "./ovm/test-helpers/StubSafetyChecker.sol"; -import {SafetyChecker} from "./SafetyChecker.sol"; -import {RLPEncode} from "./RLPEncode.sol"; -import {L2ToL1MessagePasser} from "./precompiles/L2ToL1MessagePasser.sol"; -import {L1MessageSender} from "./precompiles/L1MessageSender.sol"; +import { DataTypes } from "../utils/DataTypes.sol"; +import { ContractAddressGenerator } from "../utils/ContractAddressGenerator.sol"; +import { RLPEncode } from "../utils/RLPEncode.sol"; +import { L2ToL1MessagePasser } from "./precompiles/L2ToL1MessagePasser.sol"; +import { L1MessageSender } from "./precompiles/L1MessageSender.sol"; +import { FullStateManager } from "./FullStateManager.sol"; +import { StubSafetyChecker } from "./test-helpers/StubSafetyChecker.sol"; +import { SafetyChecker } from "./SafetyChecker.sol"; /** * @title ExecutionManager - * @notice The execution manager ensures that the execution of each transaction is sandboxed in a distinct environment as defined - * by the supplied backend. Only state / contracts from that backend will be accessed. + * @notice The execution manager ensures that the execution of each transaction + * is sandboxed in a distinct environment as defined by the supplied + * backend. Only state / contracts from that backend will be accessed. */ contract ExecutionManager { - /************ - * Constants * - ************/ + /* + * Contract Constants + */ + address constant ZERO_ADDRESS = 0x0000000000000000000000000000000000000000; // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes @@ -30,22 +32,22 @@ contract ExecutionManager { address constant l2ToL1MessagePasserOvmAddress = 0x4200000000000000000000000000000000000000; address constant l1MsgSenderAddress = 0x4200000000000000000000000000000000000001; - /************************ - * Contract Dependencies * - ************************/ + + /* + * Contract Variables + */ + FullStateManager stateManager; ContractAddressGenerator contractAddressGenerator; RLPEncode rlp; SafetyChecker safetyChecker; + DataTypes.ExecutionContext executionContext; - /*************** - * Other Fields * - ***************/ - dt.ExecutionContext executionContext; - /********* - * Events * - *********/ + /* + * Events + */ + event ActiveContract(address _activeContract); event CreatedContract( address _ovmContractAddress, @@ -68,16 +70,30 @@ contract ExecutionManager { bytes _revertMessage ); + + /* + * Constructor + */ + /** - * @notice Construct a new ExecutionManager with a specified safety checker & owner. - * @param _opcodeWhitelistMask A bit mask representing which opcodes are whitelisted or not for our safety checker + * @notice Construct a new ExecutionManager with a specified safety + * checker & owner. + * @param _opcodeWhitelistMask A bit mask representing which opcodes are + * whitelisted or not for our safety checker * @param _owner The owner of this contract. * @param _blockGasLimit The block gas limit for OVM blocks */ - constructor(uint256 _opcodeWhitelistMask, address _owner, uint _blockGasLimit, bool _overrideSafetyChecker) public { + constructor( + uint256 _opcodeWhitelistMask, + address _owner, + uint _blockGasLimit, + bool _overrideSafetyChecker + ) public { rlp = new RLPEncode(); + // Initialize new contract address generator contractAddressGenerator = new ContractAddressGenerator(); + // Deploy a default state manager stateManager = new FullStateManager(); // Deploy a safety checker. TODO: Pass this in as a constructor and remove `_overrideSafetyChecker` @@ -105,6 +121,11 @@ contract ExecutionManager { // TODO } + + /* + * Public Functions + */ + /** * @notice Sets a new state manager to be associated with the execution manager. * This is used when we want to swap out a new backend to be used for a different execution. @@ -118,13 +139,14 @@ contract ExecutionManager { * This is only used by the sequencer to correct nonces when transactions fail. * @param addr The address of the nonce to increment. */ - function incrementNonce(address addr) external { + function incrementNonce(address addr) public { stateManager.incrementOvmContractNonce(addr); } - /******************** - * Execute EOA Calls * - ********************/ + + /********************* + * Execute EOA Calls * + *********************/ /** * @notice Execute an Externally Owned Account (EOA) call. This will accept all information required @@ -151,16 +173,28 @@ contract ExecutionManager { ) public { // Get EOA address address eoaAddress = recoverEOAAddress(_nonce, _ovmEntrypoint, _callBytes, _v, _r, _s); + // Require that the EOA signature isn't zero (invalid signature) require(eoaAddress != ZERO_ADDRESS, "Failed to recover signature"); + // Require nonce to be correct require(_nonce == stateManager.getOvmContractNonce(eoaAddress), "Incorrect nonce!"); + emit CallingWithEOA( eoaAddress, _ovmEntrypoint ); + // Make the EOA call for the account - executeTransaction(_timestamp, _queueOrigin, _ovmEntrypoint, _callBytes, eoaAddress, ZERO_ADDRESS, false); + executeTransaction( + _timestamp, + _queueOrigin, + _ovmEntrypoint, + _callBytes, + eoaAddress, + ZERO_ADDRESS, + false + ); } /** @@ -184,6 +218,7 @@ contract ExecutionManager { ) public { require(_timestamp > 0, "Timestamp must be greater than 0"); uint _nonce = stateManager.getOvmContractNonce(_fromAddress); + // Initialize our context initializeContext(_timestamp, _queueOrigin, _fromAddress, _l1MsgSenderAddress); @@ -194,16 +229,19 @@ contract ExecutionManager { bytes32 methodId; uint256 callSize; bool isCreate = _ovmEntrypoint == ZERO_ADDRESS; + // Check if we're creating -- ovmEntrypoint == ZERO_ADDRESS if (isCreate) { methodId = ovmCreateMethodId; callSize = _callBytes.length + 4; + // Emit event that we are creating a contract with an EOA address _newOvmContractAddress = contractAddressGenerator.getAddressFromCREATE(_fromAddress, _nonce); emit EOACreatedContract(_newOvmContractAddress); } else { methodId = ovmCallMethodId; callSize = _callBytes.length + 32 + 4; + // Creates will get incremented, but calls need to be as well! stateManager.incrementOvmContractNonce(_fromAddress); } @@ -214,9 +252,11 @@ contract ExecutionManager { // And now set the ovmEntrypoint mstore(add(_callBytes, 4), _ovmEntrypoint) } + if eq(isCreate, 1) { _callBytes := add(_callBytes, 28) } + mstore8(_callBytes, shr(24, methodId)) mstore8(add(_callBytes, 1), shr(16, methodId)) mstore8(add(_callBytes, 2), shr(8, methodId)) @@ -235,9 +275,11 @@ contract ExecutionManager { if eq(success, 1) { return(resultData, returndatasize) } + if eq(_allowRevert, 1) { revert(resultData, returndatasize) } + mstore(result, returndatasize) mstore(0x40, add(resultData, returndatasize)) } @@ -274,12 +316,14 @@ contract ExecutionManager { message[0] = rlp.encodeUint(_nonce); // Nonce message[1] = rlp.encodeUint(0); // Gas price message[2] = rlp.encodeUint(executionContext.gasLimit); // Gas limit + // To -- Special rlp encoding handling if _to is the ZERO_ADDRESS if (_to == ZERO_ADDRESS) { message[3] = rlp.encodeUint(0); } else { message[3] = rlp.encodeAddress(_to); } + message[4] = rlp.encodeUint(0); // Value message[5] = rlp.encodeBytes(_callData); // Data message[6] = rlp.encodeUint(executionContext.chainId); // ChainID @@ -288,6 +332,7 @@ contract ExecutionManager { bytes memory encodedMessage = rlp.encodeList(message); bytes32 hash = keccak256(abi.encodePacked(encodedMessage)); + /* * Replay protection is used to prevent signatures on one chain from * being used on other chains. To support replay protection ethereum @@ -298,9 +343,10 @@ contract ExecutionManager { return ecrecover(hash, (_v - uint8(executionContext.chainId) * 2) - 8, _r, _s); } - /********************** - * OVM Context Opcodes * - **********************/ + + /*********************** + * OVM Context Opcodes * + ***********************/ /** * @notice CALLER opcode (msg.sender) -- this gets the caller of the currently-running contract. @@ -313,7 +359,10 @@ contract ExecutionManager { */ function ovmCALLER() public view { // First make sure the ovmMsgSender was set - require(executionContext.ovmMsgSender != ZERO_ADDRESS, "Error: attempting to access non-existent msgSender."); + require( + executionContext.ovmMsgSender != ZERO_ADDRESS, + "Error: attempting to access non-existent msgSender." + ); // This is returned as left-padded, big-endian, so pad it left! bytes32 addressBytes = bytes32(bytes20(executionContext.ovmMsgSender)) >> 96; @@ -336,7 +385,10 @@ contract ExecutionManager { */ function ovmADDRESS() public view { // First make sure the ovmMsgSender was set - require(executionContext.ovmActiveContract != ZERO_ADDRESS, "Error: attempting to access non-existent ovmActiveContract."); + require( + executionContext.ovmActiveContract != ZERO_ADDRESS, + "Error: attempting to access non-existent ovmActiveContract." + ); // This is returned as left-padded, big-endian, so pad it left! bytes32 addressBytes = bytes32(bytes20(executionContext.ovmActiveContract)) >> 96; @@ -447,7 +499,10 @@ contract ExecutionManager { * returndata: 32-byte ORIGIN address containing the left-padded, big-endian encoding of the address. */ function ovmORIGIN() public view { - require(executionContext.ovmTxOrigin != ZERO_ADDRESS, "Error: attempting to access non-existent txOrigin."); + require( + executionContext.ovmTxOrigin != ZERO_ADDRESS, + "Error: attempting to access non-existent txOrigin." + ); bytes32 addressBytes = bytes32(bytes20(executionContext.ovmTxOrigin)) >> 96; @@ -458,9 +513,9 @@ contract ExecutionManager { } } - /*************************** - * Contract Creation Opcode * - ***************************/ + /**************************** + * Contract Creation Opcode * + ****************************/ /** * @notice CREATE opcode -- deploying a new ovm contract to a CREATE address. @@ -498,6 +553,7 @@ contract ExecutionManager { address creator = executionContext.ovmActiveContract; uint creatorNonce = stateManager.getOvmContractNonce(creator); address _newOvmContractAddress = contractAddressGenerator.getAddressFromCREATE(creator, creatorNonce); + // Next we need to actually create the contract in our state at that address if (!createNewContract(_newOvmContractAddress, _ovmInitcode)) { // Failure: Return 0 address @@ -507,6 +563,7 @@ contract ExecutionManager { return(returnData, 0x20) } } + // We also need to increment the contract nonce stateManager.incrementOvmContractNonce(creator); @@ -560,6 +617,7 @@ contract ExecutionManager { // First we need to generate the CREATE2 address address creator = executionContext.ovmActiveContract; address _newOvmContractAddress = contractAddressGenerator.getAddressFromCREATE2(creator, _salt, _ovmInitcode); + // Next we need to actually create the contract in our state at that address if (!createNewContract(_newOvmContractAddress, _ovmInitcode)) { // Failure: Return 0 address @@ -596,6 +654,7 @@ contract ExecutionManager { } // Switch the context to be the new contract (address oldMsgSender, address oldActiveContract) = switchActiveContract(_newOvmContractAddress); + // Deploy the _ovmInitcode as a code contract -- Note the init script will run in the newly set context address codeContractAddress = deployCodeContract(_ovmInitcode); // Get the runtime bytecode @@ -605,14 +664,18 @@ contract ExecutionManager { // Contract runtime bytecode is not safe. return false; } + // Associate the code contract with our ovm contract stateManager.associateCodeContract(_newOvmContractAddress, codeContractAddress); // Get the code contract address to be emitted by a CreatedContract event bytes32 codeContractHash = keccak256(codeContractBytecode); + // Revert to the previous the context restoreContractContext(oldMsgSender, oldActiveContract); + // Emit CreatedContract event! We've created a new contract! emit CreatedContract(_newOvmContractAddress, codeContractAddress, codeContractHash); + return true; } @@ -652,6 +715,7 @@ contract ExecutionManager { uint callSize; bytes memory _callBytes; bytes32 _targetOvmContractAddressBytes; + // parse calldata assembly { // skip 4 bytes for methodID and first 12 bytes of address @@ -665,6 +729,7 @@ contract ExecutionManager { mstore(0x40, add(_callBytes, callSize)) calldatacopy(_callBytes, 0x24, callSize) } + address _targetOvmContractAddress = address(bytes20(_targetOvmContractAddressBytes)); // switch the context to the _targetOvmContractAddress @@ -829,9 +894,9 @@ contract ExecutionManager { } - /*************************** - * Contract Storage Opcodes * - ***************************/ + /**************************** + * Contract Storage Opcodes * + ****************************/ /** * @notice Load a value from storage. Note each contract has it's own storage. @@ -885,9 +950,9 @@ contract ExecutionManager { emit SetStorage(executionContext.ovmActiveContract, _storageSlot, _storageValue); } - /*********************** - * Code-related Opcodes * - ************************/ + /************************ + * Code-related Opcodes * + ************************/ /** * @notice Executes the extcodesize operation for the contract address provided. @@ -980,22 +1045,34 @@ contract ExecutionManager { } } - /******** - * Utils * - ********/ + /********* + * Utils * + *********/ /** - * @notice Initialize a new context, setting the timestamp, queue origin, and gasLimit as well as zeroing out the - * msgSender of the previous context. - * NOTE: this zeroing may not technically be needed as the context should always end up as zero at the end of each execution. + * @notice Initialize a new context, setting the timestamp, queue origin, + * and gasLimit as well as zeroing out the msgSender of the + * previous context. NOTE: this zeroing may not technically be + * needed as the context should always end up as zero at the end of + * each execution. * @param _timestamp The timestamp which should be used for this context. - * @param _queueOrigin The queue which this context's transaction was sent from. - * @param _ovmTxOrigin The tx.origin for the currently executing transaction. It will be ZERO_ADDRESS if it's not an EOA call. + * @param _queueOrigin Queue from which this transaction was sent. + * @param _ovmTxOrigin The tx.origin for the currently executing + * transaction. It will be ZERO_ADDRESS if it's not an + * EOA call. */ - function initializeContext(uint _timestamp, uint _queueOrigin, address _ovmTxOrigin, address _l1MsgSender) internal { - // First zero out the context for good measure (Note ZERO_ADDRESS is reserved for the genesis contract & initial msgSender) + function initializeContext( + uint _timestamp, + uint _queueOrigin, + address _ovmTxOrigin, + address _l1MsgSender + ) internal { + // First zero out the context for good measure (Note ZERO_ADDRESS is + // reserved for the genesis contract & initial msgSender). restoreContractContext(ZERO_ADDRESS, ZERO_ADDRESS); - // And finally set the timestamp, queue origin, tx origin, and l1MessageSender + + // And finally set the timestamp, queue origin, tx origin, and + // l1MessageSender. executionContext.timestamp = _timestamp; executionContext.queueOrigin = _queueOrigin; executionContext.ovmTxOrigin = _ovmTxOrigin; @@ -1003,19 +1080,27 @@ contract ExecutionManager { } /** - * @notice Change the active contract to be something new. This is used when a new contract is called. + * @notice Change the active contract to be something new. This is used + * when a new contract is called. * @param _newActiveContract The new active contract - * @return The old msgSender and activeContract. This will be used when we restore the old active contract. + * @return The old msgSender and activeContract. This will be used when we + * restore the old active contract. */ - function switchActiveContract(address _newActiveContract) internal returns(address _oldMsgSender, address _oldActiveContract) { + function switchActiveContract( + address _newActiveContract + ) internal returns (address _oldMsgSender, address _oldActiveContract) { // Store references to the old context _oldActiveContract = executionContext.ovmActiveContract; _oldMsgSender = executionContext.ovmMsgSender; + // Set our new context executionContext.ovmActiveContract = _newActiveContract; executionContext.ovmMsgSender = _oldActiveContract; - // Emit an event so we can track the active contract. This is used in order to parse transaction receipts in the fullnode + + // Emit an event so we can track the active contract. This is used in + // order to parse transaction receipts in the fullnode. emit ActiveContract(_newActiveContract); + // Return old context so we can later revert to it return (_oldMsgSender, _oldActiveContract); } @@ -1025,20 +1110,36 @@ contract ExecutionManager { * @param _msgSender The msgSender to be restored. * @param _activeContract The activeContract to be restored. */ - function restoreContractContext(address _msgSender, address _activeContract) internal { + function restoreContractContext( + address _msgSender, + address _activeContract + ) internal { // Revert back to the old context executionContext.ovmActiveContract = _activeContract; executionContext.ovmMsgSender = _msgSender; } /** - * @notice Getter for the execution context's L1MessageSender. Used by the L1MessageSender precompile. + * @notice Getter for the execution context's L1MessageSender. Used by the + * L1MessageSender precompile. * @return The L1MessageSender in our current execution context. */ function getL1MessageSender() public returns(address) { - require(executionContext.ovmActiveContract == l1MsgSenderAddress, "Only the L1MessageSender precompile is allowed to call getL1MessageSender(...)!"); - require(executionContext.l1MessageSender != ZERO_ADDRESS, "L1MessageSender not set!"); - require(executionContext.ovmMsgSender == ZERO_ADDRESS, "L1MessageSender only accessible in entrypoint contract!"); + require( + executionContext.ovmActiveContract == l1MsgSenderAddress, + "Only the L1MessageSender precompile is allowed to call getL1MessageSender(...)!" + ); + + require( + executionContext.l1MessageSender != ZERO_ADDRESS, + "L1MessageSender not set!" + ); + + require( + executionContext.ovmMsgSender == ZERO_ADDRESS, + "L1MessageSender only accessible in entrypoint contract!" + ); + return executionContext.l1MessageSender; } diff --git a/packages/rollup-contracts/contracts/ovm/FraudVerifier.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/FraudVerifier.sol similarity index 80% rename from packages/rollup-contracts/contracts/ovm/FraudVerifier.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/FraudVerifier.sol index 470d936c9c9..682b3869e3d 100644 --- a/packages/rollup-contracts/contracts/ovm/FraudVerifier.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/FraudVerifier.sol @@ -11,9 +11,11 @@ contract FraudVerifier { function initNewStateTransitioner(uint _preStateTransitionIndex) public returns(bool) { // TODO: - // Create a new state transitioner for some specific pre-state transition index (assuming one hasn't already been made). + // Create a new state transitioner for some specific pre-state + // transition index (assuming one hasn't already been made). // Note that the invalid state root that we are verifying is at _preStateTransitionIndex+1. - // Add it to the stateTransitioners mapping! -- stateTransitioners[_preStateTransitionIndex] = newStateTransitioner; + // Add it to the stateTransitioners mapping! + // -- stateTransitioners[_preStateTransitionIndex] = newStateTransitioner; return true; } diff --git a/packages/rollup-contracts/contracts/FullStateManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/FullStateManager.sol similarity index 75% rename from packages/rollup-contracts/contracts/FullStateManager.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/FullStateManager.sol index 63bf2d1ee36..6c490d12cfb 100644 --- a/packages/rollup-contracts/contracts/FullStateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/FullStateManager.sol @@ -2,8 +2,8 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /* Internal Imports */ -import {StateManager} from "./StateManager.sol"; -import {SafetyChecker} from "./SafetyChecker.sol"; +import { StateManager } from "./StateManager.sol"; +import { SafetyChecker } from "./SafetyChecker.sol"; /** * @title FullStateManager @@ -11,6 +11,10 @@ import {SafetyChecker} from "./SafetyChecker.sol"; * of all chain storage. */ contract FullStateManager is StateManager { + /* + * Contract Variables + */ + address ZERO_ADDRESS = 0x0000000000000000000000000000000000000000; mapping(address=>mapping(bytes32=>bytes32)) ovmContractStorage; @@ -18,9 +22,14 @@ contract FullStateManager is StateManager { mapping(address=>address) ovmCodeContracts; - /********** - * Storage * - **********/ + /* + * Public Functions + */ + + + /*********** + * Storage * + ***********/ /** * @notice Get storage for OVM contract at some slot. @@ -28,7 +37,10 @@ contract FullStateManager is StateManager { * @param _slot The slot we're querying. * @return The bytes32 value stored at the particular slot. */ - function getStorage(address _ovmContractAddress, bytes32 _slot) external view returns(bytes32) { + function getStorage( + address _ovmContractAddress, + bytes32 _slot + ) public view returns (bytes32) { return ovmContractStorage[_ovmContractAddress][_slot]; } @@ -38,22 +50,27 @@ contract FullStateManager is StateManager { * @param _slot The slot we're setting. * @param _value The value we will set the storage to. */ - function setStorage(address _ovmContractAddress, bytes32 _slot, bytes32 _value) external { + function setStorage( + address _ovmContractAddress, + bytes32 _slot, + bytes32 _value + ) public { ovmContractStorage[_ovmContractAddress][_slot] = _value; } - /********* - * Nonces * - *********/ - // This is used during contract creation to determine the contract address + /********** + * Nonces * + **********/ /** * @notice Get the nonce for a particular OVM contract * @param _ovmContractAddress The contract we're getting the nonce of. * @return The contract nonce used for contract creation. */ - function getOvmContractNonce(address _ovmContractAddress) external view returns(uint) { + function getOvmContractNonce( + address _ovmContractAddress + ) public view returns (uint) { return ovmContractNonces[_ovmContractAddress]; } @@ -62,7 +79,10 @@ contract FullStateManager is StateManager { * @param _ovmContractAddress The contract we're setting the nonce of. * @param _value The new nonce. */ - function setOvmContractNonce(address _ovmContractAddress, uint _value) external { + function setOvmContractNonce( + address _ovmContractAddress, + uint _value + ) public { ovmContractNonces[_ovmContractAddress] = _value; } @@ -70,15 +90,16 @@ contract FullStateManager is StateManager { * @notice Increment the nonce for a particular OVM contract. * @param _ovmContractAddress The contract we're incrementing by 1 the nonce of. */ - function incrementOvmContractNonce(address _ovmContractAddress) external { + function incrementOvmContractNonce( + address _ovmContractAddress + ) public { ovmContractNonces[_ovmContractAddress] += 1; } - /***************** - * Contract Codes * - *****************/ - // This is used when CALLing a contract + /****************** + * Contract Codes * + ******************/ /** * @notice Attaches some code contract to the desired OVM contract. This allows the Execution Manager @@ -86,7 +107,10 @@ contract FullStateManager is StateManager { * @param _ovmContractAddress The address of the OVM contract we'd like to associate with some code. * @param _codeContractAddress The address of the code contract that's been deployed. */ - function associateCodeContract(address _ovmContractAddress, address _codeContractAddress) public { + function associateCodeContract( + address _ovmContractAddress, + address _codeContractAddress + ) public { ovmCodeContracts[_ovmContractAddress] = _codeContractAddress; } @@ -95,7 +119,9 @@ contract FullStateManager is StateManager { * @param _ovmContractAddress The address of the OVM contract. * @return The associated code contract address. */ - function getCodeContractAddress(address _ovmContractAddress) external view returns(address) { + function getCodeContractAddress( + address _ovmContractAddress + ) public view returns (address) { return ovmCodeContracts[_ovmContractAddress]; } @@ -105,7 +131,9 @@ contract FullStateManager is StateManager { * @param _codeContractAddress The address of the code contract. * @return The bytecode at this address. */ - function getCodeContractBytecode(address _codeContractAddress) public view returns (bytes memory codeContractBytecode) { + function getCodeContractBytecode( + address _codeContractAddress + ) public view returns (bytes memory codeContractBytecode) { assembly { // retrieve the size of the code let size := extcodesize(_codeContractAddress) @@ -126,7 +154,9 @@ contract FullStateManager is StateManager { * @param _codeContractAddress The address of the code contract. * @return The hash of the bytecode at this address. */ - function getCodeContractHash(address _codeContractAddress) external view returns (bytes32 _codeContractHash) { + function getCodeContractHash( + address _codeContractAddress + ) public view returns (bytes32 _codeContractHash) { // TODO: Look up cached hash values eventually to avoid having to load all of this bytecode bytes memory codeContractBytecode = getCodeContractBytecode(_codeContractAddress); _codeContractHash = keccak256(codeContractBytecode); diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/L2ExecutionManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/L2ExecutionManager.sol new file mode 100644 index 00000000000..d52d2572235 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/L2ExecutionManager.sol @@ -0,0 +1,92 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +/* Internal Imports */ +import { ExecutionManager } from "./ExecutionManager.sol"; + +/** + * @title L2ExecutionManager + * @notice This extension of ExecutionManager that should only run in L2 because it has optimistic execution details + * that are unnecessary and inefficient to run in L1. + */ +contract L2ExecutionManager is ExecutionManager { + /* + * Contract Variables + */ + + mapping(bytes32 => bytes32) ovmHashToEvmHash; + mapping(bytes32 => bytes32) evmHashToOvmHash; + mapping(bytes32 => bytes) ovmHashToOvmTx; + + + /* + * Constructor + */ + + constructor( + uint256 _opcodeWhitelistMask, + address _owner, + uint _gasLimit, + bool _overrideSafetyChecker + ) ExecutionManager( + _opcodeWhitelistMask, + _owner, + _gasLimit, + _overrideSafetyChecker + ) public {} + + + /* + * Public Functions + */ + + /** + * @notice Stores the provided OVM transaction, mapping its hash to its value and its hash to the EVM tx hash + with which it's associated. + * @param ovmTransactionHash The OVM transaction hash, used publicly as the reference to the transaction. + * @param internalTransactionHash The internal transaction hash of the transaction actually executed. + * @param signedOvmTx The signed OVM tx that we received + */ + function storeOvmTransaction( + bytes32 ovmTransactionHash, + bytes32 internalTransactionHash, + bytes memory signedOvmTx + ) public { + evmHashToOvmHash[internalTransactionHash] = ovmTransactionHash; + ovmHashToEvmHash[ovmTransactionHash] = internalTransactionHash; + ovmHashToOvmTx[ovmTransactionHash] = signedOvmTx; + } + + /** + * @notice Gets the OVM transaction hash associated with the provided EVM transaction hash. + * @param evmTransactionHash The EVM transaction hash. + * @return The associated OVM transaction hash. + */ + function getOvmTransactionHash( + bytes32 evmTransactionHash + ) public view returns (bytes32) { + return evmHashToOvmHash[evmTransactionHash]; + } + + /** + * @notice Gets the EVM transaction hash associated with the provided OVM transaction hash. + * @param ovmTransactionHash The OVM transaction hash. + * @return The associated EVM transaction hash. + */ + function getInternalTransactionHash( + bytes32 ovmTransactionHash + ) public view returns (bytes32) { + return ovmHashToEvmHash[ovmTransactionHash]; + } + + /** + * @notice Gets the OVM transaction associated with the provided OVM transaction hash. + * @param ovmTransactionHash The OVM transaction hash. + * @return The associated signed OVM transaction. + */ + function getOvmTransaction( + bytes32 ovmTransactionHash + ) public view returns (bytes memory) { + return ovmHashToOvmTx[ovmTransactionHash]; + } +} diff --git a/packages/rollup-contracts/contracts/ovm/PartialStateManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol similarity index 82% rename from packages/rollup-contracts/contracts/ovm/PartialStateManager.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol index b87cadb0195..4d0f6ab5394 100644 --- a/packages/rollup-contracts/contracts/ovm/PartialStateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol @@ -74,12 +74,20 @@ contract PartialStateManager { * Pre-Execution * ****************/ - function insertVerifiedStorage(address _ovmContractAddress, bytes32 _slot, bytes32 _value) external onlyStateTransitioner { + function insertVerifiedStorage( + address _ovmContractAddress, + bytes32 _slot, + bytes32 _value + ) external onlyStateTransitioner { isVerifiedStorage[_ovmContractAddress][_slot] = true; ovmContractStorage[_ovmContractAddress][_slot] = _value; } - function insertVerifiedContract(address _ovmContractAddress, address _codeContractAddress, uint _nonce) external onlyStateTransitioner { + function insertVerifiedContract( + address _ovmContractAddress, + address _codeContractAddress, + uint _nonce + ) external onlyStateTransitioner { isVerifiedContract[_ovmContractAddress] = true; ovmContractNonces[_ovmContractAddress] = _nonce; ovmCodeContracts[_ovmContractAddress] = _codeContractAddress; @@ -89,7 +97,11 @@ contract PartialStateManager { * Post-Execution * *****************/ - function popUpdatedStorageSlot() external onlyStateTransitioner returns(address storageSlotContract, bytes32 storageSlotKey, bytes32 storageSlotValue) { + function popUpdatedStorageSlot() external onlyStateTransitioner returns ( + address storageSlotContract, + bytes32 storageSlotKey, + bytes32 storageSlotValue + ) { require(updatedStorageSlotCounter > 0, "No more elements to pop!"); // Get the next storage we need to update using the updatedStorageSlotCounter @@ -104,7 +116,11 @@ contract PartialStateManager { return (storageSlotContract, storageSlotKey, storageSlotValue); } - function popUpdatedContract() external onlyStateTransitioner returns(address ovmContractAddress, uint contractNonce) { + + function popUpdatedContract() external onlyStateTransitioner returns ( + address ovmContractAddress, + uint contractNonce + ) { require(updatedContractsCounter > 0, "No more elements to pop!"); // Get the next storage we need to update using the updatedStorageSlotCounter @@ -126,7 +142,10 @@ contract PartialStateManager { * @param _slot The slot we're querying. * @return The bytes32 value stored at the particular slot. */ - function getStorage(address _ovmContractAddress, bytes32 _slot) onlyExecutionManager public returns(bytes32) { + function getStorage( + address _ovmContractAddress, + bytes32 _slot + ) onlyExecutionManager public returns (bytes32) { flagIfNotVerifiedStorage(_ovmContractAddress, _slot); return ovmContractStorage[_ovmContractAddress][_slot]; @@ -138,7 +157,11 @@ contract PartialStateManager { * @param _slot The slot we're setting. * @param _value The value we will set the storage to. */ - function setStorage(address _ovmContractAddress, bytes32 _slot, bytes32 _value) onlyExecutionManager public { + function setStorage( + address _ovmContractAddress, + bytes32 _slot, + bytes32 _value + ) onlyExecutionManager public { flagIfNotVerifiedStorage(_ovmContractAddress, _slot); // Add this storage slot to the list of updated storage @@ -160,7 +183,9 @@ contract PartialStateManager { * @param _ovmContractAddress The contract we're getting the nonce of. * @return The contract nonce used for contract creation. */ - function getOvmContractNonce(address _ovmContractAddress) onlyExecutionManager public returns(uint) { + function getOvmContractNonce( + address _ovmContractAddress + ) onlyExecutionManager public returns (uint) { flagIfNotVerifiedContract(_ovmContractAddress); return ovmContractNonces[_ovmContractAddress]; @@ -171,7 +196,10 @@ contract PartialStateManager { * @param _ovmContractAddress The contract we're setting the nonce of. * @param _value The new nonce. */ - function setOvmContractNonce(address _ovmContractAddress, uint _value) onlyExecutionManager public { + function setOvmContractNonce( + address _ovmContractAddress, + uint _value + ) onlyExecutionManager public { flagIfNotVerifiedContract(_ovmContractAddress); // Add this contract to the list of updated contracts @@ -186,7 +214,9 @@ contract PartialStateManager { * @notice Increment the nonce for a particular OVM contract. * @param _ovmContractAddress The contract we're incrementing by 1 the nonce of. */ - function incrementOvmContractNonce(address _ovmContractAddress) onlyExecutionManager public { + function incrementOvmContractNonce( + address _ovmContractAddress + ) onlyExecutionManager public { flagIfNotVerifiedContract(_ovmContractAddress); // Add this contract to the list of updated contracts @@ -209,7 +239,10 @@ contract PartialStateManager { * @param _ovmContractAddress The address of the OVM contract we'd like to associate with some code. * @param _codeContractAddress The address of the code contract that's been deployed. */ - function associateCodeContract(address _ovmContractAddress, address _codeContractAddress) onlyExecutionManager public { + function associateCodeContract( + address _ovmContractAddress, + address _codeContractAddress + ) onlyExecutionManager public { ovmCodeContracts[_ovmContractAddress] = _codeContractAddress; } @@ -218,7 +251,9 @@ contract PartialStateManager { * @param _ovmContractAddress The address of the OVM contract. * @return The associated code contract address. */ - function getCodeContractAddress(address _ovmContractAddress) onlyExecutionManager public returns(address) { + function getCodeContractAddress( + address _ovmContractAddress + ) onlyExecutionManager public returns(address) { flagIfNotVerifiedContract(_ovmContractAddress); return ovmCodeContracts[_ovmContractAddress]; @@ -230,9 +265,12 @@ contract PartialStateManager { * @param _codeContractAddress The address of the code contract. * @return The bytecode at this address. */ - function getCodeContractBytecode(address _codeContractAddress) public view returns (bytes memory codeContractBytecode) { - // NOTE: We don't need to verify that this is an authenticated contract because this will always be proceeded by a - // call to getCodeContractAddress(address _ovmContractAddress) in the EM which does this check. + function getCodeContractBytecode( + address _codeContractAddress + ) public view returns (bytes memory codeContractBytecode) { + // NOTE: We don't need to verify that this is an authenticated contract + // because this will always be proceeded by a call to + // getCodeContractAddress(address _ovmContractAddress) in the EM which does this check. assembly { // retrieve the size of the code @@ -254,9 +292,12 @@ contract PartialStateManager { * @param _codeContractAddress The address of the code contract. * @return The hash of the bytecode at this address. */ - function getCodeContractHash(address _codeContractAddress) public view returns (bytes32 _codeContractHash) { - // NOTE: We don't need to verify that this is an authenticated contract because this will always be proceeded by a - // call to getCodeContractAddress(address _ovmContractAddress) in the EM which does this check. + function getCodeContractHash( + address _codeContractAddress + ) public view returns (bytes32 _codeContractHash) { + // NOTE: We don't need to verify that this is an authenticated contract + // because this will always be proceeded by a call to + // getCodeContractAddress(address _ovmContractAddress) in the EM which does this check. // TODO: Use EXTCODEHASH instead of this really inefficient stuff. bytes memory codeContractBytecode = getCodeContractBytecode(_codeContractAddress); diff --git a/packages/rollup-contracts/contracts/SafetyChecker.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/SafetyChecker.sol similarity index 72% rename from packages/rollup-contracts/contracts/SafetyChecker.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/SafetyChecker.sol index 88cb774bfe7..0968ae507ec 100644 --- a/packages/rollup-contracts/contracts/SafetyChecker.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/SafetyChecker.sol @@ -1,19 +1,23 @@ pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; /** * @title SafetyChecker - * @notice Safety Checker contract used to check whether or not bytecode is safe, meaning: - * 1. It uses only whitelisted opcodes - * 2. All CALLs are to the Execution Manager and have no value set (no ETH sent) + * @notice Safety Checker contract used to check whether or not bytecode is + * safe, meaning: + * 1. It uses only whitelisted opcodes. + * 2. All CALLs are to the Execution Manager and have no value. */ contract SafetyChecker { uint256 public opcodeWhitelistMask; address public executionManagerAddress; /** - * @notice Construct a new Safety Checker with the specified whitelist mask - * @param _opcodeWhitelistMask A hex number of 256 bits where each bit represents an opcode, 0 - 255, which is set if whitelisted and unset otherwise. - * @param _executionManagerAddress The address of the ExecutionManager.sol contract + * @notice Create a new Safety Checker with the specified whitelist mask. + * @param _opcodeWhitelistMask A hex number of 256 bits where each bit + * represents an opcode, 0 - 255, which is set + * if whitelisted and unset otherwise. + * @param _executionManagerAddress Execution manager contract address. */ constructor(uint256 _opcodeWhitelistMask, address _executionManagerAddress) public { opcodeWhitelistMask = _opcodeWhitelistMask; @@ -21,12 +25,18 @@ contract SafetyChecker { } /** - * @notice Converts the 20 bytes at _start of _bytes into an address - * @param _bytes The bytes to extract the address from - * @param _start The start index from which to extract the address from (e.g. 0 if _bytes starts with the address) + * @notice Converts the 20 bytes at _start of _bytes into an address. + * @param _bytes The bytes to extract the address from. + * @param _start The start index from which to extract the address from + * (e.g. 0 if _bytes starts with the address). + * @return Bytes converted to an address. */ - function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address addr) { + function toAddress( + bytes memory _bytes, + uint256 _start + ) internal pure returns (address addr) { require(_bytes.length >= (_start + 20), "Addresses must be at least 20 bytes"); + assembly { addr := mload(add(add(_bytes, 20), _start)) } @@ -34,8 +44,14 @@ contract SafetyChecker { /** * @notice Returns whether or not all of the provided bytecode is safe. - * @param _bytecode The bytecode to safety check. This can be either creation bytecode (aka initcode) or runtime bytecode (aka contract code). - * More info on creation vs. runtime bytecode: https://medium.com/authereum/bytecode-and-init-code-and-runtime-code-oh-my-7bcd89065904 + * @dev More info on creation vs. runtime bytecode: + * https://medium.com/authereum/bytecode-and-init-code-and-runtime-code-oh-my-7bcd89065904. + * @param _bytecode The bytecode to safety check. This can be either + * creation bytecode (aka initcode) or runtime bytecode + * (aka cont + * More info on creation vs. runtime bytecode: + * https://medium.com/authereum/bytecode-and-init-code-and-runtime-code-oh-my-7bcd89065904ract code). + * @return `true` if the bytecode is safe, `false` otherwise. */ function isBytecodeSafe( bytes memory _bytecode @@ -89,7 +105,12 @@ contract SafetyChecker { insideUnreachableCode = true; // CALL } else if (op == 0xf1) { - // Minimum 4 total ops: 1. PUSH1 value, 2. PUSH20 execution manager address,3. PUSH or DUP gas, 4. CALL + // Minimum 4 total ops: + // 1. PUSH1 value + // 2. PUSH20 execution manager address + // 3. PUSH or DUP gas + // 4. CALL + if (opIndex < 3) { return false; } @@ -109,8 +130,13 @@ contract SafetyChecker { if (gasOp >= 0x60 && gasOp <= 0x7f) { pushedBytes = gasOp - 0x5f; } - byte callValue = _bytecode[pc - (23 + pushedBytes)]; // 23 is from 1 + PUSH20 + 20 bytes of address + PUSH or DUP gas - address callAddress = toAddress(_bytecode, (pc - (21 + pushedBytes))); // 21 is from 1 + 19 bytes of address + PUSH or DUP gas + + // 23 is from 1 + PUSH20 + 20 bytes of address + PUSH or DUP gas + byte callValue = _bytecode[pc - (23 + pushedBytes)]; + + // 21 is from 1 + 19 bytes of address + PUSH or DUP gas + address callAddress = toAddress(_bytecode, (pc - (21 + pushedBytes))); + // CALL is made to the execution manager with msg.value of 0 ETH if (callAddress != executionManagerAddress || callValue != 0 ) { return false; diff --git a/packages/rollup-contracts/contracts/StateManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/StateManager.sol similarity index 77% rename from packages/rollup-contracts/contracts/StateManager.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/StateManager.sol index dc2758acb87..7248c90b7fb 100644 --- a/packages/rollup-contracts/contracts/StateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/StateManager.sol @@ -1,9 +1,11 @@ pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; /** * @title StateManager - * @notice The StateManager is a simple abstraction which can be extended by either the Stateful client - * or the stateless client so that both can share the same Execution Manager. + * @notice The StateManager is a simple abstraction which can be extended by + * either the Stateful client or the stateless client so that both can + * share the same Execution Manager. */ contract StateManager { // Storage @@ -18,6 +20,8 @@ contract StateManager { // Contract code storage / contract address retrieval function associateCodeContract(address _ovmContractAddress, address _codeContractAddress) public; function getCodeContractAddress(address _ovmContractAddress) external view returns(address); - function getCodeContractBytecode(address _codeContractAddress) public view returns (bytes memory codeContractBytecode); + function getCodeContractBytecode( + address _codeContractAddress + ) public view returns (bytes memory codeContractBytecode); function getCodeContractHash(address _codeContractAddress) external view returns (bytes32 _codeContractHash); } diff --git a/packages/rollup-contracts/contracts/ovm/StateTransitioner.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol similarity index 84% rename from packages/rollup-contracts/contracts/ovm/StateTransitioner.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol index e925c41117b..86077d3a07b 100644 --- a/packages/rollup-contracts/contracts/ovm/StateTransitioner.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol @@ -63,7 +63,11 @@ contract StateTransitioner { /**************************** * Pre-Transaction Execution * ****************************/ - function proveContractInclusion(address _ovmContractAddress, address _codeContractAddress, uint _nonce) external preExecutionPhase { + function proveContractInclusion( + address _ovmContractAddress, + address _codeContractAddress, + uint _nonce + ) external preExecutionPhase { bytes32 codeHash; assembly { codeHash := extcodehash(_codeContractAddress) @@ -73,8 +77,15 @@ contract StateTransitioner { stateManager.insertVerifiedContract(_ovmContractAddress, _codeContractAddress, _nonce); } - function proveStorageSlotInclusion(address _ovmContractAddress, bytes32 _slot, bytes32 _value) external preExecutionPhase { - require(stateManager.isVerifiedContract(_ovmContractAddress), "Contract must be verified before proving storage!"); + function proveStorageSlotInclusion( + address _ovmContractAddress, + bytes32 _slot, + bytes32 _value + ) external preExecutionPhase { + require( + stateManager.isVerifiedContract(_ovmContractAddress), + "Contract must be verified before proving storage!" + ); // TODO: Verify an inclusion proof of the storage slot! stateManager.insertVerifiedStorage(_ovmContractAddress, _slot, _value); @@ -107,11 +118,18 @@ contract StateTransitioner { * Post-Transaction Execution * ****************************/ function proveUpdatedStorageSlot() public postExecutionPhase { - (address storageSlotContract, bytes32 storageSlotKey, bytes32 storageSlotValue) = stateManager.popUpdatedStorageSlot(); + ( + address storageSlotContract, + bytes32 storageSlotKey, + bytes32 storageSlotValue + ) = stateManager.popUpdatedStorageSlot(); // TODO: Prove inclusion / make this update to the root } function proveUpdatedContract() public postExecutionPhase { - (address ovmContractAddress, uint contractNonce) = stateManager.popUpdatedContract(); + ( + address ovmContractAddress, + uint contractNonce + ) = stateManager.popUpdatedContract(); // TODO: Prove inclusion / make this update to the root } function completeTransition() public postExecutionPhase { diff --git a/packages/rollup-contracts/contracts/precompiles/L1MessageSender.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/precompiles/L1MessageSender.sol similarity index 50% rename from packages/rollup-contracts/contracts/precompiles/L1MessageSender.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/precompiles/L1MessageSender.sol index b1b2932ea0f..e5fbe12acbb 100644 --- a/packages/rollup-contracts/contracts/precompiles/L1MessageSender.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/precompiles/L1MessageSender.sol @@ -1,16 +1,18 @@ pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; -import {ExecutionManager} from "../ExecutionManager.sol"; - +import { ExecutionManager } from "../ExecutionManager.sol"; contract L1MessageSender { ExecutionManager executionManager; - constructor(address _executionManagerAddress) public { + constructor( + address _executionManagerAddress + ) public { executionManager = ExecutionManager(_executionManagerAddress); } - function getL1MessageSender() public returns(address) { + function getL1MessageSender() public returns (address) { return executionManager.getL1MessageSender(); } } diff --git a/packages/rollup-contracts/contracts/precompiles/L2ToL1MessagePasser.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/precompiles/L2ToL1MessagePasser.sol similarity index 68% rename from packages/rollup-contracts/contracts/precompiles/L2ToL1MessagePasser.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/precompiles/L2ToL1MessagePasser.sol index 0636e2d86d8..fc8525e142e 100644 --- a/packages/rollup-contracts/contracts/precompiles/L2ToL1MessagePasser.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/precompiles/L2ToL1MessagePasser.sol @@ -1,31 +1,56 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; - contract L2ToL1MessagePasser { + /* + * Events + */ + event L2ToL1Message( uint _nonce, address _ovmSender, bytes _callData ); + + /* + * Contract Variables + */ + uint nonce; address executionManagerAddress; + + + /* + * Constructor + */ + constructor(address _executionManagerAddress) public { executionManagerAddress = _executionManagerAddress; } - function passMessageToL1(bytes memory messageData) public { - // for now, to be trustfully relayed by sequencer to L1, so just emit an event for the sequencer to pick up. - address ovmMsgSender = getCALLER(); + + /* + * Public Functions + */ + + function passMessageToL1(bytes memory _messageData) public { + // For now, to be trustfully relayed by sequencer to L1, so just emit + // an event for the sequencer to pick up. + emit L2ToL1Message( nonce++, - ovmMsgSender, - messageData + getCALLER(), + _messageData ); } - function getCALLER() internal returns(address) { + + /* + * Internal Functions + */ + + function getCALLER() internal returns (address) { bytes32 methodId = keccak256("ovmCALLER()"); address addr = executionManagerAddress; @@ -45,6 +70,7 @@ contract L2ToL1MessagePasser { theCaller := mload(result) } + return theCaller; } } \ No newline at end of file diff --git a/packages/rollup-contracts/contracts/ovm/test-helpers/StubExecutionManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubExecutionManager.sol similarity index 59% rename from packages/rollup-contracts/contracts/ovm/test-helpers/StubExecutionManager.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubExecutionManager.sol index c83a95c1b3d..9f7ba5d86c2 100644 --- a/packages/rollup-contracts/contracts/ovm/test-helpers/StubExecutionManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubExecutionManager.sol @@ -22,8 +22,16 @@ contract StubExecutionManager { bool _allowRevert ) public { // Just call the state manager to store values a couple times - stateManager.setStorage(0x1111111111111111111111111111111111111111, 0x1111111111111111111111111111111111111111111111111111111111111111, 0x1111111111111111111111111111111111111111111111111111111111111111); - stateManager.setStorage(0x2222222222222222222222222222222222222222, 0x2222222222222222222222222222222222222222222222222222222222222222, 0x2222222222222222222222222222222222222222222222222222222222222222); + stateManager.setStorage( + 0x1111111111111111111111111111111111111111, + 0x1111111111111111111111111111111111111111111111111111111111111111, + 0x1111111111111111111111111111111111111111111111111111111111111111 + ); + stateManager.setStorage( + 0x2222222222222222222222222222222222222222, + 0x2222222222222222222222222222222222222222222222222222222222222222, + 0x2222222222222222222222222222222222222222222222222222222222222222 + ); // TODO: Make this a bit more comprehensive. Could even make it configurable? } } diff --git a/packages/rollup-contracts/contracts/ovm/test-helpers/StubSafetyChecker.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubSafetyChecker.sol similarity index 90% rename from packages/rollup-contracts/contracts/ovm/test-helpers/StubSafetyChecker.sol rename to packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubSafetyChecker.sol index db58a597544..688d661e8b6 100644 --- a/packages/rollup-contracts/contracts/ovm/test-helpers/StubSafetyChecker.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubSafetyChecker.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.0; -import {SafetyChecker} from "../../SafetyChecker.sol"; +import {SafetyChecker} from "../SafetyChecker.sol"; /** * @title StubSafetyChecker diff --git a/packages/rollup-contracts/contracts/L1ToL2TransactionQueue.sol b/packages/contracts/contracts/optimistic-ethereum/queue/L1ToL2TransactionQueue.sol similarity index 83% rename from packages/rollup-contracts/contracts/L1ToL2TransactionQueue.sol rename to packages/contracts/contracts/optimistic-ethereum/queue/L1ToL2TransactionQueue.sol index 7bef5786138..d9d6cd8491a 100644 --- a/packages/rollup-contracts/contracts/L1ToL2TransactionQueue.sol +++ b/packages/contracts/contracts/optimistic-ethereum/queue/L1ToL2TransactionQueue.sol @@ -2,24 +2,24 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /* Internal Imports */ -import {RollupQueue} from "./RollupQueue.sol"; +import { RollupQueue } from "./RollupQueue.sol"; contract L1ToL2TransactionQueue is RollupQueue { address public l1ToL2TransactionPasser; address public canonicalTransactionChain; constructor( - address _rollupMerkleUtilsAddress, address _l1ToL2TransactionPasser, address _canonicalTransactionChain - ) RollupQueue(_rollupMerkleUtilsAddress) public { + ) public { l1ToL2TransactionPasser = _l1ToL2TransactionPasser; canonicalTransactionChain = _canonicalTransactionChain; } - + function authenticateEnqueue(address _sender) public view returns (bool) { return _sender == l1ToL2TransactionPasser; } + function authenticateDequeue(address _sender) public view returns (bool) { return _sender == canonicalTransactionChain; } diff --git a/packages/contracts/contracts/optimistic-ethereum/queue/RollupQueue.sol b/packages/contracts/contracts/optimistic-ethereum/queue/RollupQueue.sol new file mode 100644 index 00000000000..7a5d71e2f9b --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/queue/RollupQueue.sol @@ -0,0 +1,75 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +/* Internal Imports */ +import { DataTypes } from "../utils/DataTypes.sol"; + +contract RollupQueue { + /* + * Contract Variables + */ + + DataTypes.TimestampedHash[] public batchHeaders; + uint256 public front; + + + /* + * Public Functions + */ + + function getBatchHeadersLength() public view returns (uint) { + return batchHeaders.length; + } + + function isEmpty() public view returns (bool) { + return front >= batchHeaders.length; + } + + function peek() public view returns (DataTypes.TimestampedHash memory) { + require(!isEmpty(), "Queue is empty, no element to peek at"); + return batchHeaders[front]; + } + + function peekTimestamp() public view returns (uint) { + DataTypes.TimestampedHash memory frontBatch = peek(); + return frontBatch.timestamp; + } + + function authenticateEnqueue( + address _sender + ) public view returns (bool) { + return true; + } + + function authenticateDequeue( + address _sender + ) public view returns (bool) { + return true; + } + + function enqueueTx(bytes memory _tx) public { + // Authentication. + require( + authenticateEnqueue(msg.sender), + "Message sender does not have permission to enqueue" + ); + + batchHeaders.push(DataTypes.TimestampedHash({ + timestamp: now, + txHash: keccak256(_tx) + })); + } + + function dequeue() public { + // Authentication. + require( + authenticateDequeue(msg.sender), + "Message sender does not have permission to dequeue" + ); + + require(front < batchHeaders.length, "Cannot dequeue from an empty queue"); + + delete batchHeaders[front]; + front++; + } +} diff --git a/packages/rollup-contracts/contracts/SafetyTransactionQueue.sol b/packages/contracts/contracts/optimistic-ethereum/queue/SafetyTransactionQueue.sol similarity index 75% rename from packages/rollup-contracts/contracts/SafetyTransactionQueue.sol rename to packages/contracts/contracts/optimistic-ethereum/queue/SafetyTransactionQueue.sol index 3e684a95f0c..39fe5c6404a 100644 --- a/packages/rollup-contracts/contracts/SafetyTransactionQueue.sol +++ b/packages/contracts/contracts/optimistic-ethereum/queue/SafetyTransactionQueue.sol @@ -2,18 +2,17 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /* Internal Imports */ -import {RollupQueue} from "./RollupQueue.sol"; +import { RollupQueue } from "./RollupQueue.sol"; contract SafetyTransactionQueue is RollupQueue { address public canonicalTransactionChain; constructor( - address _rollupMerkleUtilsAddress, address _canonicalTransactionChain - ) RollupQueue(_rollupMerkleUtilsAddress) public { + ) public { canonicalTransactionChain = _canonicalTransactionChain; } - + function authenticateDequeue(address _sender) public view returns (bool) { return _sender == canonicalTransactionChain; } diff --git a/packages/rollup-contracts/contracts/utils/BytesLib.sol b/packages/contracts/contracts/optimistic-ethereum/utils/BytesLib.sol similarity index 100% rename from packages/rollup-contracts/contracts/utils/BytesLib.sol rename to packages/contracts/contracts/optimistic-ethereum/utils/BytesLib.sol diff --git a/packages/contracts/contracts/optimistic-ethereum/utils/ContractAddressGenerator.sol b/packages/contracts/contracts/optimistic-ethereum/utils/ContractAddressGenerator.sol new file mode 100644 index 00000000000..5fa775fad61 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/utils/ContractAddressGenerator.sol @@ -0,0 +1,74 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +/* Internal Imports */ +import { RLPEncode } from "./RLPEncode.sol"; + +/** + * @title ContractAddressGenerator + * @notice Libary contract which generates CREATE & CREATE2 addresses. + * This is used in Rollup to make sure we have address parity with the + * Ethereum mainchain. + */ +contract ContractAddressGenerator { + RLPEncode rlp; + + constructor() public { + rlp = new RLPEncode(); + } + + /** + * @notice Generate a contract address using CREATE. + * @param _origin The address of the contract which is calling CREATE. + * @param _nonce The contract nonce of the origin contract (incremented + * each time CREATE is called). + * @return Address of the contract to be created. + */ + function getAddressFromCREATE(address _origin, uint _nonce) public view returns (address) { + // Create a list of RLP encoded parameters. + bytes[] memory list = new bytes[](2); + list[0] = rlp.encodeAddress(_origin); + list[1] = rlp.encodeUint(_nonce); + + // RLP encode the list itself. + bytes memory encodedList = rlp.encodeList(list); + + // Return an address from the hash of the encoded list. + return getAddressFromHash(keccak256(encodedList)); + } + + /** + * @notice Generate a contract address using CREATE2. + * @param _origin The address of the contract which is calling CREATE2. + * @param _salt A salt which can be any 32 byte value -- this allows you to deploy + * the same initcode twice with different addresses. + * @param _ovmInitcode The initcode for the contract we are CREATE2ing. + * @return Address of the contract to be created. + */ + function getAddressFromCREATE2( + address _origin, + bytes32 _salt, + bytes memory _ovmInitcode + ) public pure returns (address) { + // Hash all of the parameters together. + bytes32 hashedData = keccak256(abi.encodePacked( + byte(0xff), + _origin, + _salt, + keccak256(_ovmInitcode) + )); + + return getAddressFromHash(hashedData); + } + + /** + * @dev Determines an address from a 32 byte hash. Since addresses are only + * 20 bytes, we need to retrieve the last 20 bytes from the original + * hash. Converting to uint256 and then uint160 gives us these bytes. + * @param _hash Hash to convert to an address. + * @return Hash converted to an address. + */ + function getAddressFromHash(bytes32 _hash) internal pure returns (address) { + return address(bytes20(uint160(uint256(_hash)))); + } +} diff --git a/packages/rollup-contracts/contracts/DataTypes.sol b/packages/contracts/contracts/optimistic-ethereum/utils/DataTypes.sol similarity index 98% rename from packages/rollup-contracts/contracts/DataTypes.sol rename to packages/contracts/contracts/optimistic-ethereum/utils/DataTypes.sol index 353e91858d1..fe00b80cfcd 100644 --- a/packages/rollup-contracts/contracts/DataTypes.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/DataTypes.sol @@ -62,7 +62,7 @@ contract DataTypes { uint cumulativePrevElements; } - struct TimestampedHash { + struct TimestampedHash { uint timestamp; bytes32 txHash; } diff --git a/packages/rollup-contracts/contracts/utils/MerkleTrie.sol b/packages/contracts/contracts/optimistic-ethereum/utils/MerkleTrie.sol similarity index 100% rename from packages/rollup-contracts/contracts/utils/MerkleTrie.sol rename to packages/contracts/contracts/optimistic-ethereum/utils/MerkleTrie.sol diff --git a/packages/rollup-contracts/contracts/RLPEncode.sol b/packages/contracts/contracts/optimistic-ethereum/utils/RLPEncode.sol similarity index 98% rename from packages/rollup-contracts/contracts/RLPEncode.sol rename to packages/contracts/contracts/optimistic-ethereum/utils/RLPEncode.sol index e7a877b3354..6dc28539181 100644 --- a/packages/rollup-contracts/contracts/RLPEncode.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/RLPEncode.sol @@ -2,7 +2,6 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; /** - * Source: https://github.com/omisego/plasma-mvp/blob/master/plasma/root_chain/contracts/RLPEncode.sol * @title RLPEncode * @dev A simple RLP encoding library. * @author Bakaoh diff --git a/packages/rollup-contracts/contracts/utils/RLPReader.sol b/packages/contracts/contracts/optimistic-ethereum/utils/RLPReader.sol similarity index 100% rename from packages/rollup-contracts/contracts/utils/RLPReader.sol rename to packages/contracts/contracts/optimistic-ethereum/utils/RLPReader.sol diff --git a/packages/rollup-contracts/contracts/utils/RLPWriter.sol b/packages/contracts/contracts/optimistic-ethereum/utils/RLPWriter.sol similarity index 99% rename from packages/rollup-contracts/contracts/utils/RLPWriter.sol rename to packages/contracts/contracts/optimistic-ethereum/utils/RLPWriter.sol index 95346ed8d77..5660872814f 100644 --- a/packages/rollup-contracts/contracts/utils/RLPWriter.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/RLPWriter.sol @@ -261,4 +261,4 @@ library RLPWriter { return tempBytes; } -} +} \ No newline at end of file diff --git a/packages/rollup-contracts/contracts/RollupMerkleUtils.sol b/packages/contracts/contracts/optimistic-ethereum/utils/RollupMerkleUtils.sol similarity index 81% rename from packages/rollup-contracts/contracts/RollupMerkleUtils.sol rename to packages/contracts/contracts/optimistic-ethereum/utils/RollupMerkleUtils.sol index e752204cd71..91b2470aa5e 100644 --- a/packages/rollup-contracts/contracts/RollupMerkleUtils.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/RollupMerkleUtils.sol @@ -5,79 +5,91 @@ pragma experimental ABIEncoderV2; * Merkle Tree Utilities for Rollup */ contract RollupMerkleUtils { - /* Structs */ - // A partial merkle tree which can be updated with new nodes, recomputing the root + /* + * Structs + */ + struct SparseMerkleTree { - // The root bytes32 root; uint height; mapping (bytes32 => bytes32) nodes; } - /* Fields */ - // The default hashes + + /* + * Contract Variables + */ + bytes32[160] public defaultHashes; - // A tree which is used in `update()` and `store()` SparseMerkleTree public tree; + + /* + * Constructor + */ + /** - * @notice Initialize a new SparseMerkleUtils contract, computing the default hashes for the sparse merkle tree (SMT) + * @notice Initialize a new SparseMerkleUtils contract, computing the + * default hashes for the sparse merkle tree (SMT). */ constructor() public { - // Calculate & set the default hashes setDefaultHashes(); } - /* Methods */ - /** - * @notice Set default hashes + + /* + * Public Functions */ - function setDefaultHashes() private { - // Set the initial default hash. - defaultHashes[0] = keccak256(abi.encodePacked(uint(0))); - for (uint i = 1; i < defaultHashes.length; i ++) { - defaultHashes[i] = keccak256(abi.encodePacked(defaultHashes[i-1], defaultHashes[i-1])); - } - } /** * @notice Get the sparse merkle root computed from some set of data blocks. * @param _dataBlocks The data being used to generate the tree. * @return the sparse merkle tree root */ - function getMerkleRoot(bytes[] calldata _dataBlocks) external view returns(bytes32) { + function getMerkleRoot( + bytes[] memory _dataBlocks + ) public view returns (bytes32) { uint nextLevelLength = _dataBlocks.length; uint currentLevel = 0; - bytes32[] memory nodes = new bytes32[](nextLevelLength + 1); // Add one in case we have an odd number of leaves - // Generate the leaves + + // Add one in case we have an odd number of leaves. + bytes32[] memory nodes = new bytes32[](nextLevelLength + 1); + + // Generate the leaf hashes. for (uint i = 0; i < _dataBlocks.length; i++) { nodes[i] = keccak256(_dataBlocks[i]); } + + // If we only have a single leaf, then it must be the root. if (_dataBlocks.length == 1) { return nodes[0]; } - // Add a defaultNode if we've got an odd number of leaves + + // Add a defaultNode if we've got an odd number of leaves. if (nextLevelLength % 2 == 1) { nodes[nextLevelLength] = defaultHashes[currentLevel]; nextLevelLength += 1; } - // Now generate each level + // Now generate each level. while (nextLevelLength > 1) { currentLevel += 1; - // Calculate the nodes for the currentLevel + + // Calculate the nodes for the currentLevel. for (uint i = 0; i < nextLevelLength / 2; i++) { nodes[i] = getParent(nodes[i*2], nodes[i*2 + 1]); } + nextLevelLength = nextLevelLength / 2; - // Check if we will need to add an extra node + + // Check if we will need to add an extra node. if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { nodes[nextLevelLength] = defaultHashes[currentLevel]; nextLevelLength += 1; } } - // Alright! We should be left with a single node! Return it... + // Alright! We should be left with a single node! Return it. return nodes[0]; } @@ -88,18 +100,25 @@ contract RollupMerkleUtils { * @param _siblings The sibling nodes along the way. * @return The next level of the tree */ - function computeInclusionProofRoot(bytes memory _dataBlock, uint _path, bytes32[] memory _siblings) public pure returns (bytes32) { - // First compute the leaf node + function computeInclusionProofRoot( + bytes memory _dataBlock, + uint _path, + bytes32[] memory _siblings + ) public pure returns (bytes32) { + // First compute the leaf node. bytes32 computedNode = keccak256(_dataBlock); + for (uint i = 0; i < _siblings.length; i++) { bytes32 sibling = _siblings[i]; uint8 isComputedRightSibling = getNthBitFromRight(_path, i); + if (isComputedRightSibling == 0) { computedNode = getParent(computedNode, sibling); } else { computedNode = getParent(sibling, computedNode); } } + // Check if the computed node (_root) is equal to the provided root return computedNode; } @@ -112,13 +131,20 @@ contract RollupMerkleUtils { * @param _siblings The sibling nodes along the way. * @return The next level of the tree */ - function verify(bytes32 _root, bytes memory _dataBlock, uint _path, bytes32[] memory _siblings) public pure returns (bool) { + function verify( + bytes32 _root, + bytes memory _dataBlock, + uint _path, + bytes32[] memory _siblings + ) public pure returns (bool) { // First compute the leaf node bytes32 calculatedRoot = computeInclusionProofRoot( _dataBlock, _path, _siblings ); + + // Check that it matches the provided root. return calculatedRoot == _root; } @@ -148,10 +174,18 @@ contract RollupMerkleUtils { * @param _path The path from the leaf to the root / the index of the leaf. * @param _siblings The sibling nodes along the way. */ - function verifyAndStore(bytes memory _dataBlock, uint _path, bytes32[] memory _siblings) public { + function verifyAndStore( + bytes memory _dataBlock, + uint _path, + bytes32[] memory _siblings + ) public { bytes32 oldRoot = tree.root; store(_dataBlock, _path, _siblings); - require(tree.root == oldRoot, "Failed same root verification check! This was an inclusion proof for a different tree!"); + + require( + tree.root == oldRoot, + "Provided inclusion proof is invalid." + ); } /** @@ -160,7 +194,11 @@ contract RollupMerkleUtils { * @param _path The path from the leaf to the root / the index of the leaf. * @param _siblings The sibling nodes along the way. */ - function store(bytes memory _dataBlock, uint _path, bytes32[] memory _siblings) public { + function store( + bytes memory _dataBlock, + uint _path, + bytes32[] memory _siblings + ) public { // Compute the leaf node & store the leaf bytes32 leaf = keccak256(_dataBlock); storeLeaf(leaf, _path, _siblings); @@ -172,24 +210,30 @@ contract RollupMerkleUtils { * @param _path The path from the leaf to the root / the index of the leaf. * @param _siblings The sibling nodes along the way. */ - function storeLeaf(bytes32 _leaf, uint _path, bytes32[] memory _siblings) public { + function storeLeaf( + bytes32 _leaf, + uint _path, + bytes32[] memory _siblings + ) public { // First compute the leaf node bytes32 computedNode = _leaf; + for (uint i = 0; i < _siblings.length; i++) { bytes32 parent; bytes32 sibling = _siblings[i]; uint8 isComputedRightSibling = getNthBitFromRight(_path, i); + if (isComputedRightSibling == 0) { parent = getParent(computedNode, sibling); - // Store the node! storeNode(parent, computedNode, sibling); } else { parent = getParent(sibling, computedNode); - // Store the node! storeNode(parent, sibling, computedNode); } + computedNode = parent; } + // Store the new root tree.root = computedNode; } @@ -203,9 +247,11 @@ contract RollupMerkleUtils { function getSiblings(uint _path) public view returns (bytes32[] memory) { bytes32[] memory siblings = new bytes32[](tree.height); bytes32 computedNode = tree.root; + for(uint i = tree.height; i > 0; i--) { uint siblingIndex = i-1; (bytes32 leftChild, bytes32 rightChild) = getChildren(computedNode); + if (getNthBitFromRight(_path, siblingIndex) == 0) { computedNode = leftChild; siblings[siblingIndex] = rightChild; @@ -214,18 +260,15 @@ contract RollupMerkleUtils { siblings[siblingIndex] = leftChild; } } - // Now store everything + return siblings; } - /********************* - * Utility Functions * - ********************/ /** * @notice Get our stored tree's root * @return The merkle root of the tree */ - function getRoot() public view returns(bytes32) { + function getRoot() public view returns (bytes32) { return tree.root; } @@ -245,21 +288,15 @@ contract RollupMerkleUtils { * @param _leftChild The left child of the parent in the tree * @param _rightChild The right child of the parent in the tree */ - function storeNode(bytes32 _parent, bytes32 _leftChild, bytes32 _rightChild) public { + function storeNode( + bytes32 _parent, + bytes32 _leftChild, + bytes32 _rightChild + ) public { tree.nodes[getLeftSiblingKey(_parent)] = _leftChild; tree.nodes[getRightSiblingKey(_parent)] = _rightChild; } - /** - * @notice Get the parent of two children nodes in the tree - * @param _left The left child - * @param _right The right child - * @return The parent node - */ - function getParent(bytes32 _left, bytes32 _right) internal pure returns(bytes32) { - return keccak256(abi.encodePacked(_left, _right)); - } - /** * @notice get the n'th bit in a uint. * For instance, if exampleUint=binary(11), getNth(exampleUint, 0) == 1, getNth(2, 1) == 1 @@ -267,7 +304,10 @@ contract RollupMerkleUtils { * @param _index The index of the bit we want to extract * @return The bit (1 or 0) in a uint8 */ - function getNthBitFromRight(uint _intVal, uint _index) public pure returns (uint8) { + function getNthBitFromRight( + uint _intVal, + uint _index + ) public pure returns (uint8) { return uint8(_intVal >> _index & 1); } @@ -276,8 +316,13 @@ contract RollupMerkleUtils { * @param _parent The parent node * @return (rightChild, leftChild) -- the two children of the parent */ - function getChildren(bytes32 _parent) public view returns(bytes32, bytes32) { - return (tree.nodes[getLeftSiblingKey(_parent)], tree.nodes[getRightSiblingKey(_parent)]); + function getChildren( + bytes32 _parent + ) public view returns (bytes32, bytes32) { + return ( + tree.nodes[getLeftSiblingKey(_parent)], + tree.nodes[getRightSiblingKey(_parent)] + ); } /** @@ -299,4 +344,34 @@ contract RollupMerkleUtils { function getRightSiblingKey(bytes32 _parent) public pure returns(bytes32) { return _parent | 0x1000000000000000000000000000000000000000000000000000000000000000; } -} + + + /* + * Internal Functions + */ + + /** + * @notice Set default hashes + */ + function setDefaultHashes() internal { + // Set the initial default hash. + defaultHashes[0] = keccak256(abi.encodePacked(uint(0))); + + for (uint i = 1; i < defaultHashes.length; i ++) { + defaultHashes[i] = keccak256(abi.encodePacked(defaultHashes[i-1], defaultHashes[i-1])); + } + } + + /** + * @notice Get the parent of two children nodes in the tree + * @param _left The left child + * @param _right The right child + * @return The parent node + */ + function getParent( + bytes32 _left, + bytes32 _right + ) internal pure returns(bytes32) { + return keccak256(abi.encodePacked(_left, _right)); + } +} \ No newline at end of file diff --git a/packages/rollup-contracts/contracts/testing-contracts/AddThree.sol b/packages/contracts/contracts/test-helpers/AddThree.sol similarity index 100% rename from packages/rollup-contracts/contracts/testing-contracts/AddThree.sol rename to packages/contracts/contracts/test-helpers/AddThree.sol diff --git a/packages/rollup-contracts/contracts/testing-contracts/ContextContract.sol b/packages/contracts/contracts/test-helpers/ContextContract.sol similarity index 98% rename from packages/rollup-contracts/contracts/testing-contracts/ContextContract.sol rename to packages/contracts/contracts/test-helpers/ContextContract.sol index 9b2b87e31f4..a4aaffcfbb5 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/ContextContract.sol +++ b/packages/contracts/contracts/test-helpers/ContextContract.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.0; -import {ExecutionManager} from "../ExecutionManager.sol"; +import { ExecutionManager } from "../optimistic-ethereum/ovm/ExecutionManager.sol"; /** * @title ContextContract diff --git a/packages/rollup-contracts/contracts/testing-contracts/DummyContract.sol b/packages/contracts/contracts/test-helpers/DummyContract.sol similarity index 57% rename from packages/rollup-contracts/contracts/testing-contracts/DummyContract.sol rename to packages/contracts/contracts/test-helpers/DummyContract.sol index 9681cd2fe02..984ec21e836 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/DummyContract.sol +++ b/packages/contracts/contracts/test-helpers/DummyContract.sol @@ -1,5 +1,4 @@ pragma solidity ^0.5.0; -pragma experimental ABIEncoderV2; contract DummyContract { @@ -9,16 +8,19 @@ contract DummyContract { someVal = keccak256("derp"); } - function dummyFunction(uint testInt, bytes memory testBytes) public pure returns (bool success, bytes memory output) { + function dummyFunction( + uint testInt, + bytes memory testBytes + ) public pure returns (bool success, bytes memory output) { success = testInt != 0; output = testBytes; } - function dummyRevert() public { + function dummyRevert() public pure { revert("This is a test revert"); } - function dummyFailingRequire() public { + function dummyFailingRequire() public pure { require(false, "This is a test revert"); } } diff --git a/packages/contracts/contracts/test-helpers/InvalidOpcodes.sol b/packages/contracts/contracts/test-helpers/InvalidOpcodes.sol new file mode 100644 index 00000000000..1727adaebd8 --- /dev/null +++ b/packages/contracts/contracts/test-helpers/InvalidOpcodes.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.5.0; + +contract InvalidOpcodes { + function getCoinbase() public view returns (address){ + return block.coinbase; + } + + function getDifficulty() public view returns (uint){ + return block.difficulty; + } + + function getBlockNumber() public view returns (uint) { + return block.number; + } +} diff --git a/packages/rollup-contracts/contracts/testing-contracts/RevertTest.sol b/packages/contracts/contracts/test-helpers/RevertTest.sol similarity index 96% rename from packages/rollup-contracts/contracts/testing-contracts/RevertTest.sol rename to packages/contracts/contracts/test-helpers/RevertTest.sol index 98f3f624f52..1217bc72a62 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/RevertTest.sol +++ b/packages/contracts/contracts/test-helpers/RevertTest.sol @@ -1,5 +1,4 @@ pragma solidity ^0.5.0; -pragma experimental ABIEncoderV2; contract RevertTest { uint a; diff --git a/packages/rollup-contracts/contracts/testing-contracts/RollupTransactionEvents.sol b/packages/contracts/contracts/test-helpers/RollupTransactionEvents.sol similarity index 93% rename from packages/rollup-contracts/contracts/testing-contracts/RollupTransactionEvents.sol rename to packages/contracts/contracts/test-helpers/RollupTransactionEvents.sol index 8434b589828..69206b96ea3 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/RollupTransactionEvents.sol +++ b/packages/contracts/contracts/test-helpers/RollupTransactionEvents.sol @@ -1,5 +1,4 @@ pragma solidity ^0.5.0; -pragma experimental ABIEncoderV2; contract RollupTransactionEvents { event RollupTransaction(); diff --git a/packages/rollup-contracts/contracts/testing-contracts/SimpleCall.sol b/packages/contracts/contracts/test-helpers/SimpleCall.sol similarity index 99% rename from packages/rollup-contracts/contracts/testing-contracts/SimpleCall.sol rename to packages/contracts/contracts/test-helpers/SimpleCall.sol index 04e553e7bbd..b0551b1f59e 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/SimpleCall.sol +++ b/packages/contracts/contracts/test-helpers/SimpleCall.sol @@ -1,7 +1,6 @@ pragma solidity ^0.5.0; -pragma experimental ABIEncoderV2; -import {ExecutionManager} from "../ExecutionManager.sol"; +import { ExecutionManager } from "../optimistic-ethereum/ovm/ExecutionManager.sol"; /** * @title SimpleCall diff --git a/packages/rollup-contracts/contracts/testing-contracts/SimpleStorage.sol b/packages/contracts/contracts/test-helpers/SimpleStorage.sol similarity index 99% rename from packages/rollup-contracts/contracts/testing-contracts/SimpleStorage.sol rename to packages/contracts/contracts/test-helpers/SimpleStorage.sol index 823562b6049..18fa3a139e7 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/SimpleStorage.sol +++ b/packages/contracts/contracts/test-helpers/SimpleStorage.sol @@ -2,6 +2,7 @@ pragma solidity ^0.5.0; contract SimpleStorage { mapping(bytes32 => bytes32) public builtInStorage; + function setStorage(bytes32 key, bytes32 value) public { builtInStorage[key] = value; } diff --git a/packages/rollup-contracts/contracts/testing-contracts/SimpleStorageArgsFromCalldata.sol b/packages/contracts/contracts/test-helpers/SimpleStorageArgsFromCalldata.sol similarity index 96% rename from packages/rollup-contracts/contracts/testing-contracts/SimpleStorageArgsFromCalldata.sol rename to packages/contracts/contracts/test-helpers/SimpleStorageArgsFromCalldata.sol index 6f182d0f8fb..7ca1a210fda 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/SimpleStorageArgsFromCalldata.sol +++ b/packages/contracts/contracts/test-helpers/SimpleStorageArgsFromCalldata.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.0; -import {ExecutionManager} from "../ExecutionManager.sol"; +import { ExecutionManager } from "../optimistic-ethereum/ovm/ExecutionManager.sol"; /** * @title SimpleStorageArgsFromCalldata diff --git a/packages/rollup-contracts/contracts/testing-contracts/SimpleTxOrigin.sol b/packages/contracts/contracts/test-helpers/SimpleTxOrigin.sol similarity index 94% rename from packages/rollup-contracts/contracts/testing-contracts/SimpleTxOrigin.sol rename to packages/contracts/contracts/test-helpers/SimpleTxOrigin.sol index 4c880ed8e1c..164ef3514fb 100644 --- a/packages/rollup-contracts/contracts/testing-contracts/SimpleTxOrigin.sol +++ b/packages/contracts/contracts/test-helpers/SimpleTxOrigin.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.0; -import {ExecutionManager} from "../ExecutionManager.sol"; +import { ExecutionManager } from "../optimistic-ethereum/ovm/ExecutionManager.sol"; /** * @title SimpleTxOrigin diff --git a/packages/rollup-contracts/deploy/rollup-chain.ts b/packages/contracts/deploy/rollup-chain.ts similarity index 87% rename from packages/rollup-contracts/deploy/rollup-chain.ts rename to packages/contracts/deploy/rollup-chain.ts index e6d93a8877e..b76ed743970 100644 --- a/packages/rollup-contracts/deploy/rollup-chain.ts +++ b/packages/contracts/deploy/rollup-chain.ts @@ -4,11 +4,11 @@ import { Wallet } from 'ethers' import { Provider } from 'ethers/providers' /* Internal Imports */ -import * as RollupMerkleUtils from '../build/RollupMerkleUtils.json' -import * as CanonicalTransactionChain from '../build/CanonicalTransactionChain.json' -import * as StateCommitmentChain from '../build/StateCommitmentChain.json' -import * as SequencerBatchSubmitter from '../build/SequencerBatchSubmitter.json' -import * as L1ToL2TransactionPasser from '../build/L1ToL2TransactionPasser.json' +import * as RollupMerkleUtils from '../build/contracts/RollupMerkleUtils.json' +import * as CanonicalTransactionChain from '../build/contracts/CanonicalTransactionChain.json' +import * as StateCommitmentChain from '../build/contracts/StateCommitmentChain.json' +import * as SequencerBatchSubmitter from '../build/contracts/SequencerBatchSubmitter.json' +import * as L1ToL2TransactionPasser from '../build/contracts/L1ToL2TransactionPasser.json' import { resolve } from 'path' diff --git a/packages/rollup-contracts/index.ts b/packages/contracts/index.ts similarity index 100% rename from packages/rollup-contracts/index.ts rename to packages/contracts/index.ts diff --git a/packages/rollup-contracts/package.json b/packages/contracts/package.json similarity index 71% rename from packages/rollup-contracts/package.json rename to packages/contracts/package.json index dc423b9143a..f11786978e7 100644 --- a/packages/rollup-contracts/package.json +++ b/packages/contracts/package.json @@ -8,10 +8,15 @@ ], "scripts": { "all": "yarn clean && yarn build && yarn test && yarn fix && yarn lint", - "test": "waffle waffle-config.json && mocha --require ts-node/register 'test/**/*.spec.ts' --timeout 20000", - "lint": "tslint --format stylish --project .", + "test": "yarn run test:contracts", + "test:contracts": "yarn run build:contracts && mocha --require ts-node/register \"test/**/*.spec.ts\" --timeout 20000 --bail", + "lint": "yarn run lint:contracts && yarn run lint:typescript", + "lint:contracts": "solhint \"contracts/**/*.sol\"", + "lint:typescript": "tslint --format stylish --project .", "fix": "prettier --config ../../prettier-config.json --write \"index.ts\" \"{deploy,test}/**/*.ts\"", - "build": "waffle waffle-config.json && tsc -p .", + "build": "yarn run build:contracts && yarn run build:typescript", + "build:contracts": "mkdir -p ./build/contracts && waffle waffle-config.json", + "build:typescript": "tsc -p .", "clean": "rimraf build/", "deploy:rollup-chain": "yarn build && node ./build/deploy/rollup-chain.js" }, @@ -50,13 +55,16 @@ "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "ethereum-waffle": "2.1.0", + "ethereumjs-abi": "^0.6.8", "ethers": "^4.0.37", + "lodash": "^4.17.15", "merkle-patricia-tree": "git+https://github.com/kfichter/merkle-patricia-tree", "merkletreejs": "^0.1.7", "openzeppelin-solidity": "^2.2.0", "random-bytes-seed": "^1.0.3", "rlp": "^2.2.5", - "seedrandom": "^3.0.5" + "seedrandom": "^3.0.5", + "solhint": "^3.0.0" }, "gitHead": "ccce366645fca6bad46c5cf7f7ff2f407c6ba5fd" } diff --git a/packages/rollup-contracts/src/contracts.ts b/packages/contracts/src/contracts.ts similarity index 58% rename from packages/rollup-contracts/src/contracts.ts rename to packages/contracts/src/contracts.ts index e9096d308cb..786ad1bb5f1 100644 --- a/packages/rollup-contracts/src/contracts.ts +++ b/packages/contracts/src/contracts.ts @@ -2,18 +2,17 @@ import { ethers } from 'ethers' /* Contract Imports */ +import * as ExecutionManager from '../build/contracts/ExecutionManager.json' +import * as FullStateManager from '../build/contracts/FullStateManager.json' +import * as L2ExecutionManager from '../build/contracts/L2ExecutionManager.json' +import * as ContractAddressGenerator from '../build/contracts/ContractAddressGenerator.json' +import * as L2ToL1MessageReceiver from '../build/contracts/L2ToL1MessageReceiver.json' +import * as L2ToL1MessagePasser from '../build/contracts/L2ToL1MessagePasser.json' +import * as L1ToL2TransactionPasser from '../build/contracts/L1ToL2TransactionPasser.json' +import * as RLPEncode from '../build/contracts/RLPEncode.json' +import * as SafetyChecker from '../build/contracts/SafetyChecker.json' -import * as ExecutionManager from '../build/ExecutionManager.json' -import * as FullStateManager from '../build/FullStateManager.json' -import * as L2ExecutionManager from '../build/L2ExecutionManager.json' -import * as ContractAddressGenerator from '../build/ContractAddressGenerator.json' -import * as L2ToL1MessageReceiver from '../build/L2ToL1MessageReceiver.json' -import * as L2ToL1MessagePasser from '../build/L2ToL1MessagePasser.json' -import * as L1ToL2TransactionPasser from '../build/L1ToL2TransactionPasser.json' -import * as RLPEncode from '../build/RLPEncode.json' -import * as SafetyChecker from '../build/SafetyChecker.json' - -// Contract Exports +/* Contract Exports */ export const ExecutionManagerContractDefinition = ExecutionManager export const L2ExecutionManagerContractDefinition = L2ExecutionManager export const FullStateManagerContractDefinition = FullStateManager diff --git a/packages/rollup-contracts/src/index.ts b/packages/contracts/src/index.ts similarity index 100% rename from packages/rollup-contracts/src/index.ts rename to packages/contracts/src/index.ts diff --git a/packages/contracts/src/test-contracts.ts b/packages/contracts/src/test-contracts.ts new file mode 100644 index 00000000000..620f7bf1bf5 --- /dev/null +++ b/packages/contracts/src/test-contracts.ts @@ -0,0 +1,19 @@ +import * as AddThree from '../build/contracts/AddThree.json' +import * as ContextContract from '../build/contracts/ContextContract.json' +import * as DummyContract from '../build/contracts/DummyContract.json' +import * as InvalidOpcodes from '../build/contracts/InvalidOpcodes.json' +import * as RevertTest from '../build/contracts/RevertTest.json' +import * as SimpleCall from '../build/contracts/SimpleCall.json' +import * as SimpleStorage from '../build/contracts/SimpleStorage.json' +import * as SimpleStorageArgsFromCalldata from '../build/contracts/SimpleStorageArgsFromCalldata.json' +import * as SimpleTxOrigin from '../build/contracts/SimpleTxOrigin.json' + +export const TestAddThreeContractDefinition = AddThree +export const TestContextContractDefinition = ContextContract +export const TestDummyContractDefinition = DummyContract +export const TestInvalidOpcodesContractDefinition = InvalidOpcodes +export const TestRevertTestContractDefinition = RevertTest +export const TestSimpleCallContractDefinition = SimpleCall +export const TestSimpleStorageContractDefinition = SimpleStorage +export const TestSimpleStorageArgsFromCalldataDefinition = SimpleStorageArgsFromCalldata +export const TestSimpleTxOriginContractDefinition = SimpleTxOrigin diff --git a/packages/rollup-contracts/test/rollup-list/CanonicalTransactionChain.spec.ts b/packages/contracts/test/contract-tests/chain/CanonicalTransactionChain.spec.ts similarity index 98% rename from packages/rollup-contracts/test/rollup-list/CanonicalTransactionChain.spec.ts rename to packages/contracts/test/contract-tests/chain/CanonicalTransactionChain.spec.ts index aa0d5d15504..55e84792412 100644 --- a/packages/rollup-contracts/test/rollup-list/CanonicalTransactionChain.spec.ts +++ b/packages/contracts/test/contract-tests/chain/CanonicalTransactionChain.spec.ts @@ -1,4 +1,4 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' @@ -6,19 +6,19 @@ import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' import { Contract } from 'ethers' /* Internal Imports */ -import { TxChainBatch, TxQueueBatch } from './RLhelper' -import { makeRandomBatchOfSize } from '../helpers' +import { TxChainBatch, TxQueueBatch } from '../../test-helpers/rl-helpers' +import { makeRandomBatchOfSize } from '../../test-helpers' + +/* Contract Imports */ +import * as CanonicalTransactionChain from '../../../build/contracts/CanonicalTransactionChain.json' +import * as L1ToL2TransactionQueue from '../../../build/contracts/L1ToL2TransactionQueue.json' +import * as SafetyTransactionQueue from '../../../build/contracts/SafetyTransactionQueue.json' +import * as RollupMerkleUtils from '../../../build/contracts/RollupMerkleUtils.json' /* Logging */ const log = getLogger('canonical-tx-chain', true) -/* Contract Imports */ -import * as CanonicalTransactionChain from '../../build/CanonicalTransactionChain.json' -import * as L1ToL2TransactionQueue from '../../build/L1ToL2TransactionQueue.json' -import * as SafetyTransactionQueue from '../../build/SafetyTransactionQueue.json' -import * as RollupMerkleUtils from '../../build/RollupMerkleUtils.json' - -/* Begin tests */ +/* Tests */ describe('CanonicalTransactionChain', () => { const provider = createMockProvider() const [wallet, sequencer, l1ToL2TransactionPasser, randomWallet] = getWallets( diff --git a/packages/rollup-contracts/test/rollup-list/SequencerBatchSubmitter.spec.ts b/packages/contracts/test/contract-tests/chain/SequencerBatchSubmitter.spec.ts similarity index 90% rename from packages/rollup-contracts/test/rollup-list/SequencerBatchSubmitter.spec.ts rename to packages/contracts/test/contract-tests/chain/SequencerBatchSubmitter.spec.ts index 55d04d6a6b4..f4d6fcb53da 100644 --- a/packages/rollup-contracts/test/rollup-list/SequencerBatchSubmitter.spec.ts +++ b/packages/contracts/test/contract-tests/chain/SequencerBatchSubmitter.spec.ts @@ -1,4 +1,4 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' @@ -6,18 +6,18 @@ import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' import { Contract } from 'ethers' /* Internal Imports */ -import { StateChainBatch, TxChainBatch } from './RLhelper' +import { StateChainBatch, TxChainBatch } from '../../test-helpers/rl-helpers' + +/* Contract Imports */ +import * as StateCommitmentChain from '../../../build/contracts/StateCommitmentChain.json' +import * as CanonicalTransactionChain from '../../../build/contracts/CanonicalTransactionChain.json' +import * as RollupMerkleUtils from '../../../build/contracts/RollupMerkleUtils.json' +import * as SequencerBatchSubmitter from '../../../build/contracts/SequencerBatchSubmitter.json' /* Logging */ const log = getLogger('batch-submitter', true) -/* Contract Imports */ -import * as StateCommitmentChain from '../../build/StateCommitmentChain.json' -import * as CanonicalTransactionChain from '../../build/CanonicalTransactionChain.json' -import * as RollupMerkleUtils from '../../build/RollupMerkleUtils.json' -import * as SequencerBatchSubmitter from '../../build/SequencerBatchSubmitter.json' - -/* Begin tests */ +/* Tests */ describe('SequencerBatchSubmitter', () => { const provider = createMockProvider() const [ diff --git a/packages/rollup-contracts/test/rollup-list/StateCommitmentChain.spec.ts b/packages/contracts/test/contract-tests/chain/StateCommitmentChain.spec.ts similarity index 96% rename from packages/rollup-contracts/test/rollup-list/StateCommitmentChain.spec.ts rename to packages/contracts/test/contract-tests/chain/StateCommitmentChain.spec.ts index 8996ec45edc..f8dd87e0f95 100644 --- a/packages/rollup-contracts/test/rollup-list/StateCommitmentChain.spec.ts +++ b/packages/contracts/test/contract-tests/chain/StateCommitmentChain.spec.ts @@ -1,4 +1,4 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' @@ -6,18 +6,18 @@ import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' import { Contract } from 'ethers' /* Internal Imports */ -import { StateChainBatch } from './RLhelper' -import { makeRandomBatchOfSize } from '../helpers' +import { StateChainBatch } from '../../test-helpers/rl-helpers' +import { makeRandomBatchOfSize } from '../../test-helpers' + +/* Contract Imports */ +import * as StateCommitmentChain from '../../../build/contracts/StateCommitmentChain.json' +import * as CanonicalTransactionChain from '../../../build/contracts/CanonicalTransactionChain.json' +import * as RollupMerkleUtils from '../../../build/contracts/RollupMerkleUtils.json' /* Logging */ const log = getLogger('state-commitment-chain', true) -/* Contract Imports */ -import * as StateCommitmentChain from '../../build/StateCommitmentChain.json' -import * as CanonicalTransactionChain from '../../build/CanonicalTransactionChain.json' -import * as RollupMerkleUtils from '../../build/RollupMerkleUtils.json' - -/* Begin tests */ +/* Tests */ describe('StateCommitmentChain', () => { const provider = createMockProvider() const [ diff --git a/packages/ovm/test/contracts/l2-execution-manager.spec.ts b/packages/contracts/test/contract-tests/ovm/L2ExecutionManager.spec.ts similarity index 95% rename from packages/ovm/test/contracts/l2-execution-manager.spec.ts rename to packages/contracts/test/contract-tests/ovm/L2ExecutionManager.spec.ts index 2a386ef0ccb..32e0a6557ce 100644 --- a/packages/ovm/test/contracts/l2-execution-manager.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/L2ExecutionManager.spec.ts @@ -1,27 +1,24 @@ -import '../setup' +import '../../setup' /* External Imports */ import { add0x, getLogger } from '@eth-optimism/core-utils' -import { L2ExecutionManagerContractDefinition as L2ExecutionManager } from '@eth-optimism/rollup-contracts' +import { Contract, ethers } from 'ethers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' + +/* Internal Imports */ import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' +} from '../../test-helpers/core-helpers' -import { Contract, ethers } from 'ethers' -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' - -/* Internal Imports */ +/* Contract Imports */ +import { L2ExecutionManagerContractDefinition as L2ExecutionManager } from '../../../src' +/* Logging */ const log = getLogger('l2-execution-manager-calls', true) export const abi = new ethers.utils.AbiCoder() - -/********* - * TESTS * - *********/ - const zero32: string = add0x('00'.repeat(32)) const key: string = add0x('01'.repeat(32)) const value: string = add0x('02'.repeat(32)) @@ -49,6 +46,7 @@ describe('L2 Execution Manager', () => { .toString('hex') .repeat(20) ) + it('properly maps OVM tx hash to internal tx hash', async () => { await l2ExecutionManager.storeOvmTransaction(key, value, fakeSignedTx) }) diff --git a/packages/rollup-contracts/test/ovm/PartialStateManager.spec.ts b/packages/contracts/test/contract-tests/ovm/PartialStateManager.spec.ts similarity index 95% rename from packages/rollup-contracts/test/ovm/PartialStateManager.spec.ts rename to packages/contracts/test/contract-tests/ovm/PartialStateManager.spec.ts index f66f5bdf8f0..22ed6487c35 100644 --- a/packages/rollup-contracts/test/ovm/PartialStateManager.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/PartialStateManager.spec.ts @@ -1,4 +1,4 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' @@ -9,7 +9,7 @@ import { Contract } from 'ethers' const log = getLogger('partial-state-manager', true) /* Contract Imports */ -import * as PartialStateManager from '../../build/PartialStateManager.json' +import * as PartialStateManager from '../../../build/contracts/PartialStateManager.json' /* Begin tests */ describe('PartialStateManager', () => { diff --git a/packages/ovm/test/contracts/state-manager.spec.ts b/packages/contracts/test/contract-tests/ovm/StateManager.spec.ts similarity index 87% rename from packages/ovm/test/contracts/state-manager.spec.ts rename to packages/contracts/test/contract-tests/ovm/StateManager.spec.ts index 9eebe448f46..866e5e9778e 100644 --- a/packages/ovm/test/contracts/state-manager.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/StateManager.spec.ts @@ -1,20 +1,24 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger } from '@eth-optimism/core-utils' -import { ExecutionManagerContractDefinition as ExecutionManager } from '@eth-optimism/rollup-contracts' +import { Contract } from 'ethers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' + +/* Internal Imports */ import { GAS_LIMIT, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' +} from '../../test-helpers/core-helpers' -import { Contract } from 'ethers' -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' +/* Contract Imports */ +import { ExecutionManagerContractDefinition as ExecutionManager } from '../../../src' +/* Logging */ const log = getLogger('state-manager', true) -/* Begin tests */ +/* Tests */ describe('ExecutionManager', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet1, wallet2] = getWallets(provider) diff --git a/packages/rollup-contracts/test/ovm/StateTransitioner.spec.ts b/packages/contracts/test/contract-tests/ovm/StateTransitioner.spec.ts similarity index 94% rename from packages/rollup-contracts/test/ovm/StateTransitioner.spec.ts rename to packages/contracts/test/contract-tests/ovm/StateTransitioner.spec.ts index 75a45a23859..8bfcd1b8f00 100644 --- a/packages/rollup-contracts/test/ovm/StateTransitioner.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/StateTransitioner.spec.ts @@ -1,4 +1,4 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' @@ -9,9 +9,9 @@ import { Contract } from 'ethers' const log = getLogger('state-transitioner', true) /* Contract Imports */ -import * as StateTransitioner from '../../build/StateTransitioner.json' -import * as PartialStateManager from '../../build/PartialStateManager.json' -import * as StubExecutionManager from '../../build/StubExecutionManager.json' +import * as StateTransitioner from '../../../build/contracts/StateTransitioner.json' +import * as PartialStateManager from '../../../build/contracts/PartialStateManager.json' +import * as StubExecutionManager from '../../../build/contracts/StubExecutionManager.json' /* Begin tests */ describe('StateTransitioner', () => { diff --git a/packages/ovm/test/contracts/execution-manager.call-opcodes.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts similarity index 98% rename from packages/ovm/test/contracts/execution-manager.call-opcodes.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts index d54fd01de16..3ea430ef35b 100644 --- a/packages/ovm/test/contracts/execution-manager.call-opcodes.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts @@ -1,4 +1,4 @@ -import '../setup' +import '../../../setup' /* External Imports */ import { @@ -9,23 +9,17 @@ import { getCurrentTime, ZERO_ADDRESS, } from '@eth-optimism/core-utils' +import { Contract, ContractFactory, ethers } from 'ethers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' +import { fromPairs } from 'lodash' -import { - ExecutionManagerContractDefinition as ExecutionManager, - TestSimpleCallContractDefinition as SimpleCall, - TestDummyContractDefinition as DummyContract, -} from '@eth-optimism/rollup-contracts' +/* Internal Imports */ import { Address, GAS_LIMIT, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' - -import { Contract, ContractFactory, ethers } from 'ethers' -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' - -/* Internal Imports */ +} from '../../../test-helpers/core-helpers' import { manuallyDeployOvmContract, addressToBytes32Address, @@ -33,16 +27,19 @@ import { gasLimit, encodeMethodId, encodeRawArguments, -} from '../helpers' -import { fromPairs } from 'lodash' +} from '../../../test-helpers' -export const abi = new ethers.utils.AbiCoder() +/* Contract Imports */ +import { + ExecutionManagerContractDefinition as ExecutionManager, + TestSimpleCallContractDefinition as SimpleCall, + TestDummyContractDefinition as DummyContract, +} from '../../../../src' +/* Logging */ const log = getLogger('execution-manager-calls', true) -/********* - * TESTS * - *********/ +export const abi = new ethers.utils.AbiCoder() const methodIds = fromPairs( [ @@ -61,6 +58,7 @@ const sloadKey: string = '11'.repeat(32) const unpopultedSLOADResult: string = '00'.repeat(32) const populatedSLOADResult: string = '22'.repeat(32) +/* Tests */ describe('Execution Manager -- Call opcodes', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) diff --git a/packages/ovm/test/contracts/execution-manager.code-opcodes.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.code-opcodes.spec.ts similarity index 96% rename from packages/ovm/test/contracts/execution-manager.code-opcodes.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.code-opcodes.spec.ts index 1f62cd2ea07..fcf430a8b5f 100644 --- a/packages/ovm/test/contracts/execution-manager.code-opcodes.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.code-opcodes.spec.ts @@ -1,12 +1,6 @@ -import '../setup' +import '../../../setup' /* External Imports */ -import { - Address, - GAS_LIMIT, - DEFAULT_OPCODE_WHITELIST_MASK, - DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' import { getLogger, BigNumber, @@ -14,30 +8,34 @@ import { remove0x, keccak256, } from '@eth-optimism/core-utils' - -import { - ExecutionManagerContractDefinition as ExecutionManager, - TestDummyContractDefinition as DummyContract, -} from '@eth-optimism/rollup-contracts' - import { Contract, ContractFactory, ethers } from 'ethers' import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' /* Internal Imports */ +import { + Address, + GAS_LIMIT, + DEFAULT_OPCODE_WHITELIST_MASK, + DEFAULT_ETHNODE_GAS_LIMIT, +} from '../../../test-helpers/core-helpers' import { manuallyDeployOvmContract, executeOVMCall, addressToBytes32Address, -} from '../helpers' +} from '../../../test-helpers' -export const abi = new ethers.utils.AbiCoder() +/* Contract Imports */ +import { + ExecutionManagerContractDefinition as ExecutionManager, + TestDummyContractDefinition as DummyContract, +} from '../../../../src' +/* Logging */ const log = getLogger('execution-manager-code-opcodes', true) -/********* - * TESTS * - *********/ +export const abi = new ethers.utils.AbiCoder() +/* Tests */ describe('Execution Manager -- Code-related opcodes', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) diff --git a/packages/ovm/test/contracts/execution-manager.context-opcodes.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts similarity index 97% rename from packages/ovm/test/contracts/execution-manager.context-opcodes.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts index c352ff24d2b..ffaaa52c9f4 100644 --- a/packages/ovm/test/contracts/execution-manager.context-opcodes.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts @@ -1,4 +1,4 @@ -import { should } from '../setup' +import { should } from '../../../setup' /* External Imports */ import { @@ -10,35 +10,36 @@ import { TestUtils, getCurrentTime, } from '@eth-optimism/core-utils' +import { Contract, ContractFactory, ethers } from 'ethers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' +import { fromPairs } from 'lodash' + +/* Internal Imports */ import { Address, GAS_LIMIT, DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' - -import { - ExecutionManagerContractDefinition as ExecutionManager, - TestContextContractDefinition as ContextContract, -} from '@eth-optimism/rollup-contracts' - -import { Contract, ContractFactory, ethers } from 'ethers' -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' - -/* Internal Imports */ +} from '../../../test-helpers/core-helpers' import { manuallyDeployOvmContract, addressToBytes32Address, encodeRawArguments, encodeMethodId, gasLimit, -} from '../helpers' -import { fromPairs } from 'lodash' +} from '../../../test-helpers' -export const abi = new ethers.utils.AbiCoder() +/* Contract Imports */ +import { + ExecutionManagerContractDefinition as ExecutionManager, + TestContextContractDefinition as ContextContract, +} from '../../../../src' +/* Logging */ const log = getLogger('execution-manager-context', true) +export const abi = new ethers.utils.AbiCoder() + const methodIds = fromPairs( [ 'callThroughExecutionManager', @@ -52,10 +53,7 @@ const methodIds = fromPairs( ].map((methodId) => [methodId, encodeMethodId(methodId)]) ) -/********* - * TESTS * - *********/ - +/* Tests */ describe('Execution Manager -- Context opcodes', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) diff --git a/packages/ovm/test/contracts/execution-manager.create-opcodes.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.create-opcodes.spec.ts similarity index 96% rename from packages/ovm/test/contracts/execution-manager.create-opcodes.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.create-opcodes.spec.ts index 3915bbca9a6..aa534be33c6 100644 --- a/packages/ovm/test/contracts/execution-manager.create-opcodes.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.create-opcodes.spec.ts @@ -1,31 +1,33 @@ -import '../setup' +import '../../../setup' /* External Imports */ import { getLogger, remove0x, add0x } from '@eth-optimism/core-utils' -import { - ExecutionManagerContractDefinition as ExecutionManager, - TestSimpleStorageArgsFromCalldataDefinition as SimpleStorage, - TestInvalidOpcodesContractDefinition as InvalidOpcodes, -} from '@eth-optimism/rollup-contracts' -import { - DEFAULT_OPCODE_WHITELIST_MASK, - GAS_LIMIT, - DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' - import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' import { Contract, ContractFactory } from 'ethers' - -const log = getLogger('execution-manager-create', true) +import { fromPairs } from 'lodash' /* Internal Imports */ +import { + DEFAULT_OPCODE_WHITELIST_MASK, + GAS_LIMIT, + DEFAULT_ETHNODE_GAS_LIMIT, +} from '../../../test-helpers/core-helpers' import { gasLimit, executeOVMCall, encodeMethodId, encodeRawArguments, -} from '../helpers' -import { fromPairs } from 'lodash' +} from '../../../test-helpers' + +/* Contract Imports */ +import { + ExecutionManagerContractDefinition as ExecutionManager, + TestSimpleStorageArgsFromCalldataDefinition as SimpleStorage, + TestInvalidOpcodesContractDefinition as InvalidOpcodes, +} from '../../../../src' + +/* Logging */ +const log = getLogger('execution-manager-create', true) const methodIds = fromPairs( ['ovmCREATE', 'ovmCREATE2'].map((methodId) => [ @@ -34,10 +36,7 @@ const methodIds = fromPairs( ]) ) -/********* - * TESTS * - *********/ - +/* Tests */ describe('ExecutionManager -- Create opcodes', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) diff --git a/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.executeCall.spec.ts similarity index 97% rename from packages/ovm/test/contracts/execution-manager.executeCall.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.executeCall.spec.ts index c8b4429a313..d416ac50c78 100644 --- a/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.executeCall.spec.ts @@ -1,14 +1,6 @@ -import '../setup' +import '../../../setup' /* External Imports */ -import { - Address, - GAS_LIMIT, - CHAIN_ID, - DEFAULT_OPCODE_WHITELIST_MASK, - DEFAULT_ETHNODE_GAS_LIMIT, - getUnsignedTransactionCalldata, -} from '@eth-optimism/rollup-core' import { getLogger, padToLength, @@ -16,32 +8,38 @@ import { TestUtils, getCurrentTime, } from '@eth-optimism/core-utils' - -import { - ExecutionManagerContractDefinition as ExecutionManager, - FullStateManagerContractDefinition as StateManager, - TestDummyContractDefinition as DummyContract, -} from '@eth-optimism/rollup-contracts' - import { Contract, ContractFactory, ethers } from 'ethers' import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' import * as ethereumjsAbi from 'ethereumjs-abi' /* Internal Imports */ -import { manuallyDeployOvmContract, ZERO_UINT } from '../helpers' +import { + Address, + GAS_LIMIT, + CHAIN_ID, + DEFAULT_OPCODE_WHITELIST_MASK, + DEFAULT_ETHNODE_GAS_LIMIT, + getUnsignedTransactionCalldata, +} from '../../../test-helpers/core-helpers' +import { manuallyDeployOvmContract, ZERO_UINT } from '../../../test-helpers' -export const abi = new ethers.utils.AbiCoder() +/* Contract Imports */ +import { + ExecutionManagerContractDefinition as ExecutionManager, + FullStateManagerContractDefinition as StateManager, + TestDummyContractDefinition as DummyContract, +} from '../../../../src' +/* Logging */ const log = getLogger('execution-manager-calls', true) -/********* - * TESTS * - *********/ +export const abi = new ethers.utils.AbiCoder() const unsignedCallMethodId: string = ethereumjsAbi .methodID('executeTransaction', []) .toString('hex') +/* Tests */ describe('Execution Manager -- Call opcodes', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) diff --git a/packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts similarity index 97% rename from packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts index ddb518186a8..371f2544fbd 100644 --- a/packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts @@ -1,13 +1,6 @@ -import '../setup' +import '../../../setup' /* External Imports */ -import { - Address, - GAS_LIMIT, - DEFAULT_OPCODE_WHITELIST_MASK, - L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS, - DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' import { getLogger, getCurrentTime, @@ -15,29 +8,38 @@ import { bufToHexString, ZERO_ADDRESS, } from '@eth-optimism/core-utils' - -import { - ExecutionManagerContractDefinition as ExecutionManager, - TestSimpleCallContractDefinition as SimpleCall, -} from '@eth-optimism/rollup-contracts' - import { Contract, ethers } from 'ethers' import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' import * as ethereumjsAbi from 'ethereumjs-abi' +import { cloneDeep, fromPairs } from 'lodash' /* Internal Imports */ +import { + Address, + GAS_LIMIT, + DEFAULT_OPCODE_WHITELIST_MASK, + L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS, + DEFAULT_ETHNODE_GAS_LIMIT, +} from '../../../test-helpers/core-helpers' import { manuallyDeployOvmContract, addressToBytes32Address, gasLimit, encodeMethodId, encodeRawArguments, -} from '../helpers' -import { cloneDeep, fromPairs } from 'lodash' +} from '../../../test-helpers' -export const abi = new ethers.utils.AbiCoder() +/* Contract Imports */ +import { + ExecutionManagerContractDefinition as ExecutionManager, + TestSimpleCallContractDefinition as SimpleCall, +} from '../../../../src' +/* 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)]) ) @@ -89,10 +91,7 @@ function callExecutionManagerExecuteTransaction( return callableExecutionManager.executeTransaction.apply(null, parameters) } -/********* - * TESTS * - *********/ - +/* Tests */ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) // debug: true, logger: console }) const [wallet] = getWallets(provider) diff --git a/packages/ovm/test/contracts/execution-manager.purity-checking.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.purity-checking.spec.ts similarity index 93% rename from packages/ovm/test/contracts/execution-manager.purity-checking.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.purity-checking.spec.ts index f462c71a8b3..b57df65dbd6 100644 --- a/packages/ovm/test/contracts/execution-manager.purity-checking.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.purity-checking.spec.ts @@ -1,35 +1,33 @@ -import '../setup' +import '../../../setup' /* External Imports */ import { getLogger } from '@eth-optimism/core-utils' +import { Contract } from 'ethers' +import { TransactionReceipt } from 'ethers/providers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' + +/* Internal Imports */ import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' +} from '../../../test-helpers/core-helpers' +import { + manuallyDeployOvmContractReturnReceipt, + didCreateSucceed, +} from '../../../test-helpers' +/* Contract Imports */ import { ExecutionManagerContractDefinition as ExecutionManager, TestAddThreeContractDefinition as AddThree, TestDummyContractDefinition as DummyContract, -} from '@eth-optimism/rollup-contracts' - -import { Contract } from 'ethers' -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' - -/* Internal Imports */ -import { - manuallyDeployOvmContractReturnReceipt, - didCreateSucceed, -} from '../helpers' -import { TransactionReceipt } from 'ethers/providers' +} from '../../../../src' +/* Logging */ const log = getLogger('execution-manager-safety-checking', true) -/********* - * TESTS * - *********/ - +/* Tests */ describe('Execution Manager -- Safety Checking', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) @@ -45,6 +43,7 @@ describe('Execution Manager -- Safety Checking', () => { { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) }) + describe('Safety Checking within Execution Manager', async () => { it('should fail when given an unsafe contract', async () => { // For transactions, @@ -65,6 +64,7 @@ describe('Execution Manager -- Safety Checking', () => { `DummyContract.sol should not have been considered safe because it uses storage in its constructor` ) }) + it('should successfully deploy a safe contract', async () => { const receipt = await manuallyDeployOvmContractReturnReceipt( wallet, diff --git a/packages/ovm/test/contracts/execution-manager.recover-eoa-address.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.recover-eoa-address.spec.ts similarity index 95% rename from packages/ovm/test/contracts/execution-manager.recover-eoa-address.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.recover-eoa-address.spec.ts index dedce1adfc9..663951f4778 100644 --- a/packages/ovm/test/contracts/execution-manager.recover-eoa-address.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.recover-eoa-address.spec.ts @@ -1,28 +1,27 @@ -import '../setup' +import '../../../setup' /* External Imports */ import { getLogger } from '@eth-optimism/core-utils' +import { Contract, ethers } from 'ethers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' + +/* Internal Imports */ import { CHAIN_ID, DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' -import { ExecutionManagerContractDefinition as ExecutionManager } from '@eth-optimism/rollup-contracts' +} from '../../../test-helpers/core-helpers' -import { Contract, ethers } from 'ethers' -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' - -/* Internal Imports */ - -export const abi = new ethers.utils.AbiCoder() +/* Contract Imports */ +import { ExecutionManagerContractDefinition as ExecutionManager } from '../../../../src' +/* Logging */ const log = getLogger('execution-manager-recover-eoa-address', true) -/********* - * TESTS * - *********/ +export const abi = new ethers.utils.AbiCoder() +/* Tests */ describe('Execution Manager -- Recover EOA Address', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) diff --git a/packages/ovm/test/contracts/execution-manager.storage-opcodes.spec.ts b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.storage-opcodes.spec.ts similarity index 92% rename from packages/ovm/test/contracts/execution-manager.storage-opcodes.spec.ts rename to packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.storage-opcodes.spec.ts index 54d3655af7d..8b000b7b996 100644 --- a/packages/ovm/test/contracts/execution-manager.storage-opcodes.spec.ts +++ b/packages/contracts/test/contract-tests/ovm/execution-manager/ExecutionManager.storage-opcodes.spec.ts @@ -1,22 +1,29 @@ -import '../setup' +import '../../../setup' /* External Imports */ import { abi, getLogger, add0x } from '@eth-optimism/core-utils' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' +import { Contract } from 'ethers' +import { fromPairs } from 'lodash' + +/* Internal Imports */ import { DEFAULT_OPCODE_WHITELIST_MASK, GAS_LIMIT, DEFAULT_ETHNODE_GAS_LIMIT, -} from '@eth-optimism/rollup-core' -import { ExecutionManagerContractDefinition as ExecutionManager } from '@eth-optimism/rollup-contracts' - -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' -import { Contract } from 'ethers' +} from '../../../test-helpers/core-helpers' +import { + gasLimit, + encodeMethodId, + encodeRawArguments, +} from '../../../test-helpers' -/* Internal Imports */ -import { gasLimit, encodeMethodId, encodeRawArguments } from '../helpers' -import { fromPairs } from 'lodash' +/* Contract Imports */ +import { ExecutionManagerContractDefinition as ExecutionManager } from '../../../../src' +/* Logging */ const log = getLogger('execution-manager-storage', true) + const methodIds = fromPairs( ['ovmSSTORE', 'ovmSLOAD'].map((methodId) => [ methodId, @@ -24,10 +31,7 @@ const methodIds = fromPairs( ]) ) -/********* - * TESTS * - *********/ - +/* Tests */ describe('ExecutionManager -- Storage opcodes', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) diff --git a/packages/rollup-contracts/test/rollup-list/L1ToL2TransactionQueue.spec.ts b/packages/contracts/test/contract-tests/queue/L1ToL2TransactionQueue.spec.ts similarity index 81% rename from packages/rollup-contracts/test/rollup-list/L1ToL2TransactionQueue.spec.ts rename to packages/contracts/test/contract-tests/queue/L1ToL2TransactionQueue.spec.ts index afeff5c3c2a..d5e83ea0e2a 100644 --- a/packages/rollup-contracts/test/rollup-list/L1ToL2TransactionQueue.spec.ts +++ b/packages/contracts/test/contract-tests/queue/L1ToL2TransactionQueue.spec.ts @@ -1,17 +1,17 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' +/* Contract Imports */ +import * as L1ToL2TransactionQueue from '../../../build/contracts/L1ToL2TransactionQueue.json' +import * as RollupMerkleUtils from '../../../build/contracts/RollupMerkleUtils.json' + /* Logging */ const log = getLogger('l1-to-l2-tx-queue', true) -/* Contract Imports */ -import * as L1ToL2TransactionQueue from '../../build/L1ToL2TransactionQueue.json' -import * as RollupMerkleUtils from '../../build/RollupMerkleUtils.json' - -/* Begin tests */ +/* Tests */ describe('L1ToL2TransactionQueue', () => { const provider = createMockProvider() const [ @@ -21,25 +21,13 @@ describe('L1ToL2TransactionQueue', () => { ] = getWallets(provider) const defaultTx = '0x1234' let l1ToL2TxQueue - let rollupMerkleUtils - - /* Link libraries before tests */ - before(async () => { - rollupMerkleUtils = await deployContract(wallet, RollupMerkleUtils, [], { - gasLimit: 6700000, - }) - }) /* Deploy a new RollupChain before each test */ beforeEach(async () => { l1ToL2TxQueue = await deployContract( wallet, L1ToL2TransactionQueue, - [ - rollupMerkleUtils.address, - l1ToL2TransactionPasser.address, - canonicalTransactionChain.address, - ], + [l1ToL2TransactionPasser.address, canonicalTransactionChain.address], { gasLimit: 6700000, } @@ -52,6 +40,7 @@ describe('L1ToL2TransactionQueue', () => { const batchesLength = await l1ToL2TxQueue.getBatchHeadersLength() batchesLength.should.equal(1) }) + it('should not allow enqueue from other address', async () => { await TestUtils.assertRevertsAsync( 'Message sender does not have permission to enqueue', @@ -76,6 +65,7 @@ describe('L1ToL2TransactionQueue', () => { const front = await l1ToL2TxQueue.front() front.should.equal(1) }) + it('should not allow dequeue from other address', async () => { await l1ToL2TxQueue.connect(l1ToL2TransactionPasser).enqueueTx(defaultTx) await TestUtils.assertRevertsAsync( diff --git a/packages/rollup-contracts/test/rollup-list/RollupQueue.spec.ts b/packages/contracts/test/contract-tests/queue/RollupQueue.spec.ts similarity index 92% rename from packages/rollup-contracts/test/rollup-list/RollupQueue.spec.ts rename to packages/contracts/test/contract-tests/queue/RollupQueue.spec.ts index 13f33aedad7..5056c60917f 100644 --- a/packages/rollup-contracts/test/rollup-list/RollupQueue.spec.ts +++ b/packages/contracts/test/contract-tests/queue/RollupQueue.spec.ts @@ -1,42 +1,33 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' /* Internal Imports */ -import { TxQueueBatch } from './RLhelper' +import { TxQueueBatch } from '../../test-helpers/rl-helpers' + +/* Contract Imports */ +import * as RollupQueue from '../../../build/contracts/RollupQueue.json' +import * as RollupMerkleUtils from '../../../build/contracts/RollupMerkleUtils.json' /* Logging */ const log = getLogger('rollup-queue', true) -/* Contract Imports */ -import * as RollupQueue from '../../build/RollupQueue.json' -import * as RollupMerkleUtils from '../../build/RollupMerkleUtils.json' - +/* Helpers */ const DEFAULT_TX = '0x1234' +/* Tests */ describe('RollupQueue', () => { const provider = createMockProvider() const [wallet] = getWallets(provider) let rollupQueue - let rollupMerkleUtils - before(async () => { - rollupMerkleUtils = await deployContract(wallet, RollupMerkleUtils, [], { + beforeEach(async () => { + rollupQueue = await deployContract(wallet, RollupQueue, [], { gasLimit: 6700000, }) }) - beforeEach(async () => { - rollupQueue = await deployContract( - wallet, - RollupQueue, - [rollupMerkleUtils.address], - { - gasLimit: 6700000, - } - ) - }) const enqueueAndGenerateBatch = async (tx: string): Promise => { // Submit the rollup batch on-chain @@ -55,6 +46,7 @@ describe('RollupQueue', () => { const batchesLength = await rollupQueue.getBatchHeadersLength() batchesLength.toNumber().should.equal(1) }) + it('should set the TimestampedHash correctly', async () => { const localBatch = await enqueueAndGenerateBatch(DEFAULT_TX) const { txHash, timestamp } = await rollupQueue.batchHeaders(0) @@ -150,6 +142,7 @@ describe('RollupQueue', () => { ) }) }) + describe('peek() and peekTimestamp()', async () => { it('should peek successfully with single element', async () => { const localBatch = await enqueueAndGenerateBatch(DEFAULT_TX) diff --git a/packages/rollup-contracts/test/rollup-list/SafetyTransactionQueue.spec.ts b/packages/contracts/test/contract-tests/queue/SafetyTransactionQueue.spec.ts similarity index 81% rename from packages/rollup-contracts/test/rollup-list/SafetyTransactionQueue.spec.ts rename to packages/contracts/test/contract-tests/queue/SafetyTransactionQueue.spec.ts index 62a4d38a0a8..5c5bee8df95 100644 --- a/packages/rollup-contracts/test/rollup-list/SafetyTransactionQueue.spec.ts +++ b/packages/contracts/test/contract-tests/queue/SafetyTransactionQueue.spec.ts @@ -1,35 +1,28 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, TestUtils } from '@eth-optimism/core-utils' import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' +/* Contract Imports */ +import * as SafetyTransactionQueue from '../../../build/contracts/SafetyTransactionQueue.json' +import * as RollupMerkleUtils from '../../../build/contracts/RollupMerkleUtils.json' + /* Logging */ const log = getLogger('safety-tx-queue', true) -/* Contract Imports */ -import * as SafetyTransactionQueue from '../../build/SafetyTransactionQueue.json' -import * as RollupMerkleUtils from '../../build/RollupMerkleUtils.json' - +/* Tests */ describe('SafetyTransactionQueue', () => { const provider = createMockProvider() const [wallet, canonicalTransactionChain, randomWallet] = getWallets(provider) const defaultTx = '0x1234' let safetyTxQueue - let rollupMerkleUtils - - /* Link libraries before tests */ - before(async () => { - rollupMerkleUtils = await deployContract(wallet, RollupMerkleUtils, [], { - gasLimit: 6700000, - }) - }) beforeEach(async () => { safetyTxQueue = await deployContract( wallet, SafetyTransactionQueue, - [rollupMerkleUtils.address, canonicalTransactionChain.address], + [canonicalTransactionChain.address], { gasLimit: 6700000, } @@ -58,6 +51,7 @@ describe('SafetyTransactionQueue', () => { const front = await safetyTxQueue.front() front.should.equal(1) }) + it('should not allow dequeue from other address', async () => { await safetyTxQueue.enqueueTx(defaultTx) await TestUtils.assertRevertsAsync( diff --git a/packages/ovm/test/contracts/contract-address-generator.spec.ts b/packages/contracts/test/contract-tests/utils/ContractAddressGenerator.spec.ts similarity index 83% rename from packages/ovm/test/contracts/contract-address-generator.spec.ts rename to packages/contracts/test/contract-tests/utils/ContractAddressGenerator.spec.ts index 108d8670f14..97229dc9f9f 100644 --- a/packages/ovm/test/contracts/contract-address-generator.spec.ts +++ b/packages/contracts/test/contract-tests/utils/ContractAddressGenerator.spec.ts @@ -1,33 +1,29 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger } from '@eth-optimism/core-utils' -import { ContractAddressGeneratorContractDefinition } from '@eth-optimism/rollup-contracts' -import { DEFAULT_ETHNODE_GAS_LIMIT } from '@eth-optimism/rollup-core' - import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' -import { utils } from 'ethers' +import { utils, Contract } from 'ethers' /* Internal Imports */ -import { create2Tests } from './test-files/create2test.json' -import { buildCreate2Address } from '../helpers' +import { create2Tests } from '../../test-helpers/data/create2.test.json' +import { DEFAULT_ETHNODE_GAS_LIMIT } from '../../test-helpers/core-helpers' +import { buildCreate2Address } from '../../test-helpers' -const log = getLogger('contract-address-generator', true) +/* Contract Imports */ +import { ContractAddressGeneratorContractDefinition } from '../../../src' -/********* - * TESTS * - *********/ +/* Logging */ +const log = getLogger('contract-address-generator', true) +/* Tests */ describe('ContractAddressGenerator', () => { const [wallet1, wallet2] = getWallets( createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) ) - // Create pointers to our contractAddressGenerator - let contractAddressGenerator + let contractAddressGenerator: Contract - /* Deploy contracts before each test */ beforeEach(async () => { - // First deploy the contract address contractAddressGenerator = await deployContract( wallet1, ContractAddressGeneratorContractDefinition, @@ -38,9 +34,6 @@ describe('ContractAddressGenerator', () => { ) }) - /* - * Test getAddressFromCREATE - */ describe('getAddressFromCREATE', async () => { it('returns expected address, nonce: 1', async () => { const nonce = 1 @@ -54,6 +47,7 @@ describe('ContractAddressGenerator', () => { ) computedAddress.should.equal(expectedAddress) }) + it('returns expected address, nonce: 1, different origin address', async () => { const nonce = 1 const expectedAddress = utils.getContractAddress({ @@ -66,6 +60,7 @@ describe('ContractAddressGenerator', () => { ) computedAddress.should.equal(expectedAddress) }) + it('returns expected address, nonce: 999999999 ', async () => { const nonce = 999999999 const expectedAddress = utils.getContractAddress({ @@ -78,6 +73,7 @@ describe('ContractAddressGenerator', () => { ) computedAddress.should.equal(expectedAddress) }) + // test around nonce 128, or 0x80, due to edge cases. See https://github.com/ethereum/wiki/wiki/RLP#definition for (let nonce = 127; nonce < 129; nonce++) { it(`returns expected address, nonce: ${nonce}`, async () => { @@ -94,9 +90,6 @@ describe('ContractAddressGenerator', () => { } }) - /* - * Test buildCreate2Address helper function - */ describe('buildCreate2Address helper', async () => { for (const test of Object.keys(create2Tests)) { it(`should properly generate CREATE2 address from ${test}`, async () => { @@ -107,9 +100,6 @@ describe('ContractAddressGenerator', () => { } }) - /* - * Test getAddressFromCREATE2 - */ describe('getAddressFromCREATE2', async () => { for (const test of Object.keys(create2Tests)) { it(`should properly generate CREATE2 address from ${test}`, async () => { diff --git a/packages/rollup-contracts/test/merklization/MerkleTrie.spec.ts b/packages/contracts/test/contract-tests/utils/MerkleTrie.spec.ts similarity index 98% rename from packages/rollup-contracts/test/merklization/MerkleTrie.spec.ts rename to packages/contracts/test/contract-tests/utils/MerkleTrie.spec.ts index 64ba56a527c..c0499eb05fa 100644 --- a/packages/rollup-contracts/test/merklization/MerkleTrie.spec.ts +++ b/packages/contracts/test/contract-tests/utils/MerkleTrie.spec.ts @@ -1,16 +1,16 @@ -import { expect } from '../setup' +import { expect } from '../../setup' import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' import { Contract } from 'ethers' -import * as MerkleTrie from '../../build/MerkleTrie.json' +import * as MerkleTrie from '../../../build/contracts/MerkleTrie.json' import { makeAllProofTests, makeRandomProofTest, makeProofTest, makeUpdateTest, makeRandomUpdateTest, -} from '../helpers/trie-helpers' +} from '../../test-helpers/trie-helpers' describe('MerkleTrie', () => { const [wallet] = getWallets(createMockProvider()) diff --git a/packages/ovm/test/contracts/rlp-encode.spec.ts b/packages/contracts/test/contract-tests/utils/RLPEncode.spec.ts similarity index 54% rename from packages/ovm/test/contracts/rlp-encode.spec.ts rename to packages/contracts/test/contract-tests/utils/RLPEncode.spec.ts index 3deab330a48..9e1b59f1c99 100644 --- a/packages/ovm/test/contracts/rlp-encode.spec.ts +++ b/packages/contracts/test/contract-tests/utils/RLPEncode.spec.ts @@ -1,55 +1,63 @@ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger } from '@eth-optimism/core-utils' -import { RLPEncodeContractDefinition as RLPEncode } from '@eth-optimism/rollup-contracts' - import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' -import { rlpTests } from './test-files/rlptest.json' +import { Contract } from 'ethers' + +/* Internal Imports */ +import { rlpTests } from '../../test-helpers/data/rlp.test.json' +/* Contract Imports */ +import { RLPEncodeContractDefinition as RLPEncode } from '../../../src' + +/* Logging */ const log = getLogger('rlp-encode', true) -/* Begin tests */ +/* Tests */ describe('RLP Encoder', () => { const provider = createMockProvider() - const [wallet1, wallet2] = getWallets(provider) - let rlpEncode + const [wallet1] = getWallets(provider) + let rlpWriter: Contract - /* Link libraries before tests */ before(async () => { - rlpEncode = await deployContract(wallet1, RLPEncode, [], { + rlpWriter = await deployContract(wallet1, RLPEncode, [], { gasLimit: 6700000, }) }) - const encode = async (input) => { - // handle lists + const encode = async (input: any) => { if (Array.isArray(input)) { + // Handle lists. const encodedElements = [] - // recursively encode every element in the list + + // Recursively encode every element in the list. for (const element of input) { const encodedElement = encode(element) encodedElements.push(encodedElement) } - // encode the list of encoded elements - const encodedList = await rlpEncode.encodeList(encodedElements) + + // Encode the list of encoded elements. + const encodedList = await rlpWriter.encodeList(encodedElements) return encodedList - // handle integers } else if (Number.isInteger(input)) { - const encodedUint = await rlpEncode.encodeUint(input) + // Handle integers. + const encodedUint = await rlpWriter.encodeUint(input) return encodedUint - // handle big numbers } else if (input[0] === '#') { - // remove '#'' from big int + // Handle big numbers. + // Remove '#'' from the input. input = input.slice(1) - const encodedUint = await rlpEncode.encodeInt(input) + + const encodedUint = await rlpWriter.encodeInt(input) return encodedUint - // handle strings } else { - const encodedString = await rlpEncode.encodeString(input) + // Handle strings. + const encodedString = await rlpWriter.encodeString(input) return encodedString } } + describe('Official Ethereum RLP Tests', async () => { for (const test of Object.keys(rlpTests)) { it(`should properly encode ${test}`, async () => { diff --git a/packages/ovm/test/contracts/safety-checker.spec.ts b/packages/contracts/test/contract-tests/utils/SafetyChecker.spec.ts similarity index 99% rename from packages/ovm/test/contracts/safety-checker.spec.ts rename to packages/contracts/test/contract-tests/utils/SafetyChecker.spec.ts index b33e3c159a2..2649cd55f08 100644 --- a/packages/ovm/test/contracts/safety-checker.spec.ts +++ b/packages/contracts/test/contract-tests/utils/SafetyChecker.spec.ts @@ -1,22 +1,25 @@ -/* Internal Imports */ -import '../setup' +import '../../setup' /* External Imports */ import { getLogger, add0x, remove0x } from '@eth-optimism/core-utils' +import { Contract } from 'ethers' +import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' + +/* Internal Imports */ import { DEFAULT_OPCODE_WHITELIST_MASK, DEFAULT_UNSAFE_OPCODES, EVMOpcode, Opcode, -} from '@eth-optimism/rollup-core' -import { SafetyCheckerContractDefinition as SafetyChecker } from '@eth-optimism/rollup-contracts' +} from '../../test-helpers/core-helpers' -import { Contract } from 'ethers' -import { createMockProvider, deployContract, getWallets } from 'ethereum-waffle' +/* Contract Imports */ +import { SafetyCheckerContractDefinition as SafetyChecker } from '../../../src' /* 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( @@ -30,12 +33,12 @@ const whitelistedNotHaltingOrCALL: EVMOpcode[] = Opcode.ALL_OP_CODES.filter( x.name !== 'CALL' ) +/* Tests */ describe('Safety Checker', () => { const provider = createMockProvider() const [wallet] = getWallets(provider) let safetyChecker: Contract - /* Deploy a new whitelist contract before each test */ beforeEach(async () => { safetyChecker = await deployContract( wallet, diff --git a/packages/rollup-contracts/test/setup.ts b/packages/contracts/test/setup.ts similarity index 100% rename from packages/rollup-contracts/test/setup.ts rename to packages/contracts/test/setup.ts diff --git a/packages/contracts/test/test-helpers/core-helpers.ts b/packages/contracts/test/test-helpers/core-helpers.ts new file mode 100644 index 00000000000..e48344faab6 --- /dev/null +++ b/packages/contracts/test/test-helpers/core-helpers.ts @@ -0,0 +1,1054 @@ +import { + ZERO_ADDRESS, + bufToHexString, + remove0x, +} from '@eth-optimism/core-utils' + +export type Address = string + +/** + * Creates an unsigned transaction and returns its calldata. + * + * @param contract The contract containing the function being invoked + * @param functionName The function being invoked + * @param args The arguments of the function call + * @returns The unsigned transaction's calldata + */ +export const getUnsignedTransactionCalldata = ( + contract: any, + functionName: string, + args: any[] = [] +) => { + return contract.interface.functions[functionName].encode(args) +} + +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) + ) + } +} + +export const L1ToL2TransactionEventName = 'L1ToL2Transaction' +export const L1ToL2TransactionBatchEventName = 'NewTransactionBatchAdded' + +export const CREATOR_CONTRACT_ADDRESS = ZERO_ADDRESS +export const GAS_LIMIT = 1_000_000_000 +export const DEFAULT_ETHNODE_GAS_LIMIT = 10_000_000 + +export const CHAIN_ID = 108 + +export const DEFAULT_UNSAFE_OPCODES: EVMOpcode[] = [ + Opcode.ADDRESS, + Opcode.BALANCE, + Opcode.BLOCKHASH, + Opcode.CALLCODE, + Opcode.CALLER, + 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, +] + +// use whitelist-mask-generator.spec.ts to re-generate this +export const DEFAULT_OPCODE_WHITELIST_MASK = + '0x600a0000000000000000001fffffffffffffffff0fcf004063f000013fff0fff' + +export const L2_TO_L1_MESSAGE_PASSER_OVM_ADDRESS = + '0x4200000000000000000000000000000000000000' diff --git a/packages/ovm/test/contracts/test-files/create2test.json b/packages/contracts/test/test-helpers/data/create2.test.json similarity index 100% rename from packages/ovm/test/contracts/test-files/create2test.json rename to packages/contracts/test/test-helpers/data/create2.test.json diff --git a/packages/ovm/test/contracts/test-files/rlptest.json b/packages/contracts/test/test-helpers/data/rlp.test.json similarity index 100% rename from packages/ovm/test/contracts/test-files/rlptest.json rename to packages/contracts/test/test-helpers/data/rlp.test.json diff --git a/packages/ovm/test/helpers.ts b/packages/contracts/test/test-helpers/index.ts similarity index 57% rename from packages/ovm/test/helpers.ts rename to packages/contracts/test/test-helpers/index.ts index 0fd9241a0a2..e2786204b18 100644 --- a/packages/ovm/test/helpers.ts +++ b/packages/contracts/test/test-helpers/index.ts @@ -1,5 +1,32 @@ +import { Transaction } from 'ethers/utils' +import * as ethereumjsAbi from 'ethereumjs-abi' +import { executionManagerInterface } from '../../src' + +/********************************** + * Byte String Generation Helpers * + *********************************/ + +// Create a byte string of some length in bytes. It repeats the value provided until the +// string hits that length +export function makeRepeatedBytes(value: string, length: number): string { + const repeated = value.repeat((length * 2) / value.length + 1) + const sliced = repeated.slice(0, length * 2) + return '0x' + sliced +} + +export function makeRandomBlockOfSize(blockSize: number): string[] { + const block = [] + for (let i = 0; i < blockSize; i++) { + block.push(makeRepeatedBytes('' + Math.floor(Math.random() * 500 + 1), 32)) + } + return block +} + +export function makeRandomBatchOfSize(batchSize: number): string[] { + return makeRandomBlockOfSize(batchSize) +} + /* External Imports */ -import { Address, CHAIN_ID, GAS_LIMIT } from '@eth-optimism/rollup-core' import { ZERO_ADDRESS, getLogger, @@ -12,6 +39,9 @@ import { hexStrToBuf, bufToHexString, bufferUtils, + logError, + BloomFilter, + numberToHexString, } from '@eth-optimism/core-utils' import { Contract, ContractFactory, Wallet, ethers } from 'ethers' import { @@ -20,20 +50,198 @@ import { JsonRpcProvider, Log, } from 'ethers/providers' -import { Transaction } from 'ethers/utils' -import * as ethereumjsAbi from 'ethereumjs-abi' - -/* Contract Imports */ -import { internalTxReceiptToOvmTxReceipt } from '../src/app' -import { OvmTransactionReceipt } from '../src/types' +/* Internal Imports */ +import { Address, CHAIN_ID, GAS_LIMIT } from './core-helpers' type Signature = [string, string, string] +/** + * Convert internal transaction logs into OVM logs. Or in other words, take the logs which + * are emitted by a normal Ganache or Geth node (this will include logs from the ExecutionManager), + * parse them, and then convert them into logs which look like they would if you were running this tx + * using an OVM backend. + * + * NOTE: The input logs MUST NOT be stripped of any Execution Manager events, or this function will break. + * + * @param logs An array of internal transaction logs which we will parse and then convert. + * @param executionManagerAddress The address of the Execution Manager contract for log parsing. + * @return the converted logs + */ +export const convertInternalLogsToOvmLogs = ( + logs: Log[], + executionManagerAddress: string +): Log[] => { + const uppercaseExecutionMangerAddress: string = executionManagerAddress.toUpperCase() + let activeContractAddress: string = logs[0] ? logs[0].address : ZERO_ADDRESS + const stringsToDebugLog = [`Parsing internal logs ${JSON.stringify(logs)}: `] + const ovmLogs = [] + let numberOfEMLogs = 0 + let prevEMLogIndex = 0 + logs.forEach((log) => { + if (log.address.toUpperCase() === uppercaseExecutionMangerAddress) { + if (log.logIndex <= prevEMLogIndex) { + // This indicates a new TX, so reset number of EM logs to 0 + numberOfEMLogs = 0 + } + numberOfEMLogs++ + prevEMLogIndex = log.logIndex + const executionManagerLog = executionManagerInterface.parseLog(log) + if (!executionManagerLog) { + stringsToDebugLog.push( + `Execution manager emitted log with topics: ${log.topics}. These were unrecognized by the interface parser-but definitely not an ActiveContract event, ignoring...` + ) + } else if (executionManagerLog.name === 'ActiveContract') { + activeContractAddress = executionManagerLog.values['_activeContract'] + } + } else { + const newIndex = log.logIndex - numberOfEMLogs + ovmLogs.push({ + ...log, + address: activeContractAddress, + logIndex: newIndex, + }) + } + }) + return ovmLogs +} + +export const revertMessagePrefix: string = + 'VM Exception while processing transaction: revert ' + +/** + * Gets ovm transaction metadata from an internal transaction receipt. + * + * @param internalTxReceipt the internal transaction receipt + * @return ovm transaction metadata + */ +export const getSuccessfulOvmTransactionMetadata = ( + internalTxReceipt: TransactionReceipt +): any => { + let ovmTo + let ovmFrom + let ovmCreatedContractAddress + let ovmTxSucceeded + + if (!internalTxReceipt) { + return undefined + } + + const logs = internalTxReceipt.logs + .map((log) => executionManagerInterface.parseLog(log)) + .filter((log) => log != null) + const callingWithEoaLog = logs.find((log) => log.name === 'CallingWithEOA') + + const revertEvents: any[] = logs.filter((x) => x.name === 'EOACallRevert') + ovmTxSucceeded = !revertEvents.length + + if (callingWithEoaLog) { + ovmFrom = callingWithEoaLog.values._ovmFromAddress + ovmTo = callingWithEoaLog.values._ovmToAddress + } + + const eoaContractCreatedLog = logs.find( + (log) => log.name === 'EOACreatedContract' + ) + if (eoaContractCreatedLog) { + ovmCreatedContractAddress = eoaContractCreatedLog.values._ovmContractAddress + ovmTo = ovmCreatedContractAddress + } + + const metadata: any = { + ovmTxSucceeded, + ovmTo, + ovmFrom, + ovmCreatedContractAddress, + } + + if (!ovmTxSucceeded) { + try { + if ( + !revertEvents[0].values['_revertMessage'] || + revertEvents[0].values['_revertMessage'].length <= 2 + ) { + metadata.revertMessage = revertMessagePrefix + } else { + // decode revert message from event + const msgBuf: any = abi.decode( + ['bytes'], + // Remove the first 4 bytes of the revert message that is a sighash + ethers.utils.hexDataSlice(revertEvents[0].values['_revertMessage'], 4) + ) + const revertMsg: string = hexStrToBuf(msgBuf[0]).toString('utf8') + metadata.revertMessage = `${revertMessagePrefix}${revertMsg}` + logger.debug(`Decoded revert message: [${metadata.revertMessage}]`) + } + } catch (e) { + logError(logger, `Error decoding revert event!`, e) + } + } + + return metadata +} + +/** + * Converts an EVM receipt to an OVM receipt. + * + * @param internalTxReceipt The EVM tx receipt to convert to an OVM tx receipt + * @param ovmTxHash The OVM tx hash to replace the internal tx hash with. + * @returns The converted receipt + */ +export const internalTxReceiptToOvmTxReceipt = async ( + internalTxReceipt: TransactionReceipt, + executionManagerAddress: string, + ovmTxHash?: string +): Promise => { + const ovmTransactionMetadata = getSuccessfulOvmTransactionMetadata( + internalTxReceipt + ) + // Construct a new receipt + + // Start off with the internalTxReceipt + const ovmTxReceipt: any = internalTxReceipt + // Add the converted logs + ovmTxReceipt.logs = convertInternalLogsToOvmLogs( + internalTxReceipt.logs, + executionManagerAddress + ) + // Update the to and from fields if necessary + if (ovmTransactionMetadata.ovmTo) { + ovmTxReceipt.to = ovmTransactionMetadata.ovmTo + } + // Also update the contractAddress in case we deployed a new contract + ovmTxReceipt.contractAddress = !!ovmTransactionMetadata.ovmCreatedContractAddress + ? ovmTransactionMetadata.ovmCreatedContractAddress + : null + + ovmTxReceipt.status = ovmTransactionMetadata.ovmTxSucceeded ? 1 : 0 + + if (!!ovmTxReceipt.transactionHash && !!ovmTxHash) { + ovmTxReceipt.transactionHash = ovmTxHash + } + + if (ovmTransactionMetadata.revertMessage !== undefined) { + ovmTxReceipt.revertMessage = ovmTransactionMetadata.revertMessage + } + + logger.debug('Ovm parsed logs:', ovmTxReceipt.logs) + const logsBloom = new BloomFilter() + ovmTxReceipt.logs.forEach((log, index) => { + logsBloom.add(hexStrToBuf(log.address)) + log.topics.forEach((topic) => logsBloom.add(hexStrToBuf(topic))) + log.transactionHash = ovmTxReceipt.transactionHash + log.logIndex = numberToHexString(index) as any + }) + ovmTxReceipt.logsBloom = bufToHexString(logsBloom.bitvector) + + // Return! + return ovmTxReceipt +} + export const ZERO_UINT = '00'.repeat(32) export const gasLimit = 6_700_000 -const log = getLogger('helpers', true) +const logger = getLogger('helpers', true) /** * Helper function to ensure GoVM is connected @@ -61,7 +269,7 @@ export const manuallyDeployOvmContractReturnReceipt = async ( executionManager: Contract, contractDefinition, constructorArguments: any[] -): Promise => { +): Promise => { const initCode = new ContractFactory( contractDefinition.abi, contractDefinition.bytecode diff --git a/packages/rollup-contracts/test/rollup-list/RLhelper.ts b/packages/contracts/test/test-helpers/rl-helpers.ts similarity index 100% rename from packages/rollup-contracts/test/rollup-list/RLhelper.ts rename to packages/contracts/test/test-helpers/rl-helpers.ts diff --git a/packages/rollup-contracts/test/helpers/trie-helpers.ts b/packages/contracts/test/test-helpers/trie-helpers.ts similarity index 100% rename from packages/rollup-contracts/test/helpers/trie-helpers.ts rename to packages/contracts/test/test-helpers/trie-helpers.ts diff --git a/packages/ovm/tsconfig.json b/packages/contracts/tsconfig.json similarity index 66% rename from packages/ovm/tsconfig.json rename to packages/contracts/tsconfig.json index 17fff08e562..b5777cf3806 100644 --- a/packages/ovm/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -5,5 +5,6 @@ "baseUrl": "./", "resolveJsonModule": true }, - "include": ["*.ts", "**/*.ts"] + "include": ["*.ts", "**/*.ts"], + "exclude": ["./build", "node_modules"] } diff --git a/packages/ovm/tslint.json b/packages/contracts/tslint.json similarity index 100% rename from packages/ovm/tslint.json rename to packages/contracts/tslint.json diff --git a/packages/rollup-contracts/waffle-config.json b/packages/contracts/waffle-config.json similarity index 65% rename from packages/rollup-contracts/waffle-config.json rename to packages/contracts/waffle-config.json index dae852f2c5e..a8d499e4466 100644 --- a/packages/rollup-contracts/waffle-config.json +++ b/packages/contracts/waffle-config.json @@ -1,5 +1,5 @@ { "sourcesPath": "./contracts", - "targetPath": "./build", + "targetPath": "./build/contracts", "npmPath": "../../node_modules" } diff --git a/packages/ovm/.soliumrc.json b/packages/ovm/.soliumrc.json deleted file mode 100644 index 1b39f759aba..00000000000 --- a/packages/ovm/.soliumrc.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "solium:recommended", - "plugins": [ - "security" - ], - "rules": { - "security/no-inline-assembly": "off", - "quotes": [ - "error", - "double" - ], - "indentation": [ - "error", - 4 - ], - "linebreak-style": [ - "error", - "unix" - ] - } -} diff --git a/packages/ovm/README.md b/packages/ovm/README.md deleted file mode 100644 index 4763956daf4..00000000000 --- a/packages/ovm/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Optimistic Virtual Machine -`ovm` is an implementation of the standard `StateMachine` interface defined in `core`. It provides the ability to apply transactions & exposes endpoints required for common Optimistic Rollup implementations and other L2s. - -### Requirements, Setup, & Testing -[View instructions here]('../../README.md'). - -### Deploying -TODO: You can deploy by running: - -```sh -yarn run deploy: -``` - -The `environment` parameter tells the deployment script which config file to use (expected filename `..env`). - diff --git a/packages/ovm/config/.env.example b/packages/ovm/config/.env.example deleted file mode 100644 index 7d63cc9ce5f..00000000000 --- a/packages/ovm/config/.env.example +++ /dev/null @@ -1,33 +0,0 @@ -################ -# Instructions # -################ -# Create environment file(s) named "..env" in this same location using this file as a template -# For instance `yarn run deploy:some-contract local` will use .local.env for configuration. - - -############ -# Template # -############ - -# REQUIRED -# Mnemonic for the wallet used to deploy the contracts -DEPLOY_MNEMONIC='response fresh afford leader twice silent table exist aisle pelican focus bird' - -DEPLOY_SAFETY_CHECKER_CONTRACT_ADDRESS='0x some address here' - -# Note: can use any network name. 'local' or leaving it blank will deploy to DEPLOY_LOCAL_URL -DEPLOY_NETWORK='local' - -# Only if deploying locally -DEPLOY_LOCAL_URL='http://127.0.0.1:8545' - -### OPTIONAL ### - -# Will be defaulted to this value if not overridden -- you should not set this unless you know what you're doing. -OPCODE_WHITELIST_MASK='0x600a0000000000000000001fffffffffffffffff0fcf004063f000013fff0fff' -# Default whitelist config whitelists all opcodes EXCEPT: -# ADDRESS, BALANCE, BLOCKHASH, CALLCODE, CALLER, COINBASE, -# CREATE, CREATE2, DELEGATECALL, DIFFICULTY, EXTCODECOPY, EXTCODESIZE, -# GASLIMIT, GASPRICE, NUMBER, ORIGIN, SELFDESTRUCT, SLOAD, SSTORE, -# STATICCALL, TIMESTAMP -# See test/safety-checker/whitelist-mask-generator.spec.ts for more info diff --git a/packages/ovm/deploy/execution-manager.ts b/packages/ovm/deploy/execution-manager.ts deleted file mode 100644 index 77cce424b73..00000000000 --- a/packages/ovm/deploy/execution-manager.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* External Imports */ -import { deploy, deployContract } from '@eth-optimism/core-utils' -import { ExecutionManagerContractDefinition } from '@eth-optimism/rollup-contracts' -import { - DEFAULT_OPCODE_WHITELIST_MASK, - GAS_LIMIT, -} from '@eth-optimism/rollup-core' - -import { Wallet } from 'ethers' - -/* Internal Imports */ -import { deploySafetyChecker } from './safety-checker' -import { resolve } from 'path' - -const executionManagerDeploymentFunction = async ( - wallet: Wallet -): Promise => { - console.log(`\nDeploying ExecutionManager!\n`) - - const safetyCheckerContractAddress = await deploySafetyChecker() - - const executionManager = await deployContract( - ExecutionManagerContractDefinition, - wallet, - DEFAULT_OPCODE_WHITELIST_MASK, - safetyCheckerContractAddress, - GAS_LIMIT, - true - ) - - console.log(`Execution Manager deployed to ${executionManager.address}!\n\n`) - - return executionManager.address -} - -/** - * Deploys the ExecutionManager contract. - * - * @param rootContract Whether or not this is the main contract being deployed (as compared to a dependency). - * @returns The deployed contract's address. - */ -export const deployExecutionManager = async ( - rootContract: boolean = false -): Promise => { - // Note: Path is from 'build/deploy/