Commit 7a3d3ad7 authored by ben-chain's avatar ben-chain Committed by GitHub

Eliminate EM.safeCREATE logic (#308)

* new create codepath working

* get calls ported over to new function

* remove from interface

* clean up, add comments

* remove unused commented code

* fix nuisance gas

* add return type to ovmCREATE

* focus on final failing test

* finish up tests with new contract account revert type

* remove test focus

* linting

* cleanup

* add explicit unsafe bytecode test

* linting

* remove pointless ternary

* fix docstring

* fix eth_call for creates and fix contract account reversions

* use if statement instead

* nits

* Update contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Co-authored-by: default avatarsmartcontracts <kelvinfichter@gmail.com>

* Update contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Co-authored-by: default avatarsmartcontracts <kelvinfichter@gmail.com>

* Update contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Co-authored-by: default avatarsmartcontracts <kelvinfichter@gmail.com>

* Update contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Co-authored-by: default avatarsmartcontracts <kelvinfichter@gmail.com>

* Update contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Co-authored-by: default avatarsmartcontracts <kelvinfichter@gmail.com>

* Fix nits on comments

* More small comment fixes

* fix capitalization, add ref URL

* PR feedback

* opcodes->bytecode

* doc: explain eth_call create return type

* Update contracts/optimistic-ethereum/OVM/execution/OVM_ExecutionManager.sol
Co-authored-by: default avatarsmartcontracts <kelvinfichter@gmail.com>
Co-authored-by: default avatarMaurelian <maurelian@protonmail.ch>
parent 6693d3a8
......@@ -113,14 +113,17 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.to == address(0)) {
address created = Lib_SafeExecutionManagerWrapper.safeCREATE(
(address created, bytes memory revertData) = Lib_SafeExecutionManagerWrapper.safeCREATE(
decodedTx.gasLimit,
decodedTx.data
);
// EVM doesn't tell us whether a contract creation failed, even if it reverted during
// initialization. Always return `true` for our success value here.
return (true, abi.encode(created));
// Return true if the contract creation succeeded, false w/ revertData otherwise.
if (created != address(0)) {
return (true, abi.encode(created));
} else {
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
......
......@@ -7,6 +7,7 @@ pragma experimental ABIEncoderV2;
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_EthUtils } from "../../libraries/utils/Lib_EthUtils.sol";
import { Lib_ErrorUtils } from "../../libraries/utils/Lib_ErrorUtils.sol";
/* Interface Imports */
import { iOVM_ExecutionManager } from "../../iOVM/execution/iOVM_ExecutionManager.sol";
......@@ -364,7 +365,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
/**
* @notice Overrides CREATE.
* @param _bytecode Code to be used to CREATE a new contract.
* @return _contract Address of the created contract.
* @return Address of the created contract.
* @return Revert data, if and only if the creation threw an exception.
*/
function ovmCREATE(
bytes memory _bytecode
......@@ -374,7 +376,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
notStatic
fixedGasDiscount(40000)
returns (
address _contract
address,
bytes memory
)
{
// Creator is always the current ADDRESS.
......@@ -400,7 +403,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
* @notice Overrides CREATE2.
* @param _bytecode Code to be used to CREATE2 a new contract.
* @param _salt Value used to determine the contract's address.
* @return _contract Address of the created contract.
* @return Address of the created contract.
* @return Revert data, if and only if the creation threw an exception.
*/
function ovmCREATE2(
bytes memory _bytecode,
......@@ -411,7 +415,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
notStatic
fixedGasDiscount(40000)
returns (
address _contract
address,
bytes memory
)
{
// Creator is always the current ADDRESS.
......@@ -769,90 +774,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
);
}
/**************************************
* Public Functions: Execution Safety *
**************************************/
/**
* Performs the logic to create a contract and revert under various potential conditions.
* @dev This function is implemented as `public` because we need to be able to revert a
* contract creation without losing information about exactly *why* the contract reverted.
* In particular, we want to be sure that contracts cannot trigger an INVALID_STATE_ACCESS
* flag and then revert to reset the flag. We're able to do this by making an external
* call from `ovmCREATE` and `ovmCREATE2` to `safeCREATE`, which can capture and relay
* information before reverting.
* @param _address Address of the contract to associate with the one being created.
* @param _bytecode Code to be used to create the new contract.
*/
function safeCREATE(
address _address,
bytes memory _bytecode
)
override
public
{
// Since this function is public, anyone can attempt to directly call it. We need to make
// sure that the OVM_ExecutionManager itself is the only party that can actually try to
// call this function.
if (msg.sender != address(this)) {
return;
}
// We need to be sure that the user isn't trying to use a contract creation to overwrite
// some existing contract. On L1, users will prove that no contract exists at the address
// and the OVM_FraudVerifier will populate the code hash of this address with a special
// value that represents "known to be an empty account."
if (_hasEmptyAccount(_address) == false) {
_revertWithFlag(RevertFlag.CREATE_COLLISION);
}
// Check the creation bytecode against the OVM_SafetyChecker.
if (ovmSafetyChecker.isBytecodeSafe(_bytecode) == false) {
_revertWithFlag(RevertFlag.UNSAFE_BYTECODE);
}
// We always need to initialize the contract with the default account values.
_initPendingAccount(_address);
// Actually deploy the contract and retrieve its address. This step is hiding a lot of
// complexity because we need to ensure that contract creation *never* reverts by itself.
// We cover this partially by storing a revert flag and returning (instead of reverting)
// when we know that we're inside a contract's creation code.
address ethAddress = Lib_EthUtils.createContract(_bytecode);
// Contract creation returns the zero address when it fails, which should only be possible
// if the user intentionally runs out of gas. However, we might still have a bit of gas
// left over since contract calls can only be passed 63/64ths of total gas, so we need to
// explicitly handle this case here.
if (ethAddress == address(0)) {
_revertWithFlag(RevertFlag.CREATE_EXCEPTION);
}
// Here we pull out the revert flag that would've been set during creation code. Now that
// we're out of creation code again, we can just revert normally while passing the flag
// through the revert data.
if (messageRecord.revertFlag != RevertFlag.DID_NOT_REVERT) {
_revertWithFlag(messageRecord.revertFlag);
}
// Again simply checking that the deployed code is safe too. Contracts can generate
// arbitrary deployment code, so there's no easy way to analyze this beforehand.
bytes memory deployedCode = Lib_EthUtils.getCode(ethAddress);
if (ovmSafetyChecker.isBytecodeSafe(deployedCode) == false) {
_revertWithFlag(RevertFlag.UNSAFE_BYTECODE);
}
// Contract creation didn't need to be reverted and the bytecode is safe. We finish up by
// associating the desired address with the newly created contract's code hash and address.
_commitPendingAccount(
_address,
ethAddress,
Lib_EthUtils.getCodeHash(ethAddress)
);
}
/***************************************
* Public Functions: Execution Context *
***************************************/
......@@ -873,7 +794,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
********************************************/
/**
* Checks whether the given address is on the whitelst to ovmCREATE/ovmCREATE2, and reverts if not.
* Checks whether the given address is on the whitelist to ovmCREATE/ovmCREATE2, and reverts if not.
* @param _deployerAddress Address attempting to deploy a contract.
*/
function _checkDeployerAllowed(
......@@ -881,7 +802,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
)
internal
{
// From an OVM semanitcs perspectibe, this will appear the identical to
// From an OVM semantics perspective, this will appear identical to
// the deployer ovmCALLing the whitelist. This is fine--in a sense, we are forcing them to.
(bool success, bytes memory data) = ovmCALL(
gasleft(),
......@@ -903,7 +824,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
* Creates a new contract and associates it with some contract address.
* @param _contractAddress Address to associate the created contract with.
* @param _bytecode Bytecode to be used to create the contract.
* @return _created Final OVM contract address.
* @return Final OVM contract address.
* @return Revertdata, if and only if the creation threw an exception.
*/
function _createContract(
address _contractAddress,
......@@ -911,7 +833,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
)
internal
returns (
address _created
address,
bytes memory
)
{
// We always update the nonce of the creating account, even if the creation fails.
......@@ -923,25 +846,21 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
nextMessageContext.ovmCALLER = messageContext.ovmADDRESS;
nextMessageContext.ovmADDRESS = _contractAddress;
// Run `safeCREATE` in a new EVM message so that our changes can be reflected even if
// `safeCREATE` reverts.
(bool _success, ) = _handleExternalInteraction(
// Run the common logic which occurs between call-type and create-type messages,
// passing in the creation bytecode and `true` to trigger create-specific logic.
(bool success, bytes memory data) = _handleExternalMessage(
nextMessageContext,
gasleft(),
address(this),
abi.encodeWithSignature(
"safeCREATE(address,bytes)",
_contractAddress,
_bytecode
)
_contractAddress,
_bytecode,
true
);
// Need to make sure that this flag is reset so that it isn't propagated to creations in
// some parent EVM message.
messageRecord.revertFlag = RevertFlag.DID_NOT_REVERT;
// Yellow paper requires that address returned is zero if the contract deployment fails.
return _success ? _contractAddress : address(0);
return (
success ? _contractAddress : address(0),
data
);
}
/**
......@@ -971,6 +890,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
(uint256(_contract) & uint256(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000))
== uint256(0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000)
) {
// EVM does not return data in the success case, see: https://github.com/ethereum/go-ethereum/blob/aae7660410f0ef90279e14afaaf2f429fdc2a186/core/vm/instructions.go#L600-L604
return (true, hex'');
}
......@@ -980,33 +900,38 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
? _contract
: _getAccountEthAddress(_contract);
return _handleExternalInteraction(
return _handleExternalMessage(
_nextMessageContext,
_gasLimit,
codeContractAddress,
_calldata
_calldata,
false
);
}
/**
* Handles the logic of making an external call and parsing revert information.
* @param _nextMessageContext Message context to be used for the call.
* @param _gasLimit Amount of gas to be passed into this call.
* @param _target Address of the contract to call.
* @param _data Data to send along with the call.
* @return _success Whether or not the call returned (rather than reverted).
* @return _returndata Data returned by the call.
* Handles all interactions which involve the execution manager calling out to untrusted code (both calls and creates).
* Ensures that OVM-related measures are enforced, including L2 gas refunds, nuisance gas, and flagged reversions.
*
* @param _nextMessageContext Message context to be used for the external message.
* @param _gasLimit Amount of gas to be passed into this message.
* @param _contract OVM address being called or deployed to
* @param _data Data for the message (either calldata or creation code)
* @param _isCreate Whether this is a create-type message.
* @return Whether or not the message (either a call or deployment) succeeded.
* @return Data returned by the message.
*/
function _handleExternalInteraction(
function _handleExternalMessage(
MessageContext memory _nextMessageContext,
uint256 _gasLimit,
address _target,
bytes memory _data
address _contract,
bytes memory _data,
bool _isCreate
)
internal
returns (
bool _success,
bytes memory _returndata
bool,
bytes memory
)
{
// We need to switch over to our next message context for the duration of this call.
......@@ -1022,11 +947,15 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
messageRecord.nuisanceGasLeft = nuisanceGasLimit;
// Make the call and make sure to pass in the gas limit. Another instance of hidden
// complexity. `_target` is guaranteed to be a safe contract, meaning its return/revert
// complexity. `_contract` is guaranteed to be a safe contract, meaning its return/revert
// behavior can be controlled. In particular, we enforce that flags are passed through
// revert data as to retrieve execution metadata that would normally be reverted out of
// existence.
(bool success, bytes memory returndata) = _target.call{gas: _gasLimit}(_data);
(bool success, bytes memory returndata) =
_isCreate
? _handleContractCreation(_gasLimit, _data, _contract)
: _contract.call{gas: _gasLimit}(_data);
// Switch back to the original message context now that we're out of the call.
_switchMessageContext(_nextMessageContext, prevMessageContext);
......@@ -1066,7 +995,10 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// INTENTIONAL_REVERT needs to pass up the user-provided return data encoded into the
// flag, *not* the full encoded flag. All other revert types return no data.
if (flag == RevertFlag.INTENTIONAL_REVERT) {
if (
flag == RevertFlag.INTENTIONAL_REVERT
|| _isCreate
) {
returndata = returndataFromFlag;
} else {
returndata = hex'';
......@@ -1089,6 +1021,99 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
);
}
/**
* Handles the creation-specific safety measures required for OVM contract deployment.
* This function sanitizes the return types for creation messages to match calls (bool, bytes).
* This allows for consistent handling of both types of messages in _handleExternalMessage().
*
* @param _gasLimit Amount of gas to be passed into this creation.
* @param _creationCode Code to pass into CREATE for deployment.
* @param _address OVM address being deployed to.
* @return Whether or not the call succeeded.
* @return If creation fails: revert data. Otherwise: empty.
*/
function _handleContractCreation(
uint _gasLimit,
bytes memory _creationCode,
address _address
)
internal
returns(
bool,
bytes memory
)
{
// Check that there is not already code at this address.
if (_hasEmptyAccount(_address) == false) {
// Note: in the EVM, this case burns all allotted gas. For improved
// developer experience, we do return the remaining ones.
return (
false,
_encodeRevertData(
RevertFlag.CREATE_COLLISION,
Lib_ErrorUtils.encodeRevertString("A contract has already been deployed to this address")
)
);
}
// Check the creation bytecode against the OVM_SafetyChecker.
if (ovmSafetyChecker.isBytecodeSafe(_creationCode) == false) {
return (
false,
_encodeRevertData(
RevertFlag.UNSAFE_BYTECODE,
Lib_ErrorUtils.encodeRevertString("Contract creation code contains unsafe opcodes. Did you use the right compiler or pass an unsafe constructor argument?")
)
);
}
// We always need to initialize the contract with the default account values.
_initPendingAccount(_address);
// Actually execute the EVM create message,
address ethAddress = Lib_EthUtils.createContract(_creationCode);
if (ethAddress == address(0)) {
// If the creation fails, the EVM lets us grab its revert data. This may contain a revert flag
// to be used above in _handleExternalMessage.
uint256 revertDataSize;
assembly { revertDataSize := returndatasize() }
bytes memory revertdata = new bytes(revertDataSize);
assembly {
returndatacopy(
add(revertdata, 0x20),
0,
revertDataSize
)
}
// Return that the creation failed, and the data it reverted with.
return (false, revertdata);
}
// Again simply checking that the deployed code is safe too. Contracts can generate
// arbitrary deployment code, so there's no easy way to analyze this beforehand.
bytes memory deployedCode = Lib_EthUtils.getCode(ethAddress);
if (ovmSafetyChecker.isBytecodeSafe(deployedCode) == false) {
return (
false,
_encodeRevertData(
RevertFlag.UNSAFE_BYTECODE,
Lib_ErrorUtils.encodeRevertString("Constructor attempted to deploy unsafe bytecode.")
)
);
}
// Contract creation didn't need to be reverted and the bytecode is safe. We finish up by
// associating the desired address with the newly created contract's code hash and address.
_commitPendingAccount(
_address,
ethAddress,
Lib_EthUtils.getCodeHash(ethAddress)
);
// Successful deployments will not give access to returndata, in both the EVM and the OVM.
return (true, hex'');
}
/******************************************
* Internal Functions: State Manipulation *
......@@ -1426,7 +1451,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// Out of gas and create exceptions will fundamentally return no data, so simulating it shouldn't either.
if (
_flag == RevertFlag.OUT_OF_GAS
|| _flag == RevertFlag.CREATE_EXCEPTION
) {
return bytes('');
}
......@@ -1494,27 +1518,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
bytes memory _data
)
internal
view
{
// We don't want to revert when we're inside a CREATE or CREATE2, because those opcodes
// fail silently (we can't pass any data upwards). Instead, we set a flag and return a
// *single* byte, something the OVM_ExecutionManager will not return in any other case.
// We're thereby allowed to communicate failure without allowing contracts to trick us into
// thinking there was a failure.
bool isCreation;
assembly {
isCreation := eq(extcodesize(caller()), 0)
}
if (isCreation) {
messageRecord.revertFlag = _flag;
assembly {
return(0, 1)
}
}
// If we're not inside a CREATE or CREATE2, we can simply encode the necessary data and
// revert normally.
bytes memory revertdata = _encodeRevertData(
_flag,
_data
......@@ -1805,7 +1810,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
messageContext.isStatic = false;
messageRecord.nuisanceGasLeft = 0;
messageRecord.revertFlag = RevertFlag.DID_NOT_REVERT;
}
/*****************************
......@@ -1834,17 +1838,18 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
ovmStateManager = _ovmStateManager;
_initContext(_transaction);
messageRecord.nuisanceGasLeft = uint(-1);
messageContext.ovmADDRESS = _from;
bool isCreate = _transaction.entrypoint == address(0);
if (isCreate) {
address created = ovmCREATE(_transaction.data);
(address created, bytes memory revertData) = ovmCREATE(_transaction.data);
if (created == address(0)) {
return (false, hex"");
return (false, revertData);
} else {
// The eth_call RPC endpoint for to = undefined will return the deployed bytecode
// in the success case, differing from standard create messages.
return (true, Lib_EthUtils.getCode(created));
}
} else {
......
......@@ -11,7 +11,6 @@ interface iOVM_ExecutionManager {
*********/
enum RevertFlag {
DID_NOT_REVERT,
OUT_OF_GAS,
INTENTIONAL_REVERT,
EXCEEDS_NUISANCE_GAS,
......@@ -19,7 +18,6 @@ interface iOVM_ExecutionManager {
UNSAFE_BYTECODE,
CREATE_COLLISION,
STATIC_VIOLATION,
CREATE_EXCEPTION,
CREATOR_NOT_ALLOWED
}
......@@ -67,7 +65,6 @@ interface iOVM_ExecutionManager {
struct MessageRecord {
uint256 nuisanceGasLeft;
RevertFlag revertFlag;
}
......@@ -112,8 +109,8 @@ interface iOVM_ExecutionManager {
* Contract Creation Opcodes *
*****************************/
function ovmCREATE(bytes memory _bytecode) external returns (address _contract);
function ovmCREATE2(bytes memory _bytecode, bytes32 _salt) external returns (address _contract);
function ovmCREATE(bytes memory _bytecode) external returns (address _contract, bytes memory _revertdata);
function ovmCREATE2(bytes memory _bytecode, bytes32 _salt) external returns (address _contract, bytes memory _revertdata);
/*******************************
......@@ -151,12 +148,6 @@ interface iOVM_ExecutionManager {
function ovmEXTCODEHASH(address _contract) external returns (bytes32 _hash);
/**************************************
* Public Functions: Execution Safety *
**************************************/
function safeCREATE(address _address, bytes memory _bytecode) external;
/***************************************
* Public Functions: Execution Context *
***************************************/
......
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/**
* @title Lib_ErrorUtils
*/
library Lib_ErrorUtils {
/**********************
* Internal Functions *
**********************/
/**
* Encodes an error string into raw solidity-style revert data.
* (i.e. ascii bytes, prefixed with bytes4(keccak("Error(string))"))
* Ref: https://docs.soliditylang.org/en/v0.8.2/control-structures.html?highlight=Error(string)#panic-via-assert-and-error-via-require
* @param _reason Reason for the reversion.
* @return Standard solidity revert data for the given reason.
*/
function encodeRevertString(
string memory _reason
)
internal
pure
returns (
bytes memory
)
{
return abi.encodeWithSignature(
"Error(string)",
_reason
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/* Library Imports */
import { Lib_ErrorUtils } from "../utils/Lib_ErrorUtils.sol";
/**
* @title Lib_SafeExecutionManagerWrapper
* @dev The Safe Execution Manager Wrapper provides functions which facilitate writing OVM safe
......@@ -90,7 +93,8 @@ library Lib_SafeExecutionManagerWrapper {
)
internal
returns (
address _contract
address,
bytes memory
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
......@@ -101,7 +105,7 @@ library Lib_SafeExecutionManagerWrapper {
)
);
return abi.decode(returndata, (address));
return abi.decode(returndata, (address, bytes));
}
/**
......@@ -258,8 +262,7 @@ library Lib_SafeExecutionManagerWrapper {
_safeExecutionManagerInteraction(
abi.encodeWithSignature(
"ovmREVERT(bytes)",
abi.encodeWithSignature(
"Error(string)",
Lib_ErrorUtils.encodeRevertString(
_reason
)
)
......
......@@ -54,7 +54,7 @@ contract mockOVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.to == address(0)) {
address created = Lib_SafeExecutionManagerWrapper.safeCREATE(
(address created, ) = Lib_SafeExecutionManagerWrapper.safeCREATE(
decodedTx.gasLimit,
decodedTx.data
);
......
......@@ -128,12 +128,8 @@ contract Helper_TestRunner {
}
}
if (success == false || (success == true && returndata.length == 1)) {
if (success == false) {
assembly {
if eq(extcodesize(address()), 0) {
return(0, 1)
}
revert(add(returndata, 0x20), mload(returndata))
}
}
......
......@@ -77,9 +77,10 @@ describe('OVM_ECDSAContractAccount', () => {
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.ovmCREATE.will.return.with([
NON_ZERO_ADDRESS,
'0x',
])
Mock__OVM_ExecutionManager.smocked.ovmCALLER.will.return.with(
NON_ZERO_ADDRESS
)
......
......@@ -110,7 +110,7 @@ describe('OVM_ExecutionManager gas consumption', () => {
)
console.log(`calculated gas cost of ${gasCost}`)
const benchmark: number = 226_516
const benchmark: number = 225_500
expect(gasCost).to.be.lte(benchmark)
expect(gasCost).to.be.gte(
benchmark - 1_000,
......
......@@ -7,11 +7,13 @@ import {
NON_NULL_BYTES32,
REVERT_FLAGS,
DUMMY_BYTECODE,
UNSAFE_BYTECODE,
ZERO_ADDRESS,
VERIFIED_EMPTY_CONTRACT_HASH,
DUMMY_BYTECODE_BYTELEN,
DUMMY_BYTECODE_HASH,
getStorageXOR,
encodeSolidityError,
} from '../../../../helpers'
const CREATED_CONTRACT_1 = '0x2bda4a99d5be88609d23b1e4ab5d1d34fb1c2feb'
......@@ -68,7 +70,7 @@ const test_ovmCREATE: TestDefinition = {
ethAddress: '0x' + '00'.repeat(20),
},
[CREATED_CONTRACT_BY_2_2]: {
codeHash: '0x' + '01'.repeat(32),
codeHash: VERIFIED_EMPTY_CONTRACT_HASH,
ethAddress: '0x' + '00'.repeat(20),
},
[NESTED_CREATED_CONTRACT]: {
......@@ -79,6 +81,7 @@ const test_ovmCREATE: TestDefinition = {
contractStorage: {
$DUMMY_OVM_ADDRESS_2: {
[NULL_BYTES32]: getStorageXOR(NULL_BYTES32),
[NON_NULL_BYTES32]: getStorageXOR(NULL_BYTES32),
},
},
verifiedContractStorage: {
......@@ -87,6 +90,7 @@ const test_ovmCREATE: TestDefinition = {
},
$DUMMY_OVM_ADDRESS_2: {
[NULL_BYTES32]: true,
[NON_NULL_BYTES32]: true,
},
},
},
......@@ -167,20 +171,24 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmREVERT',
revertData: DUMMY_REVERT_DATA,
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INTENTIONAL_REVERT,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
expectedReturnValue: {
address: ZERO_ADDRESS,
revertData: DUMMY_REVERT_DATA,
},
},
],
},
{
name: 'ovmCREATE => ovmREVERT, ovmEXTCODESIZE(CREATED)',
// TODO: Appears to be failing because of a bug in smock.
skip: true,
steps: [
{
functionName: 'ovmCREATE',
......@@ -189,13 +197,19 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmREVERT',
revertData: DUMMY_REVERT_DATA,
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INTENTIONAL_REVERT,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
expectedReturnValue: {
address: ZERO_ADDRESS,
revertData: DUMMY_REVERT_DATA,
},
},
{
functionName: 'ovmEXTCODESIZE',
......@@ -209,8 +223,6 @@ const test_ovmCREATE: TestDefinition = {
},
{
name: 'ovmCREATE => ovmREVERT, ovmEXTCODEHASH(CREATED)',
// TODO: Appears to be failing because of a bug in smock.
skip: true,
steps: [
{
functionName: 'ovmCREATE',
......@@ -219,13 +231,19 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmREVERT',
revertData: DUMMY_REVERT_DATA,
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INTENTIONAL_REVERT,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
expectedReturnValue: {
address: ZERO_ADDRESS,
revertData: DUMMY_REVERT_DATA,
},
},
{
functionName: 'ovmEXTCODEHASH',
......@@ -239,8 +257,6 @@ const test_ovmCREATE: TestDefinition = {
},
{
name: 'ovmCREATE => ovmREVERT, ovmEXTCODECOPY(CREATED)',
// TODO: Appears to be failing because of a bug in smock.
skip: true,
steps: [
{
functionName: 'ovmCREATE',
......@@ -249,13 +265,19 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmREVERT',
revertData: DUMMY_REVERT_DATA,
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INTENTIONAL_REVERT,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
expectedReturnValue: {
address: ZERO_ADDRESS,
revertData: DUMMY_REVERT_DATA,
},
},
{
functionName: 'ovmEXTCODECOPY',
......@@ -472,10 +494,10 @@ const test_ovmCREATE: TestDefinition = {
],
},
{
// TODO: appears to be failing due to a smoddit issue
skip: true,
name:
'ovmCREATE => (ovmCALL(ADDRESS_2) => ovmSSTORE) + ovmREVERT, ovmCALL(ADDRESS_2) => ovmSLOAD',
// TODO: Appears to be failing because of a bug in smock.
skip: true,
steps: [
{
functionName: 'ovmCREATE',
......@@ -487,10 +509,18 @@ const test_ovmCREATE: TestDefinition = {
gasLimit: OVM_TX_GAS_LIMIT,
target: '$DUMMY_OVM_ADDRESS_2',
subSteps: [
{
functionName: 'ovmSLOAD',
functionParams: {
key: NON_NULL_BYTES32,
},
expectedReturnStatus: true,
expectedReturnValue: NON_NULL_BYTES32,
},
{
functionName: 'ovmSSTORE',
functionParams: {
key: NULL_BYTES32,
key: NON_NULL_BYTES32,
value: NON_NULL_BYTES32,
},
expectedReturnStatus: true,
......@@ -502,13 +532,19 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmREVERT',
revertData: DUMMY_REVERT_DATA,
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INTENTIONAL_REVERT,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
expectedReturnValue: {
address: ZERO_ADDRESS,
revertData: DUMMY_REVERT_DATA,
},
},
{
functionName: 'ovmCALL',
......@@ -519,10 +555,10 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmSLOAD',
functionParams: {
key: NULL_BYTES32,
key: NON_NULL_BYTES32,
},
expectedReturnStatus: true,
expectedReturnValue: NULL_BYTES32,
expectedReturnValue: NON_NULL_BYTES32,
},
],
},
......@@ -545,8 +581,11 @@ const test_ovmCREATE: TestDefinition = {
target: '$DUMMY_OVM_ADDRESS_3',
calldata: '0x',
},
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INVALID_STATE_ACCESS,
onlyValidateFlag: true,
},
},
],
},
......@@ -558,7 +597,7 @@ const test_ovmCREATE: TestDefinition = {
],
},
{
name: 'ovmCREATE => ovmCREATE => ovmCALL(ADDRESS_NONEXIST)',
name: 'ovmCALL => ovmCREATE => ovmCREATE',
steps: [
{
functionName: 'ovmCALL',
......@@ -573,10 +612,10 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmCREATE',
functionParams: {
bytecode: '0x',
bytecode: '0x', // this will still succeed with empty bytecode
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
expectedReturnValue: CREATED_CONTRACT_BY_2_2,
},
],
},
......@@ -608,19 +647,26 @@ const test_ovmCREATE: TestDefinition = {
target: '$DUMMY_OVM_ADDRESS_3',
calldata: '0x',
},
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INVALID_STATE_ACCESS,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INVALID_STATE_ACCESS,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INVALID_STATE_ACCESS,
onlyValidateFlag: true,
},
},
],
......@@ -648,13 +694,19 @@ const test_ovmCREATE: TestDefinition = {
{
functionName: 'ovmREVERT',
revertData: DUMMY_REVERT_DATA,
expectedReturnStatus: true,
expectedReturnValue: '0x00',
expectedReturnStatus: false,
expectedReturnValue: {
flag: REVERT_FLAGS.INTENTIONAL_REVERT,
onlyValidateFlag: true,
},
},
],
},
expectedReturnStatus: true,
expectedReturnValue: ZERO_ADDRESS,
expectedReturnValue: {
address: ZERO_ADDRESS,
revertData: DUMMY_REVERT_DATA,
},
},
],
},
......@@ -675,6 +727,24 @@ const test_ovmCREATE: TestDefinition = {
},
],
},
{
name: 'ovmCREATE(UNSAFE_CODE)',
steps: [
{
functionName: 'ovmCREATE',
functionParams: {
bytecode: UNSAFE_BYTECODE,
},
expectedReturnStatus: true,
expectedReturnValue: {
address: ZERO_ADDRESS,
revertData: encodeSolidityError(
'Constructor attempted to deploy unsafe bytecode.'
),
},
},
],
},
],
subTests: [
{
......
......@@ -34,14 +34,12 @@ export const decodeRevertData = (revertData: string): any => {
}
export const REVERT_FLAGS = {
DID_NOT_REVERT: 0,
OUT_OF_GAS: 1,
INTENTIONAL_REVERT: 2,
EXCEEDS_NUISANCE_GAS: 3,
INVALID_STATE_ACCESS: 4,
UNSAFE_BYTECODE: 5,
CREATE_COLLISION: 6,
STATIC_VIOLATION: 7,
CREATE_EXCEPTION: 8,
CREATOR_NOT_ALLOWED: 9,
OUT_OF_GAS: 0,
INTENTIONAL_REVERT: 1,
EXCEEDS_NUISANCE_GAS: 2,
INVALID_STATE_ACCESS: 3,
UNSAFE_BYTECODE: 4,
CREATE_COLLISION: 5,
STATIC_VIOLATION: 6,
CREATOR_NOT_ALLOWED: 7,
}
......@@ -3,4 +3,5 @@ import { keccak256 } from 'ethers/lib/utils'
export const DUMMY_BYTECODE = '0x123412341234'
export const DUMMY_BYTECODE_BYTELEN = 6
export const UNSAFE_BYTECODE = '0x6069606955'
export const DUMMY_BYTECODE_HASH = keccak256(DUMMY_BYTECODE)
......@@ -38,6 +38,7 @@ import {
NULL_BYTES32,
} from '../constants'
import { getStorageXOR } from '../'
import { UNSAFE_BYTECODE } from '../dummy'
export class ExecutionManagerTestRunner {
private snapshot: string
......@@ -194,7 +195,11 @@ export class ExecutionManagerTestRunner {
).deploy()
const MockSafetyChecker = await smockit(SafetyChecker)
MockSafetyChecker.smocked.isBytecodeSafe.will.return.with(true)
MockSafetyChecker.smocked.isBytecodeSafe.will.return.with(
(bytecode: string) => {
return bytecode !== UNSAFE_BYTECODE
}
)
this.contracts.OVM_SafetyChecker = MockSafetyChecker
......@@ -494,6 +499,19 @@ export class ExecutionManagerTestRunner {
}
}
if (isTestStep_CREATE(step) || isTestStep_CREATE2(step)) {
if (!isRevertFlagError(step.expectedReturnValue)) {
if (typeof step.expectedReturnValue === 'string') {
returnData = [step.expectedReturnValue, '0x']
} else {
returnData = [
step.expectedReturnValue.address,
step.expectedReturnValue.revertData || '0x',
]
}
}
}
return this.contracts.OVM_ExecutionManager.interface.encodeFunctionResult(
step.functionName,
returnData
......
......@@ -118,7 +118,13 @@ interface TestStep_CREATE {
subSteps?: TestStep[]
}
expectedReturnStatus: boolean
expectedReturnValue: string | RevertFlagError
expectedReturnValue:
| string
| {
address: string
revertData: string
}
| RevertFlagError
}
interface TestStep_CREATE2 {
......@@ -129,7 +135,13 @@ interface TestStep_CREATE2 {
subSteps?: TestStep[]
}
expectedReturnStatus: boolean
expectedReturnValue: string | RevertFlagError
expectedReturnValue:
| string
| {
address: string
revertData: string
}
| RevertFlagError
}
interface TestStep_CREATEEOA {
......
......@@ -16,3 +16,7 @@ const errorABI = new ethers.utils.Interface([
export const decodeSolidityError = (err: string): string => {
return errorABI.decodeFunctionData('Error', err)[0]
}
export const encodeSolidityError = (message: string): string => {
return errorABI.encodeFunctionData('Error', [message])
}
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