diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ECDSAContractAccount.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ECDSAContractAccount.sol index 1622c5d11527..2a0291eb6756 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ECDSAContractAccount.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ECDSAContractAccount.sol @@ -15,6 +15,8 @@ import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_Sa */ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { + address constant ETH_ERC20_ADDRESS = 0x4200000000000000000000000000000000000006; + /******************** * Public Functions * ********************/ @@ -44,35 +46,48 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ) { address ovmExecutionManager = msg.sender; + bool isEthSign = _signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE; // Address of this contract within the ovm (ovmADDRESS) should be the same as the // recovered address of the user who signed this message. This is how we manage to shim // account abstraction even though the user isn't a contract. - require( + // Need to make sure that the transaction nonce is right and bump it if so. + Lib_SafeExecutionManagerWrapper.safeREQUIRE( + msg.sender, Lib_ECDSAUtils.recover( _transaction, - _signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE, + isEthSign, _v, _r, - _s, - Lib_SafeExecutionManagerWrapper.safeCHAINID(ovmExecutionManager) + _s ) == Lib_SafeExecutionManagerWrapper.safeADDRESS(ovmExecutionManager), "Signature provided for EOA transaction execution is invalid." ); - Lib_OVMCodec.EOATransaction memory decodedTx = Lib_OVMCodec.decodeEOATransaction(_transaction); + Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction(_transaction, isEthSign); - // Need to make sure that the transaction nonce is right and bump it if so. - require( - decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager) + 1, + // Need to make sure that the transaction nonce is right. + Lib_SafeExecutionManagerWrapper.safeREQUIRE( + msg.sender, + decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager), "Transaction nonce does not match the expected nonce." ); + // Transfer fee to relayer. + address relayer = Lib_SafeExecutionManagerWrapper.safeCALLER(ovmExecutionManager); + uint256 fee = decodedTx.gasLimit * decodedTx.gasPrice; + Lib_SafeExecutionManagerWrapper.safeCALL( + ovmExecutionManager, + gasleft(), + ETH_ERC20_ADDRESS, + abi.encodeWithSignature("transfer(address,uint256)", relayer, fee) + ); + // Contract creations are signalled by sending a transaction to the zero address. - if (decodedTx.target == address(0)) { + if (decodedTx.to == address(0)) { address created = Lib_SafeExecutionManagerWrapper.safeCREATE( ovmExecutionManager, - decodedTx.gasLimit, + decodedTx.gasLimit - 2000, decodedTx.data ); @@ -83,12 +98,12 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { // We only want to bump the nonce for `ovmCALL` because `ovmCREATE` automatically bumps // the nonce of the calling account. Normally an EOA would bump the nonce for both // cases, but since this is a contract we'd end up bumping the nonce twice. - Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, decodedTx.nonce); + Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, decodedTx.nonce + 1); return Lib_SafeExecutionManagerWrapper.safeCALL( ovmExecutionManager, decodedTx.gasLimit, - decodedTx.target, + decodedTx.to, decodedTx.data ); } diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ProxyEOA.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ProxyEOA.sol new file mode 100644 index 000000000000..92176373d4b8 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ProxyEOA.sol @@ -0,0 +1,99 @@ +pragma solidity ^0.7.0; + +/* Library Imports */ +import { Lib_BytesUtils } from "../../libraries/utils/Lib_BytesUtils.sol"; +import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; +import { Lib_ECDSAUtils } from "../../libraries/utils/Lib_ECDSAUtils.sol"; +import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol"; + +/** + * @title OVM_ProxyEOA + */ +contract OVM_ProxyEOA { + + /*************** + * Constructor * + ***************/ + + constructor( + address _implementation + ) { + _setImplementation(_implementation); + } + + + /********************* + * Fallback Function * + *********************/ + + fallback() + external + { + (bool success, bytes memory returndata) = Lib_SafeExecutionManagerWrapper.safeDELEGATECALL( + msg.sender, + gasleft(), + getImplementation(), + msg.data + ); + + if (success) { + assembly { + return(add(returndata, 0x20), mload(returndata)) + } + } else { + Lib_SafeExecutionManagerWrapper.safeREVERT( + msg.sender, + string(returndata) + ); + } + } + + + /******************** + * Public Functions * + ********************/ + + function upgrade( + address _implementation + ) + external + { + Lib_SafeExecutionManagerWrapper.safeREQUIRE( + msg.sender, + Lib_SafeExecutionManagerWrapper.safeADDRESS(msg.sender) == Lib_SafeExecutionManagerWrapper.safeCALLER(msg.sender), + "EOAs can only upgrade their own EOA implementation" + ); + + _setImplementation(_implementation); + } + + function getImplementation() + public + returns ( + address _implementation + ) + { + return address(uint160(uint256( + Lib_SafeExecutionManagerWrapper.safeSLOAD( + msg.sender, + bytes32(uint256(0)) + ) + ))); + } + + /********************** + * Internal Functions * + **********************/ + + function _setImplementation( + address _implementation + ) + internal + { + Lib_SafeExecutionManagerWrapper.safeSSTORE( + msg.sender, + bytes32(uint256(0)), + bytes32(uint256(uint160(_implementation))) + ); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol index 0da331db9b9d..523f302b4d4e 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol @@ -14,6 +14,7 @@ import { iOVM_SafetyChecker } from "../../iOVM/execution/iOVM_SafetyChecker.sol" /* Contract Imports */ import { OVM_ECDSAContractAccount } from "../accounts/OVM_ECDSAContractAccount.sol"; +import { OVM_ProxyEOA } from "../accounts/OVM_ProxyEOA.sol"; /** * @title OVM_ExecutionManager @@ -71,7 +72,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { globalContext = _globalContext; } - + /********************** * Function Modifiers * **********************/ @@ -129,8 +130,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { // OVM_StateManager (expected to be an OVM_StateTransitioner). We can revert here because // this would make the `run` itself invalid. require( - msg.sender == ovmStateManager.owner(), - "Only the owner of the ovmStateManager can call this function" + ovmStateManager.isAuthenticated(msg.sender), + "Only authenticated addresses in ovmStateManager can call this function" ); // Check whether we need to start a new epoch, do so if necessary. @@ -444,7 +445,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { // actually execute the transaction). address eoa = ecrecover( _messageHash, - (_v - uint8(ovmCHAINID()) * 2) - 8, + _v + 27, _r, _s ); @@ -463,16 +464,22 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver { // We always need to initialize the contract with the default account values. _initPendingAccount(eoa); + // Temporarily set the current address so it's easier to access on L2. + address prevADDRESS = messageContext.ovmADDRESS; + messageContext.ovmADDRESS = eoa; + // Now actually create the account and get its bytecode. We're not worried about reverts // (other than out of gas, which we can't capture anyway) because this contract is trusted. - OVM_ECDSAContractAccount eoaContractAccount = new OVM_ECDSAContractAccount(); - bytes memory deployedCode = Lib_EthUtils.getCode(address(eoaContractAccount)); + OVM_ProxyEOA proxyEOA = new OVM_ProxyEOA(0x4200000000000000000000000000000000000003); + + // Reset the address now that we're done deploying. + messageContext.ovmADDRESS = prevADDRESS; // Commit the account with its final values. _commitPendingAccount( eoa, - address(eoaContractAccount), - keccak256(deployedCode) + address(proxyEOA), + keccak256(Lib_EthUtils.getCode(address(proxyEOA))) ); } diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_StateManager.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_StateManager.sol index 93e47dc5d913..f2efa38f91f8 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_StateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_StateManager.sol @@ -71,11 +71,26 @@ contract OVM_StateManager is iOVM_StateManager { _; } + /*************************** + * Public Functions: Misc * + ***************************/ + + + function isAuthenticated( + address _address + ) + override + public + view + returns (bool) + { + return (_address == owner || _address == ovmExecutionManager); + } /*************************** * Public Functions: Setup * ***************************/ - + /** * Sets the address of the OVM_ExecutionManager. * @param _ovmExecutionManager Address of the OVM_ExecutionManager. diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/precompiles/OVM_ProxySequencerEntrypoint.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/precompiles/OVM_ProxySequencerEntrypoint.sol new file mode 100644 index 000000000000..715ee0f1c041 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/precompiles/OVM_ProxySequencerEntrypoint.sol @@ -0,0 +1,116 @@ +pragma solidity ^0.7.0; + +/* Library Imports */ +import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol"; + +/** + * @title OVM_ProxySequencerEntrypoint + */ +contract OVM_ProxySequencerEntrypoint { + + /********************* + * Fallback Function * + *********************/ + + fallback() + external + { + Lib_SafeExecutionManagerWrapper.safeDELEGATECALL( + msg.sender, + gasleft(), + _getImplementation(), + msg.data + ); + } + + + /******************** + * Public Functions * + ********************/ + + function init( + address _implementation, + address _owner + ) + external + { + Lib_SafeExecutionManagerWrapper.safeREQUIRE( + msg.sender, + _getOwner() == address(0), + "ProxySequencerEntrypoint has already been inited" + ); + _setOwner(_owner); + _setImplementation(_implementation); + } + + function upgrade( + address _implementation + ) + external + { + Lib_SafeExecutionManagerWrapper.safeREQUIRE( + msg.sender, + _getOwner() == Lib_SafeExecutionManagerWrapper.safeCALLER(msg.sender), + "Only owner can upgrade the Entrypoint" + ); + + _setImplementation(_implementation); + } + + + /********************** + * Internal Functions * + **********************/ + + function _setImplementation( + address _implementation + ) + internal + { + Lib_SafeExecutionManagerWrapper.safeSSTORE( + msg.sender, + bytes32(uint256(0)), + bytes32(uint256(uint160(_implementation))) + ); + } + + function _getImplementation() + internal + returns ( + address _implementation + ) + { + return address(uint160(uint256( + Lib_SafeExecutionManagerWrapper.safeSLOAD( + msg.sender, + bytes32(uint256(0)) + ) + ))); + } + + function _setOwner( + address _owner + ) + internal + { + Lib_SafeExecutionManagerWrapper.safeSSTORE( + msg.sender, + bytes32(uint256(1)), + bytes32(uint256(uint160(_owner))) + ); + } + + function _getOwner() + internal + returns ( + address _owner + ) + { + return address(uint160(uint256( + Lib_SafeExecutionManagerWrapper.safeSLOAD( + msg.sender, + bytes32(uint256(1)) + ) + ))); + } +} diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/precompiles/OVM_SequencerEntrypoint.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/precompiles/OVM_SequencerEntrypoint.sol new file mode 100644 index 000000000000..befa3624032b --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/precompiles/OVM_SequencerEntrypoint.sol @@ -0,0 +1,119 @@ +pragma solidity ^0.7.0; + +/* Library Imports */ +import { Lib_BytesUtils } from "../../libraries/utils/Lib_BytesUtils.sol"; +import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; +import { Lib_ECDSAUtils } from "../../libraries/utils/Lib_ECDSAUtils.sol"; +import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol"; + +/** + * @title OVM_SequencerEntrypoint + */ +contract OVM_SequencerEntrypoint { + + /********* + * Enums * + *********/ + + enum TransactionType { + NATIVE_ETH_TRANSACTION, + ETH_SIGNED_MESSAGE + } + + + /********************* + * Fallback Function * + *********************/ + + /** + * Uses a custom "compressed" format to save on calldata gas: + * calldata[00:01]: transaction type (0 == EIP 155, 2 == Eth Sign Message) + * calldata[01:33]: signature "r" parameter + * calldata[33:65]: signature "s" parameter + * calldata[66:69]: transaction gas limit + * calldata[69:72]: transaction gas price + * calldata[72:75]: transaction nonce + * calldata[75:95]: transaction target address + * calldata[95:XX]: transaction data + */ + fallback() + external + { + TransactionType transactionType = _getTransactionType(Lib_BytesUtils.toUint8(msg.data, 0)); + + bytes32 r = Lib_BytesUtils.toBytes32(Lib_BytesUtils.slice(msg.data, 1, 32)); + bytes32 s = Lib_BytesUtils.toBytes32(Lib_BytesUtils.slice(msg.data, 33, 32)); + uint8 v = Lib_BytesUtils.toUint8(msg.data, 65); + + // Remainder is the transaction to execute. + bytes memory compressedTx = Lib_BytesUtils.slice(msg.data, 66); + bool isEthSignedMessage = transactionType == TransactionType.ETH_SIGNED_MESSAGE; + + // Need to decompress and then re-encode the transaction based on the original encoding. + bytes memory encodedTx = Lib_OVMCodec.encodeEIP155Transaction( + Lib_OVMCodec.decompressEIP155Transaction(compressedTx), + isEthSignedMessage + ); + + address target = Lib_ECDSAUtils.recover( + encodedTx, + isEthSignedMessage, + uint8(v), + r, + s + ); + + if (Lib_SafeExecutionManagerWrapper.safeEXTCODESIZE(msg.sender, target) == 0) { + // ProxyEOA has not yet been deployed for this EOA. + bytes32 messageHash = Lib_ECDSAUtils.getMessageHash(encodedTx, isEthSignedMessage); + Lib_SafeExecutionManagerWrapper.safeCREATEEOA(msg.sender, messageHash, uint8(v), r, s); + } + + // ProxyEOA has been deployed for this EOA, continue to CALL. + bytes memory callbytes = abi.encodeWithSignature( + "execute(bytes,uint8,uint8,bytes32,bytes32)", + encodedTx, + isEthSignedMessage, + uint8(v), + r, + s + ); + + Lib_SafeExecutionManagerWrapper.safeCALL( + msg.sender, + gasleft(), + target, + callbytes + ); + } + + + /********************** + * Internal Functions * + **********************/ + + /** + * Converts a uint256 into a TransactionType enum. + * @param _transactionType Transaction type index. + * @return Transaction type enum value. + */ + function _getTransactionType( + uint8 _transactionType + ) + internal + returns ( + TransactionType + ) + { + if (_transactionType == 0) { + return TransactionType.NATIVE_ETH_TRANSACTION; + } if (_transactionType == 2) { + return TransactionType.ETH_SIGNED_MESSAGE; + } else { + Lib_SafeExecutionManagerWrapper.safeREVERT( + msg.sender, + "Transaction type must be 0 or 2" + ); + } + } +} diff --git a/packages/contracts/contracts/optimistic-ethereum/iOVM/execution/iOVM_StateManager.sol b/packages/contracts/contracts/optimistic-ethereum/iOVM/execution/iOVM_StateManager.sol index 2269f79224ac..11c34eeedbff 100644 --- a/packages/contracts/contracts/optimistic-ethereum/iOVM/execution/iOVM_StateManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/iOVM/execution/iOVM_StateManager.sol @@ -21,6 +21,11 @@ interface iOVM_StateManager { ITEM_COMMITTED } + /*************************** + * Public Functions: Misc * + ***************************/ + + function isAuthenticated(address _address) external view returns (bool); /*************************** * Public Functions: Setup * diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol index c2c3ffe2f59d..c644d55bc07e 100644 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol @@ -5,6 +5,7 @@ pragma experimental ABIEncoderV2; /* Library Imports */ import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol"; import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol"; +import { Lib_BytesUtils } from "../utils/Lib_BytesUtils.sol"; /** * @title Lib_OVMCodec @@ -32,8 +33,8 @@ library Lib_OVMCodec { *********/ enum EOASignatureType { - ETH_SIGNED_MESSAGE, - NATIVE_TRANSACTON + EIP155_TRANSACTON, + ETH_SIGNED_MESSAGE } enum QueueOrigin { @@ -99,11 +100,14 @@ library Lib_OVMCodec { uint40 blockNumber; } - struct EOATransaction { - address target; + struct EIP155Transaction { uint256 nonce; + uint256 gasPrice; uint256 gasLimit; + address to; + uint256 value; bytes data; + uint256 chainId; } @@ -116,25 +120,118 @@ library Lib_OVMCodec { * @param _transaction Encoded EOA transaction. * @return _decoded Transaction decoded into a struct. */ - function decodeEOATransaction( - bytes memory _transaction + function decodeEIP155Transaction( + bytes memory _transaction, + bool _isEthSignedMessage ) internal pure returns ( - EOATransaction memory _decoded + EIP155Transaction memory _decoded ) { - Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_transaction); + if (_isEthSignedMessage) { + ( + uint _nonce, + uint _gasLimit, + uint _gasPrice, + uint _chainId, + address _to, + bytes memory _data + ) = abi.decode( + _transaction, + (uint, uint, uint, uint, address ,bytes) + ); + return EIP155Transaction({ + nonce: _nonce, + gasPrice: _gasPrice, + gasLimit: _gasLimit, + to: _to, + value: 0, + data: _data, + chainId: _chainId + }); + } else { + Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_transaction); + + return EIP155Transaction({ + nonce: Lib_RLPReader.readUint256(decoded[0]), + gasPrice: Lib_RLPReader.readUint256(decoded[1]), + gasLimit: Lib_RLPReader.readUint256(decoded[2]), + to: Lib_RLPReader.readAddress(decoded[3]), + value: Lib_RLPReader.readUint256(decoded[4]), + data: Lib_RLPReader.readBytes(decoded[5]), + chainId: Lib_RLPReader.readUint256(decoded[6]) + }); + } + } - return EOATransaction({ - nonce: Lib_RLPReader.readUint256(decoded[0]), - gasLimit: Lib_RLPReader.readUint256(decoded[2]), - target: Lib_RLPReader.readAddress(decoded[3]), - data: Lib_RLPReader.readBytes(decoded[5]) + function decompressEIP155Transaction( + bytes memory _transaction + ) + internal + pure + returns ( + EIP155Transaction memory _decompressed + ) + { + return EIP155Transaction({ + gasLimit: Lib_BytesUtils.toUint24(_transaction, 0), + gasPrice: uint256(Lib_BytesUtils.toUint24(_transaction, 3)) * 1000000, + nonce: Lib_BytesUtils.toUint24(_transaction, 6), + to: Lib_BytesUtils.toAddress(_transaction, 9), + data: Lib_BytesUtils.slice(_transaction, 29), + chainId: 420, + value: 0 }); } + /** + * Encodes an EOA transaction back into the original transaction. + * @param _transaction EIP155transaction to encode. + * @param _isEthSignedMessage Whether or not this was an eth signed message. + * @return Encoded transaction. + */ + function encodeEIP155Transaction( + EIP155Transaction memory _transaction, + bool _isEthSignedMessage + ) + internal + pure + returns ( + bytes memory + ) + { + if (_isEthSignedMessage) { + return abi.encode( + _transaction.nonce, + _transaction.gasLimit, + _transaction.gasPrice, + _transaction.chainId, + _transaction.to, + _transaction.data + ); + } else { + bytes[] memory raw = new bytes[](9); + + raw[0] = Lib_RLPWriter.writeUint(_transaction.nonce); + raw[1] = Lib_RLPWriter.writeUint(_transaction.gasPrice); + raw[2] = Lib_RLPWriter.writeUint(_transaction.gasLimit); + if (_transaction.to == address(0)) { + raw[3] = Lib_RLPWriter.writeBytes(''); + } else { + raw[3] = Lib_RLPWriter.writeAddress(_transaction.to); + } + raw[4] = Lib_RLPWriter.writeUint(0); + raw[5] = Lib_RLPWriter.writeBytes(_transaction.data); + raw[6] = Lib_RLPWriter.writeUint(_transaction.chainId); + raw[7] = Lib_RLPWriter.writeBytes(bytes('')); + raw[8] = Lib_RLPWriter.writeBytes(bytes('')); + + return Lib_RLPWriter.writeList(raw); + } + } + /** * Encodes a standard OVM transaction. * @param _transaction OVM transaction to encode. diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_BytesUtils.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_BytesUtils.sol index 6f54529895df..28060e872f1d 100644 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_BytesUtils.sol +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_BytesUtils.sol @@ -192,6 +192,42 @@ library Lib_BytesUtils { return uint256(toBytes32(_bytes)); } + function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { + require(_start + 3 >= _start, "toUint24_overflow"); + require(_bytes.length >= _start + 3 , "toUint24_outOfBounds"); + uint24 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x3), _start)) + } + + return tempUint; + } + + function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { + require(_start + 1 >= _start, "toUint8_overflow"); + require(_bytes.length >= _start + 1 , "toUint8_outOfBounds"); + uint8 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x1), _start)) + } + + return tempUint; + } + + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { + require(_start + 20 >= _start, "toAddress_overflow"); + require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); + address tempAddress; + + assembly { + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + } + + return tempAddress; + } + function toNibbles( bytes memory _bytes ) diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_ECDSAUtils.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_ECDSAUtils.sol index a164a2c1f854..fb5f187c46c0 100644 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_ECDSAUtils.sol +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/utils/Lib_ECDSAUtils.sol @@ -17,7 +17,6 @@ library Lib_ECDSAUtils { * @param _v Signature `v` parameter. * @param _r Signature `r` parameter. * @param _s Signature `s` parameter. - * @param _chainId Chain ID parameter. * @return _sender Signer address. */ function recover( @@ -25,8 +24,7 @@ library Lib_ECDSAUtils { bool _isEthSignedMessage, uint8 _v, bytes32 _r, - bytes32 _s, - uint256 _chainId + bytes32 _s ) internal pure @@ -34,24 +32,29 @@ library Lib_ECDSAUtils { address _sender ) { - bytes32 messageHash; - uint8 v; - if (_isEthSignedMessage) { - messageHash = getEthSignedMessageHash(_message); - v = _v; - } else { - messageHash = getNativeMessageHash(_message); - v = (_v - uint8(_chainId) * 2) - 8; - } + bytes32 messageHash = getMessageHash(_message, _isEthSignedMessage); return ecrecover( messageHash, - v, + _v + 27, _r, _s ); } + function getMessageHash( + bytes memory _message, + bool _isEthSignedMessage + ) + internal + pure + returns (bytes32) { + if (_isEthSignedMessage) { + return getEthSignedMessageHash(_message); + } + return getNativeMessageHash(_message); + } + /************************************* * Private Functions: ECDSA Recovery * diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol index 57e8b517fcc0..f8d586a43bc8 100644 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol @@ -44,6 +44,40 @@ library Lib_SafeExecutionManagerWrapper { return abi.decode(returndata, (bool, bytes)); } + /** + * Makes an ovmCALL and performs all the necessary safety checks. + * @param _ovmExecutionManager Address of the OVM_ExecutionManager. + * @param _gasLimit Gas limit for the call. + * @param _target Address to call. + * @param _calldata Data to send to the call. + * @return _success Whether or not the call reverted. + * @return _returndata Data returned by the call. + */ + function safeDELEGATECALL( + address _ovmExecutionManager, + uint256 _gasLimit, + address _target, + bytes memory _calldata + ) + internal + returns ( + bool _success, + bytes memory _returndata + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + _ovmExecutionManager, + abi.encodeWithSignature( + "ovmDELEGATECALL(uint256,address,bytes)", + _gasLimit, + _target, + _calldata + ) + ); + + return abi.decode(returndata, (bool, bytes)); + } + /** * Performs an ovmCREATE and the necessary safety checks. * @param _ovmExecutionManager Address of the OVM_ExecutionManager. @@ -73,6 +107,32 @@ library Lib_SafeExecutionManagerWrapper { return abi.decode(returndata, (address)); } + /** + * Performs an ovmEXTCODESIZE and the necessary safety checks. + * @param _ovmExecutionManager Address of the OVM_ExecutionManager. + * @param _contract Address of the contract to query the size of. + * @return _EXTCODESIZE Size of the requested contract in bytes. + */ + function safeEXTCODESIZE( + address _ovmExecutionManager, + address _contract + ) + internal + returns ( + uint256 _EXTCODESIZE + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + _ovmExecutionManager, + abi.encodeWithSignature( + "ovmEXTCODESIZE(address)", + _contract + ) + ); + + return abi.decode(returndata, (uint256)); + } + /** * Performs a safe ovmCHAINID call. * @param _ovmExecutionManager Address of the OVM_ExecutionManager. @@ -96,6 +156,29 @@ library Lib_SafeExecutionManagerWrapper { return abi.decode(returndata, (uint256)); } + /** + * Performs a safe ovmCALLER call. + * @param _ovmExecutionManager Address of the OVM_ExecutionManager. + * @return _CALLER Result of calling ovmCALLER. + */ + function safeCALLER( + address _ovmExecutionManager + ) + internal + returns ( + address _CALLER + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + _ovmExecutionManager, + abi.encodeWithSignature( + "ovmCALLER()" + ) + ); + + return abi.decode(returndata, (address)); + } + /** * Performs a safe ovmADDRESS call. * @param _ovmExecutionManager Address of the OVM_ExecutionManager. @@ -162,6 +245,118 @@ library Lib_SafeExecutionManagerWrapper { ); } + /** + * Performs a safe ovmCREATEEOA call. + * @param _ovmExecutionManager Address of the OVM_ExecutionManager. + * @param _messageHash Message hash which was signed by EOA + * @param _v v value of signature (0 or 1) + * @param _r r value of signature + * @param _s s value of signature + */ + function safeCREATEEOA( + address _ovmExecutionManager, + bytes32 _messageHash, + uint8 _v, + bytes32 _r, + bytes32 _s + ) + internal + { + _safeExecutionManagerInteraction( + _ovmExecutionManager, + abi.encodeWithSignature( + "ovmCREATEEOA(bytes32,uint8,bytes32,bytes32)", + _messageHash, + _v, + _r, + _s + ) + ); + } + + /** + * Performs a safe REVERT. + * @param _ovmExecutionManager Address of the OVM_ExecutionManager. + * @param _reason String revert reason to pass along with the REVERT. + */ + function safeREVERT( + address _ovmExecutionManager, + string memory _reason + ) + internal + { + _safeExecutionManagerInteraction( + _ovmExecutionManager, + abi.encodeWithSignature( + "ovmREVERT(bytes)", + bytes(_reason) + ) + ); + } + + /** + * Performs a safe "require". + * @param _ovmExecutionManager Address of the OVM_ExecutionManager. + * @param _condition Boolean condition that must be true or will revert. + * @param _reason String revert reason to pass along with the REVERT. + */ + function safeREQUIRE( + address _ovmExecutionManager, + bool _condition, + string memory _reason + ) + internal + { + if (!_condition) { + safeREVERT( + _ovmExecutionManager, + _reason + ); + } + } + + /** + * Performs a safe ovmSLOAD call. + */ + function safeSLOAD( + address _ovmExecutionManager, + bytes32 _key + ) + internal + returns ( + bytes32 + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + _ovmExecutionManager, + abi.encodeWithSignature( + "ovmSLOAD(bytes32)", + _key + ) + ); + + return abi.decode(returndata, (bytes32)); + } + + /** + * Performs a safe ovmSSTORE call. + */ + function safeSSTORE( + address _ovmExecutionManager, + bytes32 _key, + bytes32 _value + ) + internal + { + _safeExecutionManagerInteraction( + _ovmExecutionManager, + abi.encodeWithSignature( + "ovmSSTORE(bytes32,bytes32)", + _key, + _value + ) + ); + } /********************* * Private Functions * diff --git a/packages/contracts/contracts/optimistic-ethereum/mockOVM/accounts/mockOVM_ECDSAContractAccount.sol b/packages/contracts/contracts/optimistic-ethereum/mockOVM/accounts/mockOVM_ECDSAContractAccount.sol index 58a2905cf9ab..dd4009f25156 100644 --- a/packages/contracts/contracts/optimistic-ethereum/mockOVM/accounts/mockOVM_ECDSAContractAccount.sol +++ b/packages/contracts/contracts/optimistic-ethereum/mockOVM/accounts/mockOVM_ECDSAContractAccount.sol @@ -44,12 +44,18 @@ contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ) { address ovmExecutionManager = msg.sender; + bool isEthSign = _signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE; + Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction(_transaction, isEthSign); - // Skip signature validation in this mock. - Lib_OVMCodec.EOATransaction memory decodedTx = Lib_OVMCodec.decodeEOATransaction(_transaction); + // Need to make sure that the transaction nonce is right. + Lib_SafeExecutionManagerWrapper.safeREQUIRE( + msg.sender, + decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager), + "Transaction nonce does not match the expected nonce." + ); // Contract creations are signalled by sending a transaction to the zero address. - if (decodedTx.target == address(0)) { + if (decodedTx.to == address(0)) { address created = Lib_SafeExecutionManagerWrapper.safeCREATE( ovmExecutionManager, decodedTx.gasLimit, @@ -59,21 +65,36 @@ contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { // If the created address is the ZERO_ADDRESS then we know the deployment failed, though not why return (created != address(0), abi.encode(created)); } else { - _incrementNonce(); - (_success, _returndata) = Lib_SafeExecutionManagerWrapper.safeCALL( + // We only want to bump the nonce for `ovmCALL` because `ovmCREATE` automatically bumps + // the nonce of the calling account. Normally an EOA would bump the nonce for both + // cases, but since this is a contract we'd end up bumping the nonce twice. + Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, decodedTx.nonce + 1); + + return Lib_SafeExecutionManagerWrapper.safeCALL( ovmExecutionManager, decodedTx.gasLimit, - decodedTx.target, + decodedTx.to, decodedTx.data ); } } - function _incrementNonce() - internal + function qall( + uint256 _gasLimit, + address _to, + bytes memory _data + ) + public + returns ( + bool _success, + bytes memory _returndata + ) { - address ovmExecutionManager = msg.sender; - uint nonce = Lib_SafeExecutionManagerWrapper.safeGETNONCE(ovmExecutionManager) + 1; - Lib_SafeExecutionManagerWrapper.safeSETNONCE(ovmExecutionManager, nonce); + return Lib_SafeExecutionManagerWrapper.safeCALL( + msg.sender, + _gasLimit, + _to, + _data + ); } } diff --git a/packages/contracts/contracts/test-helpers/Helper_PrecompileCaller.sol b/packages/contracts/contracts/test-helpers/Helper_PrecompileCaller.sol index 930209edcd26..60bc34b26269 100644 --- a/packages/contracts/contracts/test-helpers/Helper_PrecompileCaller.sol +++ b/packages/contracts/contracts/test-helpers/Helper_PrecompileCaller.sol @@ -17,6 +17,27 @@ contract Helper_PrecompileCaller is Helper_SimpleProxy { } } + function callPrecompileAbi( + address _precompile, + bytes memory _data + ) + public + returns ( + bytes memory + ) + { + + bool success; + bytes memory returndata; + if (msg.sender == owner) { + (success, returndata) = _precompile.call(_data); + } else { + (success, returndata) = target.call(msg.data); + } + require(success, "Precompile call reverted"); + return returndata; + } + function getL1MessageSender( address _precompile, bytes memory _data diff --git a/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol b/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol index 3333cb80e27a..6b0e233b888a 100644 --- a/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol +++ b/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol @@ -10,16 +10,17 @@ import { Lib_OVMCodec } from "../../optimistic-ethereum/libraries/codec/Lib_OVMC */ contract TestLib_OVMCodec { - function decodeEOATransaction( - bytes memory _transaction + function decodeEIP155Transaction( + bytes memory _transaction, + bool _isEthSignedMessage ) public pure returns ( - Lib_OVMCodec.EOATransaction memory _decoded + Lib_OVMCodec.EIP155Transaction memory _decoded ) { - return Lib_OVMCodec.decodeEOATransaction(_transaction); + return Lib_OVMCodec.decodeEIP155Transaction(_transaction, _isEthSignedMessage); } function encodeTransaction( @@ -45,4 +46,16 @@ contract TestLib_OVMCodec { { return Lib_OVMCodec.hashTransaction(_transaction); } + + function decompressEIP155Transaction( + bytes memory _transaction + ) + public + pure + returns ( + Lib_OVMCodec.EIP155Transaction memory _decompressed + ) + { + return Lib_OVMCodec.decompressEIP155Transaction(_transaction); + } } diff --git a/packages/contracts/contracts/test-libraries/utils/TestLib_ECDSAUtils.sol b/packages/contracts/contracts/test-libraries/utils/TestLib_ECDSAUtils.sol index d3ebc5c632d2..fb843312a458 100644 --- a/packages/contracts/contracts/test-libraries/utils/TestLib_ECDSAUtils.sol +++ b/packages/contracts/contracts/test-libraries/utils/TestLib_ECDSAUtils.sol @@ -14,8 +14,7 @@ contract TestLib_ECDSAUtils { bool _isEthSignedMessage, uint8 _v, bytes32 _r, - bytes32 _s, - uint256 _chainId + bytes32 _s ) public pure @@ -28,8 +27,7 @@ contract TestLib_ECDSAUtils { _isEthSignedMessage, _v, _r, - _s, - _chainId + _s ); } } diff --git a/packages/contracts/src/contract-deployment/config.ts b/packages/contracts/src/contract-deployment/config.ts index 1d3a91173ea5..2a58ad932e8b 100644 --- a/packages/contracts/src/contract-deployment/config.ts +++ b/packages/contracts/src/contract-deployment/config.ts @@ -129,6 +129,12 @@ export const makeContractDeployConfig = async ( OVM_ECDSAContractAccount: { factory: getContractFactory('OVM_ECDSAContractAccount'), }, + OVM_SequencerEntrypoint: { + factory: getContractFactory('OVM_SequencerEntrypoint'), + }, + OVM_ProxySequencerEntrypoint: { + factory: getContractFactory('OVM_ProxySequencerEntrypoint'), + }, mockOVM_ECDSAContractAccount: { factory: getContractFactory('mockOVM_ECDSAContractAccount'), }, diff --git a/packages/contracts/src/contract-dumps.ts b/packages/contracts/src/contract-dumps.ts index 1b803c17311f..dc4dee5e2ee0 100644 --- a/packages/contracts/src/contract-dumps.ts +++ b/packages/contracts/src/contract-dumps.ts @@ -44,7 +44,8 @@ const getStorageDump = async ( const stream = trie.createReadStream() stream.on('data', (val: any) => { - storage[val.key.toString('hex')] = val.value.toString('hex').slice(2) + storage['0x' + val.key.toString('hex')] = + '0x' + val.value.toString('hex').slice(2) }) stream.on('end', () => { @@ -131,6 +132,10 @@ export const makeStateDump = async (): Promise => { 'OVM_DeployerWhitelist', 'OVM_L1MessageSender', 'OVM_L2ToL1MessagePasser', + 'OVM_ProxyEOA', + 'OVM_ECDSAContractAccount', + 'OVM_ProxySequencerEntrypoint', + 'OVM_SequencerEntrypoint', 'OVM_L2CrossDomainMessenger', 'OVM_SafetyChecker', 'OVM_ExecutionManager', @@ -143,6 +148,9 @@ export const makeStateDump = async (): Promise => { OVM_L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000', OVM_L1MessageSender: '0x4200000000000000000000000000000000000001', OVM_DeployerWhitelist: '0x4200000000000000000000000000000000000002', + OVM_ECDSAContractAccount: '0x4200000000000000000000000000000000000003', + OVM_ProxySequencerEntrypoint: '0x4200000000000000000000000000000000000004', + OVM_SequencerEntrypoint: '0x4200000000000000000000000000000000000005', } const deploymentResult = await deploy(config) diff --git a/packages/contracts/src/utils/byte-utils.ts b/packages/contracts/src/utils/byte-utils.ts index 92d47182dfe7..aeea7863e2ce 100644 --- a/packages/contracts/src/utils/byte-utils.ts +++ b/packages/contracts/src/utils/byte-utils.ts @@ -1,3 +1,5 @@ +const hexRegex = /^(0x)?[0-9a-fA-F]*$/ + /** * Generates a hex string of repeated bytes. * @param byte Byte to repeat. @@ -29,3 +31,59 @@ export const remove0x = (str: string): string => { return str } } + +/** + * Returns whether or not the provided string is a hex string. + * + * @param str The string to test. + * @returns True if the provided string is a hex string, false otherwise. + */ +export const isHexString = (str: string): boolean => { + return hexRegex.test(str) +} + +/** + * Converts a hex string to a buffer + * @param hexString the hex string to be converted + * @returns the hexString as a buffer. + */ +export const hexStrToBuf = (hexString: string): Buffer => { + if (!isHexString(hexString)) { + throw new RangeError(`Invalid hex string [${hexString}]`) + } + if (hexString.length % 2 !== 0) { + throw new RangeError( + `Invalid hex string -- odd number of characters: [${hexString}]` + ) + } + return Buffer.from(remove0x(hexString), 'hex') +} + +/** + * Converts a JavaScript number to a big-endian hex string. + * @param number the JavaScript number to be converted. + * @param padToBytes the number of numeric bytes the resulting string should be, -1 if no padding should be done. + * @returns the JavaScript number as a string. + */ +export const numberToHexString = ( + number: number, + padToBytes: number = -1 +): string => { + let str = number.toString(16) + if (padToBytes > 0 || str.length < padToBytes * 2) { + str = `${'0'.repeat(padToBytes * 2 - str.length)}${str}` + } + return add0x(str) +} + +/** + * Adds "0x" to the start of a string if necessary. + * @param str String to modify. + * @returns the string with "0x". + */ +export const add0x = (str: string): string => { + if (str === undefined) { + return str + } + return str.startsWith('0x') ? str : '0x' + str +} diff --git a/packages/contracts/test/contracts/OVM/accounts/OVM_ECDSAContractAccount.spec.ts b/packages/contracts/test/contracts/OVM/accounts/OVM_ECDSAContractAccount.spec.ts index 69fb0c7b2544..d3d89df84c1b 100644 --- a/packages/contracts/test/contracts/OVM/accounts/OVM_ECDSAContractAccount.spec.ts +++ b/packages/contracts/test/contracts/OVM/accounts/OVM_ECDSAContractAccount.spec.ts @@ -1,26 +1,204 @@ -/* tslint:disable:no-empty */ import { expect } from '../../../setup' +/* External Imports */ +import { ethers, waffle } from '@nomiclabs/buidler' +import { ContractFactory, Contract, Wallet } from 'ethers' +import { MockContract, smockit } from '@eth-optimism/smock' +import { NON_ZERO_ADDRESS, ZERO_ADDRESS } from '../../../helpers/constants' +import { + serializeNativeTransaction, + signNativeTransaction, + DEFAULT_EIP155_TX, + serializeEthSignTransaction, + signEthSignMessage, + EIP155Transaction, +} from '../../../helpers' +import { defaultAbiCoder } from 'ethers/lib/utils' + +const callPrecompile = async ( + Helper_PrecompileCaller: Contract, + precompile: Contract, + functionName: string, + functionParams?: any[] +): Promise => { + return Helper_PrecompileCaller.callPrecompile( + precompile.address, + precompile.interface.encodeFunctionData(functionName, functionParams || []) + ) +} + describe('OVM_ECDSAContractAccount', () => { - describe('execute', () => { - describe('when provided an invalid signature', () => { - it('should revert', async () => {}) + let wallet: Wallet + let badWallet: Wallet + before(async () => { + const provider = waffle.provider + ;[wallet, badWallet] = provider.getWallets() + }) + + let Mock__OVM_ExecutionManager: MockContract + let Helper_PrecompileCaller: Contract + before(async () => { + Mock__OVM_ExecutionManager = smockit( + await ethers.getContractFactory('OVM_ExecutionManager') + ) + + Helper_PrecompileCaller = await ( + await ethers.getContractFactory('Helper_PrecompileCaller') + ).deploy() + Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address) + }) + + let Factory__OVM_ECDSAContractAccount: ContractFactory + before(async () => { + Factory__OVM_ECDSAContractAccount = await ethers.getContractFactory( + 'OVM_ECDSAContractAccount' + ) + }) + + let OVM_ECDSAContractAccount: Contract + beforeEach(async () => { + OVM_ECDSAContractAccount = await Factory__OVM_ECDSAContractAccount.deploy() + + Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with( + await wallet.getAddress() + ) + Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420) + Mock__OVM_ExecutionManager.smocked.ovmGETNONCE.will.return.with(100) + Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with([true, '0x']) + Mock__OVM_ExecutionManager.smocked.ovmCREATE.will.return.with( + NON_ZERO_ADDRESS + ) + Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with( + NON_ZERO_ADDRESS + ) + }) + + describe('fallback()', () => { + it(`should successfully execute an EIP155Transaction`, async () => { + const message = serializeNativeTransaction(DEFAULT_EIP155_TX) + const sig = await signNativeTransaction(wallet, DEFAULT_EIP155_TX) + + await callPrecompile( + Helper_PrecompileCaller, + OVM_ECDSAContractAccount, + 'execute', + [ + message, + 0, // isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) + + // The ovmCALL is the 2nd call because the first call transfers the fee. + const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1] + expect(ovmCALL._gasLimit).to.equal(DEFAULT_EIP155_TX.gasLimit) + expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to) + expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data) + + const ovmSETNONCE: any = + Mock__OVM_ExecutionManager.smocked.ovmSETNONCE.calls[0] + expect(ovmSETNONCE._nonce).to.equal(DEFAULT_EIP155_TX.nonce + 1) + }) + + it(`should successfully execute an ETHSignedTransaction`, async () => { + const message = serializeEthSignTransaction(DEFAULT_EIP155_TX) + const sig = await signEthSignMessage(wallet, DEFAULT_EIP155_TX) + + await callPrecompile( + Helper_PrecompileCaller, + OVM_ECDSAContractAccount, + 'execute', + [ + message, + 1, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) + + // The ovmCALL is the 2nd call because the first call transfers the fee. + const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[1] + expect(ovmCALL._gasLimit).to.equal(DEFAULT_EIP155_TX.gasLimit) + expect(ovmCALL._address).to.equal(DEFAULT_EIP155_TX.to) + expect(ovmCALL._calldata).to.equal(DEFAULT_EIP155_TX.data) + + const ovmSETNONCE: any = + Mock__OVM_ExecutionManager.smocked.ovmSETNONCE.calls[0] + expect(ovmSETNONCE._nonce).to.equal(DEFAULT_EIP155_TX.nonce + 1) }) - describe('when provided a valid signature', () => { - describe('when provided an invalid nonce', () => { - it('should revert', async () => {}) - }) + it(`should ovmCREATE if EIP155Transaction.to is zero address`, async () => { + const createTx = { ...DEFAULT_EIP155_TX, to: '' } + const message = serializeNativeTransaction(createTx) + const sig = await signNativeTransaction(wallet, createTx) + + await callPrecompile( + Helper_PrecompileCaller, + OVM_ECDSAContractAccount, + 'execute', + [ + message, + 0, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) + + const ovmCREATE: any = + Mock__OVM_ExecutionManager.smocked.ovmCREATE.calls[0] + expect(ovmCREATE._bytecode).to.equal(createTx.data) + }) + + it(`should revert on invalid signature`, async () => { + const message = serializeNativeTransaction(DEFAULT_EIP155_TX) + const sig = await signNativeTransaction(badWallet, DEFAULT_EIP155_TX) + + await callPrecompile( + Helper_PrecompileCaller, + OVM_ECDSAContractAccount, + 'execute', + [ + message, + 0, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) + const ovmREVERT: any = + Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0] + expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal( + 'Signature provided for EOA transaction execution is invalid.' + ) + }) - describe('when provided a valid nonce', () => { - describe('when executing ovmCREATE', () => { - it('should return the address of the created contract', async () => {}) - }) + it(`should revert on incorrect nonce`, async () => { + const alteredNonceTx = DEFAULT_EIP155_TX + alteredNonceTx.nonce = 99 + const message = serializeNativeTransaction(alteredNonceTx) + const sig = await signNativeTransaction(wallet, alteredNonceTx) - describe('when executing ovmCALL', () => { - it('should return the result of the call', async () => {}) - }) - }) + await callPrecompile( + Helper_PrecompileCaller, + OVM_ECDSAContractAccount, + 'execute', + [ + message, + 0, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) + const ovmREVERT: any = + Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0] + expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal( + 'Transaction nonce does not match the expected nonce.' + ) }) }) }) diff --git a/packages/contracts/test/contracts/OVM/accounts/OVM_ProxyEOA.spec.ts b/packages/contracts/test/contracts/OVM/accounts/OVM_ProxyEOA.spec.ts new file mode 100644 index 000000000000..3dd4f71fde75 --- /dev/null +++ b/packages/contracts/test/contracts/OVM/accounts/OVM_ProxyEOA.spec.ts @@ -0,0 +1,158 @@ +import { expect } from '../../../setup' + +/* External Imports */ +import { ethers, waffle } from '@nomiclabs/buidler' +import { ContractFactory, Contract, Wallet } from 'ethers' +import { MockContract, smockit } from '@eth-optimism/smock' +import { NON_ZERO_ADDRESS } from '../../../helpers/constants' +import { AbiCoder, keccak256 } from 'ethers/lib/utils' +import { + DEFAULT_EIP155_TX, + remove0x, + serializeNativeTransaction, + signNativeTransaction, +} from '../../../helpers' +import { getContractInterface } from '../../../../src' + +const callPrecompile = async ( + Helper_PrecompileCaller: Contract, + precompile: Contract, + functionName: string, + functionParams?: any[], + ethCall: boolean = false +): Promise => { + if (ethCall) { + return Helper_PrecompileCaller.callStatic.callPrecompileAbi( + precompile.address, + precompile.interface.encodeFunctionData( + functionName, + functionParams || [] + ) + ) + } + return Helper_PrecompileCaller.callPrecompile( + precompile.address, + precompile.interface.encodeFunctionData(functionName, functionParams || []) + ) +} + +const addrToBytes32 = (addr: string) => '0x' + '00'.repeat(12) + remove0x(addr) + +const eoaDefaultAddr = '0x4200000000000000000000000000000000000003' + +describe('OVM_ProxyEOA', () => { + let wallet: Wallet + before(async () => { + const provider = waffle.provider + ;[wallet] = provider.getWallets() + }) + + let Mock__OVM_ExecutionManager: MockContract + let Mock__OVM_ECDSAContractAccount: MockContract + let Helper_PrecompileCaller: Contract + before(async () => { + Mock__OVM_ExecutionManager = smockit( + await ethers.getContractFactory('OVM_ExecutionManager') + ) + + Helper_PrecompileCaller = await ( + await ethers.getContractFactory('Helper_PrecompileCaller') + ).deploy() + + Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address) + + Mock__OVM_ECDSAContractAccount = smockit( + await ethers.getContractFactory('OVM_ECDSAContractAccount') + ) + }) + + let OVM_ProxyEOAFactory: ContractFactory + before(async () => { + OVM_ProxyEOAFactory = await ethers.getContractFactory('OVM_ProxyEOA') + }) + + let OVM_ProxyEOA: Contract + beforeEach(async () => { + OVM_ProxyEOA = await OVM_ProxyEOAFactory.deploy(eoaDefaultAddr) + + Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with( + OVM_ProxyEOA.address + ) + Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with( + OVM_ProxyEOA.address + ) + }) + + describe('getImplementation()', () => { + it(`should be created with implementation at precompile address`, async () => { + const eoaDefaultAddrBytes32 = addrToBytes32(eoaDefaultAddr) + Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( + eoaDefaultAddrBytes32 + ) + const implAddrBytes32 = await callPrecompile( + Helper_PrecompileCaller, + OVM_ProxyEOA, + 'getImplementation', + [], + true + ) + expect(implAddrBytes32).to.equal(eoaDefaultAddrBytes32) + }) + }) + describe('upgrade()', () => { + it(`should upgrade the proxy implementation`, async () => { + const newImpl = `0x${'81'.repeat(20)}` + const newImplBytes32 = addrToBytes32(newImpl) + await callPrecompile(Helper_PrecompileCaller, OVM_ProxyEOA, 'upgrade', [ + newImpl, + ]) + const ovmSSTORE: any = + Mock__OVM_ExecutionManager.smocked.ovmSSTORE.calls[0] + expect(ovmSSTORE._key).to.equal(`0x${'00'.repeat(32)}`) + expect(ovmSSTORE._value).to.equal(newImplBytes32) + }) + it(`should not allow upgrade of the proxy implementation by another account`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with( + await wallet.getAddress() + ) + const newImpl = `0x${'81'.repeat(20)}` + await callPrecompile(Helper_PrecompileCaller, OVM_ProxyEOA, 'upgrade', [ + newImpl, + ]) + const ovmREVERT: any = + Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0] + expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal( + 'EOAs can only upgrade their own EOA implementation' + ) + }) + }) + describe('fallback()', () => { + it(`should call delegateCall with right calldata`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( + addrToBytes32(Mock__OVM_ECDSAContractAccount.address) + ) + Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.will.return.with([ + true, + '0x1234', + ]) + const calldata = '0xdeadbeef' + await Helper_PrecompileCaller.callPrecompile( + OVM_ProxyEOA.address, + calldata + ) + + const ovmDELEGATECALL: any = + Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.calls[0] + expect(ovmDELEGATECALL._address).to.equal( + Mock__OVM_ECDSAContractAccount.address + ) + expect(ovmDELEGATECALL._calldata).to.equal(calldata) + }) + it.skip(`should return data from fallback`, async () => { + //TODO test return data from fallback + }) + it.skip(`should revert in fallback`, async () => { + //TODO test reversion from fallback + }) + }) +}) diff --git a/packages/contracts/test/contracts/OVM/bridge/OVM_L1CrossDomainMessenger.spec.ts b/packages/contracts/test/contracts/OVM/bridge/OVM_L1CrossDomainMessenger.spec.ts index 08a39b1f7562..8d60c437f3ec 100644 --- a/packages/contracts/test/contracts/OVM/bridge/OVM_L1CrossDomainMessenger.spec.ts +++ b/packages/contracts/test/contracts/OVM/bridge/OVM_L1CrossDomainMessenger.spec.ts @@ -42,9 +42,9 @@ const deployProxyXDomainMessenger = async ( 'OVM_L1CrossDomainMessenger', l1XDomainMessenger.address ) - const proxy = await (await ethers.getContractFactory( - 'Lib_ResolvedDelegateProxy' - )).deploy(addressManager.address, 'OVM_L1CrossDomainMessenger') + const proxy = await ( + await ethers.getContractFactory('Lib_ResolvedDelegateProxy') + ).deploy(addressManager.address, 'OVM_L1CrossDomainMessenger') return l1XDomainMessenger.attach(proxy.address) } @@ -105,7 +105,10 @@ describe('OVM_L1CrossDomainMessenger', () => { beforeEach(async () => { const xDomainMessenerImpl = await Factory__OVM_L1CrossDomainMessenger.deploy() // We use an upgradable proxy for the XDomainMessenger--deploy & set up the proxy. - OVM_L1CrossDomainMessenger = await deployProxyXDomainMessenger(AddressManager, xDomainMessenerImpl) + OVM_L1CrossDomainMessenger = await deployProxyXDomainMessenger( + AddressManager, + xDomainMessenerImpl + ) await OVM_L1CrossDomainMessenger.initialize(AddressManager.address) }) diff --git a/packages/contracts/test/contracts/OVM/execution/OVM_ExecutionManager/ovmCREATEEOA.spec.ts b/packages/contracts/test/contracts/OVM/execution/OVM_ExecutionManager/ovmCREATEEOA.spec.ts new file mode 100644 index 000000000000..278b97391745 --- /dev/null +++ b/packages/contracts/test/contracts/OVM/execution/OVM_ExecutionManager/ovmCREATEEOA.spec.ts @@ -0,0 +1,121 @@ +/* Internal Imports */ +import { + ExecutionManagerTestRunner, + TestDefinition, + OVM_TX_GAS_LIMIT, + NON_NULL_BYTES32, + REVERT_FLAGS, + VERIFIED_EMPTY_CONTRACT_HASH, +} from '../../../../helpers' + +const test_ovmCREATEEOA: TestDefinition = { + name: 'Basic tests for CREATEEOA', + preState: { + ExecutionManager: { + ovmStateManager: '$OVM_STATE_MANAGER', + ovmSafetyChecker: '$OVM_SAFETY_CHECKER', + messageRecord: { + nuisanceGasLeft: OVM_TX_GAS_LIMIT, + }, + }, + StateManager: { + owner: '$OVM_EXECUTION_MANAGER', + accounts: { + $DUMMY_OVM_ADDRESS_1: { + codeHash: NON_NULL_BYTES32, + ethAddress: '$OVM_CALL_HELPER', + }, + $DUMMY_OVM_ADDRESS_2: { + codeHash: NON_NULL_BYTES32, + ethAddress: '$OVM_CALL_HELPER', + }, + '0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff': { + codeHash: VERIFIED_EMPTY_CONTRACT_HASH, + ethAddress: '0x' + '00'.repeat(20), + }, + }, + verifiedContractStorage: { + $DUMMY_OVM_ADDRESS_1: { + [NON_NULL_BYTES32]: true, + }, + }, + }, + }, + parameters: [ + { + name: 'ovmCREATEEOA, ovmEXTCODESIZE(CREATED)', + steps: [ + { + functionName: 'ovmCREATEEOA', + functionParams: { + _messageHash: + '0x92d658d25f963af824e9d4bd533c165773d4a694a67d88135d119d5bca97c001', + _v: 1, + _r: + '0x73757c671fae2c3fb6825766c724b7715720bda4b309d3612f2c623364556967', + _s: + '0x2fc9b7222783390b9f10e22e92a52871beaff2613193d6e2dbf18d0e2d2eb8ff', + }, + expectedReturnStatus: true, + expectedReturnValue: undefined, + }, + { + functionName: 'ovmEXTCODESIZE', + functionParams: { + address: '0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff', + }, + expectedReturnStatus: true, + expectedReturnValue: 1638, + }, + ], + }, + { + name: 'ovmCALL(ADDRESS_1) => ovmGETNONCE', + steps: [ + { + functionName: 'ovmCALL', + functionParams: { + gasLimit: OVM_TX_GAS_LIMIT, + target: '$DUMMY_OVM_ADDRESS_1', + subSteps: [ + { + functionName: 'ovmGETNONCE', + expectedReturnValue: 0, + }, + ], + }, + expectedReturnStatus: true, + }, + ], + }, + { + name: 'ovmCALL(ADDRESS_1) => ovmSETNONCE(3) => ovmGETNONCE', + steps: [ + { + functionName: 'ovmCALL', + functionParams: { + gasLimit: OVM_TX_GAS_LIMIT, + target: '$DUMMY_OVM_ADDRESS_1', + subSteps: [ + { + functionName: 'ovmSETNONCE', + functionParams: { + _nonce: '0x03', + }, + expectedReturnStatus: true, + }, + { + functionName: 'ovmGETNONCE', + expectedReturnValue: 3, + }, + ], + }, + expectedReturnStatus: true, + }, + ], + }, + ], +} + +const runner = new ExecutionManagerTestRunner() +runner.run(test_ovmCREATEEOA) diff --git a/packages/contracts/test/contracts/OVM/precompiles/OVM_ProxySequencerEntrypoint.spec.ts b/packages/contracts/test/contracts/OVM/precompiles/OVM_ProxySequencerEntrypoint.spec.ts new file mode 100644 index 000000000000..9e3cea62e372 --- /dev/null +++ b/packages/contracts/test/contracts/OVM/precompiles/OVM_ProxySequencerEntrypoint.spec.ts @@ -0,0 +1,166 @@ +import { expect } from '../../../setup' + +/* External Imports */ +import { ethers, waffle } from '@nomiclabs/buidler' +import { ContractFactory, Contract, Wallet } from 'ethers' +import { MockContract, smockit } from '@eth-optimism/smock' +import { + encodeSequencerCalldata, + DEFAULT_EIP155_TX, + ZERO_ADDRESS, + remove0x, +} from '../../../helpers' + +const callPrecompile = async ( + Helper_PrecompileCaller: Contract, + precompile: Contract, + functionName: string, + functionParams?: any[] +): Promise => { + return Helper_PrecompileCaller.callPrecompile( + precompile.address, + precompile.interface.encodeFunctionData(functionName, functionParams || []) + ) +} + +const addrToBytes32 = (addr: string) => + '0x' + '00'.repeat(12) + remove0x(addr.toLowerCase()) + +describe('OVM_ProxySequencerEntrypoint', () => { + let wallet: Wallet + before(async () => { + const provider = waffle.provider + ;[wallet] = provider.getWallets() + }) + + let Factory__OVM_ProxySequencerEntrypoint: ContractFactory + before(async () => { + Factory__OVM_ProxySequencerEntrypoint = await ethers.getContractFactory( + 'OVM_ProxySequencerEntrypoint' + ) + }) + + let Mock__OVM_ExecutionManager: MockContract + let Helper_PrecompileCaller: Contract + let OVM_SequencerEntrypoint: Contract + before(async () => { + Mock__OVM_ExecutionManager = smockit( + await ethers.getContractFactory('OVM_ExecutionManager') + ) + + Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with( + await wallet.getAddress() + ) + + Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(0) + Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420) + + Helper_PrecompileCaller = await ( + await ethers.getContractFactory('Helper_PrecompileCaller') + ).deploy() + + Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address) + + OVM_SequencerEntrypoint = await ( + await ethers.getContractFactory('OVM_SequencerEntrypoint') + ).deploy() + }) + + let OVM_ProxySequencerEntrypoint: Contract + beforeEach(async () => { + OVM_ProxySequencerEntrypoint = await Factory__OVM_ProxySequencerEntrypoint.deploy() + }) + it(`should init the proxy with owner and implementation`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( + `0x${'00'.repeat(32)}` + ) + await callPrecompile( + Helper_PrecompileCaller, + OVM_ProxySequencerEntrypoint, + 'init', + [OVM_SequencerEntrypoint.address, await wallet.getAddress()] + ) + const ovmSSTOREs: any = Mock__OVM_ExecutionManager.smocked.ovmSSTORE.calls + + expect(ovmSSTOREs[0]._key).to.equal(`0x${'00'.repeat(31)}01`) + expect(ovmSSTOREs[0]._value).to.equal( + addrToBytes32(await wallet.getAddress()) + ) + + expect(ovmSSTOREs[1]._key).to.equal(`0x${'00'.repeat(32)}`) + expect(ovmSSTOREs[1]._value).to.equal( + addrToBytes32(OVM_SequencerEntrypoint.address) + ) + + // expect(await OVM_ProxySequencerEntrypoint.implementation()).to.equal( + // OVM_SequencerEntrypoint.address + // ) + }) + it(`should revert if proxy has already been inited`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( + addrToBytes32(await wallet.getAddress()) + ) + await callPrecompile( + Helper_PrecompileCaller, + OVM_ProxySequencerEntrypoint, + 'init', + [ZERO_ADDRESS, ZERO_ADDRESS] + ) + + const ovmREVERT: any = Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0] + expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal( + 'ProxySequencerEntrypoint has already been inited' + ) + }) + + it(`should allow owner to upgrade Entrypoint`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( + addrToBytes32(await wallet.getAddress()) + ) + await callPrecompile( + Helper_PrecompileCaller, + OVM_ProxySequencerEntrypoint, + 'upgrade', + [`0x${'12'.repeat(20)}`] + ) + + const ovmSSTORE: any = Mock__OVM_ExecutionManager.smocked.ovmSSTORE.calls[0] + expect(ovmSSTORE._key).to.equal(`0x${'00'.repeat(32)}`) + expect(ovmSSTORE._value).to.equal(addrToBytes32(`0x${'12'.repeat(20)}`)) + }) + + it(`should revert if non-owner tries to upgrade Entrypoint`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( + `0x${'00'.repeat(32)}` + ) + await callPrecompile( + Helper_PrecompileCaller, + OVM_ProxySequencerEntrypoint, + 'upgrade', + [`0x${'12'.repeat(20)}`] + ) + const ovmREVERT: any = Mock__OVM_ExecutionManager.smocked.ovmREVERT.calls[0] + expect(ethers.utils.toUtf8String(ovmREVERT._data)).to.equal( + 'Only owner can upgrade the Entrypoint' + ) + }) + + it(`successfully calls ovmCREATEEOA through Entrypoint fallback`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmSLOAD.will.return.with( + addrToBytes32(OVM_SequencerEntrypoint.address) + ) + Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.will.return.with([ + true, + '0x', + ]) + const calldata = '0xdeadbeef' + await Helper_PrecompileCaller.callPrecompile( + OVM_ProxySequencerEntrypoint.address, + calldata + ) + const ovmDELEGATECALL: any = + Mock__OVM_ExecutionManager.smocked.ovmDELEGATECALL.calls[0] + expect(ovmDELEGATECALL._address).to.equal(OVM_SequencerEntrypoint.address) + expect(ovmDELEGATECALL._calldata).to.equal(calldata) + }) +}) diff --git a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts new file mode 100644 index 000000000000..5df153cca4f1 --- /dev/null +++ b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts @@ -0,0 +1,185 @@ +import { expect } from '../../../setup' + +/* External Imports */ +import { waffle, ethers } from '@nomiclabs/buidler' +import { ContractFactory, Wallet, Contract } from 'ethers' +import { zeroPad } from '@ethersproject/bytes' +import { getContractInterface } from '../../../../src' +import { + encodeSequencerCalldata, + EIP155Transaction, + signNativeTransaction, + signEthSignMessage, + DEFAULT_EIP155_TX, + serializeNativeTransaction, + serializeEthSignTransaction, + ZERO_ADDRESS, +} from '../../../helpers' +import { smockit, MockContract } from '@eth-optimism/smock' +import { create } from 'lodash' + +describe('OVM_SequencerEntrypoint', () => { + let wallet: Wallet + before(async () => { + const provider = waffle.provider + ;[wallet] = provider.getWallets() + }) + + let Mock__OVM_ExecutionManager: MockContract + let Helper_PrecompileCaller: Contract + before(async () => { + Mock__OVM_ExecutionManager = smockit( + await ethers.getContractFactory('OVM_ExecutionManager') + ) + + Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420) + Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with([true, '0x']) + + Helper_PrecompileCaller = await ( + await ethers.getContractFactory('Helper_PrecompileCaller') + ).deploy() + + Helper_PrecompileCaller.setTarget(Mock__OVM_ExecutionManager.address) + }) + + let OVM_SequencerEntrypointFactory: ContractFactory + before(async () => { + OVM_SequencerEntrypointFactory = await ethers.getContractFactory( + 'OVM_SequencerEntrypoint' + ) + }) + + let OVM_SequencerEntrypoint: Contract + beforeEach(async () => { + OVM_SequencerEntrypoint = await OVM_SequencerEntrypointFactory.deploy() + Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(1) + Mock__OVM_ExecutionManager.smocked.ovmREVERT.will.revert() + }) + + describe('fallback()', async () => { + it('should call EIP155 if the transaction type is 0', async () => { + const calldata = await encodeSequencerCalldata( + wallet, + DEFAULT_EIP155_TX, + 0 + ) + await Helper_PrecompileCaller.callPrecompile( + OVM_SequencerEntrypoint.address, + calldata + ) + + const encodedTx = serializeNativeTransaction(DEFAULT_EIP155_TX) + const sig = await signNativeTransaction(wallet, DEFAULT_EIP155_TX) + + const expectedEOACalldata = getContractInterface( + 'OVM_ECDSAContractAccount' + ).encodeFunctionData('execute', [ + encodedTx, + 0, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ]) + const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0] + expect(ovmCALL._address).to.equal(await wallet.getAddress()) + expect(ovmCALL._calldata).to.equal(expectedEOACalldata) + }) + + it('should send correct calldata if tx is a create and the transaction type is 0', async () => { + const createTx = { ...DEFAULT_EIP155_TX, to: '' } + const calldata = await encodeSequencerCalldata(wallet, createTx, 0) + await Helper_PrecompileCaller.callPrecompile( + OVM_SequencerEntrypoint.address, + calldata + ) + + const encodedTx = serializeNativeTransaction(createTx) + const sig = await signNativeTransaction(wallet, createTx) + + const expectedEOACalldata = getContractInterface( + 'OVM_ECDSAContractAccount' + ).encodeFunctionData('execute', [ + encodedTx, + 0, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ]) + const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0] + expect(ovmCALL._address).to.equal(await wallet.getAddress()) + expect(ovmCALL._calldata).to.equal(expectedEOACalldata) + }) + + for (let i = 0; i < 3; i += 2) { + it(`should call ovmCreateEOA when tx type is ${i} and ovmEXTCODESIZE returns 0`, async () => { + Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(0) + const calldata = await encodeSequencerCalldata( + wallet, + DEFAULT_EIP155_TX, + i + ) + await Helper_PrecompileCaller.callPrecompile( + OVM_SequencerEntrypoint.address, + calldata + ) + const call: any = + Mock__OVM_ExecutionManager.smocked.ovmCREATEEOA.calls[0] + const eoaAddress = ethers.utils.recoverAddress(call._messageHash, { + v: call._v + 27, + r: call._r, + s: call._s, + }) + expect(eoaAddress).to.equal(await wallet.getAddress()) + }) + } + + it('should submit ETHSignedTypedData if TransactionType is 2', async () => { + const calldata = await encodeSequencerCalldata( + wallet, + DEFAULT_EIP155_TX, + 2 + ) + await Helper_PrecompileCaller.callPrecompile( + OVM_SequencerEntrypoint.address, + calldata + ) + + const encodedTx = serializeEthSignTransaction(DEFAULT_EIP155_TX) + const sig = await signEthSignMessage(wallet, DEFAULT_EIP155_TX) + + const expectedEOACalldata = getContractInterface( + 'OVM_ECDSAContractAccount' + ).encodeFunctionData('execute', [ + encodedTx, + 1, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ]) + const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0] + expect(ovmCALL._address).to.equal(await wallet.getAddress()) + expect(ovmCALL._calldata).to.equal(expectedEOACalldata) + }) + + // TODO: These tests should pass when smock is updated to >=0.1.0 + it.skip('should revert if TransactionType is >2', async () => { + const calldata = '0x03' + await expect( + Helper_PrecompileCaller.callPrecompile( + OVM_SequencerEntrypoint.address, + calldata + ) + ).to.be.reverted + }) + + it.skip('should revert if TransactionType is 1', async () => { + const calldata = '0x01' + await expect( + Helper_PrecompileCaller.callPrecompile( + OVM_SequencerEntrypoint.address, + calldata + ) + ).to.be.reverted + }) + }) +}) diff --git a/packages/contracts/test/contracts/libraries/codec/Lib_OVMCodec.spec.ts b/packages/contracts/test/contracts/libraries/codec/Lib_OVMCodec.spec.ts index aa2ede2938f5..6e6b9fe9feb4 100644 --- a/packages/contracts/test/contracts/libraries/codec/Lib_OVMCodec.spec.ts +++ b/packages/contracts/test/contracts/libraries/codec/Lib_OVMCodec.spec.ts @@ -1,18 +1,12 @@ /* tslint:disable:no-empty */ import { expect } from '../../../setup' -describe('Lib_OVMCodec', () => { - describe('decodeEOATransaction', () => { - describe('when given a valid RLP-encoded transaction', () => { - it('should return the decoded transaction struct', async () => {}) - }) - }) +/* Internal Imports */ +import { Lib_OVMCodec_TEST_JSON } from '../../../data' +import { runJsonTest, toHexString } from '../../../helpers' - describe('encodeTransaction', () => { - it('should ABI encode (packed) the given transaction', async () => {}) - }) - - describe('hashTransaction', () => { - it('should return the hash of the encoded transaction', async () => {}) +describe('Lib_OVMCodec', () => { + describe('JSON tests', () => { + runJsonTest('TestLib_OVMCodec', Lib_OVMCodec_TEST_JSON) }) }) diff --git a/packages/contracts/test/data/index.ts b/packages/contracts/test/data/index.ts index ddb4e7be51c8..8a4a1f7e65b8 100644 --- a/packages/contracts/test/data/index.ts +++ b/packages/contracts/test/data/index.ts @@ -4,5 +4,6 @@ export { tests as Lib_Bytes32Utils_TEST_JSON } from './json/libraries/utils/Lib_ export { tests as Lib_BytesUtils_TEST_JSON } from './json/libraries/utils/Lib_BytesUtils.test.json' export { tests as Lib_ECDSAUtils_TEST_JSON } from './json/libraries/utils/Lib_ECDSAUtils.test.json' export { tests as Lib_MerkleTrie_TEST_JSON } from './json/libraries/trie/Lib_MerkleTrie.test.json' +export { tests as Lib_OVMCodec_TEST_JSON } from './json/libraries/codec/Lib_OVMCodec.test.json' export { tests as CREATE2_TEST_JSON } from './json/create2.test.json' export { tests as SAFETY_CHECKER_TEST_JSON } from './json/safety-checker.test.json' diff --git a/packages/contracts/test/data/json/libraries/codec/Lib_OVMCodec.test.json b/packages/contracts/test/data/json/libraries/codec/Lib_OVMCodec.test.json new file mode 100644 index 000000000000..b442f4e07c8c --- /dev/null +++ b/packages/contracts/test/data/json/libraries/codec/Lib_OVMCodec.test.json @@ -0,0 +1,22 @@ +{ + "tests": { + "decompressEIP155Transaction": { + "decompression": { + "in": [ + "0x0001f4000064000064121212121212121212121212121212121212121299999999999999999999" + ], + "out": [ + [ + 100, + 100000000, + 500, + "0x1212121212121212121212121212121212121212", + 0, + "0x99999999999999999999", + 420 + ] + ] + } + } + } +} diff --git a/packages/contracts/test/data/json/libraries/utils/Lib_ECDSAUtils.test.json b/packages/contracts/test/data/json/libraries/utils/Lib_ECDSAUtils.test.json index 6711357742a5..40f15819442f 100644 --- a/packages/contracts/test/data/json/libraries/utils/Lib_ECDSAUtils.test.json +++ b/packages/contracts/test/data/json/libraries/utils/Lib_ECDSAUtils.test.json @@ -5,10 +5,9 @@ "in": [ "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", false, - "0xfc", + "0x01", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", - "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", - "0x6c" + "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3" ], "out": [ "0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff" @@ -18,10 +17,9 @@ "in": [ "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", false, - "0x00", + "0xfc", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", - "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", - "0x6c" + "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3" ], "out": [ "0x0000000000000000000000000000000000000000" @@ -31,10 +29,9 @@ "in": [ "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", false, - "0xfc", + "0x01", "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", - "0x6c" + "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3" ], "out": [ "0x0000000000000000000000000000000000000000" @@ -44,23 +41,9 @@ "in": [ "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", false, - "0xfc", - "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x6c" - ], - "out": [ - "0x0000000000000000000000000000000000000000" - ] - }, - "standard hash, invalid chainid parameter": { - "in": [ - "0xf83d80808094121212121212121212121212121212121212121280a0bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a6c8080", - false, - "0xfc", + "0x01", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", - "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", - "0x00" + "0x0000000000000000000000000000000000000000000000000000000000000000" ], "out": [ "0x0000000000000000000000000000000000000000" @@ -70,10 +53,9 @@ "in": [ "0x0000000000000000000000000000000000000000000000000000000000000000", false, - "0xfc", + "0x01", "0x057bf1c0edf0a9002a79f8c9b39683f6453a18e73e02364270a2b6ee8117f11a", - "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3", - "0x6c" + "0x5f8181365a222c7b05a84c29a29754e6a925952d3bf4bd65a6259ef37ee048e3" ], "out": [ "0x1397890fcfC2D7563Aa01cE93A217b9809D3447B" @@ -83,10 +65,9 @@ "in": [ "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", true, - "0x1c", + "0x01", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", - "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", - "0x6c" + "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98" ], "out": [ "0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff" @@ -96,10 +77,9 @@ "in": [ "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", true, - "0x00", + "0x1c", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", - "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", - "0x6c" + "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98" ], "out": [ "0x0000000000000000000000000000000000000000" @@ -109,10 +89,9 @@ "in": [ "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", true, - "0x1c", + "0x01", "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", - "0x6c" + "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98" ], "out": [ "0x0000000000000000000000000000000000000000" @@ -122,10 +101,9 @@ "in": [ "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c000000000000000000000000121212121212121212121212121212121212121200000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000020bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a", true, - "0x1c", + "0x01", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x6c" + "0x0000000000000000000000000000000000000000000000000000000000000000" ], "out": [ "0x0000000000000000000000000000000000000000" @@ -135,10 +113,9 @@ "in": [ "0x0000000000000000000000000000000000000000000000000000000000000000", true, - "0x1c", + "0x01", "0xe5a2edb71b6d76a8aacd59467d3063fd455ca13a4e9cb57da6f25849d40e4e2a", - "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98", - "0x6c" + "0x5465373d68d521845e3ffd2baf4c51f3d21c990914c09490b32ffc0b322dbf98" ], "out": [ "0xf8bd1421f9D28C8F58f33B25a6faB16F3f89b749" diff --git a/packages/contracts/test/helpers/codec/encoding.ts b/packages/contracts/test/helpers/codec/encoding.ts new file mode 100644 index 000000000000..4d3fe92f0df3 --- /dev/null +++ b/packages/contracts/test/helpers/codec/encoding.ts @@ -0,0 +1,156 @@ +/* External Imports */ +import { ethers } from '@nomiclabs/buidler' +import { zeroPad } from '@ethersproject/bytes' +import { Wallet } from 'ethers' +import { + remove0x, + numberToHexString, + hexStrToBuf, + makeAddressManager, +} from '../' +import { ZERO_ADDRESS } from '../constants' + +export interface EIP155Transaction { + nonce: number + gasLimit: number + gasPrice: number + to: string + data: string + chainId: number +} + +export interface SignatureParameters { + messageHash: string + v: string + r: string + s: string +} + +export const DEFAULT_EIP155_TX: EIP155Transaction = { + to: `0x${'12'.repeat(20)}`, + nonce: 100, + gasLimit: 1000000, + gasPrice: 100000000, + data: `0x${'99'.repeat(10)}`, + chainId: 420, +} + +export const getRawSignedComponents = (signed: string): any[] => { + return [signed.slice(130, 132), signed.slice(2, 66), signed.slice(66, 130)] +} + +export const getSignedComponents = (signed: string): any[] => { + return ethers.utils.RLP.decode(signed).slice(-3) +} + +export const encodeCompactTransaction = (transaction: any): string => { + const nonce = zeroPad(transaction.nonce, 3) + const gasLimit = zeroPad(transaction.gasLimit, 3) + if (transaction.gasPrice % 1000000 !== 0) + throw Error('gas price must be a multiple of 1000000') + const compressedGasPrice: any = transaction.gasPrice / 1000000 + const gasPrice = zeroPad(compressedGasPrice, 3) + const to = !transaction.to.length + ? hexStrToBuf(ZERO_ADDRESS) + : hexStrToBuf(transaction.to) + const data = hexStrToBuf(transaction.data) + + return Buffer.concat([ + Buffer.from(gasLimit), + Buffer.from(gasPrice), + Buffer.from(nonce), + Buffer.from(to), + data, + ]).toString('hex') +} + +export const serializeEthSignTransaction = ( + transaction: EIP155Transaction +): string => { + return ethers.utils.defaultAbiCoder.encode( + ['uint256', 'uint256', 'uint256', 'uint256', 'address', 'bytes'], + [ + transaction.nonce, + transaction.gasLimit, + transaction.gasPrice, + transaction.chainId, + transaction.to, + transaction.data, + ] + ) +} + +export const serializeNativeTransaction = ( + transaction: EIP155Transaction +): string => { + return ethers.utils.serializeTransaction(transaction) +} + +export const signEthSignMessage = async ( + wallet: Wallet, + transaction: EIP155Transaction +): Promise => { + const serializedTransaction = serializeEthSignTransaction(transaction) + const transactionHash = ethers.utils.keccak256(serializedTransaction) + const transactionHashBytes = ethers.utils.arrayify(transactionHash) + const transactionSignature = await wallet.signMessage(transactionHashBytes) + + const messageHash = ethers.utils.hashMessage(transactionHashBytes) + let [v, r, s] = getRawSignedComponents(transactionSignature).map( + (component) => { + return remove0x(component) + } + ) + v = '0' + (parseInt(v, 16) - 27) + return { + messageHash, + v, + r, + s, + } +} + +export const signNativeTransaction = async ( + wallet: Wallet, + transaction: EIP155Transaction +): Promise => { + const serializedTransaction = serializeNativeTransaction(transaction) + const transactionSignature = await wallet.signTransaction(transaction) + + const messageHash = ethers.utils.keccak256(serializedTransaction) + let [v, r, s] = getSignedComponents(transactionSignature).map((component) => { + return remove0x(component) + }) + v = '0' + (parseInt(v, 16) - 420 * 2 - 8 - 27) + return { + messageHash, + v, + r, + s, + } +} + +export const signTransaction = async ( + wallet: Wallet, + transaction: EIP155Transaction, + transactionType: number +): Promise => { + return transactionType === 2 + ? signEthSignMessage(wallet, transaction) //ETH Signed tx + : signNativeTransaction(wallet, transaction) //Create EOA tx or EIP155 tx +} + +export const encodeSequencerCalldata = async ( + wallet: Wallet, + transaction: EIP155Transaction, + transactionType: number +) => { + const sig = await signTransaction(wallet, transaction, transactionType) + const encodedTransaction = encodeCompactTransaction(transaction) + const dataPrefix = `0x0${transactionType}${sig.r}${sig.s}${sig.v}` + const calldata = + transactionType === 1 + ? `${dataPrefix}${remove0x(sig.messageHash)}` // Create EOA tx + : `${dataPrefix}${encodedTransaction}` // EIP155 tx or ETH Signed Tx + return calldata +} diff --git a/packages/contracts/test/helpers/codec/index.ts b/packages/contracts/test/helpers/codec/index.ts index 610733a09563..f6fc49f8a30a 100644 --- a/packages/contracts/test/helpers/codec/index.ts +++ b/packages/contracts/test/helpers/codec/index.ts @@ -1 +1,2 @@ export * from './revert-flags' +export * from './encoding' diff --git a/packages/contracts/test/helpers/test-runner/json-test-runner.ts b/packages/contracts/test/helpers/test-runner/json-test-runner.ts index 73f2c1e0b804..d9325f1f5351 100644 --- a/packages/contracts/test/helpers/test-runner/json-test-runner.ts +++ b/packages/contracts/test/helpers/test-runner/json-test-runner.ts @@ -4,6 +4,18 @@ import { expect } from '../../setup' import { ethers } from '@nomiclabs/buidler' import { Contract, BigNumber } from 'ethers' +const bigNumberify = (arr) => { + return arr.map((el: any) => { + if (typeof el === 'number') { + return BigNumber.from(el) + } else if (Array.isArray(el)) { + return bigNumberify(el) + } else { + return el + } + }) +} + export const runJsonTest = (contractName: string, json: any): void => { let contract: Contract before(async () => { @@ -20,15 +32,7 @@ export const runJsonTest = (contractName: string, json: any): void => { } else { expect( await contract.functions[functionName](...test.in) - ).to.deep.equal( - test.out.map((out: any) => { - if (typeof out === 'number') { - return BigNumber.from(out) - } else { - return out - } - }) - ) + ).to.deep.equal(bigNumberify(test.out)) } }) } diff --git a/packages/contracts/test/helpers/test-runner/test-runner.ts b/packages/contracts/test/helpers/test-runner/test-runner.ts index 8562cf01ec20..54ffd34675e7 100644 --- a/packages/contracts/test/helpers/test-runner/test-runner.ts +++ b/packages/contracts/test/helpers/test-runner/test-runner.ts @@ -20,6 +20,7 @@ import { isTestStep_CALL, isTestStep_CREATE, isTestStep_CREATE2, + isTestStep_CREATEEOA, isTestStep_Context, isTestStep_evm, isTestStep_Run, @@ -27,6 +28,7 @@ import { isTestStep_EXTCODEHASH, isTestStep_EXTCODECOPY, isTestStep_REVERT, + isTestStep_SETNONCE, } from './test.types' import { encodeRevertData, REVERT_FLAGS } from '../codec' import { @@ -333,9 +335,11 @@ export class ExecutionManagerTestRunner { if ( isTestStep_SSTORE(step) || isTestStep_SLOAD(step) || + isTestStep_SETNONCE(step) || isTestStep_EXTCODESIZE(step) || isTestStep_EXTCODEHASH(step) || - isTestStep_EXTCODECOPY(step) + isTestStep_EXTCODECOPY(step) || + isTestStep_CREATEEOA(step) ) { functionParams = Object.values(step.functionParams) } else if (isTestStep_CALL(step)) { diff --git a/packages/contracts/test/helpers/test-runner/test.types.ts b/packages/contracts/test/helpers/test-runner/test.types.ts index 91eb740ed996..4d3d04fb1558 100644 --- a/packages/contracts/test/helpers/test-runner/test.types.ts +++ b/packages/contracts/test/helpers/test-runner/test.types.ts @@ -10,6 +10,7 @@ export type ContextOpcode = | 'ovmTIMESTAMP' | 'ovmGASLIMIT' | 'ovmCHAINID' + | 'ovmGETNONCE' type CallOpcode = 'ovmCALL' | 'ovmSTATICCALL' | 'ovmDELEGATECALL' @@ -85,6 +86,15 @@ interface TestStep_SLOAD { expectedReturnValue: string | RevertFlagError } +interface TestStep_SETNONCE { + functionName: 'ovmSETNONCE' + functionParams: { + _nonce: string + } + expectedReturnStatus: boolean + expectedReturnValue?: RevertFlagError +} + export interface TestStep_CALL { functionName: CallOpcode functionParams: { @@ -121,6 +131,18 @@ interface TestStep_CREATE2 { expectedReturnValue: string | RevertFlagError } +interface TestStep_CREATEEOA { + functionName: 'ovmCREATEEOA' + functionParams: { + _messageHash: string + _v: number + _r: string + _s: string + } + expectedReturnStatus: boolean + expectedReturnValue: string | RevertFlagError +} + export interface TestStep_Run { functionName: 'run' functionParams: { @@ -139,9 +161,11 @@ export type TestStep = | TestStep_Context | TestStep_SSTORE | TestStep_SLOAD + | TestStep_SETNONCE | TestStep_CALL | TestStep_CREATE | TestStep_CREATE2 + | TestStep_CREATEEOA | TestStep_EXTCODESIZE | TestStep_EXTCODEHASH | TestStep_EXTCODECOPY @@ -182,6 +206,7 @@ export const isTestStep_Context = ( 'ovmGASLIMIT', 'ovmCHAINID', 'ovmL1QUEUEORIGIN', + 'ovmGETNONCE', ].includes(step.functionName) } @@ -193,6 +218,12 @@ export const isTestStep_SLOAD = (step: TestStep): step is TestStep_SLOAD => { return step.functionName === 'ovmSLOAD' } +export const isTestStep_SETNONCE = ( + step: TestStep +): step is TestStep_SETNONCE => { + return step.functionName === 'ovmSETNONCE' +} + export const isTestStep_EXTCODESIZE = ( step: TestStep ): step is TestStep_EXTCODESIZE => { @@ -225,6 +256,12 @@ export const isTestStep_CREATE = (step: TestStep): step is TestStep_CREATE => { return step.functionName === 'ovmCREATE' } +export const isTestStep_CREATEEOA = ( + step: TestStep +): step is TestStep_CREATEEOA => { + return step.functionName === 'ovmCREATEEOA' +} + export const isTestStep_CREATE2 = ( step: TestStep ): step is TestStep_CREATE2 => {