Skip to content
This repository has been archived by the owner on Apr 12, 2021. It is now read-only.

More rigorous reverts for unsafe bytecode #369

Merged
merged 13 commits into from
Apr 9, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -963,10 +963,24 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// revert data as to retrieve execution metadata that would normally be reverted out of
// existence.

(bool success, bytes memory returndata) =
_isCreate
? _handleContractCreation(_gasLimit, _data, _contract)
: _contract.call{gas: _gasLimit}(_data);
bool success;
bytes memory returndata;

if (_isCreate) {
// safeCREATE() is a function which replicates a CREATE message, but uses return values
// Which match that of CALL (i.e. bool, bytes). This allows many security checks to be
// to be shared between untrusted call and create call frames.
(success, returndata) = address(this).call(
smartcontracts marked this conversation as resolved.
Show resolved Hide resolved
abi.encodeWithSelector(
this.safeCREATE.selector,
_gasLimit,
_data,
_contract
)
);
} else {
(success, returndata) = _contract.call{gas: _gasLimit}(_data);
}

// Switch back to the original message context now that we're out of the call.
_switchMessageContext(_nextMessageContext, prevMessageContext);
Expand Down Expand Up @@ -1034,83 +1048,69 @@ 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 function sanitizes the return types for creation messages to match calls (bool, bytes),
* by being an external function which the EM can call, that mimics the success/fail case of the CREATE.
* This allows for consistent handling of both types of messages in _handleExternalMessage().
*
* Having this step occur as a separate call frame also allows us to easily revert the
* contract deployment in the event that the code is unsafe.
*
* @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(
function safeCREATE(
uint _gasLimit,
bytes memory _creationCode,
address _address
)
internal
returns(
bool,
bytes memory
)
external
{
// The only way this should callable is from within _createContract(),
// and it should DEFINITELY not be callable by a non-EM code contract.
if (msg.sender != address(this)) {
return;
}
// 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")
)
// developer experience, we do return the remaining gas.
_revertWithFlag(
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?")
)
_revertWithFlag(
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,
// Actually execute the EVM create message.
// NOTE: The inline assembly below means we can NOT make any evm calls between here and then.
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
)
// to be used above in _handleExternalMessage, so we pass the revert data back up unmodified.
assembly {
returndatacopy(0,0,returndatasize())
ben-chain marked this conversation as resolved.
Show resolved Hide resolved
revert(0, returndatasize())
}
// 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.")
)
_revertWithFlag(
RevertFlag.UNSAFE_BYTECODE,
Lib_ErrorUtils.encodeRevertString("Constructor attempted to deploy unsafe bytecode.")
);
}

Expand All @@ -1121,9 +1121,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
ethAddress,
Lib_EthUtils.getCodeHash(ethAddress)
);

// Successful deployments will not give access to returndata, in both the EVM and the OVM.
return (true, hex'');
}

/******************************************
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
},
"devDependencies": {
"@eth-optimism/dev": "^1.1.1",
"@eth-optimism/plugins": "^1.0.0-alpha.2",
"@eth-optimism/smock": "1.0.0-alpha.3",
"@eth-optimism/plugins": "1.0.0-alpha.2",
"@eth-optimism/smock": "1.0.0-alpha.6",
"@ethersproject/abstract-signer": "^5.0.14",
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ const test_nuisanceGas: TestDefinition = {
// This is because there is natural gas consumption between the ovmCALL(GAS/2) and ovmCREATE, which allots nuisance gas via _getNuisanceGasLimit.
// This means that the ovmCREATE exception, DOES consumes all nuisance gas allotted, but that allotment
// is less than the full OVM_TX_GAS_LIMIT / 2 which is alloted to the parent ovmCALL.
nuisanceGasLeft: 4531286,
nuisanceGasLeft: 4197042,
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion test/contracts/OVM/verification/OVM_BondManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ describe('OVM_BondManager', () => {
// smodify the canClaim value to true to test claiming
const block = await provider.getBlock('latest')
timestamp = block.timestamp
bondManager.smodify.set({
await bondManager.smodify.put({
witnessProviders: {
[preStateRoot]: {
canClaim: true,
Expand Down
16 changes: 8 additions & 8 deletions test/contracts/OVM/verification/OVM_StateTransitioner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ describe('OVM_StateTransitioner', () => {
)
)

OVM_StateTransitioner.smodify.set({
await OVM_StateTransitioner.smodify.put({
phase: 0,
transactionHash,
})
Expand All @@ -329,7 +329,7 @@ describe('OVM_StateTransitioner', () => {

describe('commitContractState', () => {
beforeEach(async () => {
OVM_StateTransitioner.smodify.set({
await OVM_StateTransitioner.smodify.put({
phase: 1,
})
})
Expand Down Expand Up @@ -394,7 +394,7 @@ describe('OVM_StateTransitioner', () => {
proof = test.accountTrieWitness
postStateRoot = test.newAccountTrieRoot

OVM_StateTransitioner.smodify.put({
await OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot,
})
})
Expand All @@ -413,8 +413,8 @@ describe('OVM_StateTransitioner', () => {
})

describe('commitStorageSlot', () => {
beforeEach(() => {
OVM_StateTransitioner.smodify.set({
beforeEach(async () => {
await OVM_StateTransitioner.smodify.put({
phase: 1,
})
})
Expand Down Expand Up @@ -510,7 +510,7 @@ describe('OVM_StateTransitioner', () => {

storageTrieProof = storageTest.proof

OVM_StateTransitioner.smodify.put({
await OVM_StateTransitioner.smodify.put({
postStateRoot: test.accountTrieRoot,
})
})
Expand All @@ -529,8 +529,8 @@ describe('OVM_StateTransitioner', () => {
})

describe('completeTransition', () => {
beforeEach(() => {
OVM_StateTransitioner.smodify.set({
beforeEach(async () => {
await OVM_StateTransitioner.smodify.put({
phase: 1,
})
})
Expand Down
15 changes: 13 additions & 2 deletions test/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { defaultAccounts } from 'ethereum-waffle'
import { fromHexString, toHexString } from '@eth-optimism/core-utils'
import xor from 'buffer-xor'

/* Internal Imports */
import { getContractDefinition } from '../../src'

export const DEFAULT_ACCOUNTS = defaultAccounts
export const DEFAULT_ACCOUNTS_HARDHAT = defaultAccounts.map((account) => {
return {
Expand Down Expand Up @@ -35,8 +38,16 @@ export const NUISANCE_GAS_COSTS = {
MIN_GAS_FOR_INVALID_STATE_ACCESS: 30000,
}

// TODO: get this exported/imported somehow in a way that we can do math on it. unfortunately using require('.....artifacts/contract.json') is erroring...
export const Helper_TestRunner_BYTELEN = 3654
let len
// This is hacky, but `hardhat compile` evaluates this file for some reason.
// Feels better to have something hacky then a constant we have to keep re-hardcoding.
try {
len = fromHexString(
getContractDefinition('Helper_TestRunner').deployedBytecode
).byteLength
} catch {}

export const Helper_TestRunner_BYTELEN = len

export const STORAGE_XOR =
'0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef'
Expand Down
10 changes: 5 additions & 5 deletions test/helpers/test-runner/test-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ export class ExecutionManagerTestRunner {
replacedParameter = this.setPlaceholderStrings(parameter)
})

beforeEach(() => {
this.contracts.OVM_StateManager.smodify.set({
beforeEach(async () => {
await this.contracts.OVM_StateManager.smodify.put({
accounts: {
[this.contracts.Helper_TestRunner.address]: {
nonce: 0,
Expand All @@ -133,11 +133,11 @@ export class ExecutionManagerTestRunner {
})
})

beforeEach(() => {
this.contracts.OVM_ExecutionManager.smodify.set(
beforeEach(async () => {
await this.contracts.OVM_ExecutionManager.smodify.put(
replacedTest.preState.ExecutionManager
)
this.contracts.OVM_StateManager.smodify.set(
await this.contracts.OVM_StateManager.smodify.put(
replacedTest.preState.StateManager
)
})
Expand Down
Loading