diff --git a/.changeset/beige-needles-kiss.md b/.changeset/beige-needles-kiss.md new file mode 100644 index 0000000000000..381e5f89792df --- /dev/null +++ b/.changeset/beige-needles-kiss.md @@ -0,0 +1,5 @@ +--- +"@eth-optimism/contracts": patch +--- + +Ports OVM_ECDSAContractAccount to use optimistic-solc. 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 ee013f1e69345..ecbf2fae8e338 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ECDSAContractAccount.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ECDSAContractAccount.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// @unsupported: evm pragma solidity >0.5.0 <0.8.0; pragma experimental ABIEncoderV2; @@ -8,8 +9,13 @@ import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContrac /* Library Imports */ 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"; -import { Lib_SafeMathWrapper } from "../../libraries/wrappers/Lib_SafeMathWrapper.sol"; +import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol"; + +/* Contract Imports */ +import { OVM_ETH } from "../predeploys/OVM_ETH.sol"; + +/* External Imports */ +import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; /** * @title OVM_ECDSAContractAccount @@ -17,7 +23,7 @@ import { Lib_SafeMathWrapper } from "../../libraries/wrappers/Lib_SafeMathWrappe * ovmCREATEEOA operation. It enables backwards compatibility with Ethereum's Layer 1, by * providing eth_sign and EIP155 formatted transaction encodings. * - * Compiler used: solc + * Compiler used: optimistic-solc * Runtime target: OVM */ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { @@ -29,7 +35,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { // TODO: should be the amount sufficient to cover the gas costs of all of the transactions up // to and including the CALL/CREATE which forms the entrypoint of the transaction. uint256 constant EXECUTION_VALIDATION_GAS_OVERHEAD = 25000; - address constant ETH_ERC20_ADDRESS = 0x4200000000000000000000000000000000000006; + OVM_ETH constant ovmETH = OVM_ETH(0x4200000000000000000000000000000000000006); /******************** @@ -66,75 +72,75 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { // 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. // Need to make sure that the transaction nonce is right and bump it if so. - Lib_SafeExecutionManagerWrapper.safeREQUIRE( + require( Lib_ECDSAUtils.recover( _transaction, isEthSign, _v, _r, _s - ) == Lib_SafeExecutionManagerWrapper.safeADDRESS(), + ) == address(this), "Signature provided for EOA transaction execution is invalid." ); - Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction(_transaction, isEthSign); + Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction( + _transaction, + isEthSign + ); + + // Grab the chain ID of the current network. + uint256 chainId; + assembly { + chainId := chainid() + } // Need to make sure that the transaction chainId is correct. - Lib_SafeExecutionManagerWrapper.safeREQUIRE( - decodedTx.chainId == Lib_SafeExecutionManagerWrapper.safeCHAINID(), + require( + decodedTx.chainId == chainId, "Transaction chainId does not match expected OVM chainId." ); // Need to make sure that the transaction nonce is right. - Lib_SafeExecutionManagerWrapper.safeREQUIRE( - decodedTx.nonce == Lib_SafeExecutionManagerWrapper.safeGETNONCE(), + require( + decodedTx.nonce == Lib_ExecutionManagerWrapper.ovmGETNONCE(), "Transaction nonce does not match the expected nonce." ); // TEMPORARY: Disable gas checks for mainnet. // // Need to make sure that the gas is sufficient to execute the transaction. - // Lib_SafeExecutionManagerWrapper.safeREQUIRE( - // gasleft() >= Lib_SafeMathWrapper.add(decodedTx.gasLimit, EXECUTION_VALIDATION_GAS_OVERHEAD), + // require( + // gasleft() >= SafeMath.add(decodedTx.gasLimit, EXECUTION_VALIDATION_GAS_OVERHEAD), // "Gas is not sufficient to execute the transaction." // ); // Transfer fee to relayer. - address relayer = Lib_SafeExecutionManagerWrapper.safeCALLER(); - uint256 fee = Lib_SafeMathWrapper.mul(decodedTx.gasLimit, decodedTx.gasPrice); - (bool success, ) = Lib_SafeExecutionManagerWrapper.safeCALL( - gasleft(), - ETH_ERC20_ADDRESS, - abi.encodeWithSignature("transfer(address,uint256)", relayer, fee) - ); - Lib_SafeExecutionManagerWrapper.safeREQUIRE( - success == true, + require( + ovmETH.transfer( + msg.sender, + SafeMath.mul(decodedTx.gasLimit, decodedTx.gasPrice) + ), "Fee was not transferred to relayer." ); // Contract creations are signalled by sending a transaction to the zero address. if (decodedTx.to == address(0)) { - (address created, bytes memory revertData) = Lib_SafeExecutionManagerWrapper.safeCREATE( - gasleft(), + (address created, bytes memory revertdata) = Lib_ExecutionManagerWrapper.ovmCREATE( decodedTx.data ); - // Return true if the contract creation succeeded, false w/ revertData otherwise. + // Return true if the contract creation succeeded, false w/ revertdata otherwise. if (created != address(0)) { return (true, abi.encode(created)); } else { - return (false, revertData); + return (false, revertdata); } } else { // 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.safeINCREMENTNONCE(); + Lib_ExecutionManagerWrapper.ovmINCREMENTNONCE(); - return Lib_SafeExecutionManagerWrapper.safeCALL( - gasleft(), - decodedTx.to, - decodedTx.data - ); + return decodedTx.to.call(decodedTx.data); } } } 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 0c0437a1ee4e9..2c05b33ffb16d 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol @@ -15,7 +15,6 @@ import { iOVM_StateManager } from "../../iOVM/execution/iOVM_StateManager.sol"; import { iOVM_SafetyChecker } from "../../iOVM/execution/iOVM_SafetyChecker.sol"; /* Contract Imports */ -import { OVM_ECDSAContractAccount } from "../accounts/OVM_ECDSAContractAccount.sol"; import { OVM_DeployerWhitelist } from "../predeploys/OVM_DeployerWhitelist.sol"; /** diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol index 1546922a6fc32..f719246bd1b55 100644 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol @@ -17,6 +17,30 @@ library Lib_ExecutionManagerWrapper { * Internal Functions * **********************/ + /** + * Performs a safe ovmCREATE call. + * @param _bytecode Code for the new contract. + * @return _contract Address of the created contract. + */ + function ovmCREATE( + bytes memory _bytecode + ) + internal + returns ( + address, + bytes memory + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + abi.encodeWithSignature( + "ovmCREATE(bytes)", + _bytecode + ) + ); + + return abi.decode(returndata, (address, bytes)); + } + /** * Performs a safe ovmGETNONCE call. * @return _nonce Result of calling ovmGETNONCE. diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_SafeMathWrapper.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_SafeMathWrapper.sol deleted file mode 100644 index 767be1ff0b11b..0000000000000 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_SafeMathWrapper.sol +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: MIT -// Pulled from @openzeppelin/contracts/math/SafeMath.sol -// SPDX-License-Identifier: MIT -pragma solidity >0.5.0 <0.8.0; - -/* Library Imports */ -import { Lib_SafeExecutionManagerWrapper } from "./Lib_SafeExecutionManagerWrapper.sol"; - -/** - * @title Lib_SafeMathWrapper - */ - -/** - * @dev Wrappers over Solidity's arithmetic operations with added overflow - * checks. - * - * Arithmetic operations in Solidity wrap on overflow. This can easily result - * in bugs, because programmers usually assume that an overflow raises an - * error, which is the standard behavior in high level programming languages. - * `SafeMath` restores this intuition by reverting the transaction when an - * operation overflows. - * - * Using this library instead of the unchecked operations eliminates an entire - * class of bugs, so it's recommended to use it always. - */ - -library Lib_SafeMathWrapper { - /** - * @dev Returns the addition of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `+` operator. - * - * Requirements: - * - * - Addition cannot overflow. - */ - function add(uint256 a, uint256 b) internal returns (uint256) { - uint256 c = a + b; - Lib_SafeExecutionManagerWrapper.safeREQUIRE(c >= a, "Lib_SafeMathWrapper: addition overflow"); - - return c; - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting on - * overflow (when the result is negative). - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b) internal returns (uint256) { - return sub(a, b, "Lib_SafeMathWrapper: subtraction overflow"); - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting with custom message on - * overflow (when the result is negative). - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b, string memory errorMessage) internal returns (uint256) { - Lib_SafeExecutionManagerWrapper.safeREQUIRE(b <= a, errorMessage); - uint256 c = a - b; - - return c; - } - - /** - * @dev Returns the multiplication of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `*` operator. - * - * Requirements: - * - * - Multiplication cannot overflow. - */ - function mul(uint256 a, uint256 b) internal returns (uint256) { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) { - return 0; - } - - uint256 c = a * b; - Lib_SafeExecutionManagerWrapper.safeREQUIRE(c / a == b, "Lib_SafeMathWrapper: multiplication overflow"); - - return c; - } - - /** - * @dev Returns the integer division of two unsigned integers. Reverts on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b) internal returns (uint256) { - return div(a, b, "Lib_SafeMathWrapper: division by zero"); - } - - /** - * @dev Returns the integer division of two unsigned integers. Reverts with custom message on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b, string memory errorMessage) internal returns (uint256) { - Lib_SafeExecutionManagerWrapper.safeREQUIRE(b > 0, errorMessage); - uint256 c = a / b; - // assert(a == b * c + a % b); // There is no case in which this doesn't hold - - return c; - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * Reverts when dividing by zero. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b) internal returns (uint256) { - return mod(a, b, "Lib_SafeMathWrapper: modulo by zero"); - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * Reverts with custom message when dividing by zero. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b, string memory errorMessage) internal returns (uint256) { - Lib_SafeExecutionManagerWrapper.safeREQUIRE(b != 0, errorMessage); - return a % b; - } -} \ No newline at end of file diff --git a/packages/contracts/src/contract-deployment/config.ts b/packages/contracts/src/contract-deployment/config.ts index d62dec7f69458..76cb3a08306e4 100644 --- a/packages/contracts/src/contract-deployment/config.ts +++ b/packages/contracts/src/contract-deployment/config.ts @@ -217,7 +217,7 @@ export const makeContractDeployConfig = async ( params: [AddressManager.address], }, OVM_ECDSAContractAccount: { - factory: getContractFactory('OVM_ECDSAContractAccount'), + factory: getContractFactory('OVM_ECDSAContractAccount', undefined, true), }, OVM_SequencerEntrypoint: { factory: getContractFactory('OVM_SequencerEntrypoint', undefined, true), diff --git a/packages/contracts/src/contract-dumps.ts b/packages/contracts/src/contract-dumps.ts index 6ee260f03967a..c2225d32361cb 100644 --- a/packages/contracts/src/contract-dumps.ts +++ b/packages/contracts/src/contract-dumps.ts @@ -167,6 +167,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise => { 'OVM_SequencerEntrypoint', 'Lib_AddressManager', 'OVM_ETH', + 'OVM_ECDSAContractAccount', 'OVM_ProxyEOA', ] diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index f86b060521374..ba9a1959a5686 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -1,3 +1,4 @@ export * from './contract-defs' export { getLatestStateDump, StateDump } from './contract-dumps' export * from './contract-deployment' +export * from './predeploys' 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 9bb7fc5bd1652..521b172b8b623 100644 --- a/packages/contracts/test/contracts/OVM/accounts/OVM_ECDSAContractAccount.spec.ts +++ b/packages/contracts/test/contracts/OVM/accounts/OVM_ECDSAContractAccount.spec.ts @@ -2,11 +2,11 @@ import { expect } from '../../../setup' /* External Imports */ import { ethers, waffle } from 'hardhat' -import { ContractFactory, Contract, Wallet } from 'ethers' +import { ContractFactory, Contract, Wallet, BigNumber } from 'ethers' import { MockContract, smockit } from '@eth-optimism/smock' +import { fromHexString, toHexString } from '@eth-optimism/core-utils' /* Internal Imports */ -import { NON_ZERO_ADDRESS } from '../../../helpers/constants' import { serializeNativeTransaction, signNativeTransaction, @@ -14,7 +14,9 @@ import { serializeEthSignTransaction, signEthSignMessage, decodeSolidityError, + NON_ZERO_ADDRESS, } from '../../../helpers' +import { getContractFactory, predeploys } from '../../../../src' const callPredeploy = async ( Helper_PredeployCaller: Contract, @@ -57,13 +59,16 @@ describe('OVM_ECDSAContractAccount', () => { Helper_PredeployCaller = await ( await ethers.getContractFactory('Helper_PredeployCaller') ).deploy() + Helper_PredeployCaller.setTarget(Mock__OVM_ExecutionManager.address) }) let Factory__OVM_ECDSAContractAccount: ContractFactory before(async () => { - Factory__OVM_ECDSAContractAccount = await ethers.getContractFactory( - 'OVM_ECDSAContractAccount' + Factory__OVM_ECDSAContractAccount = getContractFactory( + 'OVM_ECDSAContractAccount', + wallet, + true ) }) @@ -74,9 +79,38 @@ describe('OVM_ECDSAContractAccount', () => { Mock__OVM_ExecutionManager.smocked.ovmADDRESS.will.return.with( await wallet.getAddress() ) + Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(1) 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.ovmCALL.will.return.with( + (gasLimit, target, data) => { + if (target === predeploys.OVM_ETH) { + return [ + true, + '0x0000000000000000000000000000000000000000000000000000000000000001', + ] + } else { + return [true, '0x'] + } + } + ) + Mock__OVM_ExecutionManager.smocked.ovmSTATICCALL.will.return.with( + (gasLimit, target, data) => { + // Duplicating the behavior of the ecrecover precompile. + if (target === '0x0000000000000000000000000000000000000001') { + const databuf = fromHexString(data) + const addr = ethers.utils.recoverAddress(databuf.slice(0, 32), { + v: BigNumber.from(databuf.slice(32, 64)).toNumber(), + r: toHexString(databuf.slice(64, 96)), + s: toHexString(databuf.slice(96, 128)), + }) + const ret = ethers.utils.defaultAbiCoder.encode(['address'], [addr]) + return [true, ret] + } else { + return [true, '0x'] + } + } + ) Mock__OVM_ExecutionManager.smocked.ovmCREATE.will.return.with([ NON_ZERO_ADDRESS, '0x', @@ -266,7 +300,18 @@ describe('OVM_ECDSAContractAccount', () => { it(`should revert if fee is not transferred to the relayer`, async () => { const message = serializeNativeTransaction(DEFAULT_EIP155_TX) const sig = await signNativeTransaction(wallet, DEFAULT_EIP155_TX) - Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with([false, '0x']) + Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with( + (gasLimit, target, data) => { + if (target === '0x4200000000000000000000000000000000000006') { + return [ + true, + '0x0000000000000000000000000000000000000000000000000000000000000000', + ] + } else { + return [true, '0x'] + } + } + ) await callPredeploy( Helper_PredeployCaller, diff --git a/packages/contracts/test/contracts/OVM/accounts/OVM_ProxyEOA.spec.ts b/packages/contracts/test/contracts/OVM/accounts/OVM_ProxyEOA.spec.ts index 588068d0ae8ce..2d9f00f9939dc 100644 --- a/packages/contracts/test/contracts/OVM/accounts/OVM_ProxyEOA.spec.ts +++ b/packages/contracts/test/contracts/OVM/accounts/OVM_ProxyEOA.spec.ts @@ -8,7 +8,7 @@ import { remove0x } from '@eth-optimism/core-utils' /* Internal Imports */ import { decodeSolidityError } from '../../../helpers' -import { getContractFactory } from '../../../../src' +import { getContractInterface, getContractFactory } from '../../../../src' const callPredeploy = async ( Helper_PredeployCaller: Contract, @@ -55,7 +55,7 @@ describe('OVM_ProxyEOA', () => { Helper_PredeployCaller.setTarget(Mock__OVM_ExecutionManager.address) Mock__OVM_ECDSAContractAccount = await smockit( - await ethers.getContractFactory('OVM_ECDSAContractAccount') + getContractInterface('OVM_ECDSAContractAccount', true) ) })