From 407555674bb2e5cf1805026755786553ecdffd97 Mon Sep 17 00:00:00 2001 From: Mason Fischer Date: Tue, 9 Jun 2020 19:18:34 -0400 Subject: [PATCH] Extract StateManager from ExecutionManager (#132) * Pull StateManager out into its own contract * Pass remaining StateManager tests * Pass OVM tests * Move deployCodeContract into the StateManager * Move purity checking into the FullStateManager * Fix bug where we didn't check initcode purity * Fix fullnode & StateManager lint errors * Refactor deployContract(...) to remove params * Reduce gas limits to more reasonable values * Update packages/ovm/test/contracts/execution-manager.l1-l2-opcodes.spec.ts Co-authored-by: ben-chain * Update packages/rollup-contracts/contracts/FullStateManager.sol Co-authored-by: ben-chain * Address PR feedback * Fix lint errors Co-authored-by: Karl Floersch Co-authored-by: ben-chain --- .../execution-manager.executeCall.spec.ts | 22 ++-- .../ovm/test/contracts/simple-storage.spec.ts | 10 +- packages/ovm/test/contracts/tx-origin.spec.ts | 9 +- .../contracts/ExecutionManager.sol | 114 ++++++++---------- .../contracts/FullStateManager.sol | 74 ++++++++++-- .../contracts/StateManager.sol | 19 +-- packages/rollup-contracts/src/contracts.ts | 2 + packages/rollup-core/src/app/constants.ts | 2 +- packages/rollup-core/src/app/util/l2-node.ts | 8 ++ .../rollup-core/src/types/node-context.ts | 1 + .../src/app/web3-rpc-handler.ts | 8 +- 11 files changed, 169 insertions(+), 100 deletions(-) diff --git a/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts b/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts index 578407625bb2..c8b4429a3135 100644 --- a/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts +++ b/packages/ovm/test/contracts/execution-manager.executeCall.spec.ts @@ -19,6 +19,7 @@ import { import { ExecutionManagerContractDefinition as ExecutionManager, + FullStateManagerContractDefinition as StateManager, TestDummyContractDefinition as DummyContract, } from '@eth-optimism/rollup-contracts' @@ -46,6 +47,7 @@ describe('Execution Manager -- Call opcodes', () => { const [wallet] = getWallets(provider) // Create pointers to our execution manager & simple copier contract let executionManager: Contract + let stateManager: Contract let dummyContract: ContractFactory let dummyContractAddress: Address @@ -59,6 +61,12 @@ describe('Execution Manager -- Call opcodes', () => { [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) + // Set the state manager as well + stateManager = new Contract( + await executionManager.getStateManagerAddress(), + StateManager.abi, + wallet + ) // Deploy SimpleCopier with the ExecutionManager dummyContractAddress = await manuallyDeployOvmContract( @@ -89,7 +97,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await executionManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonce(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -131,7 +139,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await executionManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonce(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -167,7 +175,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await executionManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonce(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -204,7 +212,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await executionManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonce(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -229,9 +237,7 @@ describe('Execution Manager -- Call opcodes', () => { s ) await provider.waitForTransaction(tx.hash) - const nonceAfter = await executionManager.getOvmContractNonce( - wallet.address - ) + const nonceAfter = await stateManager.getOvmContractNonce(wallet.address) nonceAfter.should.equal(parseInt(nonce, 10) + 1) }) @@ -244,7 +250,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await executionManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonce(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, diff --git a/packages/ovm/test/contracts/simple-storage.spec.ts b/packages/ovm/test/contracts/simple-storage.spec.ts index 41600d7c701a..0d6adc51b3a3 100644 --- a/packages/ovm/test/contracts/simple-storage.spec.ts +++ b/packages/ovm/test/contracts/simple-storage.spec.ts @@ -4,6 +4,7 @@ import '../setup' import { getLogger, add0x, getCurrentTime } from '@eth-optimism/core-utils' import { ExecutionManagerContractDefinition as ExecutionManager, + FullStateManagerContractDefinition as StateManager, TestSimpleStorageArgsFromCalldataDefinition as SimpleStorage, } from '@eth-optimism/rollup-contracts' import { @@ -38,6 +39,7 @@ describe('SimpleStorage', () => { const [wallet] = getWallets(provider) // Create pointers to our execution manager & simple storage contract let executionManager: Contract + let stateManager: Contract let simpleStorage: ContractFactory let simpleStorageOvmAddress: Address @@ -51,6 +53,12 @@ describe('SimpleStorage', () => { [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) + // Set the state manager as well + stateManager = new Contract( + await executionManager.getStateManagerAddress(), + StateManager.abi, + wallet + ) // Deploy SimpleStorage with the ExecutionManager simpleStorageOvmAddress = await manuallyDeployOvmContract( @@ -104,7 +112,7 @@ describe('SimpleStorage', () => { .toString('hex') const innerCallData: string = add0x(`${getStorageMethodId}${slot}`) - const nonce = await executionManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonce(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, diff --git a/packages/ovm/test/contracts/tx-origin.spec.ts b/packages/ovm/test/contracts/tx-origin.spec.ts index a4680b10a04c..747ac75b05ed 100644 --- a/packages/ovm/test/contracts/tx-origin.spec.ts +++ b/packages/ovm/test/contracts/tx-origin.spec.ts @@ -12,6 +12,7 @@ import { } from '@eth-optimism/rollup-core' import { ExecutionManagerContractDefinition as ExecutionManager, + FullStateManagerContractDefinition as StateManager, TestSimpleTxOriginContractDefinition as SimpleTxOrigin, } from '@eth-optimism/rollup-contracts' @@ -36,6 +37,7 @@ describe('SimpleTxOrigin', () => { const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }) const [wallet] = getWallets(provider) let executionManager: Contract + let stateManager: Contract let simpleTxOrigin: ContractFactory let simpleTxOriginOvmAddress: Address @@ -49,6 +51,11 @@ describe('SimpleTxOrigin', () => { [DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, true], { gasLimit: DEFAULT_ETHNODE_GAS_LIMIT } ) + stateManager = new Contract( + await executionManager.getStateManagerAddress(), + StateManager.abi, + wallet + ) // Deploy SimpleTxOrigin with the ExecutionManager simpleTxOriginOvmAddress = await manuallyDeployOvmContract( @@ -72,7 +79,7 @@ describe('SimpleTxOrigin', () => { .toString('hex') const innerCallData: string = add0x(`${getStorageMethodId}`) - const nonce = await executionManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonce(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, diff --git a/packages/rollup-contracts/contracts/ExecutionManager.sol b/packages/rollup-contracts/contracts/ExecutionManager.sol index bbfa6ac8b343..f2037296eb2c 100644 --- a/packages/rollup-contracts/contracts/ExecutionManager.sol +++ b/packages/rollup-contracts/contracts/ExecutionManager.sol @@ -5,7 +5,6 @@ pragma experimental ABIEncoderV2; import {DataTypes as dt} from "./DataTypes.sol"; import {FullStateManager} from "./FullStateManager.sol"; import {ContractAddressGenerator} from "./ContractAddressGenerator.sol"; -import {SafetyChecker} from "./SafetyChecker.sol"; import {RLPEncode} from "./RLPEncode.sol"; import {L2ToL1MessagePasser} from "./precompiles/L2ToL1MessagePasser.sol"; import {L1MessageSender} from "./precompiles/L1MessageSender.sol"; @@ -15,11 +14,11 @@ import {L1MessageSender} from "./precompiles/L1MessageSender.sol"; * @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 is FullStateManager { - address ZERO_ADDRESS = 0x0000000000000000000000000000000000000000; - - // expected queue origin for calls from L1 - uint constant L1_QUEUE_ORIGIN = 1; +contract ExecutionManager { + /************ + * Constants * + ************/ + address constant ZERO_ADDRESS = 0x0000000000000000000000000000000000000000; // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes bytes32 constant ovmCallMethodId = keccak256("ovmCALL()") >> 224; @@ -29,17 +28,21 @@ contract ExecutionManager is FullStateManager { address constant l2ToL1MessagePasserOvmAddress = 0x4200000000000000000000000000000000000000; address constant l1MsgSenderAddress = 0x4200000000000000000000000000000000000001; - // Execution storage - dt.ExecutionContext executionContext; - // Add Contract Address Generation contract + /************************ + * Contract Dependencies * + ************************/ + FullStateManager stateManager; ContractAddressGenerator contractAddressGenerator; - // Add Safety Checker contract - SafetyChecker safetyChecker; RLPEncode rlp; - // for testing: if true, then do not perform safety checking on init code or deployed bytecode - bool overrideSafetyChecker; - // Events + /*************** + * Other Fields * + ***************/ + dt.ExecutionContext executionContext; + + /********* + * Events * + *********/ event ActiveContract(address _activeContract); event CreatedContract( address _ovmContractAddress, @@ -67,27 +70,24 @@ contract ExecutionManager is FullStateManager { * @param _opcodeWhitelistMask A bit mask representing which opcodes are whitelisted or not for our safety checker * @param _owner The owner of this contract. * @param _blockGasLimit The block gas limit for OVM blocks - * @param _overrideSafetyChecker Set to true to disable safety checking (WARNING: Only do this in test environments) */ constructor(uint256 _opcodeWhitelistMask, address _owner, uint _blockGasLimit, bool _overrideSafetyChecker) public { rlp = new RLPEncode(); - // Set override safety checker flag - overrideSafetyChecker = _overrideSafetyChecker; - // Set the safety checker address - safetyChecker = new SafetyChecker(_opcodeWhitelistMask, address(this)); // Initialize new contract address generator contractAddressGenerator = new ContractAddressGenerator(); + // Deploy a default state manager + stateManager = new FullStateManager(_opcodeWhitelistMask, _overrideSafetyChecker); // Associate all Ethereum precompiles for (uint160 i = 1; i < 20; i++) { - associateCodeContract(address(i), address(i)); + stateManager.associateCodeContract(address(i), address(i)); } // Deploy custom precompiles L2ToL1MessagePasser l1ToL2MessagePasser = new L2ToL1MessagePasser(address(this)); - associateCodeContract(l2ToL1MessagePasserOvmAddress, address(l1ToL2MessagePasser)); + stateManager.associateCodeContract(l2ToL1MessagePasserOvmAddress, address(l1ToL2MessagePasser)); L1MessageSender l1MessageSender = new L1MessageSender(address(this)); - associateCodeContract(l1MsgSenderAddress, address(l1MessageSender)); + stateManager.associateCodeContract(l1MsgSenderAddress, address(l1MessageSender)); executionContext.gasLimit = _blockGasLimit; executionContext.chainId = 108; @@ -102,7 +102,7 @@ contract ExecutionManager is FullStateManager { * @param addr The address of the nonce to increment. */ function incrementNonce(address addr) external { - incrementOvmContractNonce(addr); + stateManager.incrementOvmContractNonce(addr); } /******************** @@ -137,7 +137,7 @@ contract ExecutionManager is FullStateManager { // 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 == getOvmContractNonce(eoaAddress), "Incorrect nonce!"); + require(_nonce == stateManager.getOvmContractNonce(eoaAddress), "Incorrect nonce!"); emit CallingWithEOA( eoaAddress, _ovmEntrypoint @@ -166,7 +166,7 @@ contract ExecutionManager is FullStateManager { bool _allowRevert ) public { require(_timestamp > 0, "Timestamp must be greater than 0"); - uint _nonce = getOvmContractNonce(_fromAddress); + uint _nonce = stateManager.getOvmContractNonce(_fromAddress); // Initialize our context initializeContext(_timestamp, _queueOrigin, _fromAddress, _l1MsgSenderAddress); @@ -188,7 +188,7 @@ contract ExecutionManager is FullStateManager { methodId = ovmCallMethodId; callSize = _callBytes.length + 32 + 4; // Creates will get incremented, but calls need to be as well! - incrementOvmContractNonce(_fromAddress); + stateManager.incrementOvmContractNonce(_fromAddress); } assembly { @@ -479,7 +479,7 @@ contract ExecutionManager is FullStateManager { // First we need to generate the CREATE address address creator = executionContext.ovmActiveContract; - uint creatorNonce = getOvmContractNonce(creator); + 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)) { @@ -491,7 +491,7 @@ contract ExecutionManager is FullStateManager { } } // We also need to increment the contract nonce - incrementOvmContractNonce(creator); + stateManager.incrementOvmContractNonce(creator); // Shifting so that it is left-padded, big-endian ('00'x12 + 20 bytes of address) bytes32 newOvmContractAddressBytes32 = bytes32(bytes20(_newOvmContractAddress)) >> 96; @@ -573,24 +573,18 @@ contract ExecutionManager is FullStateManager { * @return True if this succeeded, false otherwise. */ function createNewContract(address _newOvmContractAddress, bytes memory _ovmInitcode) internal returns (bool){ - // Safety check the initcode -- unless the overrideSafetyChecker flag is set to true - if (!overrideSafetyChecker && !safetyChecker.isBytecodeSafe(_ovmInitcode)) { - // Contract init code is not safe. - return false; - } // 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 - bytes memory codeContractBytecode = getCodeContractBytecode(codeContractAddress); - // Safety check the runtime bytecode -- unless the overrideSafetyChecker flag is set to true - if (!overrideSafetyChecker && !safetyChecker.isBytecodeSafe(codeContractBytecode)) { - // Contract runtime bytecode is not safe. + address codeContractAddress = stateManager.deployContract(_newOvmContractAddress, _ovmInitcode); + // Return false if the contract failed to deploy + if (codeContractAddress == ZERO_ADDRESS) { + restoreContractContext(oldMsgSender, oldActiveContract); return false; } // Associate the code contract with our ovm contract - associateCodeContract(_newOvmContractAddress, codeContractAddress); + stateManager.associateCodeContract(_newOvmContractAddress, codeContractAddress); + bytes memory codeContractBytecode = stateManager.getCodeContractBytecode(codeContractAddress); // Get the code contract address to be emitted by a CreatedContract event bytes32 codeContractHash = keccak256(codeContractBytecode); // Revert to the previous the context @@ -600,24 +594,6 @@ contract ExecutionManager is FullStateManager { return true; } - /** - * @notice Deploys a code contract, and then registers it to the state - * @param _ovmContractInitcode The bytecode of the contract to be deployed - * @return the codeContractAddress. - */ - function deployCodeContract(bytes memory _ovmContractInitcode) internal returns(address codeContractAddress) { - // Deploy a new contract with this _ovmContractInitCode - assembly { - // Set our codeContractAddress to the address returned by our CREATE operation - codeContractAddress := create(0, add(_ovmContractInitcode, 0x20), mload(_ovmContractInitcode)) - // Make sure that the CREATE was successful (actually deployed something) - if iszero(extcodesize(codeContractAddress)) { - revert(0, 0) - } - } - return codeContractAddress; - } - /************************ * Contract CALL Opcodes * ************************/ @@ -653,7 +629,7 @@ contract ExecutionManager is FullStateManager { // switch the context to the _targetOvmContractAddress (address oldMsgSender, address oldActiveContract) = switchActiveContract(_targetOvmContractAddress); - address codeAddress = getCodeContractAddress(_targetOvmContractAddress); + address codeAddress = stateManager.getCodeContractAddress(_targetOvmContractAddress); bytes memory returnData; uint returnSize; @@ -723,7 +699,7 @@ contract ExecutionManager is FullStateManager { // switch the context to the _targetOvmContractAddress (address oldMsgSender, address oldActiveContract) = switchActiveContract(_targetOvmContractAddress); - address codeAddress = getCodeContractAddress(_targetOvmContractAddress); + address codeAddress = stateManager.getCodeContractAddress(_targetOvmContractAddress); bytes memory returnData; uint returnSize; @@ -788,7 +764,7 @@ contract ExecutionManager is FullStateManager { address _targetOvmContractAddress = address(bytes20(_targetOvmContractAddressBytes)); // NOTE: WE DO NOT SWITCH CONTEXTS HERE. - address codeAddress = getCodeContractAddress(_targetOvmContractAddress); + address codeAddress = stateManager.getCodeContractAddress(_targetOvmContractAddress); // make the call assembly { @@ -833,7 +809,7 @@ contract ExecutionManager is FullStateManager { _storageSlot := calldataload(4) } - bytes32 slotValue = getStorage(executionContext.ovmActiveContract, _storageSlot); + bytes32 slotValue = stateManager.getStorage(executionContext.ovmActiveContract, _storageSlot); assembly { let ret := mload(0x40) @@ -864,7 +840,7 @@ contract ExecutionManager is FullStateManager { _storageValue := calldataload(0x24) } - setStorage(executionContext.ovmActiveContract, _storageSlot, _storageValue); + stateManager.setStorage(executionContext.ovmActiveContract, _storageSlot, _storageValue); // Emit SetStorage event! emit SetStorage(executionContext.ovmActiveContract, _storageSlot, _storageValue); } @@ -890,7 +866,7 @@ contract ExecutionManager is FullStateManager { } address _targetOvmContractAddress = address(bytes20(_targetAddressBytes)); - address codeContractAddress = getCodeContractAddress(_targetOvmContractAddress); + address codeContractAddress = stateManager.getCodeContractAddress(_targetOvmContractAddress); assembly { let sizeBytes := mload(0x40) @@ -916,9 +892,9 @@ contract ExecutionManager is FullStateManager { } address _targetOvmContractAddress = address(bytes20(_targetAddressBytes)); - address codeContractAddress = getCodeContractAddress(_targetOvmContractAddress); + address codeContractAddress = stateManager.getCodeContractAddress(_targetOvmContractAddress); - bytes32 hash = getCodeContractHash(codeContractAddress); + bytes32 hash = stateManager.getCodeContractHash(codeContractAddress); assembly { let hashBytes := mload(0x40) @@ -952,7 +928,7 @@ contract ExecutionManager is FullStateManager { } address _targetOvmContractAddress = address(bytes20(_targetAddressBytes)); - address codeContractAddress = getCodeContractAddress(_targetOvmContractAddress); + address codeContractAddress = stateManager.getCodeContractAddress(_targetOvmContractAddress); assembly { let codeContractBytecode := mload(0x40) @@ -1024,4 +1000,8 @@ contract ExecutionManager is FullStateManager { require(executionContext.ovmMsgSender == ZERO_ADDRESS, "L1MessageSender only accessible in entrypoint contract!"); return executionContext.l1MessageSender; } + + function getStateManagerAddress() public view returns (address){ + return address(stateManager); + } } diff --git a/packages/rollup-contracts/contracts/FullStateManager.sol b/packages/rollup-contracts/contracts/FullStateManager.sol index 0c488c2d7fb0..d6618e500f9b 100644 --- a/packages/rollup-contracts/contracts/FullStateManager.sol +++ b/packages/rollup-contracts/contracts/FullStateManager.sol @@ -3,6 +3,7 @@ pragma experimental ABIEncoderV2; /* Internal Imports */ import {StateManager} from "./StateManager.sol"; +import {SafetyChecker} from "./SafetyChecker.sol"; /** * @title FullStateManager @@ -10,10 +11,30 @@ import {StateManager} from "./StateManager.sol"; * of all chain storage. */ contract FullStateManager is StateManager { + // Add Safety Checker contract + SafetyChecker safetyChecker; + // for testing: if true, then do not perform safety checking on init code or deployed bytecode + bool overrideSafetyChecker; + + address ZERO_ADDRESS = 0x0000000000000000000000000000000000000000; + mapping(address=>mapping(bytes32=>bytes32)) ovmContractStorage; mapping(address=>uint) ovmContractNonces; mapping(address=>address) ovmCodeContracts; + /** + * @notice Construct a new FullStateManager with a specified safety checker. + * @param _opcodeWhitelistMask A bit mask representing which opcodes are whitelisted or not for our safety checker + * @param _overrideSafetyChecker Set to true to disable safety checking (WARNING: Only do this in test environments) + */ + constructor(uint256 _opcodeWhitelistMask, bool _overrideSafetyChecker) public { + // Set override safety checker flag + overrideSafetyChecker = _overrideSafetyChecker; + // Set the safety checker address -- NOTE: `msg.sender` is used as EM address because we assume + // the FullStateManager is deployed by the ExecutionManager + safetyChecker = new SafetyChecker(_opcodeWhitelistMask, msg.sender); + } + /********** * Storage * @@ -25,7 +46,7 @@ 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) internal view returns(bytes32) { + function getStorage(address _ovmContractAddress, bytes32 _slot) external view returns(bytes32) { return ovmContractStorage[_ovmContractAddress][_slot]; } @@ -35,7 +56,7 @@ 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) internal { + function setStorage(address _ovmContractAddress, bytes32 _slot, bytes32 _value) external { ovmContractStorage[_ovmContractAddress][_slot] = _value; } @@ -50,7 +71,7 @@ contract FullStateManager is StateManager { * @param _ovmContractAddress The contract we're getting the nonce of. * @return The contract nonce used for contract creation. */ - function getOvmContractNonce(address _ovmContractAddress) public view returns(uint) { + function getOvmContractNonce(address _ovmContractAddress) external view returns(uint) { return ovmContractNonces[_ovmContractAddress]; } @@ -59,7 +80,7 @@ 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) internal { + function setOvmContractNonce(address _ovmContractAddress, uint _value) external { ovmContractNonces[_ovmContractAddress] = _value; } @@ -67,7 +88,7 @@ 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) internal { + function incrementOvmContractNonce(address _ovmContractAddress) external { ovmContractNonces[_ovmContractAddress] += 1; } @@ -83,7 +104,7 @@ 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) internal { + function associateCodeContract(address _ovmContractAddress, address _codeContractAddress) external { ovmCodeContracts[_ovmContractAddress] = _codeContractAddress; } @@ -92,7 +113,7 @@ contract FullStateManager is StateManager { * @param _ovmContractAddress The address of the OVM contract. * @return The associated code contract address. */ - function getCodeContractAddress(address _ovmContractAddress) public view returns(address) { + function getCodeContractAddress(address _ovmContractAddress) external view returns(address) { return ovmCodeContracts[_ovmContractAddress]; } @@ -102,7 +123,7 @@ contract FullStateManager is StateManager { * @param _codeContractAddress The address of the code contract. * @return The bytecode at this address. */ - function getCodeContractBytecode(address _codeContractAddress) internal 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) @@ -123,10 +144,45 @@ 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) internal view returns (bytes32 _codeContractHash) { + function getCodeContractHash(address _codeContractAddress) external 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); return _codeContractHash; } + + /** + * @notice Deploys a code contract, and then registers it to the state + * @param _newOvmContractAddress The contract address to deploy the new contract to + * @param _ovmContractInitcode The bytecode of the contract to be deployed + * @return the codeContractAddress. + */ + function deployContract( + address _newOvmContractAddress, + bytes memory _ovmContractInitcode + ) public returns(address codeContractAddress) { + // Safety check the initcode, unless the overrideSafetyChecker flag is set to true + if (!overrideSafetyChecker && !safetyChecker.isBytecodeSafe(_ovmContractInitcode)) { + // Contract initcode is not pure. + return ZERO_ADDRESS; + } + + // Deploy a new contract with this _ovmContractInitCode + assembly { + // Set our codeContractAddress to the address returned by our CREATE operation + codeContractAddress := create(0, add(_ovmContractInitcode, 0x20), mload(_ovmContractInitcode)) + // Make sure that the CREATE was successful (actually deployed something) + if iszero(extcodesize(codeContractAddress)) { + revert(0, 0) + } + } + + // Safety check the runtime bytecode, unless the overrideSafetyChecker flag is set to true + bytes memory codeContractBytecode = getCodeContractBytecode(codeContractAddress); + if (!overrideSafetyChecker && !safetyChecker.isBytecodeSafe(codeContractBytecode)) { + // Contract runtime bytecode is not pure. + return ZERO_ADDRESS; + } + return codeContractAddress; + } } diff --git a/packages/rollup-contracts/contracts/StateManager.sol b/packages/rollup-contracts/contracts/StateManager.sol index 2369c3731154..1ccc220777a4 100644 --- a/packages/rollup-contracts/contracts/StateManager.sol +++ b/packages/rollup-contracts/contracts/StateManager.sol @@ -7,17 +7,18 @@ pragma solidity ^0.5.0; */ contract StateManager { // Storage - function getStorage(address _ovmContractAddress, bytes32 _slot) internal view returns(bytes32); - function setStorage(address _ovmContractAddress, bytes32 _slot, bytes32 _value) internal; + function getStorage(address _ovmContractAddress, bytes32 _slot) external view returns(bytes32); + function setStorage(address _ovmContractAddress, bytes32 _slot, bytes32 _value) external; // Nonces (this is used during contract creation to determine the contract address) - function getOvmContractNonce(address _ovmContractAddress) public view returns(uint); - function setOvmContractNonce(address _ovmContractAddress, uint _value) internal; - function incrementOvmContractNonce(address _ovmContractAddress) internal; + function getOvmContractNonce(address _ovmContractAddress) external view returns(uint); + function setOvmContractNonce(address _ovmContractAddress, uint _value) external; + function incrementOvmContractNonce(address _ovmContractAddress) external; // Contract code storage / contract address retrieval - function associateCodeContract(address _ovmContractAddress, address _codeContractAddress) internal; - function getCodeContractAddress(address _ovmContractAddress) public view returns(address); - function getCodeContractBytecode(address _codeContractAddress) internal view returns (bytes memory codeContractBytecode); - function getCodeContractHash(address _codeContractAddress) internal view returns (bytes32 _codeContractHash); + function deployContract(address _newOvmContractAddress, bytes memory _ovmContractInitcode) public returns(address codeContractAddress); + function associateCodeContract(address _ovmContractAddress, address _codeContractAddress) external; + function getCodeContractAddress(address _ovmContractAddress) external view returns(address); + 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/src/contracts.ts b/packages/rollup-contracts/src/contracts.ts index 7defcb78203d..e9096d308cb1 100644 --- a/packages/rollup-contracts/src/contracts.ts +++ b/packages/rollup-contracts/src/contracts.ts @@ -4,6 +4,7 @@ import { ethers } from 'ethers' /* Contract Imports */ 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' @@ -15,6 +16,7 @@ import * as SafetyChecker from '../build/SafetyChecker.json' // Contract Exports export const ExecutionManagerContractDefinition = ExecutionManager export const L2ExecutionManagerContractDefinition = L2ExecutionManager +export const FullStateManagerContractDefinition = FullStateManager export const ContractAddressGeneratorContractDefinition = ContractAddressGenerator export const L2ToL1MessageReceiverContractDefinition = L2ToL1MessageReceiver export const L2ToL1MessagePasserContractDefinition = L2ToL1MessagePasser diff --git a/packages/rollup-core/src/app/constants.ts b/packages/rollup-core/src/app/constants.ts index b566a027f308..fc173a6d64a3 100644 --- a/packages/rollup-core/src/app/constants.ts +++ b/packages/rollup-core/src/app/constants.ts @@ -5,7 +5,7 @@ export const L1ToL2TransactionEventName = 'L1ToL2Transaction' export const CREATOR_CONTRACT_ADDRESS = ZERO_ADDRESS export const GAS_LIMIT = 1_000_000_000 -export const DEFAULT_ETHNODE_GAS_LIMIT = 9_000_000 +export const DEFAULT_ETHNODE_GAS_LIMIT = 10_000_000 export const CHAIN_ID = 108 diff --git a/packages/rollup-core/src/app/util/l2-node.ts b/packages/rollup-core/src/app/util/l2-node.ts index 608a0ffd6d89..7efff4ac303a 100644 --- a/packages/rollup-core/src/app/util/l2-node.ts +++ b/packages/rollup-core/src/app/util/l2-node.ts @@ -7,6 +7,7 @@ import { } from '@eth-optimism/core-utils' import { L2ExecutionManagerContractDefinition, + FullStateManagerContractDefinition, L2ToL1MessagePasserContractDefinition, } from '@eth-optimism/rollup-contracts' @@ -53,12 +54,19 @@ export async function initializeL2Node( wallet, doNotDeploy ) + const stateManager: Contract = new Contract( + await executionManager.getStateManagerAddress(), + FullStateManagerContractDefinition.abi, + wallet + ) + const l2ToL1MessagePasser: Contract = getL2ToL1MessagePasserContract(wallet) return { wallet, provider, executionManager, + stateManager, l2ToL1MessagePasser, } } diff --git a/packages/rollup-core/src/types/node-context.ts b/packages/rollup-core/src/types/node-context.ts index 15982d04f0cd..1527ca895b8e 100644 --- a/packages/rollup-core/src/types/node-context.ts +++ b/packages/rollup-core/src/types/node-context.ts @@ -12,5 +12,6 @@ export interface L2NodeContext { provider: JsonRpcProvider wallet: Wallet executionManager: Contract + stateManager: Contract l2ToL1MessagePasser: Contract } diff --git a/packages/rollup-full-node/src/app/web3-rpc-handler.ts b/packages/rollup-full-node/src/app/web3-rpc-handler.ts index 4cb5fcaab15d..dc63fb857d53 100644 --- a/packages/rollup-full-node/src/app/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/web3-rpc-handler.ts @@ -488,7 +488,7 @@ export class DefaultWeb3Handler `Getting code for address: [${address}], defaultBlock: [${defaultBlock}]` ) // First get the code contract address at the requested OVM address - const codeContractAddress = await this.context.executionManager.getCodeContractAddress( + const codeContractAddress = await this.context.stateManager.getCodeContractAddress( address ) const response = await this.context.provider.send(Web3RpcMethods.getCode, [ @@ -515,7 +515,7 @@ export class DefaultWeb3Handler const codeContractAddresses = [] for (const address of filter['address']) { codeContractAddresses.push( - await this.context.executionManager.getCodeContractAddress(address) + await this.context.stateManager.getCodeContractAddress(address) ) } filter['address'] = [ @@ -611,7 +611,7 @@ export class DefaultWeb3Handler log.debug( `Requesting transaction count. Address [${address}], block: [${defaultBlock}].` ) - const ovmContractNonce = await this.context.executionManager.getOvmContractNonce( + const ovmContractNonce = await this.context.stateManager.getOvmContractNonce( address ) const response = add0x(ovmContractNonce.toNumber().toString(16)) @@ -1036,7 +1036,7 @@ export class DefaultWeb3Handler const ovmFrom = ovmTx.from === undefined ? ZERO_ADDRESS : ovmTx.from // Check the nonce const expectedNonce = ( - await this.context.executionManager.getOvmContractNonce(ovmFrom) + await this.context.stateManager.getOvmContractNonce(ovmFrom) ).toNumber() if (expectedNonce !== ovmTx.nonce) { throw new InvalidParametersError(