Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Renaming Purity Checker -> Safety Checker #126

Merged
merged 1 commit into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/docs/src/core/src/spec/jump-transpilation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ Duplicate above code once for each (compare jumpdest, post-transpile jumpdest) p

**Note on bytecode interpretation**

Note that properly processing these conditions requires preprocessing the code; a particularly pathological use case is ``PUSH2 JUMPDEST PUSH1 PUSH2 JUMPDEST PUSH1 PUSH2 JUMPDEST PUSH1 ...``, as this code has all ``JUMPDEST``s invalid but an alternative piece of code equivalent to this but only with the leading ``PUSH2`` replaced with another op (eg. ``BALANCE``) will have all ``JUMPDESTS`` valid. We appropriately deal with this, both in our transpiler and purity checker.
Note that properly processing these conditions requires preprocessing the code; a particularly pathological use case is ``PUSH2 JUMPDEST PUSH1 PUSH2 JUMPDEST PUSH1 PUSH2 JUMPDEST PUSH1 ...``, as this code has all ``JUMPDEST``s invalid but an alternative piece of code equivalent to this but only with the leading ``PUSH2`` replaced with another op (eg. ``BALANCE``) will have all ``JUMPDESTS`` valid. We appropriately deal with this, both in our transpiler and safety checker.
6 changes: 3 additions & 3 deletions packages/docs/src/core/src/spec/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ OVM Overview and Architecture

The core functionality of the OVM is to run transactions in such a way that they are "pure" or "deterministic"--that is, no matter what time in the future a dispute about them is triggered on layer 1, the output of the computation is the same--*no matter what the state of the L1.

To accomplish this, there are two critical smart contracts: the Execution Manager, and the Purity Checker.
To accomplish this, there are two critical smart contracts: the Execution Manager, and the Safety Checker.

*****************
Execution Manager
Expand All @@ -19,10 +19,10 @@ The execution manager interfaces with "code contracts," which are contracts comp
<img src="../../_static/images/execution-manager.png" alt="The Execution Manager">

**************
Purity Checker
Safety Checker
**************

To ensure that the execution of an OVM transaction is deterministic between L1 and L2, we must enforce that **only** the container interface described above is used. To accomplish this, we have a "purity checker." The purity checker analyzes the low-level assembly bytecode of an EVM contract to tell the execution manager whether the code conforms to the OVM interface. If it does not, then the execution manager does not allow such a contract to be created or used in a fraud proof.
To ensure that the execution of an OVM transaction is deterministic between L1 and L2, we must enforce that **only** the container interface described above is used. To accomplish this, we have a "safety checker." The safety checker analyzes the low-level assembly bytecode of an EVM contract to tell the execution manager whether the code conforms to the OVM interface. If it does not, then the execution manager does not allow such a contract to be created or used in a fraud proof.

.. raw:: html

Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/core/src/spec/transpilation-details.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The following opcodes perform stack operations which are constant in terms of L1
- "Pure" memory modifying operations:
- ``MLOAD, MSTORE, MSTORE8, MSIZE``.
- Permitted execution-context-dependent operations:
- ``CALLVALUE*, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, RETURNDATASIZE, RETURNDATACOPY`` \*Note: ``CALLVALUE`` will always be 0 because we enforce that all ``CALL`` s always pass 0 in our purity checking.
- ``CALLVALUE*, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, RETURNDATASIZE, RETURNDATACOPY`` \*Note: ``CALLVALUE`` will always be 0 because we enforce that all ``CALL`` s always pass 0 in our safety checking.

Replaced Opcodes
================
Expand Down Expand Up @@ -119,7 +119,7 @@ These opcodes are banned simply because we don't want to support them currently.
ETH-native Value
-----------------------------------------

We have made the decision for now not to use native ETH, and instead do everything with wrapped ETH (WETH). Note: ``CALLVALUE`` is actually able to be whitelisted, because our Purity Checker enforces that all Calls are made with a value of 0. Contracts are welcome to use msg.value, it will just always return 0. This means that the following opcodes are banned, not just transpiled:
We have made the decision for now not to use native ETH, and instead do everything with wrapped ETH (WETH). Note: ``CALLVALUE`` is actually able to be whitelisted, because our Safety Checker enforces that all Calls are made with a value of 0. Contracts are welcome to use msg.value, it will just always return 0. This means that the following opcodes are banned, not just transpiled:
- ``BALANCE`` -- gets ``address(this).balance``
While not a ban, another note here is that all ``value``-related inputs to other opcodes like ``CREATE`` or ``CALL`` are overridden to ``0`` by their transpiled counterparts. We do have good inline documentation for how a native ``value`` could be added if needed. Another option is we could even transpile the native ETH opcodes to use ``WETH`` instead. TBD.

Expand Down
4 changes: 2 additions & 2 deletions packages/ovm/config/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# Mnemonic for the wallet used to deploy the contracts
DEPLOY_MNEMONIC='response fresh afford leader twice silent table exist aisle pelican focus bird'

DEPLOY_PURITY_CHECKER_CONTRACT_ADDRESS='0x some address here'
DEPLOY_SAFETY_CHECKER_CONTRACT_ADDRESS='0x some address here'

# Note: can use any network name. 'local' or leaving it blank will deploy to DEPLOY_LOCAL_URL
DEPLOY_NETWORK='local'
Expand All @@ -30,4 +30,4 @@ OPCODE_WHITELIST_MASK='0x600a0000000000000000001fffffffffffffffff0fcf000063f0000
# CREATE, CREATE2, DELEGATECALL, DIFFICULTY, EXTCODECOPY, EXTCODESIZE,
# GASLIMIT, GASPRICE, NUMBER, ORIGIN, SELFDESTRUCT, SLOAD, SSTORE,
# STATICCALL, TIMESTAMP
# See test/purity-checker/whitelist-mask-generator.spec.ts for more info
# See test/safety-checker/whitelist-mask-generator.spec.ts for more info
6 changes: 3 additions & 3 deletions packages/ovm/deploy/execution-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { deploy, deployContract } from '@eth-optimism/core-utils'
import { Wallet } from 'ethers'

/* Internal Imports */
import { deployPurityChecker } from './purity-checker'
import { deploySafetyChecker } from './safety-checker'
import * as ExecutionManager from '../build/contracts/ExecutionManager.json'
import { resolve } from 'path'
import { GAS_LIMIT, DEFAULT_OPCODE_WHITELIST_MASK } from '../src/app'
Expand All @@ -13,13 +13,13 @@ const executionManagerDeploymentFunction = async (
): Promise<string> => {
console.log(`\nDeploying ExecutionManager!\n`)

const purityCheckerContractAddress = await deployPurityChecker()
const safetyCheckerContractAddress = await deploySafetyChecker()

const executionManager = await deployContract(
ExecutionManager,
wallet,
DEFAULT_OPCODE_WHITELIST_MASK,
purityCheckerContractAddress,
safetyCheckerContractAddress,
GAS_LIMIT,
true
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import { deploy, deployContract, add0x } from '@eth-optimism/core-utils'
import { Wallet } from 'ethers'

/* Internal Imports */
import * as PurityChecker from '../build/contracts/PurityChecker.json'
import * as SafetyChecker from '../build/contracts/SafetyChecker.json'
import { resolve } from 'path'

const purityCheckerDeploymentFunction = async (
const safetyCheckerDeploymentFunction = async (
wallet: Wallet
): Promise<Address> => {
let purityCheckerContractAddress =
process.env.DEPLOY_PURITY_CHECKER_CONTRACT_ADDRESS
if (!purityCheckerContractAddress) {
console.log(`\nDeploying Purity Checker!\n`)
let safetyCheckerContractAddress =
process.env.DEPLOY_SAFETY_CHECKER_CONTRACT_ADDRESS
if (!safetyCheckerContractAddress) {
console.log(`\nDeploying Safety Checker!\n`)

// Default config whitelists all opcodes EXCEPT:
// ADDRESS, BALANCE, BLOCKHASH, CALLCODE, CALLER, COINBASE,
// CREATE, CREATE2, DELEGATECALL, DIFFICULTY, EXTCODECOPY, EXTCODESIZE,
// GASLIMIT, GASPRICE, NUMBER, ORIGIN, SELFDESTRUCT, SLOAD, SSTORE,
// STATICCALL, TIMESTAMP
// See test/purity-checker/whitelist-mask-generator.spec.ts for more info
// See test/contracts/whitelist-mask-generator.spec.ts for more info
const whitelistMask =
process.env.OPCODE_WHITELIST_MASK ||
'0x600a0000000000000000001fffffffffffffffff0fcf000063f000013fff0fff'
Expand All @@ -29,41 +29,41 @@ const purityCheckerDeploymentFunction = async (
process.env.EXECUTION_MANAGER_ADDRESS || add0x('12'.repeat(20))

console.log(
`Deploying Purity Checker using mask '${whitelistMask}' and execution manager '${executionManagerAddress}'...`
`Deploying Safety Checker using mask '${whitelistMask}' and execution manager '${executionManagerAddress}'...`
)
whitelistMask
const purityChecker = await deployContract(
PurityChecker,
const safetyChecker = await deployContract(
SafetyChecker,
wallet,
whitelistMask,
executionManagerAddress
)
purityCheckerContractAddress = purityChecker.address
safetyCheckerContractAddress = safetyChecker.address

console.log(
`Purity Checker deployed to ${purityCheckerContractAddress}!\n\n`
`Safety Checker deployed to ${safetyCheckerContractAddress}!\n\n`
)
} else {
console.log(
`Using Purity Checker contract at ${purityCheckerContractAddress}\n`
`Using Safety Checker contract at ${safetyCheckerContractAddress}\n`
)
}
return purityCheckerContractAddress
return safetyCheckerContractAddress
}

/**
* Deploys the Purity Checker contract.
* Deploys the Safety Checker contract.
*
* @param rootContract Whether or not this is the main contract being deployed (as compared to a dependency).
* @returns The deployed contract's address.
*/
export const deployPurityChecker = async (
export const deploySafetyChecker = async (
rootContract: boolean = false
): Promise<string> => {
// Note: Path is from 'build/deploy/<script>.js'
const configDirPath = resolve(__dirname, `../../config/`)

return deploy(purityCheckerDeploymentFunction, configDirPath, rootContract)
return deploy(safetyCheckerDeploymentFunction, configDirPath, rootContract)
}

deployPurityChecker(true)
deploySafetyChecker(true)
2 changes: 1 addition & 1 deletion packages/ovm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"build": "mkdir -p ./build && waffle waffle-config.json && tsc -p .",
"clean": "rimraf build/",
"deploy:execution-manager": "yarn build && node ./build/deploy/execution-manager.js",
"deploy:purity-checker": "yarn build && node ./build/deploy/purity-checker.js"
"deploy:safety-checker": "yarn build && node ./build/deploy/safety-checker.js"
},
"keywords": [
"optimistic",
Expand Down
38 changes: 19 additions & 19 deletions packages/ovm/src/contracts/ExecutionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma experimental ABIEncoderV2;
import {DataTypes as dt} from "./DataTypes.sol";
import {FullStateManager} from "./FullStateManager.sol";
import {ContractAddressGenerator} from "./ContractAddressGenerator.sol";
import {PurityChecker} from "./PurityChecker.sol";
import {SafetyChecker} from "./SafetyChecker.sol";
import {RLPEncode} from "./RLPEncode.sol";
import {L2ToL1MessagePasser} from "./precompiles/L2ToL1MessagePasser.sol";
import {L1MessageSender} from "./precompiles/L1MessageSender.sol";
Expand Down Expand Up @@ -33,11 +33,11 @@ contract ExecutionManager is FullStateManager {
dt.ExecutionContext executionContext;
// Add Contract Address Generation contract
ContractAddressGenerator contractAddressGenerator;
// Add Purity Checker contract
PurityChecker purityChecker;
// Add Safety Checker contract
SafetyChecker safetyChecker;
RLPEncode rlp;
// for testing: if true, then do not perform purity checking on init code or deployed bytecode
bool overridePurityChecker;
// for testing: if true, then do not perform safety checking on init code or deployed bytecode
bool overrideSafetyChecker;

// Events
event ActiveContract(address _activeContract);
Expand All @@ -63,18 +63,18 @@ contract ExecutionManager is FullStateManager {
);

/**
* @notice Construct a new ExecutionManager with a specified purity checker & owner.
* @param _opcodeWhitelistMask A bit mask representing which opcodes are whitelisted or not for our purity checker
* @notice Construct a new ExecutionManager with a specified safety checker & owner.
* @param _opcodeWhitelistMask A bit mask representing which opcodes are whitelisted or not for our safety checker
* @param _owner The owner of this contract.
* @param _blockGasLimit The block gas limit for OVM blocks
* @param _overridePurityChecker Set to true to disable purity checking (WARNING: Only do this in test environments)
* @param _overrideSafetyChecker Set to true to disable safety checking (WARNING: Only do this in test environments)
*/
constructor(uint256 _opcodeWhitelistMask, address _owner, uint _blockGasLimit, bool _overridePurityChecker) public {
constructor(uint256 _opcodeWhitelistMask, address _owner, uint _blockGasLimit, bool _overrideSafetyChecker) public {
rlp = new RLPEncode();
// Set override purity checker flag
overridePurityChecker = _overridePurityChecker;
// Set the purity checker address
purityChecker = new PurityChecker(_opcodeWhitelistMask, address(this));
// Set override safety checker flag
overrideSafetyChecker = _overrideSafetyChecker;
// Set the safety checker address
safetyChecker = new SafetyChecker(_opcodeWhitelistMask, address(this));
// Initialize new contract address generator
contractAddressGenerator = new ContractAddressGenerator();

Expand Down Expand Up @@ -573,9 +573,9 @@ contract ExecutionManager is FullStateManager {
* @return True if this succeeded, false otherwise.
*/
function createNewContract(address _newOvmContractAddress, bytes memory _ovmInitcode) internal returns (bool){
// Purity check the initcode -- unless the overridePurityChecker flag is set to true
if (!overridePurityChecker && !purityChecker.isBytecodePure(_ovmInitcode)) {
// Contract init code is not pure.
// Safety check the initcode -- unless the overrideSafetyChecker flag is set to true
if (!overrideSafetyChecker && !safetyChecker.isBytecodeSafe(_ovmInitcode)) {
// Contract init code is not safe.
return false;
}
// Switch the context to be the new contract
Expand All @@ -584,9 +584,9 @@ contract ExecutionManager is FullStateManager {
address codeContractAddress = deployCodeContract(_ovmInitcode);
// Get the runtime bytecode
bytes memory codeContractBytecode = getCodeContractBytecode(codeContractAddress);
// Purity check the runtime bytecode -- unless the overridePurityChecker flag is set to true
if (!overridePurityChecker && !purityChecker.isBytecodePure(codeContractBytecode)) {
// Contract runtime bytecode is not pure.
// Safety check the runtime bytecode -- unless the overrideSafetyChecker flag is set to true
if (!overrideSafetyChecker && !safetyChecker.isBytecodeSafe(codeContractBytecode)) {
// Contract runtime bytecode is not safe.
return false;
}
// Associate the code contract with our ovm contract
Expand Down
4 changes: 2 additions & 2 deletions packages/ovm/src/contracts/L2ExecutionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ contract L2ExecutionManager is ExecutionManager {
uint256 _opcodeWhitelistMask,
address _owner,
uint _gasLimit,
bool _overridePurityChecker
) ExecutionManager(_opcodeWhitelistMask, _owner, _gasLimit, _overridePurityChecker) public {}
bool _overrideSafetyChecker
) ExecutionManager(_opcodeWhitelistMask, _owner, _gasLimit, _overrideSafetyChecker) public {}

/**
@notice Stores the provided OVM transaction, mapping its hash to its value and its hash to the EVM tx hash
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
pragma solidity ^0.5.0;

/**
* @title PurityChecker
* @notice Purity Checker contract used to check whether or not bytecode is pure, meaning:
* @title SafetyChecker
* @notice Safety Checker contract used to check whether or not bytecode is safe, meaning:
* 1. It uses only whitelisted opcodes
* 2. All CALLs are to the Execution Manager and have no value set (no ETH sent)
*/
contract PurityChecker {
contract SafetyChecker {
uint256 public opcodeWhitelistMask;
address public executionManagerAddress;

/**
* @notice Construct a new Purity Checker with the specified whitelist mask
* @notice Construct a new Safety Checker with the specified whitelist mask
* @param _opcodeWhitelistMask A hex number of 256 bits where each bit represents an opcode, 0 - 255, which is set if whitelisted and unset otherwise.
* @param _executionManagerAddress The address of the ExecutionManager.sol contract
*/
Expand All @@ -33,11 +33,11 @@ contract PurityChecker {
}

/**
* @notice Returns whether or not all of the provided bytecode is pure.
* @param _bytecode The bytecode to purity check. This can be either creation bytecode (aka initcode) or runtime bytecode (aka contract code).
* @notice Returns whether or not all of the provided bytecode is safe.
* @param _bytecode The bytecode to safety check. This can be either creation bytecode (aka initcode) or runtime bytecode (aka contract code).
* More info on creation vs. runtime bytecode: https://medium.com/authereum/bytecode-and-init-code-and-runtime-code-oh-my-7bcd89065904
*/
function isBytecodePure(
function isBytecodeSafe(
bytes memory _bytecode
) public view returns (bool) {
bool seenJUMP = false;
Expand Down Expand Up @@ -79,7 +79,7 @@ contract PurityChecker {
seenJUMP = true;
// we are now inside unreachable code until we hit a JUMPDEST!
insideUnreachableCode = true;
// STOP or REVERT or INVALID or RETURN (see purity checker docs in wiki for more info)
// STOP or REVERT or INVALID or RETURN (see safety checker docs in wiki for more info)
} else if (op == 0x00 || op == 0xfd || op == 0xfe || op == 0xf3) {
// If we can't jump to JUMPDESTs, then all remaining bytecode is unreachable
if (!seenJUMP) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('ExecutionManager -- Create opcodes', () => {
const provider = createMockProvider({ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT })
const [wallet] = getWallets(provider)
let executionManager: Contract
let purityCheckedExecutioManager: Contract
let safetyCheckedExecutionManager: Contract
let deployTx
let deployInvalidTx

Expand All @@ -57,10 +57,10 @@ describe('ExecutionManager -- Create opcodes', () => {
SimpleStorage.bytecode
).getDeployTransaction(executionManager.address)

purityCheckedExecutioManager = await deployContract(
safetyCheckedExecutionManager = await deployContract(
wallet,
ExecutionManager, // Note: this is false, so it's purity checked.
[DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, false],
ExecutionManager,
[DEFAULT_OPCODE_WHITELIST_MASK, '0x' + '00'.repeat(20), GAS_LIMIT, false], // Note: this is false, so it's safety checked.
{ gasLimit: DEFAULT_ETHNODE_GAS_LIMIT }
)

Expand Down Expand Up @@ -90,7 +90,7 @@ describe('ExecutionManager -- Create opcodes', () => {

// Now actually apply it to our execution manager
const result = await executionManager.provider.call({
to: purityCheckedExecutioManager.address,
to: safetyCheckedExecutionManager.address,
data,
gasLimit,
})
Expand Down Expand Up @@ -130,7 +130,7 @@ describe('ExecutionManager -- Create opcodes', () => {

// Now actually apply it to our execution manager
const result = await executionManager.provider.call({
to: purityCheckedExecutioManager.address,
to: safetyCheckedExecutionManager.address,
data,
gasLimit,
})
Expand Down
Loading