Commit ce5d5967 authored by smartcontracts's avatar smartcontracts Committed by GitHub

refactor[contracts]: Port OVM_ECDSAContractAccount to use ovm-solc (#546)

* Port OVM_ECDSAContractAccount to use ovm-solc

* refactor[contracts]: Use OVM_ETH instead of iOVM_ERC20

* chore[contracts]: Add changeset
parent edb43461
---
"@eth-optimism/contracts": patch
---
Ports OVM_ECDSAContractAccount to use optimistic-solc.
// 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);
}
}
}
......@@ -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";
/**
......
......@@ -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.
......
// 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
......@@ -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),
......
......@@ -167,6 +167,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
'OVM_SequencerEntrypoint',
'Lib_AddressManager',
'OVM_ETH',
'OVM_ECDSAContractAccount',
'OVM_ProxyEOA',
]
......
export * from './contract-defs'
export { getLatestStateDump, StateDump } from './contract-dumps'
export * from './contract-deployment'
export * from './predeploys'
......@@ -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,
......
......@@ -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)
)
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment