diff --git a/packages/contracts/.solcover.js b/packages/contracts/.solcover.js index 29eb49216cce9..985eef88f4596 100644 --- a/packages/contracts/.solcover.js +++ b/packages/contracts/.solcover.js @@ -1,23 +1,23 @@ /** * SolCover configuration management: - * + * * SolCover unfortunately doesn't provide us with the ability to exclusively * generate coverage reports for a given file. Although we can use the * `--testfiles`` parameter to limit tests to a particular glob, SolCover will * still try to generate coverage reports for anything not covered within the * `skipFiles` option exported below. `skipFiles` additionally does not parse * globs, creating a mismatch between it and the `--testfiles` option. - * + * * To address the above issues, we take the following steps: * 1. Parse the `--testfiles` option from our command-line arguments. * 2. Use the `--testfiles` option to find the list of contracts to be tested. * 3. Find *all* contracts and exclude the results of (2). * 4. Add the result of (3) to `skipFiles`. - * + * * NOTE: The above will *only* work if contract test files follow the * `.spec.ts` convention. Our function will fail to find the * correct contracts otherwise. - */ + */ const path = require('path') const glob = require('glob') diff --git a/packages/contracts/buidler.config.ts b/packages/contracts/buidler.config.ts index 1a76852435313..bc1e5be5719d1 100644 --- a/packages/contracts/buidler.config.ts +++ b/packages/contracts/buidler.config.ts @@ -16,6 +16,7 @@ const config: BuidlerConfig = { buidlerevm: { accounts: DEFAULT_ACCOUNTS_BUIDLER, blockGasLimit: GAS_LIMIT * 2, + allowUnlimitedContractSize: true, // TEMPORARY: Will be fixed by AddressResolver PR. }, coverage: { url: 'http://localhost:8555', diff --git a/packages/contracts/contracts/optimistic-ethereum/chain/StateCommitmentChain.sol b/packages/contracts/contracts/optimistic-ethereum/chain/StateCommitmentChain.sol index ce40091325247..bb0dfa61d7d08 100644 --- a/packages/contracts/contracts/optimistic-ethereum/chain/StateCommitmentChain.sol +++ b/packages/contracts/contracts/optimistic-ethereum/chain/StateCommitmentChain.sol @@ -29,18 +29,20 @@ contract StateCommitmentChain { constructor( address _rollupMerkleUtilsAddress, - address _canonicalTransactionChain, - address _fraudVerifier + address _canonicalTransactionChain ) public { merkleUtils = RollupMerkleUtils(_rollupMerkleUtilsAddress); canonicalTransactionChain = CanonicalTransactionChain(_canonicalTransactionChain); - fraudVerifier = _fraudVerifier; } /* * Public Functions */ + function setFraudVerifier(address _fraudVerifier) public { + fraudVerifier = _fraudVerifier; + } + function getBatchesLength() public view returns (uint) { return batches.length; } diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol index 7fc3589976a89..2304f1daa79e5 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol @@ -575,6 +575,9 @@ contract ExecutionManager { // Next we need to actually create the contract in our state at that address createNewContract(_newOvmContractAddress, _ovmInitcode); + // Insert the newly created contract into our state manager. + stateManager.associateCreatedContract(_newOvmContractAddress); + // We also need to increment the contract nonce stateManager.incrementOvmContractNonce(creator); @@ -665,6 +668,7 @@ contract ExecutionManager { // 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); @@ -899,7 +903,7 @@ contract ExecutionManager { * [storageSlot (bytes32)] * returndata: [storageValue (bytes32)] */ - function ovmSLOAD() public view { + function ovmSLOAD() public { bytes32 _storageSlot; assembly { // skip methodID (4 bytes) diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/FraudVerifier.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/FraudVerifier.sol index 682b3869e3d8b..3424a022f26d3 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/FraudVerifier.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/FraudVerifier.sol @@ -1,29 +1,289 @@ pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; -import {StateTransitioner} from "./StateTransitioner.sol"; +/* Internal Imports */ +import { DataTypes } from "../utils/DataTypes.sol"; +import { RLPWriter } from "../utils/RLPWriter.sol"; +import { StateCommitmentChain } from "../chain/StateCommitmentChain.sol"; +import { CanonicalTransactionChain } from "../chain/CanonicalTransactionChain.sol"; +import { ExecutionManager } from "./ExecutionManager.sol"; +import { StateTransitioner } from "./StateTransitioner.sol"; +import { IStateTransitioner } from "./interfaces/IStateTransitioner.sol"; +import { StubStateTransitioner } from "./test-helpers/StubStateTransitioner.sol"; +import { TransactionParser } from "./TransactionParser.sol"; /** * @title FraudVerifier - * @notice The contract which is able to delete invalid state roots. + * @notice Manages fraud proof verification and modifies the state commitment + * chain in the case that a transaction is shown to be invalid. */ contract FraudVerifier { - mapping(uint=>StateTransitioner) stateTransitioners; - - 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). - // Note that the invalid state root that we are verifying is at _preStateTransitionIndex+1. - // Add it to the stateTransitioners mapping! - // -- stateTransitioners[_preStateTransitionIndex] = newStateTransitioner; - return true; + /* + * Contract Variables + */ + + ExecutionManager executionManager; + StateCommitmentChain stateCommitmentChain; + CanonicalTransactionChain canonicalTransactionChain; + mapping (uint256 => IStateTransitioner) public stateTransitioners; + + bool isTest; + + /* + * Constructor + */ + + /** + * @param _executionManagerAddress Address of the ExecutionManager to use + * during the execution of transactions suspected to be fraudulent. + * @param _stateCommitmentChainAddress Address of the StateCommitmentChain + * to read state roots from and write to in the case that a fraud proof is + * successfully verified. + */ + constructor( + address _executionManagerAddress, + address _stateCommitmentChainAddress, + address _canonicalTransactionChainAddress, + bool _isTest + ) public { + executionManager = ExecutionManager(_executionManagerAddress); + stateCommitmentChain = StateCommitmentChain(_stateCommitmentChainAddress); + canonicalTransactionChain = CanonicalTransactionChain(_canonicalTransactionChainAddress); + + isTest = _isTest; } - function verifyFraud(uint _transitionIndex) public returns(bool) { - // TODO: - // Simply verify that the state transitioner has completed, and that the state root - // at _preStateTransitionIndex+1 is not equal to the state root which was committed for that index. - return true; + /* + * Public Functions + */ + + /** + * @notice Initializes the fraud proof verification process. Creates a new + * StateTransitioner instance if none already exists for the given state + * transition index. + * @param _preStateTransitionIndex Index of the state transition suspected + * to be fraudulent. + * @param _preStateRoot Root of the state trie before the state transition + * was executed. + * @param _preStateRootProof Inclusion proof for the given pre-state root. + * Since state roots are submitted in batches and merklized, we cannot + * simply read the state roots from the StateCommitmentChain. + * @param _transactionData Data for the transaction suspected to be + * fraudulent. + * @param _transactionProof Inclusion proof for the given transaction data. + * Since transactions are submitted in batches and merklized, we cannot + * simply read the state roots from the CanonicalTransactionChain. + */ + function initializeFraudVerification( + uint256 _preStateTransitionIndex, + bytes32 _preStateRoot, + DataTypes.StateElementInclusionProof memory _preStateRootProof, + DataTypes.OVMTransactionData memory _transactionData, + DataTypes.TxElementInclusionProof memory _transactionProof + ) public { + // For user convenience; no point in carrying out extra work here if a + // StateTransitioner instance already exists for the given state + // transition index. Return early to save the user some gas. + if (hasStateTransitioner(_preStateTransitionIndex, _preStateRoot)) { + return; + } + + require( + verifyStateRoot( + _preStateRoot, + _preStateTransitionIndex, + _preStateRootProof + ), + "Provided pre-state root inclusion proof is invalid." + ); + + require( + verifyTransaction( + _transactionData, + _preStateTransitionIndex, + _transactionProof + ), + "Provided transaction data is invalid." + ); + + // Note that a StateTransitioner may be overwritten when a state root + // *before* its pre-state root is shown to be fraudulent. This would + // invalidate the old StateTransitioner, creating the need to + // initialize a new one with the correct pre-state root. A case like + // this is handled by the hasStateTransitioner check above, which would + // fail when the existing StateTransitioner's pre-state root does not + // match the provided one. + if (isTest) { + stateTransitioners[_preStateTransitionIndex] = new StubStateTransitioner( + _preStateTransitionIndex, + _preStateRoot, + TransactionParser.getTransactionHash(_transactionData), + address(executionManager) + ); + } else { + stateTransitioners[_preStateTransitionIndex] = new StateTransitioner( + _preStateTransitionIndex, + _preStateRoot, + TransactionParser.getTransactionHash(_transactionData), + address(executionManager) + ); + } + } + + /** + * @notice Finalizes the fraud verification process. Checks that the state + * transitioner has executed the transition to completion and that the + * resulting state root differs from the one previous published. + * @param _preStateTransitionIndex Index of the state transition suspected + * to be fraudulent. + * @param _postStateRoot Published root of the state trie after the state + * transition was executed. If the transition was indeed fraudulent, then + * this root will differ from the one computed by the StateTransitioner. + * @param _postStateRootProof Inclusion proof for the given pre-state root. + * Since state roots are submitted in batches and merklized, we cannot + * simply read the state roots from the StateCommitmentChain. + */ + function finalizeFraudVerification( + uint256 _preStateTransitionIndex, + bytes32 _preStateRoot, + DataTypes.StateElementInclusionProof memory _preStateRootProof, + bytes32 _postStateRoot, + DataTypes.StateElementInclusionProof memory _postStateRootProof + ) public { + IStateTransitioner stateTransitioner = stateTransitioners[_preStateTransitionIndex]; + + // Fraud cannot be verified until the StateTransitioner has fully + // executed the given state transition. Otherwise, the + // StateTransitioner will always report an invalid root. + require( + stateTransitioner.isComplete(), + "State transition process has not been completed." + ); + + // We want the StateTransitioner to be reusable in the case that yet + // another invalid state root is published for the post-state. This + // saves users the gas cost of executing the entire state transition + // more than once. However, if a state root *before* the pre-state root + // was found to be fraudulent, then the StateTransitioner is no longer + // valid (since its execution is based on an outdated pre-state root). + // We therefore need to check that the StateTransitioner was based on + // the given pre-state root and that the pre-state root is still part + // of the StateCommitmentChain. + require( + _preStateRoot == stateTransitioner.preStateRoot(), + "Provided pre-state root does not match StateTransitioner." + ); + require( + verifyStateRoot( + _preStateRoot, + _preStateTransitionIndex, + _preStateRootProof + ), + "Provided pre-state root inclusion proof is invalid." + ); + + require( + verifyStateRoot( + _postStateRoot, + _preStateTransitionIndex + 1, + _postStateRootProof + ), + "Provided post-state root inclusion proof is invalid." + ); + + // State transitions are fraudlent when the state root published to the + // StateCommitmentChain differs from the one computed by the + // StateTransitioner. + require( + _postStateRoot != stateTransitioner.stateRoot(), + "State transition has not been proven fraudulent." + ); + + // If we're here, then the state transition was found to be fraudulent. + // We therefore need to remove all state roots from the + // StateCommitmentChain after (and including) the fraudulent root. + // However, since state roots are submitted in batches, we'll actually + // need to remove all *batches* after (and including) the one in which + // the fraudulent root was published. + stateCommitmentChain.deleteAfterInclusive( + _postStateRootProof.batchIndex, + _postStateRootProof.batchHeader + ); + } + + /** + * @notice Utility; checks whether a StateTransitioner exists for a given + * state transition index. Can be used by clients to preemtively avoid + * attempts to initialize the same StateTransitioner multiple times. + * @param _stateTransitionIndex Index of the state transition suspected to + * be fraudulent. + * @param _preStateRoot Pre-state root used to initialize the transitioner. + * @return `true` if a StateTransitioner exists, `false` otherwise. + */ + function hasStateTransitioner( + uint256 _stateTransitionIndex, + bytes32 _preStateRoot + ) public view returns (bool) { + IStateTransitioner stateTransitioner = stateTransitioners[_stateTransitionIndex]; + + return ( + (address(stateTransitioner) != address(0x0)) && + (stateTransitioner.preStateRoot() == _preStateRoot) + ); + } + + + /* + * Internal Functions + */ + + /** + * @notice Utility; verifies that a given state root is valid. Mostly just + * a convenience wrapper around the current verification method within + * StateCommitmentChain. + * @param _stateRoot State trie root to prove is included in the commitment + * chain. + * @param _stateRootIndex Global index of the state root within the list of + * all state roots. + * @param _stateRootProof Inclusion proof for the given state root and + * index pair. + * @return `true` if the root exists within the StateCommitmentChain, + * `false` otherwise. + */ + function verifyStateRoot( + bytes32 _stateRoot, + uint256 _stateRootIndex, + DataTypes.StateElementInclusionProof memory _stateRootProof + ) internal view returns (bool) { + return stateCommitmentChain.verifyElement( + abi.encodePacked(_stateRoot), + _stateRootIndex, + _stateRootProof + ); + } + + /** + * @notice Utility; verifies that a given transaction is valid. Mostly just + * a convenience wrapper around the current verification method within + * CanonicalTransactionChain. + * @param _transaction OVM transaction data to verify. + * @param _transactionIndex Global index of the transaction within the list + * of all transactions + * @param _transactionProof Inclusion proof for the given transaction and + * index pair. + * @return `true` if the transaction exists within the + * CanonicalTransactionChain, `false` otherwise. + */ + function verifyTransaction( + DataTypes.OVMTransactionData memory _transaction, + uint256 _transactionIndex, + DataTypes.TxElementInclusionProof memory _transactionProof + ) internal view returns (bool) { + return canonicalTransactionChain.verifyElement( + TransactionParser.encodeTransactionData(_transaction), + _transactionIndex, + _transactionProof + ); } } diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/FullStateManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/FullStateManager.sol index 3b562549dd70b..f63cb4515006a 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/FullStateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/FullStateManager.sol @@ -41,6 +41,13 @@ contract FullStateManager is StateManager { function getStorage( address _ovmContractAddress, bytes32 _slot + ) public returns (bytes32) { + return ovmContractStorage[_ovmContractAddress][_slot]; + } + + function getStorageView( + address _ovmContractAddress, + bytes32 _slot ) public view returns (bytes32) { return ovmContractStorage[_ovmContractAddress][_slot]; } @@ -71,6 +78,12 @@ contract FullStateManager is StateManager { */ function getOvmContractNonce( address _ovmContractAddress + ) public returns (uint) { + return ovmContractNonces[_ovmContractAddress]; + } + + function getOvmContractNonceView( + address _ovmContractAddress ) public view returns (uint) { return ovmContractNonces[_ovmContractAddress]; } @@ -116,6 +129,16 @@ contract FullStateManager is StateManager { codeContractAddressToOvmAddress[_codeContractAddress] = _ovmContractAddress; } + /** + * @notice Marks a contract as newly created. Unused within this implementation. + * @param _ovmContractAddress Address to mark as newly created. + */ + function associateCreatedContract( + address _ovmContractAddress + ) public { + return; + } + /** * @notice Lookup the code contract for some OVM contract, allowing CALL opcodes to be performed. * @param _ovmContractAddress The address of the OVM contract. diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol index 6164c28dbc72a..9c4701ce50808 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/PartialStateManager.sol @@ -27,8 +27,10 @@ contract PartialStateManager { mapping(address=>bool) public isVerifiedContract; mapping(uint=>bytes32) updatedStorageSlotContract; mapping(uint=>bytes32) updatedStorageSlotKey; + mapping(address=>mapping(bytes32=>bool)) storageSlotTouched; uint public updatedStorageSlotCounter; mapping(uint=>address) updatedContracts; + mapping(address=>bool) contractTouched; uint public updatedContractsCounter; modifier onlyStateTransitioner { @@ -97,18 +99,31 @@ contract PartialStateManager { * Post-Execution * *****************/ - function popUpdatedStorageSlot() external onlyStateTransitioner returns ( + function peekUpdatedStorageSlot() public view returns ( address storageSlotContract, bytes32 storageSlotKey, bytes32 storageSlotValue ) { - require(updatedStorageSlotCounter > 0, "No more elements to pop!"); + require(updatedStorageSlotCounter > 0, "No more elements to update."); - // Get the next storage we need to update using the updatedStorageSlotCounter - storageSlotContract = address(bytes20(updatedStorageSlotContract[updatedStorageSlotCounter])); - storageSlotKey = updatedStorageSlotKey[updatedStorageSlotCounter]; + storageSlotContract = address(bytes20(updatedStorageSlotContract[updatedStorageSlotCounter - 1])); + storageSlotKey = updatedStorageSlotKey[updatedStorageSlotCounter - 1]; storageSlotValue = ovmContractStorage[storageSlotContract][storageSlotKey]; + return (storageSlotContract, storageSlotKey, storageSlotValue); + } + + function popUpdatedStorageSlot() public onlyStateTransitioner returns ( + address storageSlotContract, + bytes32 storageSlotKey, + bytes32 storageSlotValue + ) { + ( + storageSlotContract, + storageSlotKey, + storageSlotValue + ) = peekUpdatedStorageSlot(); + // Decrement the updatedStorageSlotCounter (we go reverse through the updated storage). // Note that when this hits zero we'll have updated all storage slots that were changed during // transaction execution. @@ -117,19 +132,38 @@ contract PartialStateManager { return (storageSlotContract, storageSlotKey, storageSlotValue); } - function popUpdatedContract() external onlyStateTransitioner returns ( + function peekUpdatedContract() public view returns ( address ovmContractAddress, - uint contractNonce + uint contractNonce, + bytes32 codeHash ) { - require(updatedContractsCounter > 0, "No more elements to pop!"); + require(updatedContractsCounter > 0, "No more elements to update."); - // Get the next storage we need to update using the updatedStorageSlotCounter - ovmContractAddress = address(bytes20(updatedStorageSlotContract[updatedStorageSlotCounter])); + ovmContractAddress = address(bytes20(updatedContracts[updatedContractsCounter - 1])); contractNonce = ovmContractNonces[ovmContractAddress]; + address codeContractAddress = getCodeContractAddressView(ovmContractAddress); + assembly { + codeHash := extcodehash(codeContractAddress) + } + + return (ovmContractAddress, contractNonce, codeHash); + } + + function popUpdatedContract() public onlyStateTransitioner returns ( + address ovmContractAddress, + uint contractNonce, + bytes32 codeHash + ) { + ( + ovmContractAddress, + contractNonce, + codeHash + ) = peekUpdatedContract(); + updatedContractsCounter -= 1; - return (ovmContractAddress, contractNonce); + return (ovmContractAddress, contractNonce, codeHash); } /********** @@ -151,6 +185,13 @@ contract PartialStateManager { return ovmContractStorage[_ovmContractAddress][_slot]; } + function getStorageView( + address _ovmContractAddress, + bytes32 _slot + ) public view returns (bytes32) { + return ovmContractStorage[_ovmContractAddress][_slot]; + } + /** * @notice Set storage for OVM contract at some slot. * @param _ovmContractAddress The contract we're setting storage of. @@ -162,12 +203,12 @@ contract PartialStateManager { bytes32 _slot, bytes32 _value ) onlyExecutionManager public { - flagIfNotVerifiedStorage(_ovmContractAddress, _slot); - - // Add this storage slot to the list of updated storage - updatedStorageSlotContract[updatedStorageSlotCounter] = bytes32(bytes20(_ovmContractAddress)); - updatedStorageSlotKey[updatedStorageSlotCounter] = _slot; - updatedStorageSlotCounter += 1; + if (!storageSlotTouched[_ovmContractAddress][_slot]) { + updatedStorageSlotContract[updatedStorageSlotCounter] = bytes32(bytes20(_ovmContractAddress)); + updatedStorageSlotKey[updatedStorageSlotCounter] = _slot; + updatedStorageSlotCounter += 1; + storageSlotTouched[_ovmContractAddress][_slot] = true; + } // Set the new storage value ovmContractStorage[_ovmContractAddress][_slot] = _value; @@ -191,6 +232,12 @@ contract PartialStateManager { return ovmContractNonces[_ovmContractAddress]; } + function getOvmContractNonceView( + address _ovmContractAddress + ) public view returns (uint) { + return ovmContractNonces[_ovmContractAddress]; + } + /** * @notice Set the nonce for a particular OVM contract * @param _ovmContractAddress The contract we're setting the nonce of. @@ -200,11 +247,14 @@ contract PartialStateManager { address _ovmContractAddress, uint _value ) onlyExecutionManager public { - flagIfNotVerifiedContract(_ovmContractAddress); + // TODO: Figure out if we actually need to verify contracts here. + //flagIfNotVerifiedContract(_ovmContractAddress); - // Add this contract to the list of updated contracts - updatedContracts[updatedContractsCounter] = _ovmContractAddress; - updatedContractsCounter += 1; + if (!contractTouched[_ovmContractAddress]) { + updatedContracts[updatedContractsCounter] = _ovmContractAddress; + updatedContractsCounter += 1; + contractTouched[_ovmContractAddress] = true; + } // Return the nonce ovmContractNonces[_ovmContractAddress] = _value; @@ -219,9 +269,11 @@ contract PartialStateManager { ) onlyExecutionManager public { flagIfNotVerifiedContract(_ovmContractAddress); - // Add this contract to the list of updated contracts - updatedContracts[updatedContractsCounter] = _ovmContractAddress; - updatedContractsCounter += 1; + if (!contractTouched[_ovmContractAddress]) { + updatedContracts[updatedContractsCounter] = _ovmContractAddress; + updatedContractsCounter += 1; + contractTouched[_ovmContractAddress] = true; + } // Increment the nonce ovmContractNonces[_ovmContractAddress] += 1; @@ -246,11 +298,34 @@ contract PartialStateManager { ovmAddressToCodeContractAddress[_ovmContractAddress] = _codeContractAddress; } + /** + * @notice Marks an address as newly created via ovmCREATE. Sets its nonce to zero and automatically + * marks the contract as verified. + * @param _ovmContractAddress Address of the contract to mark as verified. + */ + function associateCreatedContract( + address _ovmContractAddress + ) onlyExecutionManager public { + isVerifiedContract[_ovmContractAddress] = true; + setOvmContractNonce(_ovmContractAddress, 0); + } + /** * @notice Lookup the code contract for some OVM contract, allowing CALL opcodes to be performed. * @param _ovmContractAddress The address of the OVM contract. * @return The associated code contract address. */ + function getCodeContractAddressView( + address _ovmContractAddress + ) public view returns (address) { + return ovmAddressToCodeContractAddress[_ovmContractAddress]; + } + + /** + * @notice Lookup the code contract for some OVM contract, allowing ovmCALL operations to be performed. + * @param _ovmContractAddress The address of the OVM contract. + * @return The associated code contract address. + */ function getCodeContractAddressFromOvmAddress( address _ovmContractAddress ) onlyExecutionManager public returns(address) { diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/StateManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/StateManager.sol index d7fca7a83f5b6..6da522a7bf91e 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/StateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/StateManager.sol @@ -9,16 +9,19 @@ pragma experimental ABIEncoderV2; */ contract StateManager { // Storage - function getStorage(address _ovmContractAddress, bytes32 _slot) external view returns(bytes32); + function getStorage(address _ovmContractAddress, bytes32 _slot) external returns(bytes32); + function getStorageView(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) external view returns(uint); + function getOvmContractNonce(address _ovmContractAddress) external returns(uint); + function getOvmContractNonceView(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) public; + function associateCreatedContract(address _ovmContractAddress) public; function getCodeContractAddressFromOvmAddress(address _ovmContractAddress) external view returns(address); function getOvmAddressFromCodeContractAddress(address _codeContractAddress) external view returns(address); function getCodeContractBytecode( diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol index cbabb51f4e585..d45ad00cdba23 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol @@ -1,142 +1,331 @@ pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; -import {FraudVerifier} from "./FraudVerifier.sol"; -import {PartialStateManager} from "./PartialStateManager.sol"; -import {ExecutionManager} from "./ExecutionManager.sol"; +import { FraudVerifier } from "./FraudVerifier.sol"; +import { PartialStateManager } from "./PartialStateManager.sol"; +import { ExecutionManager } from "./ExecutionManager.sol"; +import { IStateTransitioner } from "./interfaces/IStateTransitioner.sol"; +import { DataTypes } from "../utils/DataTypes.sol"; +import { EthMerkleTrie } from "../utils/EthMerkleTrie.sol"; +import { TransactionParser } from "./TransactionParser.sol"; /** * @title StateTransitioner - * @notice A contract which transitions a state from root one to another after a tx execution. + * @notice Manages the execution of a transaction suspected to be fraudulent. */ -contract StateTransitioner { +contract StateTransitioner is IStateTransitioner { + /* + * Data Structures + */ + enum TransitionPhases { PreExecution, PostExecution, Complete } - TransitionPhases public currentTransitionPhase; - uint public transitionIndex; + + /* + * Contract Constants + */ + + bytes32 constant BYTES32_NULL = bytes32(''); + uint256 constant UINT256_NULL = uint256(0); + + + /* + * Contract Variables + */ + + TransitionPhases public currentTransitionPhase; + uint256 public transitionIndex; + bytes32 public preStateRoot; bytes32 public stateRoot; - bool public isTransactionSuccessfullyExecuted; + bytes32 public ovmTransactionHash; FraudVerifier public fraudVerifier; PartialStateManager public stateManager; ExecutionManager executionManager; + EthMerkleTrie public ethMerkleTrie; - /************ - * Modifiers * - ************/ - modifier preExecutionPhase { - require(currentTransitionPhase == TransitionPhases.PreExecution, "Must be called during correct phase!"); - _; - } - modifier postExecutionPhase { - require(currentTransitionPhase == TransitionPhases.PostExecution, "Must be called during correct phase!"); - _; - } + /* + * Modifiers + */ - modifier completePhase { - require(currentTransitionPhase == TransitionPhases.Complete, "Must be called during correct phase!"); + modifier onlyDuringPhase(TransitionPhases _phase) { + require( + currentTransitionPhase == _phase, + "Must be called during the correct phase." + ); _; } + + /* + * Constructor + */ + + /** + * @param _transitionIndex Position of the transaction suspected to be + * fraudulent within the canonical transaction chain. + * @param _preStateRoot Root of the state trie before the transaction was + * executed. + * @param _executionManagerAddress Address of the ExecutionManager to be + * used during transaction execution. + */ constructor( - uint _transitionIndex, + uint256 _transitionIndex, bytes32 _preStateRoot, + bytes32 _ovmTransactionHash, address _executionManagerAddress ) public { - // The FraudVerifier always initializes a StateTransitioner in order to evaluate fraud. - fraudVerifier = FraudVerifier(msg.sender); - // Store the transitionIndex & state root which will be used during the proof. transitionIndex = _transitionIndex; + preStateRoot = _preStateRoot; stateRoot = _preStateRoot; - // And of course set the ExecutionManager pointer. + ovmTransactionHash = _ovmTransactionHash; + currentTransitionPhase = TransitionPhases.PreExecution; + + fraudVerifier = FraudVerifier(msg.sender); executionManager = ExecutionManager(_executionManagerAddress); - // Finally we'll initialize a new state manager! stateManager = new PartialStateManager(address(this), address(executionManager)); - // And set our TransitionPhases to the PreExecution phase. - currentTransitionPhase = TransitionPhases.PreExecution; + ethMerkleTrie = new EthMerkleTrie(); } - /**************************** - * Pre-Transaction Execution * - ****************************/ + + /* + * Public Functions + */ + + /***************************** + * Pre-Transaction Execution * + *****************************/ + + /** + * @notice Allows a user to prove the state for a given contract. Currently + * only requires that the user prove the nonce. Only callable before the + * transaction suspected to be fraudulent has been executed. + * @param _ovmContractAddress Address of the contract on the OVM. + * @param _codeContractAddress Address of the above contract on the EVM. + * @param _nonce Claimed current nonce of the contract. + * @param _stateTrieWitness Merkle trie inclusion proof for the contract + * within the state trie. + */ function proveContractInclusion( address _ovmContractAddress, address _codeContractAddress, - uint _nonce - ) external preExecutionPhase { + uint256 _nonce, + bytes memory _stateTrieWitness + ) public onlyDuringPhase(TransitionPhases.PreExecution) { bytes32 codeHash; assembly { codeHash := extcodehash(_codeContractAddress) } - // TODO: Verify an inclusion proof of the codeHash and nonce! - stateManager.insertVerifiedContract(_ovmContractAddress, _codeContractAddress, _nonce); + require ( + ethMerkleTrie.proveAccountState( + _ovmContractAddress, + DataTypes.AccountState({ + nonce: _nonce, + balance: uint256(0), + storageRoot: bytes32(''), + codeHash: codeHash + }), + DataTypes.ProofMatrix({ + checkNonce: true, + checkBalance: false, + checkStorageRoot: false, + checkCodeHash: true + }), + _stateTrieWitness, + stateRoot + ), + "Invalid account state provided." + ); + + stateManager.insertVerifiedContract( + _ovmContractAddress, + _codeContractAddress, + _nonce + ); } + /** + * @notice Allows a user to prove the value of a given storage slot for + * some contract. Only callable before the transaction suspected to be + * fraudulent has been executed. + * @param _ovmContractAddress Address of the contract on the OVM. + * @param _slot Key for the storage slot to prove. + * @param _value Value for the storage slot to prove. + * @param _stateTrieWitness Merkle trie inclusion proof for the contract + * within the state trie. + * @param _storageTrieWitness Merkle trie inclusion proof for the specific + * storage slot being proven within the account's storage trie. + */ function proveStorageSlotInclusion( address _ovmContractAddress, bytes32 _slot, - bytes32 _value - ) external preExecutionPhase { + bytes32 _value, + bytes memory _stateTrieWitness, + bytes memory _storageTrieWitness + ) public onlyDuringPhase(TransitionPhases.PreExecution) { 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); + require ( + ethMerkleTrie.proveAccountStorageSlotValue( + _ovmContractAddress, + _slot, + _value, + _stateTrieWitness, + _storageTrieWitness, + stateRoot + ), + "Invalid account state provided." + ); + + stateManager.insertVerifiedStorage( + _ovmContractAddress, + _slot, + _value + ); } - /************************ - * Transaction Execution * - ************************/ - function applyTransaction() public returns(bool) { + /************************* + * Transaction Execution * + *************************/ + + /** + * @notice Executes the transaction suspected to be fraudulent via the + * ExecutionManager. Will revert if the transaction attempts to access + * state that has not been proven during the pre-execution phase. + */ + function applyTransaction( + DataTypes.OVMTransactionData memory _transactionData + ) public { + require( + TransactionParser.getTransactionHash(_transactionData) == ovmTransactionHash, + "Provided transaction does not match the original transaction." + ); + + // Initialize our execution context. stateManager.initNewTransactionExecution(); executionManager.setStateManager(address(stateManager)); - // TODO: Get the transaction from the _transitionIndex. For now this'll just be dummy data + + // Execute the transaction via the execution manager. executionManager.executeTransaction( - 0, - 0, - 0x1212121212121212121212121212121212121212, - "0x12", - 0x1212121212121212121212121212121212121212, - 0x1212121212121212121212121212121212121212, - false + _transactionData.timestamp, + _transactionData.queueOrigin, + _transactionData.ovmEntrypoint, + _transactionData.callBytes, + _transactionData.fromAddress, + _transactionData.l1MsgSenderAddress, + _transactionData.allowRevert ); - require(stateManager.existsInvalidStateAccessFlag() == false, "Detected invalid state access!"); - currentTransitionPhase = TransitionPhases.PostExecution; - // This will allow people to start updating the state root! - return true; + require( + stateManager.existsInvalidStateAccessFlag() == false, + "Detected an invalid state access." + ); + + currentTransitionPhase = TransitionPhases.PostExecution; } - /**************************** - * Post-Transaction Execution * - ****************************/ - function proveUpdatedStorageSlot() public postExecutionPhase { + /****************************** + * Post-Transaction Execution * + ******************************/ + + /** + * @notice Updates the root of the state trie by making a modification to + * a contract's storage slot. Contract storage to be modified depends on a + * stack of slots modified during execution. + * @param _stateTrieWitness Merkle trie inclusion proof for the contract + * within the current state trie. + * @param _storageTrieWitness Merkle trie inclusion proof for the slot + * being modified within the contract's storage trie. + */ + function proveUpdatedStorageSlot( + bytes memory _stateTrieWitness, + bytes memory _storageTrieWitness + ) public onlyDuringPhase(TransitionPhases.PostExecution) { ( address storageSlotContract, bytes32 storageSlotKey, bytes32 storageSlotValue ) = stateManager.popUpdatedStorageSlot(); - // TODO: Prove inclusion / make this update to the root + + stateRoot = ethMerkleTrie.updateAccountStorageSlotValue( + storageSlotContract, + storageSlotKey, + storageSlotValue, + _stateTrieWitness, + _storageTrieWitness, + stateRoot + ); } - function proveUpdatedContract() public postExecutionPhase { + + /** + * @notice Updates the root of the state trie by making a modification to + * a contract's state. Contract to be modified depends on a stack of + * contract states modified during execution. + * @param _stateTrieWitness Merkle trie inclusion proof for the contract + * within the current state trie. + */ + function proveUpdatedContract( + bytes memory _stateTrieWitness + ) public onlyDuringPhase(TransitionPhases.PostExecution) { ( address ovmContractAddress, - uint contractNonce + uint contractNonce, + bytes32 codeHash ) = stateManager.popUpdatedContract(); - // TODO: Prove inclusion / make this update to the root + + stateRoot = ethMerkleTrie.updateAccountState( + ovmContractAddress, + DataTypes.AccountState({ + nonce: contractNonce, + balance: UINT256_NULL, + storageRoot: BYTES32_NULL, + codeHash: codeHash + }), + DataTypes.ProofMatrix({ + checkNonce: true, + checkBalance: false, + checkStorageRoot: false, + checkCodeHash: codeHash != 0x0 + }), + _stateTrieWitness, + stateRoot + ); } - function completeTransition() public postExecutionPhase { - require(stateManager.updatedStorageSlotCounter() == 0, "There's still updated storage to account for!"); - require(stateManager.updatedStorageSlotCounter() == 0, "There's still updated contracts to account for!"); - // Tada! We did it reddit! + + /** + * @notice Finalizes the state transition process once all state trie or + * storage trie modifications have been reflected in the state root. + */ + function completeTransition() + public + onlyDuringPhase(TransitionPhases.PostExecution) + { + require( + stateManager.updatedStorageSlotCounter() == 0, + "There's still updated storage to account for!" + ); + require( + stateManager.updatedStorageSlotCounter() == 0, + "There's still updated contracts to account for!" + ); currentTransitionPhase = TransitionPhases.Complete; } + + /** + * @notice Utility; checks whether the process is complete. + * @return `true` if the process is complete, `false` otherwise. + */ + function isComplete() public view returns (bool) { + return (currentTransitionPhase == TransitionPhases.Complete); + } } diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/TransactionParser.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/TransactionParser.sol new file mode 100644 index 0000000000000..23261e21d2b6f --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/TransactionParser.sol @@ -0,0 +1,44 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import { DataTypes } from "../utils/DataTypes.sol"; +import { RLPWriter } from "../utils/RLPWriter.sol"; + +library TransactionParser { + /** + * @notice Utility; computes the hash of a given transaction. + * @param _transaction OVM transaction to hash. + * @return Hash of the provided transaction. + */ + function getTransactionHash( + DataTypes.OVMTransactionData memory _transaction + ) internal pure returns (bytes32) { + bytes memory encodedTransaction = encodeTransactionData(_transaction); + return keccak256(encodedTransaction); + } + + /** + * @notice Utility; RLP encodes an OVMTransactionData struct. + * @dev Likely to be changed (if not moved to another contract). Currently + * remaining here as to avoid modifying CanonicalTransactionChain. Unclear + * whether or not this is the correct transaction structure, but it should + * work for the meantime. + * @param _transactionData Transaction data to encode. + * @return RLP encoded transaction data. + */ + function encodeTransactionData( + DataTypes.OVMTransactionData memory _transactionData + ) internal pure returns (bytes memory) { + bytes[] memory raw = new bytes[](7); + + raw[0] = RLPWriter.encodeUint(_transactionData.timestamp); + raw[1] = RLPWriter.encodeUint(_transactionData.queueOrigin); + raw[2] = RLPWriter.encodeAddress(_transactionData.ovmEntrypoint); + raw[3] = RLPWriter.encodeBytes(_transactionData.callBytes); + raw[4] = RLPWriter.encodeAddress(_transactionData.fromAddress); + raw[5] = RLPWriter.encodeAddress(_transactionData.l1MsgSenderAddress); + raw[6] = RLPWriter.encodeBool(_transactionData.allowRevert); + + return RLPWriter.encodeList(raw); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/interfaces/IStateTransitioner.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/interfaces/IStateTransitioner.sol new file mode 100644 index 0000000000000..b8bde02c23fa7 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/interfaces/IStateTransitioner.sol @@ -0,0 +1,44 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import { DataTypes } from "../../utils/DataTypes.sol"; + +/** + * @title IStateTransitioner + */ +contract IStateTransitioner { + bytes32 public preStateRoot; + bytes32 public stateRoot; + + function proveContractInclusion( + address _ovmContractAddress, + address _codeContractAddress, + uint256 _nonce, + bytes memory _stateTrieWitness + ) public; + + function proveStorageSlotInclusion( + address _ovmContractAddress, + bytes32 _slot, + bytes32 _value, + bytes memory _stateTrieWitness, + bytes memory _storageTrieWitness + ) public; + + function applyTransaction( + DataTypes.OVMTransactionData memory _transactionData + ) public; + + function proveUpdatedStorageSlot( + bytes memory _stateTrieWitness, + bytes memory _storageTrieWitness + ) public; + + function proveUpdatedContract( + bytes memory _stateTrieWitness + ) public; + + function completeTransition() public; + + function isComplete() public view returns (bool); +} diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubStateTransitioner.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubStateTransitioner.sol new file mode 100644 index 0000000000000..7f8e70b0ea5a8 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/test-helpers/StubStateTransitioner.sol @@ -0,0 +1,123 @@ +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; + +import { FraudVerifier } from "../FraudVerifier.sol"; +import { ExecutionManager } from "../ExecutionManager.sol"; +import { DataTypes } from "../../utils/DataTypes.sol"; +import { IStateTransitioner } from "../interfaces/IStateTransitioner.sol"; + +/** + * @title StubStateTransitioner + */ +contract StubStateTransitioner is IStateTransitioner { + /* + * Data Structures + */ + + enum TransitionPhases { + PreExecution, + PostExecution, + Complete + } + + + /* + * Contract Constants + */ + + bytes32 constant BYTES32_NULL = bytes32(''); + uint256 constant UINT256_NULL = uint256(0); + + + /* + * Contract Variables + */ + + TransitionPhases public currentTransitionPhase; + uint256 public transitionIndex; + bytes32 public preStateRoot; + bytes32 public stateRoot; + bytes32 public ovmTransactionHash; + + FraudVerifier public fraudVerifier; + ExecutionManager executionManager; + + + /* + * Constructor + */ + + /** + * @param _transitionIndex Position of the transaction suspected to be + * fraudulent within the canonical transaction chain. + * @param _preStateRoot Root of the state trie before the transaction was + * executed. + * @param _executionManagerAddress Address of the ExecutionManager to be + * used during transaction execution. + */ + constructor( + uint256 _transitionIndex, + bytes32 _preStateRoot, + bytes32 _ovmTransactionHash, + address _executionManagerAddress + ) public { + transitionIndex = _transitionIndex; + preStateRoot = _preStateRoot; + stateRoot = _preStateRoot; + ovmTransactionHash = _ovmTransactionHash; + currentTransitionPhase = TransitionPhases.PreExecution; + + fraudVerifier = FraudVerifier(msg.sender); + executionManager = ExecutionManager(_executionManagerAddress); + } + + function proveContractInclusion( + address _ovmContractAddress, + address _codeContractAddress, + uint256 _nonce, + bytes memory _stateTrieWitness + ) public { + return; + } + + function proveStorageSlotInclusion( + address _ovmContractAddress, + bytes32 _slot, + bytes32 _value, + bytes memory _stateTrieWitness, + bytes memory _storageTrieWitness + ) public { + return; + } + + function applyTransaction( + DataTypes.OVMTransactionData memory _transactionData + ) public { + return; + } + + function proveUpdatedStorageSlot( + bytes memory _stateTrieWitness, + bytes memory _storageTrieWitness + ) public { + return; + } + + function proveUpdatedContract( + bytes memory _stateTrieWitness + ) public { + return; + } + + function completeTransition() public { + currentTransitionPhase = TransitionPhases.Complete; + } + + function isComplete() public view returns (bool) { + return (currentTransitionPhase == TransitionPhases.Complete); + } + + function setStateRoot(bytes32 _stateRoot) public { + stateRoot = _stateRoot; + } +} diff --git a/packages/contracts/contracts/optimistic-ethereum/utils/DataTypes.sol b/packages/contracts/contracts/optimistic-ethereum/utils/DataTypes.sol index fe00b80cfcd51..19035b7b5ab32 100644 --- a/packages/contracts/contracts/optimistic-ethereum/utils/DataTypes.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/DataTypes.sol @@ -66,4 +66,28 @@ contract DataTypes { uint timestamp; bytes32 txHash; } + + struct AccountState { + uint256 nonce; + uint256 balance; + bytes32 storageRoot; + bytes32 codeHash; + } + + struct ProofMatrix { + bool checkNonce; + bool checkBalance; + bool checkStorageRoot; + bool checkCodeHash; + } + + struct OVMTransactionData { + uint256 timestamp; + uint256 queueOrigin; + address ovmEntrypoint; + bytes callBytes; + address fromAddress; + address l1MsgSenderAddress; + bool allowRevert; + } } diff --git a/packages/contracts/contracts/optimistic-ethereum/utils/EthMerkleTrie.sol b/packages/contracts/contracts/optimistic-ethereum/utils/EthMerkleTrie.sol index 8b4fbbf6465d7..97f3fe0336269 100644 --- a/packages/contracts/contracts/optimistic-ethereum/utils/EthMerkleTrie.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/EthMerkleTrie.sol @@ -1,10 +1,11 @@ pragma solidity ^0.5.0; pragma experimental ABIEncoderV2; -import './MerkleTrie.sol'; -import './RLPWriter.sol'; -import './RLPReader.sol'; -import './BytesLib.sol'; +import { MerkleTrie } from "./MerkleTrie.sol"; +import { RLPWriter } from "./RLPWriter.sol"; +import { RLPReader } from "./RLPReader.sol"; +import { BytesLib } from "./BytesLib.sol"; +import { DataTypes } from "./DataTypes.sol"; /** * @notice Convenience wrapper for ETH-related trie operations. @@ -13,21 +14,6 @@ contract EthMerkleTrie is MerkleTrie { bytes32 constant BYTES32_NULL = bytes32(''); uint256 constant UINT256_NULL = uint256(0); - struct AccountState { - uint256 nonce; - uint256 balance; - bytes32 storageRoot; - bytes32 codeHash; - } - - struct ProofMatrix { - bool checkNonce; - bool checkBalance; - bool checkStorageRoot; - bool checkCodeHash; - } - - /* * Public Functions */ @@ -53,7 +39,7 @@ contract EthMerkleTrie is MerkleTrie { bytes32 _stateTrieRoot ) public pure returns (bool) { // Retrieve the current storage root. - AccountState memory accountState = getAccountState( + DataTypes.AccountState memory accountState = getAccountState( _address, _stateTrieWitness, _stateTrieRoot @@ -89,7 +75,7 @@ contract EthMerkleTrie is MerkleTrie { bytes32 _stateTrieRoot ) public pure returns (bytes32) { // Retreive the old storage root. - AccountState memory accountState = getAccountState( + DataTypes.AccountState memory accountState = getAccountState( _address, _stateTrieWitness, _stateTrieRoot @@ -124,13 +110,13 @@ contract EthMerkleTrie is MerkleTrie { */ function proveAccountState( address _address, - AccountState memory _accountState, - ProofMatrix memory _proofMatrix, + DataTypes.AccountState memory _accountState, + DataTypes.ProofMatrix memory _proofMatrix, bytes memory _stateTrieWitness, bytes32 _stateTrieRoot ) public pure returns (bool) { // Pull the current account state. - AccountState memory accountState = getAccountState( + DataTypes.AccountState memory accountState = getAccountState( _address, _stateTrieWitness, _stateTrieRoot @@ -162,13 +148,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bool) { return proveAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: _nonce, balance: UINT256_NULL, storageRoot: BYTES32_NULL, codeHash: BYTES32_NULL }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: true, checkBalance: false, checkStorageRoot: false, @@ -196,13 +182,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bool) { return proveAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: UINT256_NULL, balance: _balance, storageRoot: BYTES32_NULL, codeHash: BYTES32_NULL }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: false, checkBalance: true, checkStorageRoot: false, @@ -230,13 +216,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bool) { return proveAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: UINT256_NULL, balance: UINT256_NULL, storageRoot: _storageRoot, codeHash: BYTES32_NULL }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: false, checkBalance: false, checkStorageRoot: true, @@ -264,13 +250,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bool) { return proveAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: UINT256_NULL, balance: UINT256_NULL, storageRoot: BYTES32_NULL, codeHash: _codeHash }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: false, checkBalance: false, checkStorageRoot: false, @@ -293,12 +279,12 @@ contract EthMerkleTrie is MerkleTrie { */ function updateAccountState( address _address, - AccountState memory _accountState, - ProofMatrix memory _proofMatrix, + DataTypes.AccountState memory _accountState, + DataTypes.ProofMatrix memory _proofMatrix, bytes memory _stateTrieWitness, bytes32 _stateTrieRoot ) public pure returns (bytes32) { - AccountState memory newAccountState = _accountState; + DataTypes.AccountState memory newAccountState = _accountState; // If the user has provided everything, don't bother pulling the // current account state. @@ -309,7 +295,7 @@ contract EthMerkleTrie is MerkleTrie { !_proofMatrix.checkCodeHash ) { // Pull the old account state. - AccountState memory oldAccountState = getAccountState( + DataTypes.AccountState memory oldAccountState = getAccountState( _address, _stateTrieWitness, _stateTrieRoot @@ -361,13 +347,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bytes32) { return updateAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: _nonce, balance: UINT256_NULL, storageRoot: BYTES32_NULL, codeHash: BYTES32_NULL }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: true, checkBalance: false, checkStorageRoot: false, @@ -395,13 +381,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bytes32) { return updateAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: UINT256_NULL, balance: _balance, storageRoot: BYTES32_NULL, codeHash: BYTES32_NULL }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: false, checkBalance: true, checkStorageRoot: false, @@ -429,13 +415,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bytes32) { return updateAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: UINT256_NULL, balance: UINT256_NULL, storageRoot: _storageRoot, codeHash: BYTES32_NULL }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: false, checkBalance: false, checkStorageRoot: true, @@ -463,13 +449,13 @@ contract EthMerkleTrie is MerkleTrie { ) public pure returns (bytes32) { return updateAccountState( _address, - AccountState({ + DataTypes.AccountState({ nonce: UINT256_NULL, balance: UINT256_NULL, storageRoot: BYTES32_NULL, codeHash: _codeHash }), - ProofMatrix({ + DataTypes.ProofMatrix({ checkNonce: false, checkBalance: false, checkStorageRoot: false, @@ -492,10 +478,10 @@ contract EthMerkleTrie is MerkleTrie { */ function decodeAccountState( bytes memory _encodedAccountState - ) internal pure returns (AccountState memory) { + ) internal pure returns (DataTypes.AccountState memory) { RLPReader.RLPItem[] memory accountState = RLPReader.toList(RLPReader.toRlpItem(_encodedAccountState)); - return AccountState({ + return DataTypes.AccountState({ nonce: RLPReader.toUint(accountState[0]), balance: RLPReader.toUint(accountState[1]), storageRoot: BytesLib.toBytes32(RLPReader.toBytes(accountState[2])), @@ -509,7 +495,7 @@ contract EthMerkleTrie is MerkleTrie { * @return RLP-encoded account state. */ function encodeAccountState( - AccountState memory _accountState + DataTypes.AccountState memory _accountState ) internal pure returns (bytes memory) { bytes[] memory raw = new bytes[](4); @@ -535,14 +521,24 @@ contract EthMerkleTrie is MerkleTrie { address _address, bytes memory _stateTrieWitness, bytes32 _stateTrieRoot - ) internal pure returns (AccountState memory) { - bytes memory encodedAccountState = get( + ) internal pure returns (DataTypes.AccountState memory) { + DataTypes.AccountState memory DEFAULT_ACCOUNT_STATE = DataTypes.AccountState({ + nonce: UINT256_NULL, + balance: UINT256_NULL, + storageRoot: keccak256(hex'80'), + codeHash: keccak256(hex'') + }); + + ( + bool exists, + bytes memory encodedAccountState + ) = get( abi.encodePacked(_address), _stateTrieWitness, _stateTrieRoot ); - return decodeAccountState(encodedAccountState); + return exists ? decodeAccountState(encodedAccountState) : DEFAULT_ACCOUNT_STATE; } /** @@ -555,7 +551,7 @@ contract EthMerkleTrie is MerkleTrie { * @return Root hash of the updated state trie. */ function setAccountState( - AccountState memory _accountState, + DataTypes.AccountState memory _accountState, address _address, bytes memory _stateTrieWitness, bytes32 _stateTrieRoot diff --git a/packages/contracts/contracts/optimistic-ethereum/utils/MerkleTrie.sol b/packages/contracts/contracts/optimistic-ethereum/utils/MerkleTrie.sol index 1684d32222a54..67bafd86c3cf2 100644 --- a/packages/contracts/contracts/optimistic-ethereum/utils/MerkleTrie.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/MerkleTrie.sol @@ -115,19 +115,23 @@ contract MerkleTrie { * @param _key Key to search for, as hex bytes. * @param _proof Merkle trie inclusion proof for the key. * @param _root Known root of the Merkle trie. - * @return Value associated with the key. + * @return Whether the node exists, value associated with the key if so. */ function get( bytes memory _key, bytes memory _proof, bytes32 _root - ) public pure returns (bytes memory) { + ) public pure returns (bool, bytes memory) { TrieNode[] memory proof = parseProof(_proof); (uint256 pathLength, bytes memory keyRemainder, ) = walkNodePath(proof, _key, _root); - require(keyRemainder.length == 0, "Could not find node in provided path."); + bool exists = keyRemainder.length == 0; + bytes memory value = exists ? getNodeValue(proof[pathLength - 1]) : bytes(''); - return getNodeValue(proof[pathLength - 1]); + return ( + exists, + value + ); } /* diff --git a/packages/contracts/contracts/test-helpers/FraudTester.sol b/packages/contracts/contracts/test-helpers/FraudTester.sol new file mode 100644 index 0000000000000..62b1dcd7220a0 --- /dev/null +++ b/packages/contracts/contracts/test-helpers/FraudTester.sol @@ -0,0 +1,54 @@ +pragma solidity ^0.5.0; + +contract BaseFraudTester { + mapping (bytes32 => bytes32) public builtInStorage; + + function setStorage(bytes32 key, bytes32 value) public { + builtInStorage[key] = value; + } + + function setStorageMultiple(bytes32 key, bytes32 value, uint256 count) public { + for (uint256 i = 0; i < count; i++) { + setStorage(keccak256(abi.encodePacked(key, i)), value); + } + } + + function setStorageMultipleSameKey(bytes32 key, bytes32 value, uint256 count) public { + for (uint256 i = 0; i < count; i++) { + setStorage( + keccak256(abi.encodePacked(key)), + keccak256(abi.encodePacked(value, i)) + ); + } + } + + function getStorage(bytes32 key) public view returns (bytes32) { + return builtInStorage[key]; + } +} + +contract FraudTester is BaseFraudTester { + function createContract(bytes memory _initcode) public { + assembly { + let newContractAddress := create(0, add(_initcode, 0x20), mload(_initcode)) + + if iszero(extcodesize(newContractAddress)) { + revert(0, 0) + } + } + } + + function createContractMultiple(bytes memory _initcode, uint256 _count) public { + for (uint256 i = 0; i < _count; i++) { + createContract(_initcode); + } + } +} + +contract MicroFraudTester { + uint256 _testValue = 123; + + function test() public view returns (uint256) { + return _testValue; + } +} \ No newline at end of file diff --git a/packages/contracts/package.json b/packages/contracts/package.json index a0d51f0e56dba..7f80cbd3d5435 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -36,8 +36,9 @@ "fix:typescript": "prettier --config ../../prettier-config.json --write \"index.ts\" \"buidler.config.ts\" \"{src,test,plugins}/**/*.ts\"" }, "dependencies": { - "@eth-optimism/core-db": "^0.0.1-alpha.26", - "@eth-optimism/core-utils": "^0.0.1-alpha.26", + "@eth-optimism/core-db": "^0.0.1-alpha.25", + "@eth-optimism/core-utils": "^0.0.1-alpha.25", + "@eth-optimism/solc-transpiler": "^0.0.1-alpha.27", "@nomiclabs/buidler": "^1.3.8", "@nomiclabs/buidler-ethers": "^2.0.0", "@nomiclabs/buidler-waffle": "^2.0.0", diff --git a/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts b/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts index e6c146cf5badb..1f03cd9b845b2 100644 --- a/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts +++ b/packages/contracts/test/contracts/chain/SequencerBatchSubmitter.spec.ts @@ -101,10 +101,11 @@ describe('SequencerBatchSubmitter', () => { stateChain = await StateCommitmentChain.deploy( rollupMerkleUtils.address, - canonicalTxChain.address, - await fraudVerifier.getAddress() + canonicalTxChain.address ) + await stateChain.setFraudVerifier(await fraudVerifier.getAddress()) + await sequencerBatchSubmitter .connect(sequencer) .initialize(canonicalTxChain.address, stateChain.address) diff --git a/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts b/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts index f50c2f369ffac..b101d8f35ce42 100644 --- a/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts +++ b/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts @@ -98,9 +98,10 @@ describe('StateCommitmentChain', () => { beforeEach(async () => { stateChain = await StateCommitmentChain.deploy( rollupMerkleUtils.address, - canonicalTxChain.address, - await fraudVerifier.getAddress() + canonicalTxChain.address ) + + await stateChain.setFraudVerifier(await fraudVerifier.getAddress()) }) describe('appendStateBatch()', async () => { diff --git a/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts b/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts new file mode 100644 index 0000000000000..ba8d0b0064b47 --- /dev/null +++ b/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts @@ -0,0 +1,542 @@ +import { expect } from '../../setup' + +/* External Imports */ +import * as rlp from 'rlp' +import { ethers } from '@nomiclabs/buidler' +import { Contract, ContractFactory, Signer } from 'ethers' + +/* Internal Imports */ +import { + DEFAULT_OPCODE_WHITELIST_MASK, + GAS_LIMIT, + TxChainBatch, + StateChainBatch, + toHexString, +} from '../../test-helpers' +import { TestUtils } from '@eth-optimism/core-utils' + +interface OVMTransactionData { + timestamp: number + queueOrigin: number + ovmEntrypoint: string + callBytes: string + fromAddress: string + l1MsgSenderAddress: string + allowRevert: boolean +} + +const NULL_ADDRESS = '0x' + '00'.repeat(20) +const FORCE_INCLUSION_PERIOD = 600 + +const makeDummyTransaction = (calldata: string): OVMTransactionData => { + return { + timestamp: Math.floor(Date.now() / 1000), + queueOrigin: 0, + ovmEntrypoint: NULL_ADDRESS, + callBytes: calldata, + fromAddress: NULL_ADDRESS, + l1MsgSenderAddress: NULL_ADDRESS, + allowRevert: false, + } +} + +const encodeTransaction = (transaction: OVMTransactionData): string => { + return toHexString( + rlp.encode([ + transaction.timestamp, + transaction.queueOrigin, + transaction.ovmEntrypoint, + transaction.callBytes, + transaction.fromAddress, + transaction.l1MsgSenderAddress, + transaction.allowRevert ? 1 : 0, + ]) + ) +} + +const appendTransactionBatch = async ( + canonicalTransactionChain: Contract, + sequencer: Signer, + batch: string[] +): Promise => { + const timestamp = Math.floor(Date.now() / 1000) + + await canonicalTransactionChain + .connect(sequencer) + .appendSequencerBatch(batch, timestamp) + + return timestamp +} + +const appendAndGenerateTransactionBatch = async ( + canonicalTransactionChain: Contract, + sequencer: Signer, + batch: string[], + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + const timestamp = await appendTransactionBatch( + canonicalTransactionChain, + sequencer, + batch + ) + + const localBatch = new TxChainBatch( + timestamp, + false, + batchIndex, + cumulativePrevElements, + batch + ) + + await localBatch.generateTree() + + return localBatch +} + +const appendAndGenerateStateBatch = async ( + stateCommitmentChain: Contract, + batch: string[], + batchIndex: number = 0, + cumulativePrevElements: number = 0 +): Promise => { + await stateCommitmentChain.appendStateBatch(batch) + + const localBatch = new StateChainBatch( + batchIndex, + cumulativePrevElements, + batch + ) + + await localBatch.generateTree() + + return localBatch +} + +const DUMMY_STATE_BATCH = [ + '0x' + '01'.repeat(32), + '0x' + '02'.repeat(32), + '0x' + '03'.repeat(32), + '0x' + '04'.repeat(32), +] + +const DUMMY_TRANSACTION_BATCH = DUMMY_STATE_BATCH.map((element) => { + return makeDummyTransaction(element) +}) + +const ENCODED_DUMMY_TRANSACTION_BATCH = DUMMY_TRANSACTION_BATCH.map( + (transaction) => { + return encodeTransaction(transaction) + } +) + +/* Tests */ +describe('FraudVerifier', () => { + let wallet: Signer + let sequencer: Signer + let l1ToL2TransactionPasser: Signer + before(async () => { + ;[wallet, sequencer, l1ToL2TransactionPasser] = await ethers.getSigners() + }) + + let ExecutionManager: ContractFactory + let RollupMerkleUtils: ContractFactory + let StateCommitmentChain: ContractFactory + let CanonicalTransactonChain: ContractFactory + let FraudVerifier: ContractFactory + let StubStateTransitioner: ContractFactory + let executionManager: Contract + let rollupMerkleUtils: Contract + before(async () => { + ExecutionManager = await ethers.getContractFactory('ExecutionManager') + RollupMerkleUtils = await ethers.getContractFactory('RollupMerkleUtils') + StateCommitmentChain = await ethers.getContractFactory( + 'StateCommitmentChain' + ) + CanonicalTransactonChain = await ethers.getContractFactory( + 'CanonicalTransactionChain' + ) + FraudVerifier = await ethers.getContractFactory('FraudVerifier') + StubStateTransitioner = await ethers.getContractFactory( + 'StubStateTransitioner' + ) + + executionManager = await ExecutionManager.deploy( + DEFAULT_OPCODE_WHITELIST_MASK, + NULL_ADDRESS, + GAS_LIMIT, + true + ) + + rollupMerkleUtils = await RollupMerkleUtils.deploy() + }) + + let canonicalTransactonChain: Contract + let stateCommitmentChain: Contract + let fraudVerifier: Contract + beforeEach(async () => { + canonicalTransactonChain = await CanonicalTransactonChain.deploy( + rollupMerkleUtils.address, + await sequencer.getAddress(), + await l1ToL2TransactionPasser.getAddress(), + FORCE_INCLUSION_PERIOD + ) + + stateCommitmentChain = await StateCommitmentChain.deploy( + rollupMerkleUtils.address, + canonicalTransactonChain.address + ) + + fraudVerifier = await FraudVerifier.deploy( + executionManager.address, + stateCommitmentChain.address, + canonicalTransactonChain.address, + true // Throw the verifier into testing mode. + ) + + await stateCommitmentChain.setFraudVerifier(fraudVerifier.address) + }) + + let transactionBatch: TxChainBatch + let stateBatch: StateChainBatch + beforeEach(async () => { + transactionBatch = await appendAndGenerateTransactionBatch( + canonicalTransactonChain, + sequencer, + ENCODED_DUMMY_TRANSACTION_BATCH + ) + + stateBatch = await appendAndGenerateStateBatch( + stateCommitmentChain, + DUMMY_STATE_BATCH + ) + }) + + describe('initializeFraudVerification', async () => { + it('should correctly initialize with a valid state root and transaction', async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + const transaction = DUMMY_TRANSACTION_BATCH[0] + const transactionIndex = transactionBatch.getPosition(0) + const transactionProof = await transactionBatch.getElementInclusionProof( + 0 + ) + + await fraudVerifier.initializeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + transaction, + transactionProof + ) + + expect( + await fraudVerifier.hasStateTransitioner(transactionIndex, preStateRoot) + ).to.equal(true) + }) + + it('should return if initializing twice', async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + const transaction = DUMMY_TRANSACTION_BATCH[0] + const transactionIndex = transactionBatch.getPosition(0) + const transactionProof = await transactionBatch.getElementInclusionProof( + 0 + ) + + await fraudVerifier.initializeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + transaction, + transactionProof + ) + + expect( + await fraudVerifier.hasStateTransitioner(transactionIndex, preStateRoot) + ).to.equal(true) + + // Initializing again should execute correctly without actually creating + // a new state transitioner. + await fraudVerifier.initializeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + transaction, + transactionProof + ) + + expect( + await fraudVerifier.hasStateTransitioner(transactionIndex, preStateRoot) + ).to.equal(true) + }) + + it('should reject an invalid state root', async () => { + // Using the wrong state root. + const preStateRoot = DUMMY_STATE_BATCH[1] + const preStateRootProof = await stateBatch.getElementInclusionProof(1) + + const transaction = DUMMY_TRANSACTION_BATCH[0] + const transactionIndex = transactionBatch.getPosition(0) + const transactionProof = await transactionBatch.getElementInclusionProof( + 0 + ) + + await TestUtils.assertRevertsAsync( + 'Provided pre-state root inclusion proof is invalid.', + async () => { + await fraudVerifier.initializeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + transaction, + transactionProof + ) + } + ) + + expect( + await fraudVerifier.hasStateTransitioner(transactionIndex, preStateRoot) + ).to.equal(false) + }) + + it('should reject an invalid transaction', async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + // Using the wrong transaction data. + const transaction = DUMMY_TRANSACTION_BATCH[1] + const transactionIndex = transactionBatch.getPosition(0) + const transactionProof = await transactionBatch.getElementInclusionProof( + 0 + ) + + await TestUtils.assertRevertsAsync( + 'Provided transaction data is invalid.', + async () => { + await fraudVerifier.initializeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + transaction, + transactionProof + ) + } + ) + + expect( + await fraudVerifier.hasStateTransitioner(transactionIndex, preStateRoot) + ).to.equal(false) + }) + }) + + describe('finalizeFraudVerification', async () => { + let stubStateTransitioner: Contract + beforeEach(async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + const transaction = DUMMY_TRANSACTION_BATCH[0] + const transactionIndex = transactionBatch.getPosition(0) + const transactionProof = await transactionBatch.getElementInclusionProof( + 0 + ) + + await fraudVerifier.initializeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + transaction, + transactionProof + ) + + const stateTransitionerAddress = await fraudVerifier.stateTransitioners( + transactionIndex + ) + stubStateTransitioner = StubStateTransitioner.attach( + stateTransitionerAddress + ) + }) + + it('should correctly finalize when the computed state root differs', async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + const postStateRoot = DUMMY_STATE_BATCH[1] + const postStateRootProof = await stateBatch.getElementInclusionProof(1) + + const transactionIndex = transactionBatch.getPosition(0) + + await stubStateTransitioner.setStateRoot('0x' + '00'.repeat(32)) + await stubStateTransitioner.completeTransition() + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + + await fraudVerifier.finalizeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + postStateRoot, + postStateRootProof + ) + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(0) + }) + + it('should revert when the state transitioner has not been finalized', async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + const postStateRoot = DUMMY_STATE_BATCH[1] + const postStateRootProof = await stateBatch.getElementInclusionProof(1) + + const transactionIndex = transactionBatch.getPosition(0) + + // Not finalizing the state transitioner. + await stubStateTransitioner.setStateRoot('0x' + '00'.repeat(32)) + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + + await TestUtils.assertRevertsAsync( + 'State transition process has not been completed.', + async () => { + await fraudVerifier.finalizeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + postStateRoot, + postStateRootProof + ) + } + ) + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + }) + + it('should revert when the provided pre-state root is for the wrong transition index', async () => { + // Using the wrong pre-state root. + const preStateRoot = DUMMY_STATE_BATCH[1] + const preStateRootProof = await stateBatch.getElementInclusionProof(1) + + const postStateRoot = DUMMY_STATE_BATCH[1] + const postStateRootProof = await stateBatch.getElementInclusionProof(1) + + const transactionIndex = transactionBatch.getPosition(0) + + await stubStateTransitioner.setStateRoot('0x' + '00'.repeat(32)) + await stubStateTransitioner.completeTransition() + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + + await TestUtils.assertRevertsAsync( + 'Provided pre-state root does not match StateTransitioner.', + async () => { + await fraudVerifier.finalizeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + postStateRoot, + postStateRootProof + ) + } + ) + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + }) + + it('should revert when the provided pre-state root is invalid', async () => { + // Using the right root with an invalid proof. + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(1) + + const postStateRoot = DUMMY_STATE_BATCH[1] + const postStateRootProof = await stateBatch.getElementInclusionProof(1) + + const transactionIndex = transactionBatch.getPosition(0) + + await stubStateTransitioner.setStateRoot('0x' + '00'.repeat(32)) + await stubStateTransitioner.completeTransition() + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + + await TestUtils.assertRevertsAsync( + 'Provided pre-state root inclusion proof is invalid.', + async () => { + await fraudVerifier.finalizeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + postStateRoot, + postStateRootProof + ) + } + ) + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + }) + + it('should revert when the provided post-state root is invalid', async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + // Using the wrong pre-state root. + const postStateRoot = DUMMY_STATE_BATCH[2] + const postStateRootProof = await stateBatch.getElementInclusionProof(2) + + const transactionIndex = transactionBatch.getPosition(0) + + await stubStateTransitioner.setStateRoot('0x' + '00'.repeat(32)) + await stubStateTransitioner.completeTransition() + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + + await TestUtils.assertRevertsAsync( + 'Provided post-state root inclusion proof is invalid.', + async () => { + await fraudVerifier.finalizeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + postStateRoot, + postStateRootProof + ) + } + ) + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + }) + + it('should revert when the provided post-state root matches the state transitioner', async () => { + const preStateRoot = DUMMY_STATE_BATCH[0] + const preStateRootProof = await stateBatch.getElementInclusionProof(0) + + const postStateRoot = DUMMY_STATE_BATCH[1] + const postStateRootProof = await stateBatch.getElementInclusionProof(1) + + const transactionIndex = transactionBatch.getPosition(0) + + // Setting the root to match the given post-state root. + await stubStateTransitioner.setStateRoot(postStateRoot) + await stubStateTransitioner.completeTransition() + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + + await TestUtils.assertRevertsAsync( + 'State transition has not been proven fraudulent.', + async () => { + await fraudVerifier.finalizeFraudVerification( + transactionIndex, + preStateRoot, + preStateRootProof, + postStateRoot, + postStateRootProof + ) + } + ) + + expect(await stateCommitmentChain.getBatchesLength()).to.equal(1) + }) + }) +}) diff --git a/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts b/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts index be5aab5292366..a89ed9d6f1f4a 100644 --- a/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts +++ b/packages/contracts/test/contracts/ovm/PartialStateManager.spec.ts @@ -54,18 +54,6 @@ describe('PartialStateManager', () => { const existsInvalidStateAccessFlag = await partialStateManager.existsInvalidStateAccessFlag() existsInvalidStateAccessFlag.should.equal(true) }) - - it('sets existsInvalidStateAccessFlag=true if setStorage(contract, key, value) is called without being verified', async () => { - const address = '0x' + '01'.repeat(20) - const key = '0x' + '01'.repeat(32) - const value = '0x' + '01'.repeat(32) - - // Attempt to set unverified storage! - await partialStateManager.setStorage(address, key, value) - - const existsInvalidStateAccessFlag = await partialStateManager.existsInvalidStateAccessFlag() - existsInvalidStateAccessFlag.should.equal(true) - }) }) describe('Contract Verification', async () => { diff --git a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts index eb41cd0ecd52c..e93fb88a7965e 100644 --- a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts +++ b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts @@ -1,15 +1,341 @@ -import '../../setup' +import { expect } from '../../setup' /* External Imports */ +import * as path from 'path' +import * as rlp from 'rlp' import { ethers } from '@nomiclabs/buidler' -import { getLogger } from '@eth-optimism/core-utils' -import { Contract, ContractFactory, Signer } from 'ethers' +import { getLogger, TestUtils, remove0x } from '@eth-optimism/core-utils' +import * as solc from '@eth-optimism/solc-transpiler' +import { Contract, ContractFactory, Signer, BigNumber } from 'ethers' +import { keccak256 } from 'ethers/utils' +import { cloneDeep } from 'lodash' + +/* Internal Imports */ +import { + makeAccountStorageProofTest, + makeAccountStorageUpdateTest, + AccountStorageProofTest, + AccountStorageUpdateTest, + StateTrieMap, + StateTrieNode, + TrieNode, + compile, + DEFAULT_OPCODE_WHITELIST_MASK, + GAS_LIMIT, + makeStateTrieUpdateTest, + StateTrieUpdateTest, + toHexString, +} from '../../test-helpers' /* Logging */ const log = getLogger('state-transitioner', true) -/* Contract Imports */ -import * as PartialStateManager from '../../../artifacts/PartialStateManager.json' +const NULL_ADDRESS = '0x' + '00'.repeat(20) +const DUMMY_ACCOUNT_ADDRESSES = [ + '0x548855F6073c3430285c61Ed0ABf62F12084aA41', + '0xD80e66Cbc34F06d24a0a4fDdD6f2aDB41ac1517D', + '0x069889F3DC507DdA244d19b5f24caDCDd2a735c2', + '0x808E5eCe9a8EA2cdce515764139Ee24bEF7098b4', +] + +interface OVMTransactionData { + timestamp: number + queueOrigin: number + ovmEntrypoint: string + callBytes: string + fromAddress: string + l1MsgSenderAddress: string + allowRevert: boolean +} + +const makeDummyTransaction = (calldata: string): OVMTransactionData => { + return { + timestamp: Math.floor(Date.now() / 1000), + queueOrigin: 0, + ovmEntrypoint: NULL_ADDRESS, + callBytes: calldata, + fromAddress: NULL_ADDRESS, + l1MsgSenderAddress: NULL_ADDRESS, + allowRevert: false, + } +} + +const EMPTY_ACCOUNT_STATE = (): StateTrieNode => { + return cloneDeep({ + nonce: 0, + balance: 0, + storageRoot: null, + codeHash: null, + }) +} + +const STATE_TRANSITIONER_PHASES = { + PRE_EXECUTION: 0, + POST_EXECUTION: 1, + COMPLETE: 2, +} + +const DUMMY_ACCOUNT_STORAGE = (): TrieNode[] => { + return cloneDeep([ + { + key: keccak256('0x123'), + val: keccak256('0x456'), + }, + { + key: keccak256('0x123123'), + val: keccak256('0x456456'), + }, + { + key: keccak256('0x123123123'), + val: keccak256('0x456456456'), + }, + ]) +} + +const DUMMY_STATE_TRIE = { + [DUMMY_ACCOUNT_ADDRESSES[0]]: { + state: EMPTY_ACCOUNT_STATE(), + storage: DUMMY_ACCOUNT_STORAGE(), + }, + [DUMMY_ACCOUNT_ADDRESSES[1]]: { + state: EMPTY_ACCOUNT_STATE(), + storage: DUMMY_ACCOUNT_STORAGE(), + }, + [DUMMY_ACCOUNT_ADDRESSES[2]]: { + state: EMPTY_ACCOUNT_STATE(), + storage: DUMMY_ACCOUNT_STORAGE(), + }, +} + +const encodeTransaction = (transaction: OVMTransactionData): string => { + return toHexString( + rlp.encode([ + transaction.timestamp, + transaction.queueOrigin, + transaction.ovmEntrypoint, + transaction.callBytes, + transaction.fromAddress, + transaction.l1MsgSenderAddress, + transaction.allowRevert ? 1 : 0, + ]) + ) +} + +const makeStateTrie = (account: string, state: any, storage: any[]): any => { + return { + [account]: { + state, + storage, + }, + ...DUMMY_STATE_TRIE, + } +} + +const getCodeHash = async (provider: any, address: string): Promise => { + return keccak256(await provider.getCode(address)) +} + +const makeTransactionData = async ( + TargetFactory: ContractFactory, + target: Contract, + wallet: Signer, + functionName: string, + functionArgs: any[] +): Promise => { + const calldata = TargetFactory.interface.encodeFunctionData( + functionName, + functionArgs + ) + + return { + timestamp: 1, + queueOrigin: 1, + ovmEntrypoint: target.address, + callBytes: calldata, + fromAddress: target.address, + l1MsgSenderAddress: await wallet.getAddress(), + allowRevert: false, + } +} + +const proveAllStorageUpdates = async ( + stateTransitioner: Contract, + stateManager: Contract, + stateTrie: StateTrieMap +): Promise => { + let updateTest: AccountStorageUpdateTest + let trie = cloneDeep(stateTrie) + + while ((await stateManager.updatedStorageSlotCounter()) > 0) { + const [ + storageSlotContract, + storageSlotKey, + storageSlotValue, + ] = await stateManager.peekUpdatedStorageSlot() + + updateTest = await makeAccountStorageUpdateTest( + trie, + storageSlotContract, + storageSlotKey, + storageSlotValue + ) + + await stateTransitioner.proveUpdatedStorageSlot( + updateTest.stateTrieWitness, + updateTest.storageTrieWitness + ) + + trie = makeModifiedTrie(trie, [ + { + address: storageSlotContract, + storage: [ + { + key: storageSlotKey, + val: storageSlotValue, + }, + ], + }, + ]) + } + + return updateTest.newStateTrieRoot +} + +const proveAllContractUpdates = async ( + stateTransitioner: Contract, + stateManager: Contract, + stateTrie: StateTrieMap +): Promise => { + let updateTest: StateTrieUpdateTest + let trie = cloneDeep(stateTrie) + + while ((await stateManager.updatedContractsCounter()) > 0) { + const [ + updatedContract, + updatedContractNonce, + updatedCodeHash, + ] = await stateManager.peekUpdatedContract() + + const updatedAccountState = { + ...(updatedContract in trie + ? trie[updatedContract].state + : EMPTY_ACCOUNT_STATE()), + ...{ + nonce: updatedContractNonce.toNumber(), + }, + } + + if (updatedCodeHash !== '0x' + '00'.repeat(32)) { + updatedAccountState.codeHash = updatedCodeHash + } + + updateTest = await makeStateTrieUpdateTest( + trie, + updatedContract, + updatedAccountState + ) + + await stateTransitioner.proveUpdatedContract(updateTest.stateTrieWitness) + + trie = makeModifiedTrie(trie, [ + { + address: updatedContract, + state: updatedAccountState, + }, + ]) + } + + return updateTest.newStateTrieRoot +} + +const getMappingStorageSlot = (key: string, index: number): string => { + const hexIndex = remove0x(BigNumber.from(index).toHexString()).padStart( + 64, + '0' + ) + return keccak256(key + hexIndex) +} + +const initStateTransitioner = async ( + StateTransitioner: ContractFactory, + StateManager: ContractFactory, + executionManager: Contract, + stateTrieRoot: string, + transactionData: OVMTransactionData +): Promise<[Contract, Contract, OVMTransactionData]> => { + const stateTransitioner = await StateTransitioner.deploy( + 10, + stateTrieRoot, + keccak256(encodeTransaction(transactionData)), + executionManager.address + ) + const stateManager = StateManager.attach( + await stateTransitioner.stateManager() + ) + + return [stateTransitioner, stateManager, transactionData] +} + +interface StateTrieModification { + address: string + state?: Partial + storage?: TrieNode[] +} + +const makeModifiedTrie = ( + stateTrie: StateTrieMap, + modifications: StateTrieModification[] +): StateTrieMap => { + const trie = cloneDeep(stateTrie) + + for (let modification of modifications) { + modification = cloneDeep(modification) + + if (!(modification.address in trie)) { + trie[modification.address] = { + state: { + ...EMPTY_ACCOUNT_STATE(), + ...modification.state, + }, + storage: modification.storage || [], + } + } else { + if (modification.state) { + trie[modification.address].state = { + ...trie[modification.address].state, + ...modification.state, + } + } + + if (modification.storage) { + for (const element of modification.storage) { + const hasKey = trie[modification.address].storage.some((kv: any) => { + return kv.key === element.key + }) + + if (!hasKey) { + trie[modification.address].storage.push({ + key: element.key, + val: element.val, + }) + } else { + trie[modification.address].storage = trie[ + modification.address + ].storage.map((kv: any) => { + if (kv.key === element.key) { + kv.val = element.val + } + + return kv + }) + } + } + } + } + } + + return trie +} /* Begin tests */ describe('StateTransitioner', () => { @@ -20,42 +346,78 @@ describe('StateTransitioner', () => { let ExecutionManager: ContractFactory let StateTransitioner: ContractFactory + let StateManager: ContractFactory + let executionManager: Contract + let FraudTesterJson: any + let MicroFraudTesterJson: any + let FraudTester: ContractFactory + let fraudTester: Contract before(async () => { - ExecutionManager = await ethers.getContractFactory('StubExecutionManager') + ExecutionManager = await ethers.getContractFactory('ExecutionManager') StateTransitioner = await ethers.getContractFactory('StateTransitioner') - }) + StateManager = await ethers.getContractFactory('PartialStateManager') - let executionManager: Contract - let stateTransitioner: Contract - beforeEach(async () => { - executionManager = await ExecutionManager.deploy() - stateTransitioner = await StateTransitioner.deploy( - 10, - '0x' + '00'.repeat(32), - executionManager.address + executionManager = await ExecutionManager.deploy( + DEFAULT_OPCODE_WHITELIST_MASK, + '0x' + '00'.repeat(20), + GAS_LIMIT, + true ) + + const AllFraudTestJson = compile( + solc, + path.resolve( + __dirname, + '../../../contracts/test-helpers/FraudTester.sol' + ), + { + executionManagerAddress: executionManager.address, + } + ).contracts['FraudTester.sol'] + FraudTesterJson = AllFraudTestJson.FraudTester + MicroFraudTesterJson = AllFraudTestJson.MicroFraudTester + + FraudTester = new ethers.ContractFactory( + FraudTesterJson.abi, + FraudTesterJson.evm.bytecode.object, + wallet + ) + fraudTester = await FraudTester.deploy() }) - const prepareStateForTransactionExecution = async () => { - const contract1 = '0x' + '11'.repeat(20) - const storageSlot1 = '0x' + '11'.repeat(32) - const storageValue1 = '0x' + '11'.repeat(32) - const contract2 = '0x' + '22'.repeat(20) - const storageSlot2 = '0x' + '22'.repeat(32) - const storageValue2 = '0x' + '22'.repeat(32) - await stateTransitioner.proveContractInclusion(contract1, contract1, 1) - await stateTransitioner.proveStorageSlotInclusion( - contract1, - storageSlot1, - storageValue1 + let stateTrie: any + let test: AccountStorageProofTest + before(async () => { + stateTrie = makeStateTrie( + fraudTester.address, + { + nonce: 0, + balance: 0, + storageRoot: null, + codeHash: await getCodeHash(ethers.provider, fraudTester.address), + }, + DUMMY_ACCOUNT_STORAGE() + ) + + test = await makeAccountStorageProofTest( + stateTrie, + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key ) - await stateTransitioner.proveContractInclusion(contract2, contract2, 5) - await stateTransitioner.proveStorageSlotInclusion( - contract2, - storageSlot2, - storageValue2 + }) + + let stateTransitioner: Contract + let stateManager: Contract + let transactionData: OVMTransactionData + beforeEach(async () => { + ;[stateTransitioner, stateManager] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + makeDummyTransaction('0x00') ) - } + }) describe('Initialization', async () => { it('sets the fraud verifier address to the deployer', async () => { @@ -65,88 +427,611 @@ describe('StateTransitioner', () => { }) describe('Pre-Execution', async () => { - it('proves contract inclusion which allows us to query the isVerifiedContract in the state manager', async () => { - const ovmContractAddress = '0x' + '01'.repeat(20) - const codeContractAddress = stateTransitioner.address - await stateTransitioner.proveContractInclusion( - ovmContractAddress, - codeContractAddress, - 5 + describe('proveContractInclusion(...)', async () => { + it('should correctly prove inclusion of a valid contract', async () => { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + expect( + await stateManager.isVerifiedContract(fraudTester.address) + ).to.equal(true) + }) + + it('should correctly reject inclusion of a contract with an invalid nonce', async () => { + try { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 123, // Wrong nonce. + test.stateTrieWitness + ) + } catch (e) { + expect(e.toString()).to.contain('Invalid account state provided.') + } + + expect( + await stateManager.isVerifiedContract(fraudTester.address) + ).to.equal(false) + }) + }) + + describe('proveStorageSlotInclusion(...)', async () => { + it('should correctly prove inclusion of a valid storage slot', async () => { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.proveStorageSlotInclusion( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key, + DUMMY_ACCOUNT_STORAGE()[0].val, + test.stateTrieWitness, + test.storageTrieWitness + ) + + expect( + await stateManager.isVerifiedStorage( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key + ) + ).to.equal(true) + }) + + it('should correctly reject inclusion of an invalid storage slot', async () => { + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + try { + await stateTransitioner.proveStorageSlotInclusion( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key, + DUMMY_ACCOUNT_STORAGE()[1].val, // Different value. + test.stateTrieWitness, + test.storageTrieWitness + ) + } catch (e) { + expect(e.toString()).to.contain('Invalid account state provided.') + } + + expect( + await stateManager.isVerifiedStorage( + fraudTester.address, + DUMMY_ACCOUNT_STORAGE()[0].key + ) + ).to.equal(false) + }) + }) + }) + + describe('applyTransaction(...)', async () => { + it('should succeed if no state is accessed', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) ) - const stateManager = new Contract( - await stateTransitioner.stateManager(), - PartialStateManager.abi, - wallet + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness ) - const isVerified = await stateManager.isVerifiedContract( - ovmContractAddress + await stateTransitioner.applyTransaction(transactionData) + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.POST_EXECUTION ) - isVerified.should.equal(true) }) - it('proves storage slot inclusion (after contract inclusion) allows us to query the storage', async () => { - // First prove the contract - const ovmContractAddress = '0x' + '01'.repeat(20) - const codeContractAddress = stateTransitioner.address - await stateTransitioner.proveContractInclusion( - ovmContractAddress, - codeContractAddress, - 5 + it('should succeed initialized state is accessed', async () => { + const testKey = keccak256('0xabc') + const testKeySlot = getMappingStorageSlot(testKey, 0) + const testVal = keccak256('0xdef') + + const trie = makeModifiedTrie(stateTrie, [ + { + address: fraudTester.address, + storage: [ + { + key: testKeySlot, + val: testVal, + }, + ], + }, + ]) + + const accessTest = await makeAccountStorageProofTest( + trie, + fraudTester.address, + testKeySlot ) - const stateManager = new Contract( - await stateTransitioner.stateManager(), - PartialStateManager.abi, - wallet + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + accessTest.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'getStorage', + [testKey] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + accessTest.stateTrieWitness ) - // Next prove the storage - const storageSlot = '0x' + '01'.repeat(32) - const storageValue = '0x' + '11'.repeat(32) await stateTransitioner.proveStorageSlotInclusion( - ovmContractAddress, - storageSlot, - storageValue + fraudTester.address, + testKeySlot, + testVal, + accessTest.stateTrieWitness, + accessTest.storageTrieWitness ) - const isVerified = await stateManager.isVerifiedStorage( - ovmContractAddress, - storageSlot + await stateTransitioner.applyTransaction(transactionData) + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.POST_EXECUTION ) - isVerified.should.equal(true) }) - }) - describe('applyTransaction(...)', async () => { - it('fails if there was no state that was supplied', async () => { - let didFail = false - try { - await stateTransitioner.applyTransaction() - } catch (e) { - didFail = true - } - didFail.should.equal(true) + it('should succeed when a new contract is created', async () => { + // Attempting a `getStorage` call to a key that hasn't been proven. + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'createContract', + ['0x' + MicroFraudTesterJson.evm.bytecode.object] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.POST_EXECUTION + ) }) - it('does not fail if all the state is supplied', async () => { - await prepareStateForTransactionExecution() - await stateTransitioner.applyTransaction() + it('should fail if attempting to access uninitialized state', async () => { + // Attempting a `getStorage` call to a key that hasn't been proven. + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'getStorage', + [keccak256('0xabc')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await TestUtils.assertRevertsAsync( + 'Detected an invalid state access.', + async () => { + await stateTransitioner.applyTransaction(transactionData) + } + ) + + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.PRE_EXECUTION + ) + }) + + it('should fail if attempting to access an uninitialized contract', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await TestUtils.assertRevertsAsync( + 'Detected an invalid state access.', + async () => { + await stateTransitioner.applyTransaction(transactionData) + } + ) + + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.PRE_EXECUTION + ) }) }) + describe('Post-Execution', async () => { - it('moves between phases correctly', async () => { - // TODO: Add real tests - await prepareStateForTransactionExecution() - await stateTransitioner.applyTransaction() - await stateTransitioner.proveUpdatedStorageSlot() - // Check that the phase is still post execution - let phase = await stateTransitioner.currentTransitionPhase() - phase.should.equal(1) - await stateTransitioner.proveUpdatedStorageSlot() - await stateTransitioner.completeTransition() - phase = await stateTransitioner.currentTransitionPhase() - // Check that the phase is now complete! - phase.should.equal(2) + describe('proveUpdatedStorageSlot(...)', async () => { + it('should correctly update when a slot has been changed', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + + const newStateTrieRoot = await proveAllStorageUpdates( + stateTransitioner, + stateManager, + stateTrie + ) + + expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) + }) + + it('should correctly update when multiple slots have changed', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorageMultiple', + [ + keccak256('0xabc'), + keccak256('0xdef'), + 3, // Set three storage slots. + ] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + expect(await stateManager.updatedStorageSlotCounter()).to.equal(3) + + const newStateTrieRoot = await proveAllStorageUpdates( + stateTransitioner, + stateManager, + stateTrie + ) + + expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) + }) + + it('should correctly update when the same slot has changed multiple times', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorageMultipleSameKey', + [ + keccak256('0xabc'), + keccak256('0xdef'), + 3, // Set slot three times. + ] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + + const newStateTrieRoot = await proveAllStorageUpdates( + stateTransitioner, + stateManager, + stateTrie + ) + + expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) + }) + }) + + describe('proveUpdatedContract(...)', async () => { + it('should correctly update when a contract has been created', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'createContract', + ['0x' + MicroFraudTesterJson.evm.bytecode.object] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + // One update for each new contract, plus one nonce update for the creating contract. + expect(await stateManager.updatedContractsCounter()).to.equal(2) + + const newStateTrieRoot = await proveAllContractUpdates( + stateTransitioner, + stateManager, + stateTrie + ) + + expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) + expect(await stateManager.updatedContractsCounter()).to.equal(0) + }) + + it('should correctly update when multiple contracts have been created', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'createContractMultiple', + [ + '0x' + MicroFraudTesterJson.evm.bytecode.object, + 3, // Create three contracts. + ] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + + // One update for each new contract, plus one nonce update for the creating contract. + expect(await stateManager.updatedContractsCounter()).to.equal(4) + + const newStateTrieRoot = await proveAllContractUpdates( + stateTransitioner, + stateManager, + stateTrie + ) + + expect(await stateTransitioner.stateRoot()).to.equal(newStateTrieRoot) + expect(await stateManager.updatedContractsCounter()).to.equal(0) + }) + }) + + describe('completeTransition(...)', async () => { + it('should correctly finalize when no slots are changed', async () => { + const testKey = keccak256('0xabc') + const testKeySlot = getMappingStorageSlot(testKey, 0) + const testVal = keccak256('0xdef') + + const trie = makeModifiedTrie(stateTrie, [ + { + address: fraudTester.address, + storage: [ + { + key: testKeySlot, + val: testVal, + }, + ], + }, + ]) + + const accessTest = await makeAccountStorageProofTest( + trie, + fraudTester.address, + testKeySlot + ) + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + accessTest.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'getStorage', + [testKey] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + accessTest.stateTrieWitness + ) + + await stateTransitioner.proveStorageSlotInclusion( + fraudTester.address, + testKeySlot, + testVal, + accessTest.stateTrieWitness, + accessTest.storageTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) + + await stateTransitioner.completeTransition() + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.COMPLETE + ) + }) + + it('should correctly finalize when storage slots are changed', async () => { + ;[ + stateTransitioner, + stateManager, + transactionData, + ] = await initStateTransitioner( + StateTransitioner, + StateManager, + executionManager, + test.stateTrieRoot, + await makeTransactionData( + FraudTester, + fraudTester, + wallet, + 'setStorage', + [keccak256('0xabc'), keccak256('0xdef')] + ) + ) + + await stateTransitioner.proveContractInclusion( + fraudTester.address, + fraudTester.address, + 0, + test.stateTrieWitness + ) + + await stateTransitioner.applyTransaction(transactionData) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(1) + + await proveAllStorageUpdates(stateTransitioner, stateManager, stateTrie) + expect(await stateManager.updatedStorageSlotCounter()).to.equal(0) + + await stateTransitioner.completeTransition() + expect(await stateTransitioner.currentTransitionPhase()).to.equal( + STATE_TRANSITIONER_PHASES.COMPLETE + ) + }) }) }) }) diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts index 1bb09e485996c..22bb7e5a53ba2 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts @@ -80,7 +80,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await stateManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonceView(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -121,7 +121,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await stateManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonceView(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -156,7 +156,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await stateManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonceView(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -192,7 +192,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await stateManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonceView(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, @@ -217,7 +217,9 @@ describe('Execution Manager -- Call opcodes', () => { s ) await provider.waitForTransaction(tx.hash) - const nonceAfter = await stateManager.getOvmContractNonce(wallet.address) + const nonceAfter = await stateManager.getOvmContractNonceView( + wallet.address + ) nonceAfter.should.equal(parseInt(nonce, 10) + 1) }) @@ -229,7 +231,7 @@ describe('Execution Manager -- Call opcodes', () => { 'dummyFunction', [intParam, bytesParam] ) - const nonce = await stateManager.getOvmContractNonce(wallet.address) + const nonce = await stateManager.getOvmContractNonceView(wallet.address) const transaction = { nonce, gasLimit: GAS_LIMIT, diff --git a/packages/contracts/test/test-helpers/ethereum-helpers.ts b/packages/contracts/test/test-helpers/ethereum-helpers.ts index ee11deb186071..c2f4a0b9ef0bb 100644 --- a/packages/contracts/test/test-helpers/ethereum-helpers.ts +++ b/packages/contracts/test/test-helpers/ethereum-helpers.ts @@ -1,3 +1,5 @@ +import * as path from 'path' +import * as fs from 'fs' import { Transaction } from 'ethers/utils' import { Log } from 'ethers/providers' import * as ethereumjsAbi from 'ethereumjs-abi' @@ -114,3 +116,27 @@ export const addressToBytes32Address = (addr: string): string => { bufferUtils.padLeft(hexStrToBuf(addr), 32) ).toLowerCase() } + +export const compile = ( + compiler: any, + file: string, + settings: any = {} +): any => { + const input = { + language: 'Solidity', + sources: { + [path.basename(file)]: { + content: fs.readFileSync(file, 'utf8'), + }, + }, + settings: { + outputSelection: { + '*': { + '*': ['*'], + }, + }, + ...settings, + }, + } + return JSON.parse(compiler.compile(JSON.stringify(input))) +} diff --git a/packages/contracts/test/test-helpers/trie-helpers.ts b/packages/contracts/test/test-helpers/trie-helpers.ts index fd404bc39c264..d67f7c41a38fb 100644 --- a/packages/contracts/test/test-helpers/trie-helpers.ts +++ b/packages/contracts/test/test-helpers/trie-helpers.ts @@ -18,7 +18,7 @@ interface ProofTest { root: string } -interface AccountStorageProofTest { +export interface AccountStorageProofTest { address: string key: string val: string @@ -27,23 +27,34 @@ interface AccountStorageProofTest { stateTrieRoot: string } -interface AccountStorageUpdateTest extends AccountStorageProofTest { +export interface AccountStorageUpdateTest extends AccountStorageProofTest { newStateTrieRoot: string } -interface TrieNode { +export interface StateTrieProofTest { + address: string + encodedAccountState: string + stateTrieWitness: string + stateTrieRoot: string +} + +export interface StateTrieUpdateTest extends StateTrieProofTest { + newStateTrieRoot: string +} + +export interface TrieNode { key: string val: string } -interface StateTrieNode { +export interface StateTrieNode { nonce: number balance: number storageRoot: string codeHash: string } -interface StateTrieMap { +export interface StateTrieMap { [address: string]: { state: StateTrieNode storage: TrieNode[] @@ -62,7 +73,7 @@ interface StateTrie { * @param buf Element to convert. * @returns Converted element. */ -const toHexString = (buf: Buffer | string | null): string => { +export const toHexString = (buf: Buffer | string | null): string => { return '0x' + toHexBuffer(buf).toString('hex') } @@ -350,6 +361,58 @@ export const makeAccountStorageUpdateTest = async ( } } +export const makeStateTrieProofTest = async ( + state: StateTrieMap, + address: string +): Promise => { + const stateTrie = await makeStateTrie(state) + + const stateTrieWitness = await BaseTrie.prove( + stateTrie.trie, + toHexBuffer(address) + ) + + const ret = await BaseTrie.verifyProof( + stateTrie.trie.root, + toHexBuffer(address), + stateTrieWitness + ) + + return { + address, + encodedAccountState: toHexString(ret), + stateTrieWitness: toHexString(rlp.encode(stateTrieWitness)), + stateTrieRoot: toHexString(stateTrie.trie.root), + } +} + +export const makeStateTrieUpdateTest = async ( + state: StateTrieMap, + address: string, + accountState: StateTrieNode +): Promise => { + const stateTrie = await makeStateTrie(state) + + const stateTrieWitness = await BaseTrie.prove( + stateTrie.trie, + toHexBuffer(address) + ) + + const oldStateTrieRoot = toHexString(stateTrie.trie.root) + await stateTrie.trie.put( + toHexBuffer(address), + encodeAccountState(accountState) + ) + + return { + address, + encodedAccountState: toHexString(encodeAccountState(accountState)), + stateTrieWitness: toHexString(rlp.encode(stateTrieWitness)), + stateTrieRoot: oldStateTrieRoot, + newStateTrieRoot: toHexString(stateTrie.trie.root), + } +} + export const printTestParameters = (test: any): void => { console.log(Object.values(test).join(', ')) } 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 9c87fa7116d54..213ebcd46628f 100644 --- a/packages/rollup-full-node/src/app/web3-rpc-handler.ts +++ b/packages/rollup-full-node/src/app/web3-rpc-handler.ts @@ -612,7 +612,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { log.debug( `Requesting transaction count. Address [${address}], block: [${defaultBlock}].` ) - const ovmContractNonce = await this.context.stateManager.getOvmContractNonce( + const ovmContractNonce = await this.context.stateManager.getOvmContractNonceView( address ) const response = add0x(ovmContractNonce.toNumber().toString(16)) @@ -1037,7 +1037,7 @@ export class DefaultWeb3Handler implements Web3Handler, FullnodeHandler { const ovmFrom = ovmTx.from === undefined ? ZERO_ADDRESS : ovmTx.from // Check the nonce const expectedNonce = ( - await this.context.stateManager.getOvmContractNonce(ovmFrom) + await this.context.stateManager.getOvmContractNonceView(ovmFrom) ).toNumber() if (expectedNonce !== ovmTx.nonce) { throw new InvalidParametersError( diff --git a/yarn.lock b/yarn.lock index 936f6c9f557e0..7f02a365d0e1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1595,24 +1595,24 @@ form-data "^3.0.0" "@types/node@*", "@types/node@>= 8": - version "14.0.20" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.20.tgz#0da05cddbc761e1fa98af88a17244c8c1ff37231" - integrity sha512-MRn/NP3dee8yL5QhbSA6riuwkS+UOcsPUMOIOG3KMUQpuor/2TopdRBu8QaaB4fGU+gz/bzyDWt0FtUbeJ8H1A== + version "14.0.22" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.22.tgz#23ea4d88189cec7d58f9e6b66f786b215eb61bdc" + integrity sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g== "@types/node@^10.12.18", "@types/node@^10.3.2": - version "10.17.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.26.tgz#a8a119960bff16b823be4c617da028570779bcfd" - integrity sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw== + version "10.17.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.27.tgz#391cb391c75646c8ad2a7b6ed3bbcee52d1bdf19" + integrity sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg== "@types/node@^11.0.0", "@types/node@^11.11.3": - version "11.15.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.15.17.tgz#e64ad0bc4a15f68f5ad5e3d08e882f0c2f832d4a" - integrity sha512-E80F/POUH2MURsoO3XwerkVZ7HAalXqTIEHf8jrx43iTO6MPTBgNdKNxlIJCvXp0o8VhYcpY9ZSBsXqBvkf6fw== + version "11.15.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.15.18.tgz#b92ad2f5ef31a2b8a432f15db68265013d9f43db" + integrity sha512-3p2M6moxwdDFyPia2ROI8CCkRa9ZzYjvCys2TOE1xgwYDQmY49Cj0cvkdBkzh/rY9gkvzgzYOeECYtB4f0/fDA== "@types/node@^12.0.7", "@types/node@^12.6.1": - version "12.12.48" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.48.tgz#4135f064eeed9fcfb4756deea5ba2caa11603391" - integrity sha512-m3Nmo/YaDUfYzdCQlxjF5pIy7TNyDTAJhIa//xtHcF0dlgYIBKULKnmloCPtByDxtZXrWV8Pge1AKT6/lRvVWg== + version "12.12.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" + integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1654,9 +1654,9 @@ integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== "@types/underscore@*": - version "1.10.5" - resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.5.tgz#a2f741147066b04d40a4051c314d583bfcc5da00" - integrity sha512-4pI77A5w5QjFFMlEDkcMYN/B3cWACYV++J2wYT15+WcB/om3YJVejzi6i++e/13J7G4rDGNX4HR6QVq9h8fOVQ== + version "1.10.6" + resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.6.tgz#f876e9f731e3c3cc7d1d6ccbdbd118343d24ec1a" + integrity sha512-kEEmuAxiebFZrkL/si1BaP5/86sHCz491oDeSzRqB7kNokcbxDHKPTuiopj8F8xLl4p09lpiXYkxWZrBZDt41Q== "@types/web3@1.0.19": version "1.0.19" @@ -1841,6 +1841,11 @@ ansi-colors@3.2.3: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== +ansi-colors@4.1.1, ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-colors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" @@ -1848,11 +1853,6 @@ ansi-colors@^1.0.1: dependencies: ansi-wrap "^0.1.0" -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -2084,6 +2084,16 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.map@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.2.tgz#9a4159f416458a23e9483078de1106b2ef68f8ec" + integrity sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.4" + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -3290,9 +3300,9 @@ camelcase@^6.0.0: integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== caniuse-lite@^1.0.30000844: - version "1.0.30001096" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001096.tgz#5a4541af5317dc21f91f5b24d453030a35f919c0" - integrity sha512-PFTw9UyVfbkcMEFs82q8XVlRayj7HKvnhu5BLcmjGpv+SNyiWasCcWXPGJuO0rK0dhLRDJmtZcJ+LHUfypbw1w== + version "1.0.30001097" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001097.tgz#1129c40c9f5ee3282158da08fd915d301f4a9bd8" + integrity sha512-TeuSleKt/vWXaPkLVFqGDnbweYfq4IaZ6rUugFf3rWY6dlII8StUZ8Ddin0PkADfgYZ4wRqCdO2ORl4Rn5eZIA== caseless@~0.12.0: version "0.12.0" @@ -3385,6 +3395,21 @@ chokidar@3.3.0: optionalDependencies: fsevents "~2.1.1" +chokidar@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" + integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.3.0" + optionalDependencies: + fsevents "~2.1.2" + chokidar@^2.0.0: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -3591,11 +3616,6 @@ command-exists@^1.2.8: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== -commander@2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== - commander@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" @@ -4265,7 +4285,7 @@ diff@3.5.0, diff@^3.1.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== -diff@^4.0.1: +diff@4.0.2, diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== @@ -4393,9 +4413,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.47: - version "1.3.494" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.494.tgz#0d2dba65b69d696c5b71abb37552ff055fb32a5c" - integrity sha512-EOZuaDT3L1sCIMAVN5J0nGuGWVq5dThrdl0d8XeDYf4MOzbXqZ19OLKesN8TZj0RxtpYjqHpiw/fR6BKWdMwYA== + version "1.3.496" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz#3f43d32930481d82ad3663d79658e7c59a58af0b" + integrity sha512-TXY4mwoyowwi4Lsrq9vcTUYBThyc1b2hXaTZI13p8/FRhY2CTaq5lK+DVjhYkKiTLsKt569Xes+0J5JsVXFurQ== elliptic@6.3.3: version "6.3.3" @@ -4465,11 +4485,11 @@ encoding-down@^6.3.0: level-errors "^2.0.0" encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: - iconv-lite "~0.4.13" + iconv-lite "^0.6.2" end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" @@ -4514,7 +4534,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5: version "1.17.6" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== @@ -4531,6 +4551,24 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-get-iterator@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" + integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== + dependencies: + es-abstract "^1.17.4" + has-symbols "^1.0.1" + is-arguments "^1.0.4" + is-map "^2.0.1" + is-set "^2.0.1" + is-string "^1.0.5" + isarray "^2.0.5" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -5669,6 +5707,14 @@ find-up@3.0.0, find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@4.1.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -5684,14 +5730,6 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -5931,7 +5969,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.1.1: +fsevents@~2.1.1, fsevents@~2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== @@ -6243,10 +6281,10 @@ glob-watcher@^5.0.3: just-debounce "^1.0.0" object.defaults "^1.1.0" -glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -6255,10 +6293,10 @@ glob@7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== +glob@7.1.6, glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -6278,18 +6316,6 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-dirs@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -6544,6 +6570,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" @@ -6638,11 +6669,6 @@ hdkey@^1.1.0: safe-buffer "^5.1.1" secp256k1 "^3.0.1" -he@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= - he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -6751,13 +6777,20 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + idna-uts46-hx@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9" @@ -7129,6 +7162,11 @@ is-installed-globally@^0.2.0: global-dirs "^0.1.1" is-path-inside "^2.1.0" +is-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" + integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -7216,6 +7254,11 @@ is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== +is-set@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" + integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== + is-ssh@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3" @@ -7228,6 +7271,11 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-string@^1.0.4, is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -7284,6 +7332,11 @@ isarray@1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -7314,6 +7367,19 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +iterate-iterator@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" + integrity sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw== + +iterate-value@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57" + integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== + dependencies: + es-get-iterator "^1.0.2" + iterate-iterator "^1.0.1" + jest-docblock@^21.0.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" @@ -8459,11 +8525,6 @@ minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -8520,13 +8581,6 @@ mkdirp@*: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - mkdirp@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" @@ -8541,22 +8595,36 @@ mkdirp@0.5.5, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: dependencies: minimist "^1.2.5" -mocha@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" - integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== +mocha@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.0.1.tgz#fe01f0530362df271aa8f99510447bc38b88d8ed" + integrity sha512-vefaXfdYI8+Yo8nPZQQi0QO2o+5q9UIMX1jZ1XMmK3+4+CQjc7+B0hPdUeglXiTlr8IHMVRo63IhO9Mzt6fxOg== dependencies: + ansi-colors "4.1.1" browser-stdout "1.3.1" - commander "2.15.1" - debug "3.1.0" - diff "3.5.0" + chokidar "3.3.1" + debug "3.2.6" + diff "4.0.2" escape-string-regexp "1.0.5" - glob "7.1.2" + find-up "4.1.0" + glob "7.1.6" growl "1.10.5" - he "1.1.1" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "3.0.0" minimatch "3.0.4" - mkdirp "0.5.1" - supports-color "5.4.0" + ms "2.1.2" + object.assign "4.1.0" + promise.allsettled "1.0.2" + serialize-javascript "3.0.0" + strip-json-comments "3.0.1" + supports-color "7.1.0" + which "2.0.2" + wide-align "1.1.3" + workerpool "6.0.0" + yargs "13.3.2" + yargs-parser "13.1.2" + yargs-unparser "1.6.0" mocha@^6.0.2, mocha@^6.1.4: version "6.2.3" @@ -8654,7 +8722,7 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@^2.0.0, ms@^2.1.1: +ms@2.1.2, ms@^2.0.0, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -9598,7 +9666,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -9708,6 +9776,17 @@ promise-to-callback@^1.0.0: is-fn "^1.0.0" set-immediate-shim "^1.0.1" +promise.allsettled@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.2.tgz#d66f78fbb600e83e863d893e98b3d4376a9c47c9" + integrity sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg== + dependencies: + array.prototype.map "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + iterate-value "^1.0.0" + promzard@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" @@ -10105,6 +10184,13 @@ readdirp@~3.2.0: dependencies: picomatch "^2.0.4" +readdirp@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== + dependencies: + picomatch "^2.0.7" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -10464,7 +10550,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -10643,6 +10729,11 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" +serialize-javascript@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.0.0.tgz#492e489a2d77b7b804ad391a5f5d97870952548e" + integrity sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw== + serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -11269,6 +11360,11 @@ strip-json-comments@2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +strip-json-comments@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + strip-outer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" @@ -11285,13 +11381,6 @@ strong-log-transformer@^2.0.0: minimist "^1.2.0" through "^2.3.4" -supports-color@5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== - dependencies: - has-flag "^3.0.0" - supports-color@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" @@ -11299,6 +11388,13 @@ supports-color@6.0.0: dependencies: has-flag "^3.0.0" +supports-color@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -11681,12 +11777,12 @@ truffle-hdwallet-provider@^1.0.17: websocket "^1.0.28" truffle@^5.1.12: - version "5.1.33" - resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.1.33.tgz#6cd202f288d35b30a31ffec3e1b96fb367debbdf" - integrity sha512-zV220OC6YtKSOViA+eQpU61orAlNX4msDogecUsjsxjH0MZGIVPMfsh1LiA817KXIg1uEM7G5XPjTaCJeRB8iw== + version "5.1.34" + resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.1.34.tgz#dfc311142a6447137cb18abfbb2722dce65dfd62" + integrity sha512-Z+EWLI8TOqRbPOu7PhhdFZ0EypIf+1wA6Z9UPKx2LMjX9uQzCl4e+d20Qvfz9uD/2C//pJlvXbyXu9LuYeumgQ== dependencies: app-module-path "^2.2.0" - mocha "5.2.0" + mocha "8.0.1" original-require "1.0.1" ts-essentials@^2.0.7: @@ -13754,6 +13850,13 @@ which@1.3.1, which@^1.1.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: dependencies: isexe "^2.0.0" +which@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@1.1.3, wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -13783,6 +13886,11 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= +workerpool@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.0.tgz#85aad67fa1a2c8ef9386a1b43539900f61d03d58" + integrity sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA== + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"