diff --git a/.github/workflows/build-test-lint.yml b/.github/workflows/build-test-lint.yml index 25f996243c1a9..1b23b30bf18fc 100644 --- a/.github/workflows/build-test-lint.yml +++ b/.github/workflows/build-test-lint.yml @@ -69,13 +69,6 @@ jobs: path: packages/rollup-full-node/node_modules key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/rollup-full-node/package.json') }} - - name: Cache rollup-contracts deps - uses: actions/cache@v1 - id: cache_rollup-contracts - with: - path: packages/rollup-contracts/node_modules - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/rollup-contracts/package.json') }} - - name: Cache rollup-core deps uses: actions/cache@v1 id: cache_rollup-core @@ -83,20 +76,6 @@ jobs: path: packages/rollup-core/node_modules key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/rollup-core/package.json') }} - - name: Cache rollup-dev-tools deps - uses: actions/cache@v1 - id: cache_rollup-dev-tools - with: - path: packages/rollup-dev-tools/node_modules - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/rollup-dev-tools/package.json') }} - - - name: Cache solc-transpiler deps - uses: actions/cache@v1 - id: cache_solc-transpiler - with: - path: packages/solc-transpiler/node_modules - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('packages/solc-transpiler/package.json') }} - - name: Cache state-synchronizer deps uses: actions/cache@v1 id: cache_state-synchronizer diff --git a/.github/workflows/npm_publish.yml b/.github/workflows/npm_publish.yml index 5b93e36acb4ef..6ccb0180de508 100644 --- a/.github/workflows/npm_publish.yml +++ b/.github/workflows/npm_publish.yml @@ -72,20 +72,6 @@ jobs: path: packages/rollup-core/node_modules key: ${{ runner.os }}-${{ hashFiles('packages/rollup-core/package.json') }} - - name: Cache rollup-dev-tools deps - uses: actions/cache@v1 - id: cache_rollup-dev-tools - with: - path: packages/rollup-dev-tools/node_modules - key: ${{ runner.os }}-${{ hashFiles('packages/rollup-dev-tools/package.json') }} - - - name: Cache solc-transpiler deps - uses: actions/cache@v1 - id: cache_solc-transpiler - with: - path: packages/solc-transpiler/node_modules - key: ${{ runner.os }}-${{ hashFiles('packages/solc-transpiler/package.json') }} - - name: Cache state-synchronizer deps uses: actions/cache@v1 id: cache_state-synchronizer diff --git a/lerna.json b/lerna.json index 442961a6659bc..96ce38cb550e2 100644 --- a/lerna.json +++ b/lerna.json @@ -7,9 +7,7 @@ "packages/optimistic-game-semantics/*", "packages/ovm-toolchain/*", "packages/rollup-core/*", - "packages/rollup-dev-tools/*", - "packages/rollup-services/*", - "packages/solc-transpiler/*" + "packages/rollup-services/*" ], "command": { "publish": { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 9152a3b4b9596..e08557f80ae82 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -39,6 +39,7 @@ "dependencies": { "@eth-optimism/core-db": "^0.0.1-alpha.25", "@eth-optimism/core-utils": "^0.0.1-alpha.25", + "@eth-optimism/solc": "^0.5.16-alpha.0", "@eth-optimism/solc-transpiler": "^0.0.1-alpha.27", "@nomiclabs/buidler": "^1.3.8", "@nomiclabs/buidler-ethers": "^2.0.0", diff --git a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts index d2f82fb569904..22491ee3c89e5 100644 --- a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts +++ b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts @@ -10,7 +10,7 @@ import { remove0x, numberToHexString, } from '@eth-optimism/core-utils' -import * as solc from '@eth-optimism/solc-transpiler' +import * as solc from '@eth-optimism/solc' import { Contract, ContractFactory, Signer, BigNumber } from 'ethers' import { keccak256 } from 'ethers/utils' import { cloneDeep } from 'lodash' @@ -438,13 +438,7 @@ describe('StateTransitioner', () => { const AllFraudTestJson = compile( solc, - path.resolve( - __dirname, - '../../../contracts/test-helpers/FraudTester.sol' - ), - { - executionManagerAddress: executionManager.address, - } + path.resolve(__dirname, '../../../contracts/test-helpers/FraudTester.sol') ).contracts['FraudTester.sol'] FraudTesterJson = AllFraudTestJson.FraudTester MicroFraudTesterJson = AllFraudTestJson.MicroFraudTester diff --git a/packages/rollup-dev-tools/README.md b/packages/rollup-dev-tools/README.md deleted file mode 100644 index 361d1a5c5196a..0000000000000 --- a/packages/rollup-dev-tools/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Rollup Developer Tools -Package that contains developer tools for building on optimistic rollup. - -### Dependencies -Please refer to the root README of this repo. - -### Setup -Run `yarn install` to install necessary dependencies. - -### Building -Run `yarn build` to build the code. Note: `yarn all` may be used to build and run tests. - -### Testing -Run `yarn test` to run the unit tests. - -## Transpiler -Enables transpilation of L1 contracts to L2 bytecode. - -### Configuration -The transpiler is configured via the `config/default.json` file, which should not be changed. - -Sensitive config values and overrides can be configured in `config/.env` which will not be versioned. - -See: `config/.env.example` for more info. - -### Execution -The transpiler is executed by running - -`yarn transpile ` -or -`yarn transpile ` diff --git a/packages/rollup-dev-tools/config/.env.example b/packages/rollup-dev-tools/config/.env.example deleted file mode 100644 index 8ed61b2b5e008..0000000000000 --- a/packages/rollup-dev-tools/config/.env.example +++ /dev/null @@ -1,14 +0,0 @@ -################ -# Instructions # -################ -# THIS IS FOR DEV TOOL CONFIGURATION -# Default configuration is configured in `default.json`. To override, create your own `.env` file in this same location using this file as a template. - -############ -# Template # -############ -# Note: for now this contains all opcodes. TODO: Remove bad ones and this comment. -OPCODE_WHITELIST='ADD,ADDMOD,ADDRESS,AND,BALANCE,BLOCKHASH,BYTE,CALL,CALLCODE,CALLDATACOPY,CALLDATALOAD,CALLDATASIZE,CALLER,CALLVALUE,CODECOPY,CODESIZE,COINBASE,CREATE,CREATE2,DELEGATECALL,DIFFICULTY,DIV,DUP1,DUP10,DUP11,DUP12,DUP13,DUP14,DUP15,DUP16,DUP2,DUP3,DUP4,DUP5,DUP6,DUP7,DUP8,DUP9,EQ,EXP,EXTCODECOPY,EXTCODESIZE,GAS,GASLIMIT,GASPRICE,GT,INVALID,ISZERO,JUMP,JUMPDEST,JUMPI,LOG0,LOG1,LOG2,LOG3,LOG4,LT,MLOAD,MOD,MSIZE,MSTORE,MSTORE8,MUL,MULMOD,NOT,NUMBER,OR,ORIGIN,PC,POP,PUSH1,PUSH10,PUSH11,PUSH12,PUSH13,PUSH14,PUSH15,PUSH16,PUSH17,PUSH18,PUSH19,PUSH2,PUSH20,PUSH21,PUSH22,PUSH23,PUSH24,PUSH25,PUSH26,PUSH27,PUSH28,PUSH29,PUSH3,PUSH30,PUSH31,PUSH32,PUSH4,PUSH5,PUSH6,PUSH7,PUSH8,PUSH9,RETURN,RETURNDATACOPY,RETURNDATASIZE,REVERT,SAR,SDIV,SELFDESTRUCT,SGT,SHA3,SHL,SHR,SIGNEXTEND,SLT,SMOD,SLOAD,SSTORE,STATICCALL,STOP,SUB,SWAP1,SWAP10,SWAP11,SWAP12,SWAP13,SWAP14,SWAP15,SWAP16,SWAP2,SWAP3,SWAP4,SWAP5,SWAP6,SWAP7,SWAP8,SWAP9,TIMESTAMP,XOR' - -# Note: for now this is just dummy for testing. TODO: actually connect to SM.sol network deploument. -STATE_MANAGER_ADDRESS='0x0000000000000000000000000000000000000000' \ No newline at end of file diff --git a/packages/rollup-dev-tools/config/default.json b/packages/rollup-dev-tools/config/default.json deleted file mode 100644 index bee04f88e9428..0000000000000 --- a/packages/rollup-dev-tools/config/default.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "STATE_MANAGER_ADDRESS": "0x0000000000000000000000000000000000000000", - "OPCODE_WHITELIST": [ - "ADD", - "ADDMOD", - "ADDRESS", - "AND", - "BYTE", - "CALL", - "CALLCODE", - "CALLDATACOPY", - "CALLDATALOAD", - "CALLDATASIZE", - "CALLER", - "CALLVALUE", - "CODECOPY", - "CODESIZE", - "CREATE", - "CREATE2", - "DELEGATECALL", - "DIV", - "DUP1", - "DUP10", - "DUP11", - "DUP12", - "DUP13", - "DUP14", - "DUP15", - "DUP16", - "DUP2", - "DUP3", - "DUP4", - "DUP5", - "DUP6", - "DUP7", - "DUP8", - "DUP9", - "EQ", - "EXP", - "EXTCODECOPY", - "EXTCODESIZE", - "GAS", - "GT", - "INVALID", - "ISZERO", - "JUMP", - "JUMPDEST", - "JUMPI", - "LOG0", - "LOG1", - "LOG2", - "LOG3", - "LOG4", - "LT", - "MLOAD", - "MOD", - "MSIZE", - "MSTORE", - "MSTORE8", - "MUL", - "MULMOD", - "NOT", - "OR", - "ORIGIN", - "PC", - "POP", - "PUSH1", - "PUSH10", - "PUSH11", - "PUSH12", - "PUSH13", - "PUSH14", - "PUSH15", - "PUSH16", - "PUSH17", - "PUSH18", - "PUSH19", - "PUSH2", - "PUSH20", - "PUSH21", - "PUSH22", - "PUSH23", - "PUSH24", - "PUSH25", - "PUSH26", - "PUSH27", - "PUSH28", - "PUSH29", - "PUSH3", - "PUSH30", - "PUSH31", - "PUSH32", - "PUSH4", - "PUSH5", - "PUSH6", - "PUSH7", - "PUSH8", - "PUSH9", - "RETURN", - "RETURNDATACOPY", - "RETURNDATASIZE", - "REVERT", - "SAR", - "SDIV", - "SELFDESTRUCT", - "SGT", - "SHA3", - "SHL", - "SHR", - "SIGNEXTEND", - "SLT", - "SMOD", - "STATICCALL", - "STOP", - "SUB", - "SWAP1", - "SWAP10", - "SWAP11", - "SWAP12", - "SWAP13", - "SWAP14", - "SWAP15", - "SWAP16", - "SWAP2", - "SWAP3", - "SWAP4", - "SWAP5", - "SWAP6", - "SWAP7", - "SWAP8", - "SWAP9", - "TIMESTAMP", - "XOR" - ] -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/index.ts b/packages/rollup-dev-tools/index.ts deleted file mode 100644 index 5c1b4be1611fd..0000000000000 --- a/packages/rollup-dev-tools/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -const rootPath = __dirname - -export { rootPath } -export * from './src' diff --git a/packages/rollup-dev-tools/package.json b/packages/rollup-dev-tools/package.json deleted file mode 100644 index 034661325f001..0000000000000 --- a/packages/rollup-dev-tools/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@eth-optimism/rollup-dev-tools", - "version": "0.0.1-alpha.27", - "description": "[Optimism] Optimistic Rollup Dev Tools Library", - "main": "build/index.js", - "files": [ - "build/**/*.js" - ], - "scripts": { - "all": "yarn clean && yarn build && yarn test && yarn fix && yarn lint", - "build": "yarn build-contracts && tsc -p .", - "clean": "rimraf build/ && yarn clean-contracts", - "fix": "prettier --config ../../prettier-config.json --write 'index.ts' '{src,test}/**/*.ts'", - "lint": "tslint --format stylish --project .", - "clean-contracts": "rimraf ./test/contracts/build && mkdir ./test/contracts/build", - "build-contracts": "yarn clean-contracts && waffle waffle-config.json", - "test": "mocha --require ts-node/register 'test/**/*.spec.ts' --timeout 5000 --exit", - "transpile": "node ./build/src/transpiler/exec/transpiler.js " - }, - "keywords": [ - "optimism", - "optimistic", - "rollup", - "ethereum", - "client" - ], - "homepage": "https://github.com/ethereum-optimism/optimism-monorepo/tree/master/packages/rollup-dev-tools#readme", - "bugs": "https://github.com/ethereum-optimism/optimism-monorepo/labels/%40eth-optimism%2Frollup-dev-tools", - "license": "MIT", - "author": "Optimism", - "repository": { - "type": "git", - "url": "https://github.com/ethereum-optimism/optimism-monorepo.git" - }, - "dependencies": { - "@eth-optimism/core-utils": "^0.0.1-alpha.26", - "@eth-optimism/rollup-core": "^0.0.1-alpha.27", - "async-lock": "^1.2.2", - "bn.js": "^5.1.1", - "dotenv": "^8.2.0", - "ethereumjs-abi": "^0.6.8", - "ethereumjs-tx": "^2.1.2", - "ethereumjs-vm": "^4.1.3", - "ethers": "^4.0.42" - }, - "devDependencies": { - "@types/abstract-leveldown": "^5.0.1", - "@types/chai": "^4.1.7", - "@types/mocha": "^5.2.7", - "@types/node": "^12.0.7", - "chai": "^4.2.0", - "chai-as-promised": "^7.1.1", - "ethereum-waffle": "2.1.0", - "mocha": "^6.1.4", - "rimraf": "^2.6.3", - "ts-node": "^8.2.0", - "typescript": "^3.5.1" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "ccce366645fca6bad46c5cf7f7ff2f407c6ba5fd" -} diff --git a/packages/rollup-dev-tools/src/index.ts b/packages/rollup-dev-tools/src/index.ts deleted file mode 100644 index 8cc19a631721b..0000000000000 --- a/packages/rollup-dev-tools/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './tools' -export * from './types' diff --git a/packages/rollup-dev-tools/src/tools/index.ts b/packages/rollup-dev-tools/src/tools/index.ts deleted file mode 100644 index 1ec5f52fdca15..0000000000000 --- a/packages/rollup-dev-tools/src/tools/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './transpiler' -export * from './vm' diff --git a/packages/rollup-dev-tools/src/tools/transpiler/binary-search-tree.ts b/packages/rollup-dev-tools/src/tools/transpiler/binary-search-tree.ts deleted file mode 100644 index e021f9500c48d..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/binary-search-tree.ts +++ /dev/null @@ -1,328 +0,0 @@ -import { - bytecodeToBuffer, - EVMBytecode, - Opcode, - EVMOpcodeAndBytes, - formatBytecode, - getPCOfEVMBytecodeIndex, - OpcodeTagReason, -} from '@eth-optimism/rollup-core' -import { bufferUtils, getLogger } from '@eth-optimism/core-utils' -import { getPUSHOpcode, getPUSHIntegerOp, isTaggedWithReason } from './helpers' -import { BinarySearchTreeNode } from '../../types/transpiler' -import { PC_MAX_BYTES } from './constants' - -/** - * Generates a JUMP-correctiing binary search tree block which is used to map pre-transpiled JUMPDESTs too post-transpiled JUMPDESTs. - * - * @param keys The jumpdest PCs before transpilation occurred - * @param values The jumpdest PCs after transpilation occurred. - * @param indexOfThisBlock the PC that this block of bytecode will be placed in. - * @returns Bytecode block of the JUMPDEST-mapping functionality - */ - -export const buildJumpBSTBytecode = ( - keys: number[], - values: number[], - indexOfThisBlock: number -): EVMBytecode => { - if (keys.length !== values.length) { - throw new Error( - `Asked to build binary search tree, but given key array of length ${keys.length} and value array of length ${values.length}` - ) - } - const rootNode = getBSTRoot(keys, values) - const BSTBytecodeWithIncorrectJUMPs: EVMBytecode = [ - // Bytecode to JUMP to when a successful match is found by a BST node - ...getJumpdestMatchSuccessBytecode(), - // Entry point for the actual BST matching logic - { - opcode: Opcode.JUMPDEST, - consumedBytes: undefined, - }, - // Bytecode for the "root subtree" recursively generates all BST logic as bytecode. - ...getBytecodeForSubtreeRoot(rootNode, false), - ] - return fixJUMPsToNodes(BSTBytecodeWithIncorrectJUMPs, indexOfThisBlock) -} - -/** - * Generates a binary search tree based on a set of keys to search against, and values associated with each key. - * - * @param keys The (lowest-to-highest) ordered array of keys to be searched for. - * @param values The array of corresponding values to return. - * @returns A root BST node whose ancestors represent the full BST for the given k/v pairs. - */ -export const getBSTRoot = (keys: number[], values: number[]) => { - // Associate nodes with k/v pairs and sort before building the tree - const bottomNodes: BinarySearchTreeNode[] = keys.map( - (key: number, index: number) => { - return { - value: { - jumpdestBefore: keys[index], - jumpdestAfter: values[index], - }, - left: undefined, - right: undefined, - } - } - ) - const sortedBottomNodes = bottomNodes.sort( - (node1: BinarySearchTreeNode, node2: BinarySearchTreeNode) => { - return node1.value.jumpdestBefore - node2.value.jumpdestBefore - } - ) - return buildBST(sortedBottomNodes) -} - -/** - * Generates a binary search tree from a sorted list of values. - * @param sortedValues The sorted BST nodes to be searched for - * @returns The root node of the resulting BST. - */ -export const buildBST = ( - sortedAncestors: BinarySearchTreeNode[] -): BinarySearchTreeNode => { - if (sortedAncestors.length === 0) { - return undefined - } - - const rootIndex = Math.floor(sortedAncestors.length / 2) - const leftSubtreeElements: BinarySearchTreeNode[] = sortedAncestors.slice( - 0, - rootIndex - ) - const rightSubtreeElements: BinarySearchTreeNode[] = sortedAncestors.slice( - rootIndex + 1 - ) - return { - value: sortedAncestors[rootIndex].value, - left: buildBST(leftSubtreeElements), - right: buildBST(rightSubtreeElements), - } -} - -/** - * Generates bytecode executing a binary search tree for a given node's subtree. - * Recursively executes with left->right, depth-first approach. - * @param node The BST nodeto - * @returns The root node of the resulting BST. - */ -const getBytecodeForSubtreeRoot = ( - node: BinarySearchTreeNode, - isRightNode: boolean -): EVMBytecode => { - if (!node) { - return [] - } - const bytecodeToReturn: EVMBytecode = [] - // Left->right, depth first makes it so that right nodes are always JUMPed to, and left nodes are continued to from the parent node with no JUMP. - if (isRightNode) { - bytecodeToReturn.push(generateBinarySearchTreeNodeJumpdest(node)) - } - // Generate the match check for this node - bytecodeToReturn.push(...generateNodeEqualityCheckBytecode(node)) - // If there are no children to continue to, there is definitely no match--STOP as this was an invalid JUMP according to pre-transpilation JUMPDESTs - if (!node.left && !node.right) { - bytecodeToReturn.push({ - opcode: Opcode.STOP, - consumedBytes: undefined, - }) - return bytecodeToReturn - } - // If this node has a right child, check whether the stack input is greater than this node value and JUMP there. Otherwise we will continue to the left. - if (node.right) { - bytecodeToReturn.push( - ...generateIfGreaterThenJumpToRightChildBytecode(node) - ) - } - // generate bytecode for the next subtree enforcing left->right depth first execution so that every left sibling is continued to, every right sibling JUMPed to - bytecodeToReturn.push( - ...getBytecodeForSubtreeRoot(node.left, false), - ...getBytecodeForSubtreeRoot(node.right, true) - ) - return bytecodeToReturn -} - -/** - * Generates a bytecode block that checks if the current stack element is >= this node's value, and jumping to the right child node if so. - * - * @param node The BST node being inequality checked - * @returns The correctly tagged bytecode jumping to the right child of this node if needed. - */ -const generateIfGreaterThenJumpToRightChildBytecode = ( - node: BinarySearchTreeNode -): EVMBytecode => { - return [ - { - opcode: Opcode.DUP1, - consumedBytes: undefined, - }, - // PUSH the key to be compared to determine which node to proceed to - getPUSHIntegerOp(node.value.jumpdestBefore), - // Compare the keys - { - opcode: Opcode.LT, - consumedBytes: undefined, - }, - // PUSH a *placeholder* for the destination of thde right child to be JUMPed to if check passes--to be set later - { - opcode: getPUSHOpcode(PC_MAX_BYTES), - consumedBytes: Buffer.alloc(PC_MAX_BYTES), - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_PUSH_BINARY_SEARCH_NODE_LOCATION, - metadata: { node }, - }, - }, - // JUMP if the LT check passed - { - opcode: Opcode.JUMPI, - consumedBytes: undefined, - }, - ] -} - -/** - * Generates a bytecode block that checks for equality of the stack element to the node, and jumping to a match success block if so. - * - * @param node The BST node being equality checked - * @returns The correctly tagged bytecode jumping to the match success case for this node. - */ -const generateNodeEqualityCheckBytecode = ( - node: BinarySearchTreeNode -): EVMBytecode => { - return [ - // DUP the value to match without deleting it forever - { - opcode: Opcode.DUP1, - consumedBytes: undefined, - }, - // Compare to JUMPDEST before - getPUSHIntegerOp(node.value.jumpdestBefore), - { - opcode: Opcode.EQ, - consumedBytes: undefined, - }, - // If match, we will send the JUMPDEST after to the success block - getPUSHIntegerOp(node.value.jumpdestAfter), - { - opcode: Opcode.SWAP1, - consumedBytes: undefined, - }, - // PUSH success block location (via a tag--will be filled out later) - { - opcode: getPUSHOpcode(PC_MAX_BYTES), - consumedBytes: Buffer.alloc(PC_MAX_BYTES), - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_PUSH_JUMPDEST_MATCH_SUCCESS_LOCATION, - metadata: undefined, - }, - }, - // JUMPI to success block if match - { - opcode: Opcode.JUMPI, - consumedBytes: undefined, - }, - // POP the JUMPDESTafter if not a match. - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - ] -} - -/** - * Generates a tagged JUMPDEST for a binary search node - * - * @param node The BST node being jumped to (used for tagging for later correction) - * @returns The correctly tagged JUMPDEST. - */ -const generateBinarySearchTreeNodeJumpdest = ( - node: BinarySearchTreeNode -): EVMOpcodeAndBytes => { - return { - opcode: Opcode.JUMPDEST, - consumedBytes: undefined, - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_BINARY_SEARCH_NODE_JUMPDEST, - metadata: { node }, - }, - } -} - -/** - * Gets the success jumpdest for the footer switch statement. This will be jumped to when the - * switch statement finds a match. It is responsible for getting rid of extra stack arguments - * that the footer switch statement adds. - * - * @returns The success bytecode. - */ - -export const getJumpdestMatchSuccessBytecode = (): EVMBytecode => { - return [ - // This JUMPDEST is hit on successful switch match - { opcode: Opcode.JUMPDEST, consumedBytes: undefined }, - // Swaps the duped pre-transpilation JUMPDEST with the post-transpilation JUMPDEST - { opcode: Opcode.SWAP1, consumedBytes: undefined }, - // Pops the pre-transpilation JUMPDEST - { opcode: Opcode.POP, consumedBytes: undefined }, - // Jumps to the post-transpilation JUMPDEST - { opcode: Opcode.JUMP, consumedBytes: undefined }, - ] -} - -/** - * Fixes all PUSHes tagged by appendAncestorsToBytecode() to correspond to the correct nodes' JUMPDESTs - * - * @param bytecode The tagged bytecode of the jump search table with incorrect PUSHes for the jumnpdests - * @param indexOfThisBlock The offset of this block in the bytecode where the result will be inserted - * @returns The EVM Bytecode with fixed PUSHes. - */ -const fixJUMPsToNodes = ( - bytecode: EVMBytecode, - indexOfThisBlock: number -): EVMBytecode => { - for (const pushMatchSuccessOp of bytecode.filter((op) => - isTaggedWithReason(op, [ - OpcodeTagReason.IS_PUSH_JUMPDEST_MATCH_SUCCESS_LOCATION, - ]) - )) { - pushMatchSuccessOp.consumedBytes = bufferUtils.numberToBuffer( - indexOfThisBlock, - PC_MAX_BYTES, - PC_MAX_BYTES - ) - } - for (const pushBSTNodeOp of bytecode.filter((op) => - isTaggedWithReason(op, [ - OpcodeTagReason.IS_PUSH_BINARY_SEARCH_NODE_LOCATION, - ]) - )) { - const rightChild: BinarySearchTreeNode = - pushBSTNodeOp.tag.metadata.node.right - // Find the index of the right child's JUMPDEST in the bytecode, for each node. - const rightChildJumpdestIndexInBytecodeBlock = bytecode.findIndex( - (toCheck: EVMOpcodeAndBytes) => { - return ( - isTaggedWithReason(toCheck, [ - OpcodeTagReason.IS_BINARY_SEARCH_NODE_JUMPDEST, - ]) && toCheck.tag.metadata.node === rightChild - ) - } - ) - // Calculate the PC of the found JUMPDEST, offsetting by the index of this block. - const rightChildJumpdestPC = - indexOfThisBlock + - getPCOfEVMBytecodeIndex(rightChildJumpdestIndexInBytecodeBlock, bytecode) - // Set the consumed bytes to be this PC - pushBSTNodeOp.consumedBytes = bufferUtils.numberToBuffer( - rightChildJumpdestPC, - PC_MAX_BYTES, - PC_MAX_BYTES - ) - } - return bytecode -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/constants.ts b/packages/rollup-dev-tools/src/tools/transpiler/constants.ts deleted file mode 100644 index e2a6ee45b73ce..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -// The max number of bytes we expect a JUMPDEST's PC to be expressible in. Setting to 3 allows 16 MB contracts--more than enough! -export const PC_MAX_BYTES = 3 diff --git a/packages/rollup-dev-tools/src/tools/transpiler/contract-creation-opcodes.ts b/packages/rollup-dev-tools/src/tools/transpiler/contract-creation-opcodes.ts deleted file mode 100644 index 6873643939910..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/contract-creation-opcodes.ts +++ /dev/null @@ -1,316 +0,0 @@ -/* External Imports */ -import { Opcode, EVMBytecode, Address } from '@eth-optimism/rollup-core' -import { getLogger, hexStrToBuf } from '@eth-optimism/core-utils' -import * as ethereumjsAbi from 'ethereumjs-abi' - -/* Internal Imports */ -import { - getSWAPNOp, - getPUSHIntegerOp, - getDUPNOp, - pushMemoryOntoStack, - getPUSHBuffer, - storeStackInMemory, -} from './helpers' -import { BIG_ENOUGH_GAS_LIMIT } from './' - -const log = getLogger(`contract-creation-replacement-gen`) - -export const ovmCREATEName = 'ovmCREATE' -export const ovmCREATE2Name = 'ovmCREATE2' - -/** - * This replaces CREATE Opcode with a CALL to our ExecutionManager. - * Notably, this: - * * Assumes the proper memory for create and a stack of (PC to return to), (untranspiled CREATE args), ... - * * Stores memory that will be modified during the proxy operation to the stack - * * Safely stores ovmCREATE method id and arguments to memory so it can be passed with the proxy CALL. - * * CALLs the specified ovmCREATE function - * * Pushes the returned created address to the stack. - * * Returns memory to its original pre-CALL state and cleans up the stack to what a normal CREATE would do. - * - * @param executionManagerAddress The address of the Execution Manager contract. - * @param ovmCREATEFunctionName (ONLY USED FOR TESTING) The function name in the Execution Manager to handle CREATEs. - */ -export const getCREATESubstitute = ( - executionManagerAddress: Address, - ovmCREATEFunctionName: string = ovmCREATEName -): EVMBytecode => { - // CREATE params and execution do the following to the stack: - // [value, offset, length, ...] --> [addr, ...] - // Where offset and length are memory indices of the initcode. - // additionally we expect the PC to JUMP back to to be preserved, so input stack on entering this function's bytcode is [(PC to jump back to), value, offset, length, ...] - - // The execution manager expects th following calldata: (variable-length bytes) - // * [methodID (bytes4)] - // * [ovmInitcode (bytes (variable length))] - // so, we're gonna MSTORE methodId prepended to the original CREATE offset and length. - - const callMemoryWordsToPrepend: number = 1 // NOTE: if we needed to pass the call value in the future alongside addr, we would increment this - const callMemoryBytesToPrepend: number = 32 * callMemoryWordsToPrepend - - const methodId: Buffer = ethereumjsAbi.methodID(ovmCREATEFunctionName, []) - - // First, we store the memory we're going to overwrite in order to prepend methodId and params to the stack so the original memory can be recovered. - // We will use this same memory for recovering the returned created Addr after the call. So it will be referred to as retOffset in these comments. - const op: EVMBytecode = [ - // we will subtract the number of words we will prepend to get the index of memory we're pushing to stack to recover later (this will be reused as retOffset) - getPUSHIntegerOp(callMemoryBytesToPrepend), - getDUPNOp(4), // dup memory offset of initcode, this is expected at index 3, after what we just pushed -> 4 - { opcode: Opcode.SUB, consumedBytes: undefined }, // do subtraction - // actually push it to the stack - ...pushMemoryOntoStack(callMemoryWordsToPrepend), - ] - - // stack should now be [retOffset, ...[mem words pushed to stack], (PC to return to), ...[value, offset, length, ...]]] - // duplicate the two memory-related params from the original CREATE to front of stack - op.push( - ...new Array(2).fill( - getDUPNOp(1 + callMemoryWordsToPrepend + 1 + 3) // this will DUP length then offset - ) - ) - - // stack should now be [offset, length, retOffset, ...[mem words pushed to stack], (PC to return to), ...[value, offset, length, ...]] - // where the first two memory params are un-modified from the pre-transpiled CALL. - - // now, we need to store the prepended calldata in memory right before the original offset. - // NOTE: if we needed to pass the call value in the future alongside addr, we would dup that additional val to stack here and MSTORE it. - - // PUSH the method data to stack - op.push(getPUSHBuffer(methodId)) - - // store the methodId words in memory. To do this, we DUP retOffset, because that's what the storeStackInMemory() expects as first element. - op.push( - getDUPNOp(4), // Stack is [methodId, offset, length, retOffset, ... ...] - ...storeStackInMemory(callMemoryWordsToPrepend) - ) - // pop the index we were just using to store stack in memory - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - - // at this point the stack should be back to [offset, length, retOffset, ...[mem words pushed to stack], (PC to return to), ...[value, offset, length, ...]] - // now that we have prepended the correct calldata to memory, we need to update the args length and offset appropriately to actually pass the prepended data. - const numBytesForExtraArgs: number = 4 + 0 * 32 // methodId + (Num params )* 32 bytes/word // NOTE: if we want to pass value param down the line, increment this "0" - op.push( - //subtract number of additional bytes from the initial offseet, should be the first thing on the stack - getPUSHIntegerOp(numBytesForExtraArgs), - getSWAPNOp(1), - { opcode: Opcode.SUB, consumedBytes: undefined }, - // add number of additional bytes to the initial length, should be the second thing on the stack - // swap from [length, offset, ...] to [offset, length, ...] - getSWAPNOp(1), - // add - getPUSHIntegerOp(numBytesForExtraArgs), - { opcode: Opcode.ADD, consumedBytes: undefined }, - // swap initcodeLength back so stack is [length, offset, ...] again - getSWAPNOp(1) - ) - // CALL expects: - // The argOffset and argLength have already been set up, but we need retOffset and retLength above those (3rd and 4th stack elements) - // we'll overwrite the methodId word to accomplish this, since it's no longer needed once the call executes. - // This means retOffset, and retLength = 32 since execution manager will return addr as 32-byte big-endian. - const retLength: number = 32 - op.push( - getPUSHIntegerOp(retLength), // push retLength, stack should now be [retLength, argLength, argOffset, retOffset...] - getSWAPNOp(2), // swap into third element, stack should now be [argLength, argOffset, 32, retOffset...] - getDUPNOp(4), // dup retOffset, stack should now be [retOffset, argLength, argOffset, 32, retOffset...] - getSWAPNOp(2) // swap into third element, stack should now line up with last 4 inputs to CALL - ) - // now, we just need to add the first three fields and execute - op.push( - // value (0 ETH always!) - getPUSHIntegerOp(0), - // address - getPUSHBuffer(hexStrToBuf(executionManagerAddress)), - getPUSHIntegerOp(BIG_ENOUGH_GAS_LIMIT), - // CALL! - { - opcode: Opcode.CALL, - consumedBytes: undefined, - }, - // POP success--should always be successful - { - opcode: Opcode.POP, - consumedBytes: undefined, - } - ) - // stack should now be [retOffset, ...[mem pushed to stack], (PC to return to), ...[value, offset, length, ...]] - // We need to pull the addr from memory at retOffset before overwriting it back to the original memory. - op.push(getDUPNOp(1), { - opcode: Opcode.MLOAD, - consumedBytes: undefined, - }) - - // now we have the addr result at the top of the stack, so we swap it out to where it will be first after we put back the old memory and pop the original params. - // this index should be (1 for memory replacment index + callMemoryWordsToPrepend + 4 for original [(PC to return to), value, offset, length]) - op.push(getSWAPNOp(1 + callMemoryWordsToPrepend + 4)) - - // we swapped with garbage stack which we no longer need since CALL has been executed, so POP - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - // now that the success result is out of the way we can return memory to original state, the index and words are first on stack now! - op.push(...storeStackInMemory(callMemoryWordsToPrepend)) - // POP the index used to storeStackInMemeorry - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - - // now, the stack should be [(PC to JUMP back to), value, offset, addr] (note that length was swapped and popped with create) - // swap the PC with value so that we preserve it - op.push(getSWAPNOp(2)) - // lastly, POP the value and offset - op.push( - ...new Array(2).fill({ - opcode: Opcode.POP, - consumedBytes: undefined, - }) - ) - - return op -} - -/** - * This replaces CREATE2 Opcode with a CALL to our ExecutionManager. - * Notably, this: - * * Assumes the proper memory for create and a stack of (PC to return to), (untranspiled CREATE2 args), ... - * * Stores memory that will be modified during the proxy operation to the stack - * * Safely stores ovmCREATE2 method id and salt to memory so it can be passed with the proxy CALL. - * * CALLs the specified ovmCREATE2 function - * * Pushes the returned created address to the stack. - * * Returns memory to its original pre-CALL state and cleans up the stack to what a normal CREATE2 would do. - * - * @param executionManagerAddress The address of the Execution Manager contract. - * @param ovmCREATE2FunctionName The function name in the Execution Manager to handle CREATE2s. - */ -export const getCREATE2Substitute = ( - executionManagerAddress: Address, - ovmCREATE2FunctionName: string = ovmCREATE2Name -): EVMBytecode => { - // CREATE2 params and execution do the following to the stack: - // [value, offset, length, salt, ...] --> [addr, ...] - // Where offset and length are memory indices of the initcode. - // additionally we expect the PC to JUMP back to to be preserved, so input stack on entering this function's bytcode is [(PC to jump back to), value, offset, length, salt, ...] - - // The execution manager expects th following calldata: (variable-length bytes) - // * [methodID (bytes4)] - // * [salt (bytes32)] - // * [ovmInitcode (bytes (variable length))] - // so, we're gonna MSTORE methodId and salt prepended to the original CREATE2 offset and length. - - const callMemoryWordsToPrepend: number = 2 // NOTE: if we needed to pass the call value in the future alongside addr, we would increment this - const callMemoryBytesToPrepend: number = 32 * callMemoryWordsToPrepend - - const methodId: Buffer = ethereumjsAbi.methodID(ovmCREATE2FunctionName, []) - - // First, we store the memory we're going to overwrite in order to prepend methodId and params to the stack so the original memory can be recovered. - // We will use this same memory for recovering the returned created Addr after the call. So it will be referred to as retOffset in these comments. - const op: EVMBytecode = [ - // we will subtract the number of words we will prepend to get the index of memory we're pushing to stack to recover later (this will be reused as retOffset) - getPUSHIntegerOp(callMemoryBytesToPrepend), - getDUPNOp(4), // dup memory offset of initcode, this is expected at index 3, after what we just pushed -> 4 - { opcode: Opcode.SUB, consumedBytes: undefined }, // do subtraction - // actually push it to the stack - ...pushMemoryOntoStack(callMemoryWordsToPrepend), - ] - - // stack should now be [retOffset, ...[mem words pushed to stack], (pc to return to), ...[value, offset, length, salt, ...]]] - // duplicate the two memory-related params from the original CREATE to front of stack - op.push( - ...new Array(2).fill( - getDUPNOp(1 + callMemoryWordsToPrepend + 1 + 3) // this will DUP length then offset - ) - ) - - // stack should now be [offset, length, retOffset, ...[mem words pushed to stack], ...[value, offset, length, salt, ...]] - // where the first two memory params are un-modified from the pre-transpiled CALL. - - // now, we need to store the prepended calldata in memory right before the original offset. - // NOTE: if we needed to pass the call value in the future alongside addr, we would dup that additional val to stack here and MSTORE it. - - // DUP the salt - op.push(getDUPNOp(2 + 1 + callMemoryWordsToPrepend + 1 + 4)) // see "stack should now be" section above for justification of this indexing - // PUSH the method data to stack - op.push(getPUSHBuffer(methodId)) - - // store the methodId words in memory. To do this, we DUP retOffset, because that's what the storeStackInMemory() expects as first element. - op.push( - getDUPNOp(5), // Stack is [methodId, salt, offset, length, retOffset, ... ...] - ...storeStackInMemory(callMemoryWordsToPrepend) - ) - // pop the index we were just using to store stack in memory - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - - // at this point the stack should be back to [offset, length, retOffset, ...[mem words pushed to stack], (PC to return to), ...[value, offset, length, salt, ...]] - // now that we have prepended the correct calldata to memory, we need to update the args length and offset appropriately to actually pass the prepended data. - const numBytesForExtraArgs: number = 4 + 1 * 32 // methodId + (Num params AKA salt)* 32 bytes/word - op.push( - // subtract number of additional bytes from the initial offseet, should be the first thing on the stack - getPUSHIntegerOp(numBytesForExtraArgs), - getSWAPNOp(1), - { opcode: Opcode.SUB, consumedBytes: undefined }, - // add number of additional bytes to the initial length, should be the second thing on the stack - // swap from [length, offset, ...] to [offset, length, ...] - getSWAPNOp(1), - // add - getPUSHIntegerOp(numBytesForExtraArgs), - { opcode: Opcode.ADD, consumedBytes: undefined }, - // swap initcodeLength back so stack is [length, offset, ...] again - getSWAPNOp(1) - ) - // CALL expects: - // The argOffset and argLength have already been set up, but we need retOffset and retLength above those (3rd and 4th stack elements) - // we'll overwrite the methodId word to accomplish this, since it's no longer needed once the call executes. - // This means retOffset == retOffset, and retLength = 32 since execution manager will return addr as 32-byte big-endian. - const retLength: number = 32 - op.push( - getPUSHIntegerOp(retLength), // push retLength, stack should now be [retLength, argLength, argOffset, retOffset...] - getSWAPNOp(2), // swap into third element, stack should now be [argLength, argOffset, 32, retOffset...] - getDUPNOp(4), // dup retOffset, stack should now be [retOffset, argLength, argOffset, 32, retOffset...] - getSWAPNOp(2) // swap into third element, stack should now line up with last 4 inputs to CALL - ) - // now, we just need to add the first three fields and execute - op.push( - // value (0 ETH always!) - getPUSHIntegerOp(0), - // address - getPUSHBuffer(hexStrToBuf(executionManagerAddress)), - // gas TODO add sufficient_gas_constant - getPUSHIntegerOp(100010001001), - // CALL! - { - opcode: Opcode.CALL, - consumedBytes: undefined, - }, - // POP success--should always be successful - { - opcode: Opcode.POP, - consumedBytes: undefined, - } - ) - // stack should now be [retOffset, ...[mem pushed to stack], (PC to return to), ...[value, offset, length, salt, ...]] - // We need to pull the addr from memory at retLength before overwriting it back to the original memory. - op.push(getDUPNOp(1), { - opcode: Opcode.MLOAD, - consumedBytes: undefined, - }) - - // now we have the addr result at the top of the stack, so we swap it out to where it will be first after we put back the old memory and pop the original params. - // this index should be (1 for memory replacment index + callMemoryWordsToPrepend + 5 for original [(PC to return to), value, offset, length, salt]) - op.push(getSWAPNOp(1 + callMemoryWordsToPrepend + 5)) - - // we swapped with garbage stack which we no longer need since CALL has been executed, so POP - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - // now that the success result is out of the way we can return memory to original state, the index and words are first on stack now! - op.push(...storeStackInMemory(callMemoryWordsToPrepend)) - // POP the index used by storeStackInMemory - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - // stack should now be [(PC to return to), value, offset, length, (addr of CREATE2ed account)] (note salt was swapped and popped for addr above) - // SWAP (PC to return to) to preserve it as first element - op.push(getSWAPNOp(3)) - - // POP the remaining value, offset, length - op.push( - ...new Array(3).fill({ - opcode: Opcode.POP, - consumedBytes: undefined, - }) - ) - - return op -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/dynamic-memory-opcodes.ts b/packages/rollup-dev-tools/src/tools/transpiler/dynamic-memory-opcodes.ts deleted file mode 100644 index 047ea5bfa166a..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/dynamic-memory-opcodes.ts +++ /dev/null @@ -1,311 +0,0 @@ -/* External Imports */ -import { Opcode, EVMBytecode, Address } from '@eth-optimism/rollup-core' -import { getLogger, hexStrToBuf } from '@eth-optimism/core-utils' -import { - getSWAPNOp, - getPUSHIntegerOp, - getDUPNOp, - pushMemoryOntoStack, - getPUSHBuffer, - storeStackInMemory, - getPUSHOpcode, -} from './helpers' -import * as abi from 'ethereumjs-abi' - -const log = getLogger(`call-type-replacement-gen`) - -export const ovmCALLName: string = 'ovmCALL' -export const ovmSTATICCALLName: string = 'ovmSTATICCALL' -export const ovmDELEGATECALLName: string = 'ovmDELEGATECALL' -export const ovmEXTCODECOPYName: string = 'ovmEXTCODECOPY' - -/** - * This replaces the CALL Opcode with a CALL to our ExecutionManager. - * Notably, this: - * * Assumes the proper stack is in place to do the un-transpiled CALL - * * Replaces the call address in the stack with the executionManagerAddress - * * Safely prepends method id and arguments to CALL argument memory - * * Updates CALL memory index and length to account for prepended arguments - * - * @param executionManagerAddress The address of the Execution Manager contract. - * @param ovmCALLFunctionName (ONLY USE FOR TESTING) The function name in the Execution Manager to handle DELEGTECALLs. - */ -export const getCALLSubstitute = ( - executionManagerAddress: Address, - ovmCALLFunctionName: string = ovmCALLName -): EVMBytecode => { - return getCallTypeReplacement(executionManagerAddress, ovmCALLFunctionName, 3) -} - -/** - * This replaces the STATICCALL Opcode with a CALL to our ExecutionManager. - * Notably, this: - * * Assumes the proper stack is in place to do the un-transpiled CALL - * * Replaces the call address in the stack with the executionManagerAddress - * * Safely prepends method id and arguments to STATICCALL argument memory - * * Updates STATICCALL memory index and length to account for prepended arguments - * - * @param executionManagerAddress The address of the Execution Manager contract. - * @param ovmSTATICCALLFunctionName (ONLY USE FOR TESTING) The function name in the Execution Manager to handle DELEGTECALLs. - */ -export const getSTATICCALLSubstitute = ( - executionManagerAddress: Address, - ovmSTATICCALLFunctionName: string = ovmSTATICCALLName -): EVMBytecode => { - return getCallTypeReplacement( - executionManagerAddress, - ovmSTATICCALLFunctionName, - 2 - ) -} - -/** - * This replaces the DELEGATECALL Opcode with a CALL to our ExecutionManager. - * Notably, this: - * * Assumes the proper stack is in place to do the un-transpiled CALL - * * Replaces the call address in the stack with the executionManagerAddress - * * Safely prepends method id and arguments to DELEGATECALL argument memory - * * Updates DELEGATECALL memory index and length to account for prepended arguments - * - * @param executionManagerAddress The address of the Execution Manager contract. - * @param ovmDELEGATECALLFunctionName (ONLY USE FOR TESTING) The function name in the Execution Manager to handle DELEGTECALLs. - */ -export const getDELEGATECALLSubstitute = ( - executionManagerAddress: Address, - ovmDELEGATECALLFunctionName: string = ovmDELEGATECALLName -): EVMBytecode => { - return getCallTypeReplacement( - executionManagerAddress, - ovmDELEGATECALLFunctionName, - 2 - ) -} - -/** - * This replaces CALL-type Opcodes with CALLs to our ExecutionManager. - * Notably, this: - * * Assumes the input stack is: [(PC of replaced opcode), ...[CALL arguments], ...] - * * Replaces the call address in the stack with the executionManagerAddress - * * Safely prepends method id and arguments to CALL argument memory - * * Updates CALL memory index and length to account for prepended arguments - * - * @param executionManagerAddress The address of the Execution Manager contract. - * @param executionManagerCALLMethodName The function name in the Execution Manager to handle CALLs. - * @param stackPositionOfCallArgsMemOffset The position on the stack of the CALL's arguments memory offset. 0-indexed. - */ -const getCallTypeReplacement = ( - executionManagerAddress: Address, - executionManagerCALLMethodName: string, - stackPositionOfCallArgsMemOffset: number // expected to be 2 or 3 depending on value presence -): EVMBytecode => { - // we're gonna MSTORE methodId + addr - const callMemoryWordsToPrepend: number = 1 + 1 // NOTE: if we needed to pass the call value in the future alongside addr, we would increment this - const callMemoryBytesToPrepend: number = 32 * callMemoryWordsToPrepend - - const totalCALLTypeStackArguments: number = - stackPositionOfCallArgsMemOffset + 4 // 4 for retIndex, retLen, argIndex, argLen - - const methodData: Buffer = abi.methodID(executionManagerCALLMethodName, []) - - // first, we store the memory we're going to overwrite in order to prepend methodId and params to the stack so the original memory can be recovered. - const op: EVMBytecode = [ - // we will subtract the number of words we will prepend to get the index of memory we're pushing to stack to recover later - getPUSHIntegerOp(callMemoryBytesToPrepend), - getDUPNOp(1 + 1 + stackPositionOfCallArgsMemOffset + 1), // dup modified calldata arg index so it can be stored there - { opcode: Opcode.SUB, consumedBytes: undefined }, // do subtraction - // actually push it to the stack - ...pushMemoryOntoStack(callMemoryWordsToPrepend), - ] - - // stack should now be [(index of mem pushed to stack), ...[mem words pushed to stack], (PC of replaced opcode), ...[CALL params], ...] - // duplicate the four memory-related params from the original CALL to front of stack - op.push( - ...new Array(4).fill( - getDUPNOp(1 + callMemoryWordsToPrepend + totalCALLTypeStackArguments + 1) - ) - ) - - // stack should now be [argOffstet, argLen, retOffst, retLength, (index of mem pushed to stack), ...[mem words pushed to stack], (PC of replaced opcode), ...[CALL params], ...] - // where those first four memory params are un-modified from the pre-transpiled CALL. - - // now, we need to push the additional calldata to stack and store stack in memory. - // NOTE: if we needed to pass the call value in the future alongside addr, we would dup that additional val to stack here. - - // dup the ADDR param from the initial stack. based on the expected stack above (and that PUSHBuffer), its index is 4 + 1 + callMemoryWordsToPrepend + 1 + 2 - op.push(getDUPNOp(callMemoryWordsToPrepend + 8)) - // PUSH the method data to stack - op.push(getPUSHBuffer(methodData)) - - // store the [methodId, stack args] words in memory. To do this, we DUP (index of mem pushed to stack), because that's what the storeStackInMemory() expects as first element. - op.push( - getDUPNOp(2 + 4 + 1), // EM CALL args offset is immediately after (previous DUPN and PUSHBuffer = 2) + (memory args = 4) - ...storeStackInMemory(callMemoryWordsToPrepend) - ) - // pop the index we were just using to store stack in memory - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - - // at this point the stack should be [4 words of CALL memory arguments, EM CALL args offset, ...[words pulled from memory], ...original stack] - // now that we have prepended the correct calldata to memory, we need to update the args length and offset appropriately to actually pass the prepended data. - const numBytesForExtraArgs: number = 4 + 1 * 32 // methodId + (Num params )* 32 bytes/word // NOTE: if we want to pass another param down the line, increment this "1" - op.push( - //subtract it from the offset, should be the first thing on the stack - getPUSHIntegerOp(numBytesForExtraArgs), - getSWAPNOp(1), - { opcode: Opcode.SUB, consumedBytes: undefined }, - // add it to the length, should be the second thing on the stack - // swap from second to first - getSWAPNOp(1), - // add - getPUSHIntegerOp(numBytesForExtraArgs), - { opcode: Opcode.ADD, consumedBytes: undefined }, - // swap back from first to second - getSWAPNOp(1) - ) - // now we are ready to execute the call. The memory-related args have already been set up, we just need to add the first three fields and execute. - op.push( - // value (0 ETH always!) - getPUSHIntegerOp(0), - // address - getPUSHBuffer(hexStrToBuf(executionManagerAddress)), - // Gas -- just use the original gas from abov - getDUPNOp(9 + callMemoryWordsToPrepend), // 1 (address) + 1 (value) + 4 (memory args) + 1 (replacement index) + callMemoryWordsToPrepend (the preserved words themselves) + 1 (PC to return to) +1 (gas is element of a CALL stack) - // CALL! - { - opcode: Opcode.CALL, - consumedBytes: undefined, - } - ) - // now we have the success result at the top of the stack, so we swap it out to where it will be first after we put back the old memory and pop the original params. - // this index should be: - // +1 for memory replacment index - // + callMemoryWordsToPrepend - // +1 for PC of replaced opcode - // + number of args to the original CALL-type - op.push( - getSWAPNOp(1 + callMemoryWordsToPrepend + 1 + totalCALLTypeStackArguments) - ) - - // we swapped with garbage stack which we no longer need since CALL has been executed, so POP - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - // now that the success result is out of the way we can return memory to original state, the index and words are first on stack now! - op.push(...storeStackInMemory(callMemoryWordsToPrepend)) - // POP the index just used to store stack back in memory - op.push({ opcode: Opcode.POP, consumedBytes: undefined }) - // expected stack is now: [(PC of replaced opcode), ...[call args missing 1 element from the garbage POP above], (success), ...]. We need to preserve the PC, so swap it to right before the success. - op.push(getSWAPNOp(totalCALLTypeStackArguments - 1)) - - // lastly, POP all the original CALL params which were previously DUPed and modified appropriately. - op.push( - ...new Array(totalCALLTypeStackArguments - 1).fill({ - opcode: Opcode.POP, - consumedBytes: undefined, - }) - ) - - return op -} - -/** - * This replaces EXTCODECOPY Opcode with a CALL to our ExecutionManager. - * Notably, this: - * * Assumes the stack is: [(PPC to return to), ...[EXTCODECOPY untranspiled args], ...] - * * Stores memory to be modified to the stack - * * Safely stores method id and arguments to CALL argument memory - * * CALLs the specified ovmEXTCODECOPY function - * * Returns memory to its original pre-CALL state - * - * @param executionManagerAddress The address of the Execution Manager contract. - * @param ovmEXTCODECOPYFunctionName (ONLY USE FOR TESTING) The function name in the Execution Manager to handle EXTCODECOPYs. - */ - -export const getEXTCODECOPYSubstitute = ( - executionManagerAddress: Address, - ovmEXTCODECOPYFunctionName: string = ovmEXTCODECOPYName -) => { - const methodData: Buffer = abi.methodID(ovmEXTCODECOPYFunctionName, []) - const op: EVMBytecode = [] - - // EXTCODECOPY params and execution do the following to the stack: - // [addr, destOffset, offset, length, …], -> […] - - // We will only need to pass addr, index, length as calldata. - // (destOffset is what the opcode writes to, so it is not passed to the X-Mgr but instead reflected in the CALL's retOffset) - const numStackWordsToPass: number = 3 - // this is the number of memory words we'll be overwriting for the call. - const callMemoryWordsToPass: number = 1 + numStackWordsToPass // 1 extra for methodId! - - // first, we push the memory we're gonna overwrite with calldata onto the stack, so that it may be parsed later. - // to make sure there is not a collision between call and return data locations, we will store this AFTER (destOffset + length) - op.push( - getDUPNOp(5), // DUP length - getDUPNOp(4), // DUP destOffset - { opcode: Opcode.ADD, consumedBytes: undefined }, // add them to get where we expect to store - ...pushMemoryOntoStack(callMemoryWordsToPass) - ) - // Now, the stack should be [(index of memory to replace), ...[pushed memory to swap back], (PC to return to), ...[original EXTCODECOPY args], ...] - - // We now store the stack params needed by the execution manager into memory to pass as calldata. - // ovmEXTCODSIZE expects the following raw bytes as parameters: - // * [methodID (bytes4)] - // * [targetOvmContractAddress (address as bytes32)] - // * [index (uint (32)] - // * [length (uint (32))] - // so we will push thse in reverse order. - - const indexOfOriginalStack: number = 1 + callMemoryWordsToPass + 1 - op.push( - // the final params of addition here (+0, +1, +2) account for the increased stack caused by each preceeding DUP - getDUPNOp(indexOfOriginalStack + 4 + 0), // length - getDUPNOp(indexOfOriginalStack + 3 + 1), // offset - getDUPNOp(indexOfOriginalStack + 1 + 2), // addr - getPUSHBuffer(methodData), // methodId - getDUPNOp(1 + numStackWordsToPass + 1), // the mem index to store calldata -- it's right after all the words to store we just pushed - ...storeStackInMemory(1 + numStackWordsToPass), // +1 for methodId - { - // pop the storage index as it was DUPed above - opcode: Opcode.POP, - consumedBytes: undefined, - } - ) - // the stack should now be [(mem index to recover memory to), ...[memory words to recover], (PC to return to), ...[original EXTCODECOPY args], ...] - // Now we need to set up the CALL! - const numBytesForCalldata: number = 4 + numStackWordsToPass * 32 // methodId + (Num params)* 32 bytes/word - // CALL expects: - // [gas, addr, value, argsOffset, argsLength, retOffset, retLength, …] -> [success, …] - op.push( - getDUPNOp(0 + 1 + callMemoryWordsToPass + 4 + 1), // retLength is same as original stack's `len` (4th element) - getDUPNOp(1 + 1 + callMemoryWordsToPass + 2 + 1), // retOffset is second stack item of original stack - getPUSHIntegerOp(numBytesForCalldata), // argsLen - // argsOffset is wherever we stored the params in memory, with added 32-4 = 28 bytes of 0s when methodId was MSTORE'd which we don't want to pass - getDUPNOp(3 + 1), // three elements were just pushed, next is th index we stored at - getPUSHIntegerOp(28), - { opcode: Opcode.ADD, consumedBytes: undefined }, - getPUSHIntegerOp(0), // value is always 0! - getPUSHBuffer(hexStrToBuf(executionManagerAddress)), // X mgr address - getPUSHIntegerOp(10000000), // random sufficient amount of gas - // execute the call! - { - opcode: Opcode.CALL, - consumedBytes: undefined, - }, - // POP success, x mgr should never fail here. - { - opcode: Opcode.POP, - consumedBytes: undefined, - } - ) - // Cleanup time is all that's left! - op.push( - ...storeStackInMemory(callMemoryWordsToPass), // recover the original memory we pushed the stack - // POP the index of stored memory, no longer needed. - { opcode: Opcode.POP, consumedBytes: undefined }, - // stack should now be [(PC to return to), ...[EXTCODECOPY args], ...]. We need to preserve it so swap it to the end - getSWAPNOp(4), - // pop the rest of the args which have now served their purpose. RIP - ...new Array(4).fill({ - opcode: Opcode.POP, - consumedBytes: undefined, - }) - ) - return op -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/exec/transpiler.ts b/packages/rollup-dev-tools/src/tools/transpiler/exec/transpiler.ts deleted file mode 100644 index 7813afdeba647..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/exec/transpiler.ts +++ /dev/null @@ -1,279 +0,0 @@ -/* External Imports */ -import { - EVMOpcode, - Opcode, - Address, - EVMBytecode, -} from '@eth-optimism/rollup-core' -import { - getLogger, - logError, - isValidHexAddress, - remove0x, - bufToHexString, -} from '@eth-optimism/core-utils' - -import * as fs from 'fs' -import { config, parse } from 'dotenv' -import { resolve } from 'path' - -/* Internal Imports */ -import { - ErroredTranspilation, - OpcodeWhitelist, - SuccessfulTranspilation, - TranspilationResult, - Transpiler, -} from '../../../types/transpiler' -import { - OpcodeWhitelistImpl, - OpcodeReplacerImpl, - TranspilerImpl, -} from '../index' - -const log = getLogger('transpiler') - -/** - * Creates an OpcodeWhitelist from configuration. - * - * @returns The constructed OpcodeWhitelist if successful, undefined if not. - */ -function getOpcodeWhitelist(defaultConfig: {}): OpcodeWhitelist | undefined { - const configuredWhitelist: string[] = process.env.OPCODE_WHITELIST - ? process.env.OPCODE_WHITELIST.split(',') - : defaultConfig['OPCODE_WHITELIST'] - if (!configuredWhitelist) { - console.error( - `No op codes whitelisted. Please configure OPCODE_WHITELIST in either 'config/default.json' or as an environment variable.` - ) - return undefined - } - - log.debug(`Parsing whitelisted op codes: [${configuredWhitelist}].`) - - const whitelistOpcodeStrings: string[] = configuredWhitelist.map((x) => - x.trim().toUpperCase() - ) - const whiteListedOpCodes: EVMOpcode[] = [] - const invalidOpcodes: string[] = [] - for (const opString of whitelistOpcodeStrings) { - const opcode: EVMOpcode = Opcode.parseByName(opString) - if (!opcode) { - invalidOpcodes.push(opString) - } else { - whiteListedOpCodes.push(opcode) - } - } - - if (!!invalidOpcodes.length) { - console.error( - `The following configured opcodes are not valid opcodes: ${invalidOpcodes.join( - ',' - )}` - ) - return undefined - } - - if (!whiteListedOpCodes.length) { - console.error( - `There are no configured whitelisted opcodes. Transpilation cannot work without supporting some opcodes` - ) - return undefined - } - return new OpcodeWhitelistImpl(whiteListedOpCodes) -} - -/** - * Gets the specified state manager address from configuration. - * - * @returns The hex string of the state manager address if successful, undefined if not. - */ -function getStateManagerAddress(defaultConfig: {}): Address { - const stateManagerAddress: Address = - process.env.STATE_MANAGER_ADDRESS || defaultConfig['STATE_MANAGER_ADDRESS'] - if (!stateManagerAddress) { - console.error( - `No state manager address specified. Please configure STATE_MANAGER_ADDRESS in either 'config/default.json' or as an environment variable.` - ) - process.exit(1) - } - - log.debug( - `Got the following state manager address from config: [${stateManagerAddress}].` - ) - - if (!isValidHexAddress(stateManagerAddress)) { - console.error( - `[${stateManagerAddress}] does not appear to be a valid hex string address.` - ) - process.exit(1) - } - - return stateManagerAddress -} - -/** - * Helper function for getting config file paths to avoid base path duplication. - * - * @param filename The config filename without path. - * @returns The full config file path. - */ -function getConfigFilePath(filename: string): string { - return resolve(__dirname, `../../../../config/${filename}`) -} - -/** - * Loads environment variables and returns default config. - * - * @returns The default configuration key-value pairs - */ -const getConfig = (): {} => { - // Starting from build/src/transpiler/exec/ - if (!fs.existsSync(getConfigFilePath('.env'))) { - log.debug(`No override config found at 'config/.env'.`) - } else { - config({ path: getConfigFilePath('.env') }) - } - - const defaultFilepath: string = getConfigFilePath('default.json') - if (!fs.existsSync(defaultFilepath)) { - log.error(`No 'default.json' config file found in /config dir`) - process.exit(1) - } - - let defaultConfig: {} - try { - defaultConfig = JSON.parse( - fs.readFileSync(defaultFilepath, { encoding: 'utf8' }) - ) - } catch (e) { - log.error("Invalid JSON in 'config/default.json' config file.") - process.exit(1) - } - - return defaultConfig -} - -/** - * Gets the relative path of a file assuming base path is the `rollup-dev-tools` dir. - * - * @param filePath The relative filepath from `rollup-dev-tools` - * @returns The relative filepath from this built executable - */ -const getRelativePath = (filePath: string): string => { - return resolve(__dirname, `../../../../${filePath}`) -} - -/** - * Prints the usage of this executable as an error and exits. - */ -const printUsageAndExit = (): void => { - console.error( - 'Invalid argument(s). Usage: "yarn transpile " or "yarn transpile "' - ) - process.exit(1) -} - -/** - * Makes sure the necessary parameters are passed and parse them. - * @returns an array of [inputFilePath, outputFilePath] - */ -const getParams = (): [Buffer, string | undefined] => { - if (process.argv.length === 4) { - // Get the environment and read the appropriate environment file - const [inputFilePath, outputFilePath] = process.argv - .slice(process.argv.length - 2) - .map((x) => getRelativePath(x)) - - if (!fs.existsSync(inputFilePath)) { - console.error(`Input file does not exist at path ${inputFilePath}`) - printUsageAndExit() - } - - const inputBytecode: Buffer = fs.readFileSync(inputFilePath) - - return [inputBytecode, outputFilePath] - } - - if (process.argv.length === 3) { - try { - const bytes = Buffer.from(remove0x(process.argv[2]), 'hex') - return [bytes, undefined] - } catch (e) { - console.error(`Argument not a valid hex string: "${process.argv[2]}"`) - printUsageAndExit() - } - } - - printUsageAndExit() -} - -const getReplacements = (): Map => { - // TODO: Read in overrides from config - return new Map() - .set(Opcode.RETURN, [{ opcode: Opcode.AND, consumedBytes: undefined }]) - .set(Opcode.BYTE, [ - { opcode: Opcode.SUB, consumedBytes: undefined }, - { opcode: Opcode.ADD, consumedBytes: undefined }, - ]) -} - -/** - * Entrypoint for transpilation - */ -async function transpile() { - const [inputBytecode, outputFilePath] = getParams() - - const defaultConfig = getConfig() - - const opcodeWhitelist: OpcodeWhitelist = getOpcodeWhitelist(defaultConfig) - if (!opcodeWhitelist) { - return - } - - const stateManagerAddress: Address = getStateManagerAddress(defaultConfig) - log.debug(`SM address is : ${stateManagerAddress.toString()}`) - const opcodeReplacer = new OpcodeReplacerImpl( - stateManagerAddress, - getReplacements() - ) - - const transpiler: Transpiler = new TranspilerImpl( - opcodeWhitelist, - opcodeReplacer - ) - - log.debug(`Transpiling bytecode ${bufToHexString(inputBytecode)}`) - - let result: TranspilationResult - try { - result = transpiler.transpileRawBytecode(inputBytecode) - } catch (e) { - logError( - log, - `Error during transpilation! Input (hex) ${bufToHexString( - inputBytecode - )}`, - e - ) - } - - if (!result.succeeded) { - const e: ErroredTranspilation = result as ErroredTranspilation - console.error( - `Transpilation Errors: \n\t${e.errors - .map((x) => `index ${x.index}: ${x.message}`) - .join('\n\t')}` - ) - } else { - const output: SuccessfulTranspilation = result as SuccessfulTranspilation - if (!!outputFilePath) { - log.debug(`Transpilation result ${bufToHexString(output.bytecode)}`) - fs.writeFileSync(outputFilePath, output.bytecode) - } else { - console.log(bufToHexString(output.bytecode)) - } - } -} - -transpile() diff --git a/packages/rollup-dev-tools/src/tools/transpiler/helpers.ts b/packages/rollup-dev-tools/src/tools/transpiler/helpers.ts deleted file mode 100644 index 634645d59faac..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/helpers.ts +++ /dev/null @@ -1,255 +0,0 @@ -/* External Imports */ -import { - Opcode, - EVMOpcode, - EVMOpcodeAndBytes, - EVMBytecode, - OpcodeTagReason, -} from '@eth-optimism/rollup-core' -import { getLogger, BigNumber } from '@eth-optimism/core-utils' - -const log = getLogger(`helpers`) - -/** - * Returns a piece of bytecode which dynamically pushes a specified number of 32-byte words from memory onto the stack. - * Assumes that the first element of the stack is the memory index to load from. - * - * Stack before this operation: index, X, Y, Z - * Stack after this operation: index, memory[index], memory[index + 32], ..., memory[index + (wordsToPush * 32)] X, Y, Z - * (Note that each MLOAD pulls 32 byte words from memory, each stack element above is a word) - * - * Memory after this operation: unaffected - * - * @param wordsToPush The number of 32-byte words from the memory to push to the stack - * @returns Bytecode which results in the operation described above. - */ -export const pushMemoryOntoStack = (wordsToPush: number): EVMBytecode => { - const bytecodes: EVMBytecode[] = [] - // For each word to push... - for (let i = wordsToPush - 1; i >= 0; i--) { - bytecodes.push([ - // duplicate the memory index which is expected as first element on stack - { - opcode: Opcode.DUP1, - consumedBytes: undefined, - }, - // ADD the word number to the memory index to get index for this word - getPUSHIntegerOp(i * 32), // 32 because memory is byte indexed but loaded in 32 byte words - { - opcode: Opcode.ADD, - consumedBytes: undefined, - }, - // MLOAD the word from memory - { - opcode: Opcode.MLOAD, - consumedBytes: undefined, - }, - // Swap the loaded word so that memory index is first on stack for next iteration - { - opcode: Opcode.SWAP1, - consumedBytes: undefined, - }, - ]) - } - return [].concat(...bytecodes) -} - -/** - * Returns a piece of bytecode which dynamically stores a specified number of words from the stack into memory. - * Assumes that the first element of the stack is the memory index to store to. - * - * Stack before this operation: [index, wordsToStore_1, wordsToStore_2, ..., wordsToStore_n, X, Y, Z, ...] - * Stack after this operation: [index, X, Y, Z, ...] - * - * Memory after this operation: memory[index: index + (32 * n)] = M_n (for n = 0 through wordsToStore) - * - * @param wordsToStore The number of 32-byte words from the stack to store. - * @returns Bytecode which results in the operation described above. - */ -export const storeStackInMemory = (wordsToStore: number): EVMBytecode => { - const bytecodes: EVMBytecode[] = [] - // For each word to store... - for (let i = 0; i < wordsToStore; i++) { - bytecodes.push([ - // swap the next element to store to first in stack. - { - opcode: Opcode.SWAP1, - consumedBytes: undefined, - }, - // duplicate the memory index which is now the second thing on the stack. - { - opcode: Opcode.DUP2, - consumedBytes: undefined, - }, - // ADD the max words to store, subtracting the current word we're going to store - getPUSHIntegerOp(i * 32), - { - opcode: Opcode.ADD, - consumedBytes: undefined, - }, - // Store - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - ]) - } - - return [].concat(...bytecodes) -} - -/** - * Returns a piece of bytecode which pushes a specified number of words from memory, at the specified byte index, onto the stack. - * - * Stack before this operation: X, Y, Z - * Stack after this operation: memory[(wordsToPush - 1)*32 + memoryIndex], memory[(wordsToPush - 2)*32 + mmoryIndex], ..., memory[memoryIndex] X, Y, Z - * (Note that each MLOAD pulls 32 byte words from memory, each stack element above is a word) - * - * Memory after this operation: unaffected - * - * @param memoryIndex The byte index in the memory to load from - * @param wordsToPush The number of 32-byte words from the memory to load - * @returns Bytecode which results in the operation described above. - */ - -export const pushMemoryAtIndexOntoStack = ( - memoryIndex: number, - wordsToPush: number -): EVMBytecode => { - // we just use the dynamic operation, PUSHing the memoryIndex to the stack beforehand, and POPing the memoryIndex once the operation is complete. - return [ - getPUSHIntegerOp(memoryIndex), - ...pushMemoryOntoStack(wordsToPush), - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - ] -} - -/** - * Returns a piece of bytecode which sotres a specified number of words from the stack back into memory at the specified byte index. - * Assumes that the first element of the stack is the memory index to load from. - * - * Stack before this operation: [M_wordsToStore, M_(wordsToStore-1), ... M_1, X, Y, Z, ...] - * Stack after this operation: [X, Y, Z, ...] - * (Note that each MSTORE puts 32 byte words from memory, each M_* stack element above is a word) - * - * Memory after this operation: memory[memoryIndex + 32 * n] = M_n (for n = 0 through numWords) - * - * @param memoryIndex The byte index in the memory to store - * @param numWords The number of 32-byte words from the stack to store to memory - * @returns Bytecode which results in the operation described above. - */ -export const storeStackInMemoryAtIndex = ( - memoryIndex: number, - numWords: number -): EVMBytecode => { - // we just use the dynamic operation, PUSHing the memoryIndex to the stack beforehand, and POPing the memoryIndex once operation is complete. - return [ - getPUSHIntegerOp(memoryIndex), - ...storeStackInMemory(numWords), - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - ] -} - -/** - * Stores the first `numWords` elements on the stack to memory at the specified index, optionally ignoring the first stack element. - * - * Used to pass stack params into the Execution manager as calldata. - * - * @param numStackElementsToStore The number of stack elements to put in the memory - * @param memoryIndexToStoreAt The byte index in the memory to store the stack elements to. - * @param numStackElementsToIgnore The number of stack elements which should be preserved, but not stored. - * @returns Btyecode which results in the storage operation described above. - */ -export const storeStackElementsAsMemoryWords = ( - numStackElementsToStore: number, - memoryIndexToStoreAt: number = 0 -): EVMBytecode => { - const op: EVMBytecode = [] - for (let i = 0; i < numStackElementsToStore; i++) { - op.push( - ...[ - // push storage index - getPUSHIntegerOp(i * 32 + memoryIndexToStoreAt), - // store the stack item - { opcode: Opcode.MSTORE, consumedBytes: undefined }, - ] - ) - } - return op -} - -export const duplicateStackAt = ( - numStackElementsToIgnore: number, - numStackElementsToDuplicate: number -): EVMBytecode => { - // TODO: error if N is too high to DUPN - const op: EVMBytecode = [] - for (let i = 0; i < numStackElementsToDuplicate; i++) { - op.push(getDUPNOp(numStackElementsToIgnore + numStackElementsToDuplicate)) - } - return op -} - -export const POPNTimes = (numStackElementsToPop: number): EVMBytecode => { - const op: EVMBytecode = new Array(numStackElementsToPop) - op.fill({ - opcode: Opcode.POP, - consumedBytes: undefined, - }) - return op -} - -// constructs an operation which PUSHes the given integer to the stack. -export const getPUSHIntegerOp = (theInt: number): EVMOpcodeAndBytes => { - const intAsBuffer: Buffer = new BigNumber(theInt).toBuffer() - return getPUSHBuffer(intAsBuffer) -} - -// Returns a PUSH operation for the given bytes -export const getPUSHBuffer = (toPush: Buffer): EVMOpcodeAndBytes => { - const numBytesToPush: number = toPush.byteLength - // TODO: error if length exceeds 32 - return { - opcode: getPUSHOpcode(numBytesToPush), // PUSH1 is 96 in decimal - consumedBytes: toPush, - } -} - -// gets the RAW PUSHN EVMOpcode based on N. -export const getPUSHOpcode = (numBytes: number): EVMOpcode => { - return Opcode.parseByNumber(96 + numBytes - 1) // PUSH1 is 96 in decimal -} - -// returns DUPN operation for the specified N. -export const getDUPNOp = (indexToDUP: number): EVMOpcodeAndBytes => { - // TODO: error if index is too big - return { - opcode: Opcode.parseByNumber(128 + indexToDUP - 1), - consumedBytes: undefined, - } -} -// returns SWAPN operation for the specified N. -export const getSWAPNOp = (indexToSWAP: number): EVMOpcodeAndBytes => { - // TODO: error if index is too big - return { - opcode: Opcode.parseByNumber(144 + indexToSWAP - 1), - consumedBytes: undefined, - } -} - -// returns whether the given EVMOpcodeAndBytes has a tag reason matching one of the given ones -export const isTaggedWithReason = ( - opcodeAndBytes: EVMOpcodeAndBytes, - tags: Array -): boolean => { - if (opcodeAndBytes.tag === undefined) { - return false - } - return tags.includes(opcodeAndBytes.tag.reasonTagged) -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/index.ts b/packages/rollup-dev-tools/src/tools/transpiler/index.ts deleted file mode 100644 index 970a65c588f94..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './contract-creation-opcodes' -export * from './dynamic-memory-opcodes' -export * from './helpers' -export * from './jump-replacement' -export * from './opcode-replacer' -export * from './opcode-whitelist' -export * from './static-memory-opcodes' -export * from './transpiler' -export * from './util' -export * from './binary-search-tree' diff --git a/packages/rollup-dev-tools/src/tools/transpiler/jump-replacement.ts b/packages/rollup-dev-tools/src/tools/transpiler/jump-replacement.ts deleted file mode 100644 index 569ffa3bc6a33..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/jump-replacement.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { - bytecodeToBuffer, - EVMBytecode, - Opcode, - OpcodeTagReason, -} from '@eth-optimism/rollup-core' -import { - bufferUtils, - getLogger, - numberToHexString, -} from '@eth-optimism/core-utils' -import { getPUSHOpcode, getPUSHIntegerOp, isTaggedWithReason } from './helpers' -import { - JumpReplacementResult, - TranspilationError, - TranspilationErrors, -} from '../../types/transpiler' -import { createError } from './util' -import { buildJumpBSTBytecode, getJumpdestMatchSuccessBytecode } from './' - -const log = getLogger('jump-replacement') - -/** - * Takes the provided transpiled bytecode and accounts for JUMPs that may not jump - * to the intended spots now that transpilation has modified the code. - * - * @param transpiledBytecode The transpiled bytecode to operate on. - * @param jumpdestIndexesBefore The ordered indexes of JUMPDESTs before. - * @param errors The list of errors to append to if there is an error. - * @returns The new bytecode with all JUMPs accounted for. - */ -export const accountForJumps = ( - transpiledBytecode: EVMBytecode, - jumpdestIndexesBefore: number[] -): JumpReplacementResult => { - if (jumpdestIndexesBefore.length === 0) { - return { bytecode: transpiledBytecode } - } - const errors: TranspilationError[] = [] - - const footerSwitchJumpdestIndex: number = getExpectedFooterSwitchStatementJumpdestIndex( - transpiledBytecode - ) - log.debug( - `Generating jump table with exepected PC of footer switch jumpdest: ${numberToHexString( - footerSwitchJumpdestIndex - )}` - ) - const jumpdestIndexesAfter: number[] = [] - const replacedBytecode: EVMBytecode = [] - let pc: number = 0 - // Replace all original JUMP, JUMPI, and JUMPDEST, and build the post-transpilation JUMPDEST index array. - for (const opcodeAndBytes of transpiledBytecode) { - if ( - opcodeAndBytes.opcode === Opcode.JUMP && - !isTaggedWithReason(opcodeAndBytes, [ - OpcodeTagReason.IS_JUMP_TO_OPCODE_FUNCTION, - OpcodeTagReason.IS_OPCODE_FUNCTION_RETURN_JUMP, - ]) - ) { - log.debug( - `replacing jump for opcodeandbytes: ${JSON.stringify(opcodeAndBytes)}` - ) - replacedBytecode.push( - ...getJumpReplacementBytecode(footerSwitchJumpdestIndex) - ) - log.debug(`inserted JUMP replacement at PC 0x${pc.toString(16)}`) - pc += getJumpReplacementBytecodeLength() - } else if (opcodeAndBytes.opcode === Opcode.JUMPI) { - replacedBytecode.push( - ...getJumpiReplacementBytecode(footerSwitchJumpdestIndex) - ) - log.debug(`inserted JUMPI replacement at PC 0x${pc.toString(16)}`) - pc += getJumpiReplacementBytecodeLength() - } else if ( - opcodeAndBytes.opcode === Opcode.JUMPDEST && - !isTaggedWithReason(opcodeAndBytes, [ - OpcodeTagReason.IS_OPCODE_FUNCTION_JUMPDEST, - OpcodeTagReason.IS_OPCODE_FUNCTION_RETURN_JUMPDEST, - ]) - ) { - replacedBytecode.push(opcodeAndBytes) - jumpdestIndexesAfter.push(pc) - pc += 1 - } else { - replacedBytecode.push(opcodeAndBytes) - pc += 1 + opcodeAndBytes.opcode.programBytesConsumed - } - } - - if (jumpdestIndexesBefore.length !== jumpdestIndexesAfter.length) { - const message: string = `There were ${jumpdestIndexesBefore.length} JUMPDESTs before transpilation, but there are ${jumpdestIndexesAfter.length} non-replacement-table JUMPDESTs after.` - log.debug(message) - errors.push( - createError(-1, TranspilationErrors.INVALID_SUBSTITUTION, message) - ) - return { bytecode: transpiledBytecode, errors } - } - - // Add the logic to handle the pre-transpilation to post-transpilation jump dest mapping. - log.debug( - `inserting JUMP BST at PC ${numberToHexString( - bytecodeToBuffer(replacedBytecode).length - )}` - ) - replacedBytecode.push( - ...buildJumpBSTBytecode( - jumpdestIndexesBefore, - jumpdestIndexesAfter, - bytecodeToBuffer(replacedBytecode).length - ) - ) - - return { bytecode: replacedBytecode, errors } -} - -let jumpReplacementLength: number -export const getJumpReplacementBytecodeLength = (): number => { - if (jumpReplacementLength === undefined) { - jumpReplacementLength = bytecodeToBuffer(getJumpReplacementBytecode(0)) - .length - } - return jumpReplacementLength -} - -let jumpiReplacementLength: number -export const getJumpiReplacementBytecodeLength = (): number => { - if (jumpiReplacementLength === undefined) { - jumpiReplacementLength = bytecodeToBuffer(getJumpiReplacementBytecode(0)) - .length - } - return jumpiReplacementLength -} - -/** - * Gets the replacement bytecode for a JUMP operation, given the provided - * index of the footer switch statement JUMPDEST. - * See: https://github.com/op-optimism/optimistic-rollup/wiki/Transpiler#jump-transpilation-approach - * for more information on why this is necessary and how replacement occurs. - * - * @param footerSwitchJumpdestIndex The index of the footer JUMPDEST. - * @returns The EVMBytecode to replace JUMP EVMBytecode with. - */ -export const getJumpReplacementBytecode = ( - footerSwitchJumpdestIndex: number -): EVMBytecode => { - const indexBuffer: Buffer = bufferUtils.numberToBufferPacked( - footerSwitchJumpdestIndex, - 2 - ) - return [ - { - opcode: getPUSHOpcode(indexBuffer.length), - consumedBytes: indexBuffer, - }, - { - opcode: Opcode.JUMP, - consumedBytes: undefined, - }, - ] -} - -/** - * Gets the replacement bytecode for a JUMPI operation, given the provided - * index of the footer switch statement JUMPDEST. - * See: https://github.com/op-optimism/optimistic-rollup/wiki/Transpiler#jump-transpilation-approach - * for more information on why this is necessary and how replacement occurs. - * - * @param footerSwitchJumpdestIndex The index of the footer JUMPDEST. - * @returns The EVMBytecode to replace JUMPI EVMBytecode with. - */ -export const getJumpiReplacementBytecode = ( - footerSwitchJumpdestIndex: number -): EVMBytecode => { - const indexBuffer: Buffer = bufferUtils.numberToBufferPacked( - footerSwitchJumpdestIndex, - 2 - ) - return [ - { - opcode: Opcode.SWAP1, - consumedBytes: undefined, - }, - { - opcode: getPUSHOpcode(indexBuffer.length), - consumedBytes: indexBuffer, - }, - { - opcode: Opcode.JUMPI, - consumedBytes: undefined, - }, - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - ] -} - -/** - * Gets the expected index of the footer JUMP switch statement, given EVMBytecode - * that will *only* change by replacing JUMP, JUMPI, and JUMPDEST with the appropriate - * EVMBytecode. - * - * @param bytecode The bytecode in question. - * @returns The expected index of the JUMPDEST for the footer JUMP switch statement. - */ -export const getExpectedFooterSwitchStatementJumpdestIndex = ( - bytecode: EVMBytecode -): number => { - let length: number = 0 - for (const opcodeAndBytes of bytecode) { - if ( - opcodeAndBytes.opcode === Opcode.JUMP && - !isTaggedWithReason(opcodeAndBytes, [ - OpcodeTagReason.IS_JUMP_TO_OPCODE_FUNCTION, - OpcodeTagReason.IS_OPCODE_FUNCTION_RETURN_JUMP, - ]) - ) { - length += getJumpReplacementBytecodeLength() - } else if (opcodeAndBytes.opcode === Opcode.JUMPI) { - length += getJumpiReplacementBytecodeLength() - } else { - length += 1 + opcodeAndBytes.opcode.programBytesConsumed - } - } - length += bytecodeToBuffer(getJumpdestMatchSuccessBytecode()).length - return length -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/opcode-replacer.ts b/packages/rollup-dev-tools/src/tools/transpiler/opcode-replacer.ts deleted file mode 100644 index b434393c47ea9..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/opcode-replacer.ts +++ /dev/null @@ -1,382 +0,0 @@ -/* External Imports */ -import { - Opcode, - EVMOpcode, - EVMOpcodeAndBytes, - EVMBytecode, - OpcodeTagReason, - isValidOpcodeAndBytes, - Address, - bytecodeToBuffer, - formatBytecode, - getPCOfEVMBytecodeIndex, -} from '@eth-optimism/rollup-core' -import { - bufToHexString, - bufferUtils, - getLogger, - isValidHexAddress, - hexStrToBuf, -} from '@eth-optimism/core-utils' - -/* Internal Imports */ -import { OpcodeReplacer } from '../../types/transpiler' -import { - InvalidAddressError, - InvalidBytesConsumedError, - UnsupportedOpcodeError, -} from '../../index' -import { - getCALLSubstitute, - getSTATICCALLSubstitute, - getDELEGATECALLSubstitute, - getEXTCODECOPYSubstitute, -} from './dynamic-memory-opcodes' -import { - getCREATESubstitute, - getCREATE2Substitute, -} from './contract-creation-opcodes' -import { - getADDRESSSubstitute, - getCALLERSubstitute, - getCHAINIDSubstitute, - getEXTCODEHASHSubstitute, - getEXTCODESIZESubstitute, - getORIGINSubstitute, - getSLOADSubstitute, - getSSTORESubstitute, - getTIMESTAMPSubstitute, -} from './static-memory-opcodes' -import { getPUSHIntegerOp, getPUSHOpcode, isTaggedWithReason } from './helpers' -import { PC_MAX_BYTES } from './constants' - -const log = getLogger('transpiler:opcode-replacement') - -export class OpcodeReplacerImpl implements OpcodeReplacer { - public static EX_MGR_PLACEHOLDER: Buffer = Buffer.from( - `{execution manager address placeholder}` - ) - private readonly excutionManagerAddressBuffer: Buffer - - /** - * Creates an OpcodeReplacer, validating the provided address and any given replacements. - * - * @param executionManagerAddress The address of the ExecutionManager -- all calls get routed through this contract. - * @param optionalSubstitutes Optional opcodes to replace with bytecode. - */ - constructor( - executionManagerAddress: Address, - private readonly optionalSubstitutes: Map = new Map< - EVMOpcode, - EVMBytecode - >() - ) { - // check and store address - if (!isValidHexAddress(executionManagerAddress)) { - const msg: string = `Opcode replacer received ${executionManagerAddress} for the execution manager address. Not a valid hex string address!` - log.error(msg) - throw new InvalidAddressError(msg) - } - - this.excutionManagerAddressBuffer = hexStrToBuf(executionManagerAddress) - - for (const [ - toSubstitute, - bytecodeToSubstituteWith, - ] of optionalSubstitutes.entries()) { - // Make sure we're not attempting to overwrite PUSHN, not yet supported - if (toSubstitute.programBytesConsumed > 0) { - const msg: string = `Transpilation currently does not support opcodes which consume bytes, but config specified a substitute for ${JSON.stringify( - toSubstitute - )}.` - log.error(msg) - throw new UnsupportedOpcodeError(msg) - } - - // for each operation in the substituted function's bytecode for this toSubstitute... - for (const substituteBytes of bytecodeToSubstituteWith) { - // ... replace execution manager placeholder - if ( - !!substituteBytes.consumedBytes && - substituteBytes.consumedBytes.equals( - OpcodeReplacerImpl.EX_MGR_PLACEHOLDER - ) - ) { - substituteBytes.consumedBytes = this.excutionManagerAddressBuffer - } - - // ...type check consumed bytes are the right length - if (!isValidOpcodeAndBytes(substituteBytes)) { - const msg: string = `Replacement config specified a ${ - substituteBytes.opcode.name - } as an operation in the replacement bytecode for ${ - toSubstitute.name - }, but the consumed bytes specified was ${bufToHexString( - substituteBytes.consumedBytes - )}--invalid length! (length ${substituteBytes.consumedBytes.length})` - log.error(msg) - throw new InvalidBytesConsumedError(msg) - } - } - } - } - - /** - * Gets whether or not the opcode replacer is configured to change functionality of the given opcode. - * @param opcodeAndBytes EVM opcode and consumed bytes which might need to be replaced. - * - * @returns Whether this opcode needs to get replaced. - */ - public shouldSubstituteOpcodeForFunction(opcode: EVMOpcode): boolean { - return ( - !!this.getManadatorySubstitutedFunction({ - opcode, - consumedBytes: undefined, - }) || this.optionalSubstitutes.has(opcode) - ) - } - - /** - * Gets a chunk of bytecode which will JUMP to the location of the given opcode substitute, and allow JUMPing back on completion - * @param opcode The opcode whose substitute we should JUMP to - * - * @returns The EVMBytecode implementing the above functionality. - */ - public getJUMPToOpcodeFunction(opcode: EVMOpcode): EVMBytecode { - return [ - // push the PC to the stack so that we can JUMP back to it - { - opcode: Opcode.PC, - consumedBytes: undefined, - }, - // JUMP to the right location in the footer - { - opcode: getPUSHOpcode(PC_MAX_BYTES), - consumedBytes: Buffer.alloc(PC_MAX_BYTES), - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_PUSH_OPCODE_FUNCTION_LOCATION, - metadata: opcode, - }, - }, - { - opcode: Opcode.JUMP, - consumedBytes: undefined, - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_JUMP_TO_OPCODE_FUNCTION, - metadata: opcode, - }, - }, - // allow jumping back once the substituted function was executed - { - opcode: Opcode.JUMPDEST, - consumedBytes: undefined, - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_OPCODE_FUNCTION_RETURN_JUMPDEST, - metadata: undefined, - }, - }, - ] - } - - /** - * Gets a chunk of bytecode which will JUMP back to the original source of execution once the opcode function has been executed. - * expected stack: [PC of initial opcode which got substituted with getJUMPToOpcodeFunction(...)] - * @param opcode The opcode whose function is being JUMPed back from. - * - * @returns The EVMBytecode implementing the above functionality. - */ - public getJUMPOnOpcodeFunctionReturn(opcode: EVMOpcode): EVMBytecode { - // since getJUMPToOpcodeFunction(...)'s first element is the PC, and its last is the JUMPDEST to return to, we need to add its length - 1 - return [ - getPUSHIntegerOp( - bytecodeToBuffer(this.getJUMPToOpcodeFunction(opcode)).length - 1 // - 1 for the PC opcode - ), - { - opcode: Opcode.ADD, - consumedBytes: undefined, - }, - { - opcode: Opcode.JUMP, - consumedBytes: undefined, - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_OPCODE_FUNCTION_RETURN_JUMP, - metadata: undefined, - }, - }, - ] - } - - /** - * Gets a piece of bytecode containing substitutes for the given set of opcodes - * @param opcodeAndBytes The set of opcodes to provide substitutes for in the returned bytcode. - * - * @returns Bytecode which can be JUMPed to, executing the opcodes' substitutes, and returning back to the original PC. - */ - public getOpcodeFunctionTable(opcodes: Set): EVMBytecode { - const bytecodeToReturn: EVMBytecode = [] - opcodes.forEach((opcode: EVMOpcode) => { - bytecodeToReturn.push( - ...[ - // jumpdest to reach - { - opcode: Opcode.JUMPDEST, - consumedBytes: undefined, - tag: { - padPUSH: false, - reasonTagged: OpcodeTagReason.IS_OPCODE_FUNCTION_JUMPDEST, - metadata: opcode, - }, - }, - ...this.getSubstituedFunctionFor({ - opcode, - consumedBytes: undefined, - }), - ...this.getJUMPOnOpcodeFunctionReturn(opcode), - ] - ) - }) - return bytecodeToReturn - } - - /** - * Takes some bytecode which has had opcodes substituted, and the substitute table appended, - * but with tagged PUSHes of the substitutes jumpdest PC not yet set, and sets them - * @param taggedBytecode EVM bytecode with some IS_PUSH_OPCODE_FUNCTION_LOCATION tags - * - * @returns The final EVMBytecode with the correct PUSH(jumpdest PC) for all substituted jumps. - */ - public populateOpcodeFunctionJUMPs(taggedBytecode: EVMBytecode): EVMBytecode { - for (const PUSHOpcodeSubstituteLocation of taggedBytecode.filter((op) => - isTaggedWithReason(op, [OpcodeTagReason.IS_PUSH_OPCODE_FUNCTION_LOCATION]) - )) { - const indexInBytecode = taggedBytecode.findIndex( - (toCheck: EVMOpcodeAndBytes) => { - return ( - isTaggedWithReason(toCheck, [ - OpcodeTagReason.IS_OPCODE_FUNCTION_JUMPDEST, - ]) && - toCheck.tag.metadata === PUSHOpcodeSubstituteLocation.tag.metadata - ) - } - ) - if (indexInBytecode === -1) { - throw new Error( - `unable to find replacment location for opcode ${PUSHOpcodeSubstituteLocation.tag.metadata.name}` - ) - } - const PCOfBytecode = getPCOfEVMBytecodeIndex( - indexInBytecode, - taggedBytecode - ) - const destinationBuf = bufferUtils.numberToBuffer( - PCOfBytecode, - PC_MAX_BYTES, - PC_MAX_BYTES - ) - log.debug( - `fixed replacement jump with new destination ${bufToHexString( - destinationBuf - )}` - ) - PUSHOpcodeSubstituteLocation.consumedBytes = destinationBuf - } - return taggedBytecode - } - - /** - * Gets the specified function bytecode meant to be substituted for a given EVM opcode and bytes. - * The function will be JUMPed to, and back from, in place of executing the un-transpiled opcode. - * @param opcodeAndBytes EVM opcode and consumed bytes which is supposed to be replaced with JUMPing to the returned function. - * - * @returns The EVMBytecode we have decided to substitute the opcodeAndBytes with. - */ - public getSubstituedFunctionFor( - opcodeAndBytes: EVMOpcodeAndBytes - ): EVMBytecode { - const substitute: EVMBytecode = this.getManadatorySubstitutedFunction( - opcodeAndBytes - ) - if (!!substitute) { - return substitute - } - - if (!this.optionalSubstitutes.has(opcodeAndBytes.opcode)) { - return [opcodeAndBytes] - } else { - return this.optionalSubstitutes.get(opcodeAndBytes.opcode) - } - } - - private getManadatorySubstitutedFunction( - opcodeAndBytes: EVMOpcodeAndBytes - ): EVMBytecode { - switch (opcodeAndBytes.opcode) { - case Opcode.ADDRESS: - return getADDRESSSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.CALL: - return getCALLSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.CALLER: - return getCALLERSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.CHAINID: - return getCHAINIDSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.CREATE: - return getCREATESubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.CREATE2: - return getCREATE2Substitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.DELEGATECALL: - return getDELEGATECALLSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.EXTCODECOPY: - return getEXTCODECOPYSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.EXTCODEHASH: - return getEXTCODEHASHSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.EXTCODESIZE: - return getEXTCODESIZESubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.ORIGIN: - return getORIGINSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.SLOAD: - return getSLOADSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.SSTORE: - return getSSTORESubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.STATICCALL: - return getSTATICCALLSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - case Opcode.TIMESTAMP: - return getTIMESTAMPSubstitute( - bufToHexString(this.excutionManagerAddressBuffer) - ) - default: - return undefined - } - } -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/opcode-whitelist.ts b/packages/rollup-dev-tools/src/tools/transpiler/opcode-whitelist.ts deleted file mode 100644 index 0a49f62b023e8..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/opcode-whitelist.ts +++ /dev/null @@ -1,169 +0,0 @@ -/* External Imports */ -import { EVMOpcode, Opcode } from '@eth-optimism/rollup-core' - -/* Internal Imports */ -import { OpcodeWhitelist } from '../../types/transpiler' - -/** - * Default and only intended implementation of OpcodeWhitelist. - */ -export class OpcodeWhitelistImpl implements OpcodeWhitelist { - private readonly whitelist: Map - - constructor(opcodes: EVMOpcode[] = defaultWhitelist) { - this.whitelist = new Map(opcodes.map((x) => [x.name, x])) - } - - public isOpcodeWhitelisted(opcode: EVMOpcode): boolean { - return !!opcode && this.whitelist.has(opcode.name) - } - - public isOpcodeWhitelistedByCodeBuffer(opcode: Buffer): boolean { - const code: EVMOpcode = Opcode.parseByCode(opcode) - return !!code && this.whitelist.has(code.name) - } - - public isOpcodeWhitelistedByCodeValue(opcode: number): boolean { - const code: EVMOpcode = Opcode.parseByNumber(opcode) - return !!code && this.whitelist.has(code.name) - } - - public isOpcodeWhitelistedByName(opcodeName: string): boolean { - return !!opcodeName && this.whitelist.has(opcodeName) - } -} - -const defaultWhitelist: EVMOpcode[] = [ - Opcode.ADD, - Opcode.ADDMOD, - Opcode.ADDRESS, - Opcode.AND, - Opcode.BYTE, - Opcode.CALL, - Opcode.CALLDATACOPY, - Opcode.CALLDATALOAD, - Opcode.CALLDATASIZE, - Opcode.CALLER, - Opcode.CALLVALUE, - Opcode.CHAINID, - Opcode.CODECOPY, - Opcode.CODESIZE, - Opcode.CREATE, - Opcode.CREATE2, - Opcode.DELEGATECALL, - Opcode.DIV, - Opcode.DUP1, - Opcode.DUP10, - Opcode.DUP11, - Opcode.DUP12, - Opcode.DUP13, - Opcode.DUP14, - Opcode.DUP15, - Opcode.DUP16, - Opcode.DUP2, - Opcode.DUP3, - Opcode.DUP4, - Opcode.DUP5, - Opcode.DUP6, - Opcode.DUP7, - Opcode.DUP8, - Opcode.DUP9, - Opcode.EQ, - Opcode.EXP, - Opcode.EXTCODECOPY, - Opcode.EXTCODESIZE, - Opcode.EXTCODEHASH, - Opcode.GAS, - Opcode.GT, - Opcode.INVALID, - Opcode.ISZERO, - Opcode.JUMP, - Opcode.JUMPDEST, - Opcode.JUMPI, - Opcode.LOG0, - Opcode.LOG1, - Opcode.LOG2, - Opcode.LOG3, - Opcode.LOG4, - Opcode.LT, - Opcode.MLOAD, - Opcode.MOD, - Opcode.MSIZE, - Opcode.MSTORE, - Opcode.MSTORE8, - Opcode.MUL, - Opcode.MULMOD, - Opcode.NOT, - Opcode.OR, - Opcode.ORIGIN, - Opcode.PC, - Opcode.POP, - Opcode.PUSH1, - Opcode.PUSH10, - Opcode.PUSH11, - Opcode.PUSH12, - Opcode.PUSH13, - Opcode.PUSH14, - Opcode.PUSH15, - Opcode.PUSH16, - Opcode.PUSH17, - Opcode.PUSH18, - Opcode.PUSH19, - Opcode.PUSH2, - Opcode.PUSH20, - Opcode.PUSH21, - Opcode.PUSH22, - Opcode.PUSH23, - Opcode.PUSH24, - Opcode.PUSH25, - Opcode.PUSH26, - Opcode.PUSH27, - Opcode.PUSH28, - Opcode.PUSH29, - Opcode.PUSH3, - Opcode.PUSH30, - Opcode.PUSH31, - Opcode.PUSH32, - Opcode.PUSH4, - Opcode.PUSH5, - Opcode.PUSH6, - Opcode.PUSH7, - Opcode.PUSH8, - Opcode.PUSH9, - Opcode.RETURN, - Opcode.RETURNDATACOPY, - Opcode.RETURNDATASIZE, - Opcode.REVERT, - Opcode.SAR, - Opcode.SDIV, - Opcode.SGT, - Opcode.SHA3, - Opcode.SHL, - Opcode.SHR, - Opcode.SIGNEXTEND, - Opcode.SLT, - Opcode.SMOD, - Opcode.SLOAD, - Opcode.SSTORE, - Opcode.STATICCALL, - Opcode.STOP, - Opcode.SUB, - Opcode.SWAP1, - Opcode.SWAP10, - Opcode.SWAP11, - Opcode.SWAP12, - Opcode.SWAP13, - Opcode.SWAP14, - Opcode.SWAP15, - Opcode.SWAP16, - Opcode.SWAP2, - Opcode.SWAP3, - Opcode.SWAP4, - Opcode.SWAP5, - Opcode.SWAP6, - Opcode.SWAP7, - Opcode.SWAP8, - Opcode.SWAP9, - Opcode.TIMESTAMP, - Opcode.XOR, -] diff --git a/packages/rollup-dev-tools/src/tools/transpiler/static-memory-opcodes.ts b/packages/rollup-dev-tools/src/tools/transpiler/static-memory-opcodes.ts deleted file mode 100644 index 27ef33dee6921..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/static-memory-opcodes.ts +++ /dev/null @@ -1,305 +0,0 @@ -/* External Imports */ -import { Opcode, EVMBytecode, Address } from '@eth-optimism/rollup-core' -import { getLogger, hexStrToBuf } from '@eth-optimism/core-utils' -import * as abi from 'ethereumjs-abi' - -/* Internal Imports */ -import { - getPUSHIntegerOp, - getPUSHBuffer, - pushMemoryAtIndexOntoStack, - storeStackInMemoryAtIndex, - getSWAPNOp, - duplicateStackAt, - POPNTimes, - storeStackElementsAsMemoryWords, - getDUPNOp, -} from './helpers' - -const log = getLogger(`static-memory-opcodes`) - -export const ovmADDRESSName: string = 'ovmADDRESS' -export const ovmCALLERName: string = 'ovmCALLER' -export const ovmCHAINIDName: string = 'ovmCHAINID' -export const ovmEXTCODEHASHName: string = 'ovmEXTCODEHASH' -export const ovmEXTCODESIZEName: string = 'ovmEXTCODESIZE' -export const ovmORIGINName: string = 'ovmORIGIN' -export const ovmSLOADName: string = 'ovmSLOAD' -export const ovmSSTOREName: string = 'ovmSSTORE' -export const ovmTIMESTAMPName: string = 'ovmTIMESTAMP' - -export const getADDRESSSubstitute = ( - executionManagerAddress: Address, - ovmADDRESSFunctionName: string = ovmADDRESSName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmADDRESSFunctionName, - 0, - 1 - ) -} - -export const getCALLERSubstitute = ( - executionManagerAddress: Address, - ovmCALLERFunctionName: string = ovmCALLERName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmCALLERFunctionName, - 0, - 1 - ) -} - -export const getCHAINIDSubstitute = ( - executionManagerAddress: Address, - ovmCHAINIDFunctionName: string = ovmCHAINIDName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmCHAINIDFunctionName, - 0, - 1 - ) -} - -export const getEXTCODEHASHSubstitute = ( - executionManagerAddress: Address, - ovmEXTCODEHASHFunctionName: string = ovmEXTCODEHASHName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmEXTCODEHASHFunctionName, - 1, - 1 - ) -} - -export const getEXTCODESIZESubstitute = ( - executionManagerAddress: Address, - ovmEXTCODESIZEFunctionName: string = ovmEXTCODESIZEName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmEXTCODESIZEFunctionName, - 1, - 1 - ) -} - -export const getORIGINSubstitute = ( - executionManagerAddress: Address, - ovmORIGINFunctionName: string = ovmORIGINName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmORIGINFunctionName, - 0, - 1 - ) -} - -export const getSLOADSubstitute = ( - executionManagerAddress: Address, - ovmSLOADFunctionName: string = ovmSLOADName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmSLOADFunctionName, - 1, - 1 - ) -} - -export const getSSTORESubstitute = ( - executionManagerAddress: Address, - ovmSSTOREFunctionName: string = ovmSSTOREName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmSSTOREFunctionName, - 2, - 0 - ) -} - -export const getTIMESTAMPSubstitute = ( - executionManagerAddress: Address, - ovmTIMESTAMPFunctionName: string = ovmTIMESTAMPName -): EVMBytecode => { - return callContractWithStackElementsAndReturnWordToStack( - executionManagerAddress, - ovmTIMESTAMPFunctionName, - 0, - 1 - ) -} - -/** - * Uses the contiguous memory space starting at the specified index to: - * 1. Store the methodId to pass as calldata - * 2. Store the stack elements to pass as calldata - * 3. Have a single word of return data be stored at the index proceeding the above. - * - * - * @param address The address to call. - * @param methodName The human readable name of the ABI method to call - * @param numStackArgumentsToPass The number of stack elements to pass to the address as calldata - * @param memoryIndexToUse The memory index to use a contiguous range of - * @returns Btyecode which results in the store-and-call operation described above. - * @returns The total number of bytes of storage which will be used and need to be stashed beforehand if memory is to be unaffected. - */ - -export const callContractWithStackElementsAndReturnWordToMemory = ( - address: Address, - methodName: string, - numStackArgumentsToPass: number = 0, - memoryIndexToUse: number = 0 -): EVMBytecode => { - const methodData: Buffer = abi.methodID(methodName, []) - - const callDataMemoryLength: number = - methodData.byteLength + 32 * numStackArgumentsToPass - // MLOAD is 32 bytes w/ big endian, e.g. MSTOREing 4 bytes means those begin at e.g. 32 - 4 = 28 for - // in other words, we do 1+numStackArgumentsToPass because the methodId takes up 1 whole word to store, but we pass - const callDataMemoryOffset: number = - memoryIndexToUse + 32 * (1 + numStackArgumentsToPass) - callDataMemoryLength - // due to the above, this line is equivalent to memoryIndexToUse + 32. However, leaving explicit as is hopefully more readable - const returnDataMemoryIndex: number = - memoryIndexToUse + callDataMemoryLength + callDataMemoryOffset - const returnDataMemoryLength: number = 32 - - return [ - // Store method ID for callData - getPUSHBuffer(methodData), - getPUSHIntegerOp(memoryIndexToUse), // this is not callDataMemoryOffset; see comments above. - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - // Store the stack elements as 32-byte words for calldata. - // index + 32 because first word is methodId - ...storeStackElementsAsMemoryWords( - numStackArgumentsToPass, - memoryIndexToUse + 32 - ), - // CALL - // ret length - getPUSHIntegerOp(returnDataMemoryLength), - // ret offset - getPUSHIntegerOp(returnDataMemoryIndex), - // calldata args length - getPUSHIntegerOp(callDataMemoryLength), - // calldata args offset - getPUSHIntegerOp(callDataMemoryOffset), - // value (0 ETH always!) - { - opcode: Opcode.PUSH1, - consumedBytes: hexStrToBuf('0x00'), - }, - // address - getPUSHBuffer(hexStrToBuf(address)), - // Gas - { - opcode: Opcode.PUSH32, - consumedBytes: Buffer.from('00'.repeat(16) + 'ff'.repeat(16), 'hex'), - }, - // execute the call! - { - opcode: Opcode.CALL, - consumedBytes: undefined, - }, - // POP success - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - ] -} - -/** - * Uses the contiguous memory space starting at the specified index to: - * 1. Stash the original memory space into the stack to be replaced after execution. - * 2. Store the methodId to pass as calldata - * 3. Store the stack elements to pass as calldata - * 4. Have a single word of return data be stored at the index proceeding the above. - * 5. Load the returned word from memory into the stack. - * 6. Replace the original memory by unstashing. - * - * NOTE // This operation preserves the first stack element. So if the input is - * [(first element), (second), (third), ...] then the output stack should be - * [(first element), (returned value), ...] with the contract call recieving (second), (third), ... - * - * @param address The address to call. - * @param methodName The human readable name of the ABI method to call - * @param numStackArgumentsToPass The number of stack elements to pass to the address as calldata - * @param memoryIndexToUse The memory index to use a contiguous range of - * @returns Btyecode which results in the 32 byte word of return being pushed to the stack with original memory intact. - */ - -export const callContractWithStackElementsAndReturnWordToStack = ( - address: Address, - methodName: string, - numStackArgumentsToPass: number, - numStackValuesReturned: 1 | 0, - memoryIndexToUse: number = 0 -): EVMBytecode => { - // 1 word for method Id, 1 word for each stack argument, 1 word for return - const numWordsToStash: number = 1 + numStackArgumentsToPass + 1 //Math.ceil(bytesMemoryUsed / 32) - - // ad 1 word for method Id, 1 word for each stack argument, and then the immediately following index will be the return val - const returnedWordMemoryIndex: number = 32 * (1 + numStackArgumentsToPass) - - const op = [ - // Based on the contiguous memory space we expect to utilize, stash the original memory so it can be recovered. - ...pushMemoryAtIndexOntoStack(memoryIndexToUse, numWordsToStash), - // Now that the stashed memory is first on the stack, recover the original stack elements we expected to consume/pass to execution manager - // +1 so that we do not duplicate the first stack element, as we do not intend to pass it. - ...duplicateStackAt(numWordsToStash + 1, numStackArgumentsToPass), - // Do the call, with the returned word being put into memory. - ...callContractWithStackElementsAndReturnWordToMemory( - address, - methodName, - numStackArgumentsToPass, - memoryIndexToUse - ), - // MLOAD the returned word into the stack - getPUSHIntegerOp(returnedWordMemoryIndex), - { - opcode: Opcode.MLOAD, - consumedBytes: undefined, - }, - // Now that the returned value is first thing on stack, duplicate the stashed old memory so they're first on the stack. - ...duplicateStackAt(1, numWordsToStash), - // Now that stack is prepared, unstash the memory to its original state - ...storeStackInMemoryAtIndex(memoryIndexToUse, numWordsToStash), - // The above duplications need to be eliminated, but the returned word and original first stack element need to be maintained. - // DUP the original stack element - getDUPNOp(1 + numWordsToStash + 1), - // SWAP it to the second to last - getSWAPNOp(1 + numWordsToStash + numStackArgumentsToPass), - // POP the garbage we just switched - ...POPNTimes(1), - // swap the return value to be right after - getSWAPNOp(numWordsToStash + numStackArgumentsToPass + 1), - // POP the extra elements that came from the above duplications - ...POPNTimes(numWordsToStash + numStackArgumentsToPass), - ] - if (numStackValuesReturned === 0) { - // if we don't care about a return value just pop whatever was randomly grabbed from memory - op.push( - ...[ - { - opcode: Opcode.SWAP1, - consumedBytes: undefined, - }, - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - ] - ) - } - return op -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/transpiler.ts b/packages/rollup-dev-tools/src/tools/transpiler/transpiler.ts deleted file mode 100644 index 3e3e08e0345de..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/transpiler.ts +++ /dev/null @@ -1,872 +0,0 @@ -/* External Imports */ -import { - Opcode, - EVMOpcodeAndBytes, - EVMBytecode, - bytecodeToBuffer, - EVMOpcode, - formatBytecode, - bufferToBytecode, - getPCOfEVMBytecodeIndex, - OpcodeTagReason, -} from '@eth-optimism/rollup-core' -import { - getLogger, - bufToHexString, - bufferUtils, - add0x, -} from '@eth-optimism/core-utils' - -import BigNum = require('bn.js') - -/* Internal Imports */ -import { - OpcodeWhitelist, - OpcodeReplacer, - Transpiler, - TranspilationResult, - TranspilationError, - TranspilationErrors, - ErroredTranspilation, - TaggedTranspilationResult, - JumpReplacementResult, -} from '../../types/transpiler' -import { accountForJumps } from './jump-replacement' -import { isTaggedWithReason } from './helpers' - -const log = getLogger('transpiler-impl') -const statsLog = getLogger('transpiler-stats') - -export class TranspilerImpl implements Transpiler { - constructor( - private readonly opcodeWhitelist: OpcodeWhitelist, - private readonly opcodeReplacer: OpcodeReplacer - ) { - if (!opcodeWhitelist) { - throw Error('Opcode Whitelist is required for TranspilerImpl') - } - if (!opcodeReplacer) { - throw Error('Opcode Replacer is required for TranspilerImpl') - } - } - - // This function transpiles initcode--that is, all bytecode fed to CREATE/CREATE2 other than the runtime-appended constructor inputs. - // The Solidity compiler produces this initcode with the following pattern: - // 1. constructor/deployment logic - // 2. deployed bytecode - // 3. constants accessed by the constructor - // This pattern has been confirmed/explored extensively at ../../tests/transpiler/initcode-structure-check.spec.ts - // The way we transpile this: - // 1. Separate out 1, 2, and 3 above - // 2. Tag the opcodes related to CODECOPY usage, the three types are 1. constants, 2. deployed bytecode to be returned, 3. inputs to the constructor. Also, record the constants for case 1. so we know what they are. - // 3. Transpile the deployed bytecode and correct the constant indices. - // 4. Transpile the constructor/deployment logic. - // 5. Reconstruct the transpiled constructor/deployment logic, deployed bytecode, and constants accessed by the constructor - // 6. fix the remaining tagged CODECOPY indices which were detected in step 2. - - public transpile( - bytecode: Buffer, - deployedBytecode: Buffer, - originalDeployedBytecodeSize: number = deployedBytecode.length - ): TranspilationResult { - const errors: TranspilationError[] = [] - - log.debug( - `transpiling raw full bytecode: ${bufToHexString( - bytecode - )} \nAnd original deployed raw bytecode: ${bufToHexString( - deployedBytecode - )}` - ) - const startOfDeployedBytecode: number = bytecode.indexOf(deployedBytecode) - if (startOfDeployedBytecode === -1) { - const errMsg = `WARNING: Could not find deployed bytecode (${bufToHexString( - deployedBytecode - )}) within the original bytecode (${bufToHexString(bytecode)}).` - log.debug(errMsg) - errors.push( - TranspilerImpl.createError( - 0, - TranspilationErrors.MISSING_DEPLOYED_BYTECODE_ERROR, - errMsg - ) - ) - } - - // **** SEPARATE THE THREE SECTIONS OF THE INPUT BYTECODE **** - // These sections are: - // 1. constructor/deployment logic - // 2. deployed bytecode - // 3. constants accessed by the constructor (if any) - - const endOfDeployedBytecode: number = - startOfDeployedBytecode + deployedBytecode.length - let constantsUsedByConstructor: Buffer - if (endOfDeployedBytecode < bytecode.length) { - constantsUsedByConstructor = bytecode.slice(endOfDeployedBytecode) - log.debug( - `Detected constants being used by the constructor. Together they are: \n${bufToHexString( - constantsUsedByConstructor - )}` - ) - } else { - log.debug('Did not detect any constants being used by the constructor.') - } - - const originalDeployedEVMBytecode: EVMBytecode = bufferToBytecode( - deployedBytecode - ) - const originalConstructorInitLogic: EVMBytecode = bufferToBytecode( - bytecode.slice(0, startOfDeployedBytecode) - ) - - log.debug( - `original deployed evm bytecode is: ${formatBytecode( - originalDeployedEVMBytecode - )}` - ) - - // **** DETECT AND TAG THE CONSTANTS IN DEPLOYED BYTECODE AND TRANSPILE **** - - let taggedDeployedEVMBytecode: EVMBytecode - taggedDeployedEVMBytecode = this.findAndTagConstants( - originalDeployedEVMBytecode, - deployedBytecode, - errors - ) - const deployedBytecodeTranspilationResult: TaggedTranspilationResult = this.transpileBytecodePreservingTags( - taggedDeployedEVMBytecode - ) - - if (!deployedBytecodeTranspilationResult.succeeded) { - errors.push( - ...(deployedBytecodeTranspilationResult as ErroredTranspilation).errors - ) - return { - succeeded: false, - errors, - } - } - const transpiledDeployedBytecode: EVMBytecode = - deployedBytecodeTranspilationResult.bytecodeWithTags - - log.debug(`Fixing the constant indices for transpiled deployed bytecode...`) - log.debug(`errors are: ${JSON.stringify(errors)}`) - // Note that fixTaggedConstantOffsets() scrubs all fixed tags, so we do not re-fix when we use finalTranspiledDeployedBytecode to reconstruct the returned initcode - const finalTranspiledDeployedBytecode: EVMBytecode = this.fixTaggedConstantOffsets( - transpiledDeployedBytecode as EVMBytecode, - errors - ) - - // problem is after here? - log.debug( - `final transpiled deployed bytecode: \n${formatBytecode( - finalTranspiledDeployedBytecode - )}` - ) - - // **** DETECT AND TAG USES OF CODECOPY IN CONSTRUCTOR BYTECODE AND TRANSPILE **** - - let taggedOriginalConstructorInitLogic: EVMBytecode - taggedOriginalConstructorInitLogic = this.findAndTagConstants( - originalConstructorInitLogic, - bytecode, - errors - ) - taggedOriginalConstructorInitLogic = this.findAndTagConstructorParamsLoader( - originalConstructorInitLogic, - errors, - bytecode, - originalDeployedBytecodeSize - ) - taggedOriginalConstructorInitLogic = this.findAndTagDeployedBytecodeReturner( - originalConstructorInitLogic - ) - const constructorInitLogicTranspilationResult: TaggedTranspilationResult = this.transpileBytecodePreservingTags( - taggedOriginalConstructorInitLogic - ) - if (!constructorInitLogicTranspilationResult.succeeded) { - errors.push( - ...(constructorInitLogicTranspilationResult as ErroredTranspilation) - .errors - ) - return { - succeeded: false, - errors, - } - } - const transpiledConstructorInitLogic: EVMBytecode = - constructorInitLogicTranspilationResult.bytecodeWithTags - - // **** FIX THE TAGGED VALUES USED IN CONSTRUCTOR **** - - const transpiledInitLogicByteLength: number = bytecodeToBuffer( - transpiledConstructorInitLogic - ).byteLength - - const transpiledDeployedBytecodeByteLength: number = bytecodeToBuffer( - transpiledDeployedBytecode - ).byteLength - - const constantsUsedByConstructorLength: number = !constantsUsedByConstructor - ? 0 - : constantsUsedByConstructor.byteLength - - for (const [index, op] of transpiledConstructorInitLogic.entries()) { - if ( - isTaggedWithReason(op, [OpcodeTagReason.IS_CONSTRUCTOR_INPUTS_OFFSET]) - ) { - // this should be the total length of the bytecode we're about to have generated! - transpiledConstructorInitLogic[index].consumedBytes = new BigNum( - transpiledInitLogicByteLength + - transpiledDeployedBytecodeByteLength + - constantsUsedByConstructorLength - ).toBuffer('be', op.opcode.programBytesConsumed) - } - if (isTaggedWithReason(op, [OpcodeTagReason.IS_DEPLOY_CODE_LENGTH])) { - transpiledConstructorInitLogic[index].consumedBytes = new BigNum( - transpiledDeployedBytecodeByteLength - ).toBuffer('be', op.opcode.programBytesConsumed) - } - if (isTaggedWithReason(op, [OpcodeTagReason.IS_DEPLOY_CODECOPY_OFFSET])) { - transpiledConstructorInitLogic[index].consumedBytes = new BigNum( - transpiledInitLogicByteLength - ).toBuffer('be', op.opcode.programBytesConsumed) - } - } - - // **** FIX CONSTANTS IN THE INITCODE AND RETURN THE FINALIZED BYTECODE **** - - const finalTranspiledBytecode: EVMBytecode = this.fixTaggedConstantOffsets( - [ - ...transpiledConstructorInitLogic, - ...finalTranspiledDeployedBytecode, - ...(!constantsUsedByConstructor - ? [] - : bufferToBytecode(constantsUsedByConstructor)), - ], - errors - ) - - return { - succeeded: true, - bytecode: bytecodeToBuffer(finalTranspiledBytecode), - } - } - - // Fixes the tagged constants-loading offset in some transpiled bytecode. - // Since we record the constant's value when we tag it, we can just search for the original value as a constant with indexOf() and set the index to that value. - private fixTaggedConstantOffsets( - taggedBytecode: EVMBytecode, - errors - ): EVMBytecode { - log.debug(`tagged input: ${formatBytecode(taggedBytecode)}`) - const inputAsBuf: Buffer = bytecodeToBuffer(taggedBytecode) - const bytecodeToReturn: EVMBytecode = [] - for (const [index, op] of taggedBytecode.entries()) { - bytecodeToReturn[index] = { - opcode: taggedBytecode[index].opcode, - consumedBytes: taggedBytecode[index].consumedBytes, - } - if (isTaggedWithReason(op, [OpcodeTagReason.IS_CONSTANT_OFFSET])) { - const theConstant: Buffer = op.tag.metadata - const newConstantOffset: number = inputAsBuf.indexOf(theConstant) - if (newConstantOffset === -1) { - errors.push( - TranspilerImpl.createError( - index, - TranspilationErrors.MISSING_CONSTANT_ERROR, - `Could not find CODECOPYed constant in transpiled deployed bytecode for PC 0x${index.toString( - 16 - )}. We originally recorded the constant as ${bufToHexString( - theConstant - )}, but it does not exist in the post-transpiled ${bufToHexString( - inputAsBuf - )}` - ) - ) - } - const newConstantOffsetBuf: Buffer = new BigNum( - newConstantOffset - ).toBuffer('be', op.opcode.programBytesConsumed) - log.debug( - `fixing CODECOPY(constant) at PC 0x${getPCOfEVMBytecodeIndex( - index, - taggedBytecode - ).toString(16)}. The constant's value is: ${bufToHexString( - theConstant - )} Setting new index to ${bufToHexString(newConstantOffsetBuf)}` - ) - bytecodeToReturn[index].consumedBytes = newConstantOffsetBuf - } - } - return bytecodeToReturn - } - - // Finds and tags the PUSHN's which are detected to be associated with CODECOPYing deployed bytecode which is returned during CREATE/CREATE2. - // See https://github.com/ethereum-optimism/optimistic-rollup/wiki/CODECOPYs for more details. - private findAndTagDeployedBytecodeReturner( - bytecode: EVMBytecode - ): EVMBytecode { - for (let index = 0; index < bytecode.length - 6; index++) { - const op: EVMOpcodeAndBytes = bytecode[index] - // Tags based on the pattern used for deploying non-library contracts: - // PUSH2 // codecopy's and RETURN's length - // DUP1 // DUPed to use twice, for RETURN and CODECOPY both - // PUSH2 // codecopy's offset - // PUSH1 codecopy's destOffset - // CODECOPY // copy - // PUSH1 0 // RETURN offset - // RETURN // uses above RETURN offset and DUP'ed length above - if ( - Opcode.isPUSHOpcode(op.opcode) && - Opcode.isPUSHOpcode(bytecode[index + 2].opcode) && - bytecode[index + 4].opcode === Opcode.CODECOPY && - bytecode[index + 6].opcode === Opcode.RETURN - ) { - log.debug( - `detected a NON-LIBRARY [CODECOPY(deployed bytecode)... RETURN] (CREATE/2 deployment logic) pattern starting at PC: 0x${getPCOfEVMBytecodeIndex( - index, - bytecode - ).toString(16)}. Tagging the offset and size...` - ) - bytecode[index] = { - opcode: op.opcode, - consumedBytes: op.consumedBytes, - tag: { - padPUSH: true, - reasonTagged: OpcodeTagReason.IS_DEPLOY_CODE_LENGTH, - metadata: undefined, - }, - } - bytecode[index + 2] = { - opcode: bytecode[index + 2].opcode, - consumedBytes: bytecode[index + 2].consumedBytes, - tag: { - padPUSH: true, - reasonTagged: OpcodeTagReason.IS_DEPLOY_CODECOPY_OFFSET, - metadata: undefined, - }, - } - } - // Tags based on the pattern used for deploying library contracts: - // PUSH2 // deployed bytecode length - // PUSH2 // deployed bytecode start - // PUSH1: // destoffset of code to copy - // DUP3 - // DUP3 - // DUP3 - // CODECOPY - else if ( - Opcode.isPUSHOpcode(op.opcode) && - Opcode.isPUSHOpcode(bytecode[index + 1].opcode) && - Opcode.isPUSHOpcode(bytecode[index + 2].opcode) && - bytecode[index + 6].opcode === Opcode.CODECOPY - ) { - log.debug( - `detected a LIBRARY [CODECOPY(deployed bytecode)... RETURN] (library deployment logic) pattern starting at PC: 0x${getPCOfEVMBytecodeIndex( - index, - bytecode - ).toString(16)}. Tagging the offset and size...` - ) - bytecode[index] = { - opcode: op.opcode, - consumedBytes: op.consumedBytes, - tag: { - padPUSH: true, - reasonTagged: OpcodeTagReason.IS_DEPLOY_CODE_LENGTH, - metadata: undefined, - }, - } - bytecode[index + 1] = { - opcode: bytecode[index + 1].opcode, - consumedBytes: bytecode[index + 1].consumedBytes, - tag: { - padPUSH: true, - reasonTagged: OpcodeTagReason.IS_DEPLOY_CODECOPY_OFFSET, - metadata: undefined, - }, - } - } - } - return bytecode - } - - // Finds and tags the PUSHN's which are detected to be associated with CODECOPYing constructor params during CREATE/CREATE2. - // Tags based on the pattern: - // PUSH2 // should be initcode.length + deployedbytecode.length - // CODESIZE - // SUB // subtract however big the code is from the amount pushed above to get the length of constructor input - // DUP1 - // PUSH2 // should also be initcode.length + deployedbytecode.length - // DUP4 - // CODECOPY - // See https://github.com/ethereum-optimism/optimistic-rollup/wiki/CODECOPYs for more details. - - /* Inputs: - * bytecode: EVMBytcode - the subset of bytecode to tag. - * fullBytecodeBuf: Buffer - the full bytes of the code in which the subset will run. Used to grab constructor constants which come AFTER deployed bytecode, while only tagging the constructor logic. - * sizeIncreaseFromPreviousPadding: any increase in length which the bytecode has experienced since being output from solc-js - * Outputs: - * EVMBytecode - the Bytecode, but with the constructor params loader PUSHes tagged. - */ - private findAndTagConstructorParamsLoader( - bytecode: EVMBytecode, - errors, - fullBytecodeBuf: Buffer, - originalDeployedBytecodeSize: number = fullBytecodeBuf.byteLength - ): EVMBytecode { - for (let index = 0; index < bytecode.length - 6; index++) { - const op: EVMOpcodeAndBytes = bytecode[index] - if ( - Opcode.isPUSHOpcode(op.opcode) && - bytecode[index + 1].opcode === Opcode.CODESIZE && - bytecode[index + 2].opcode === Opcode.SUB && - Opcode.isPUSHOpcode(bytecode[index + 4].opcode) && - bytecode[index + 6].opcode === Opcode.CODECOPY - ) { - const pushedOffset: number = new BigNum(op.consumedBytes).toNumber() - if (pushedOffset !== originalDeployedBytecodeSize) { - errors.push( - TranspilerImpl.createError( - index, - TranspilationErrors.DETECTED_CONSTANT_OOB, - `thought we were in a CODECOPY(constructor params), but wrong length...at PC: 0x${getPCOfEVMBytecodeIndex( - index, - bytecode - ).toString( - 16 - )}. PUSH of offset which we thought was the total initcode length was 0x${pushedOffset.toString( - 16 - )}, but length of original bytecode was specified or detected to be 0x${originalDeployedBytecodeSize}` - ) - ) - } - log.debug( - `Successfully detected a CODECOPY(constructor params) pattern starting at PC: 0x${getPCOfEVMBytecodeIndex( - index, - bytecode - ).toString(16)}.` - ) - bytecode[index] = { - opcode: op.opcode, - consumedBytes: op.consumedBytes, - tag: { - padPUSH: true, - reasonTagged: OpcodeTagReason.IS_CONSTRUCTOR_INPUTS_OFFSET, - metadata: undefined, - }, - } - bytecode[index + 4] = { - opcode: bytecode[index + 4].opcode, - consumedBytes: bytecode[index + 4].consumedBytes, - tag: { - padPUSH: true, - reasonTagged: OpcodeTagReason.IS_CONSTRUCTOR_INPUTS_OFFSET, - metadata: undefined, - }, - } - } - } - return bytecode - } - - // Finds and tags the PUSHN's which are detected to be associated with CODECOPYing constants. - // Tags based on the pattern: - // ... - // PUSH2 // offset of constant in bytecode - // PUSH1 // length of constant - // SWAP2 // where to put it into memory - // CODECOPY - // It also copies the constants into the tag so that their new position can be recovered later. - // See https://github.com/ethereum-optimism/optimistic-rollup/wiki/CODECOPYs for more details. - public findAndTagConstants( - bytecode: EVMBytecode, - fullRawBytecode: Buffer, - errors - ): EVMBytecode { - const taggedBytecode: EVMBytecode = bytecode as EVMBytecode - for (let index = 0; index < bytecode.length - 3; index++) { - // this pattern is 3 long, so stop 2 early - const op: EVMOpcodeAndBytes = bytecode[index] - - // log.debug(`cur index tagging constants: 0x${getPCOfEVMBytecodeIndex(index, bytecode).toString(16)}`) - // log.debug(`at this index we see the following opcodes: \n${formatBytecode(bytecode.slice(index, index + 10))}`) - if ( - Opcode.isPUSHOpcode(op.opcode) && - Opcode.isPUSHOpcode(bytecode[index + 1].opcode) && - bytecode[index + 3].opcode === Opcode.CODECOPY - ) { - const offsetForCODECOPY: number = new BigNum( - op.consumedBytes - ).toNumber() - const lengthforCODECOPY: number = new BigNum( - bytecode[index + 1].consumedBytes - ).toNumber() - const constantStart: number = offsetForCODECOPY - const constantEnd: number = constantStart + lengthforCODECOPY - if (constantEnd > fullRawBytecode.byteLength) { - errors.push( - TranspilerImpl.createError( - index, - TranspilationErrors.DETECTED_CONSTANT_OOB, - `Thought we detected a CODECOPY(constant) pattern at starting at PC: 0x${getPCOfEVMBytecodeIndex( - index, - bytecode - ).toString( - 16 - )}, but it is out of bounds (not part of the bytecode))` - ) - ) - } - const theConstant: Buffer = fullRawBytecode.slice( - constantStart, - constantEnd - ) - log.debug( - `detected a CODECOPY(constant) pattern at starting at PC: 0x${getPCOfEVMBytecodeIndex( - index, - bytecode - ).toString( - 16 - )}. The original offset is: 0x${offsetForCODECOPY.toString( - 16 - )} and original length is: 0x${lengthforCODECOPY.toString( - 16 - )} which produced this value: ${bufToHexString(theConstant)}` - ) - taggedBytecode[index] = { - opcode: op.opcode, - consumedBytes: op.consumedBytes, - tag: { - padPUSH: true, - reasonTagged: OpcodeTagReason.IS_CONSTANT_OFFSET, - metadata: theConstant, - }, - } - } - } - return taggedBytecode - } - - // This function transpiles "deployed bytecode"-type bytecode, operating on potentially tagged EVMBytecode (==EVMOpcodeAndBytes[]). - // It preserves all .tags values of the EVMOpcodeAndBytes UNLESS: - // 1. The opcode is substituted by the replacer. In that case, it will use the tags related to JUMPing to the substituted opcode function, - // 2. The EVMOpcodeAndBytes is a JUMP/JUMPI/JUMPDEST (aka affected by the JUMP table) - private transpileBytecodePreservingTags( - bytecode: EVMBytecode - ): TaggedTranspilationResult { - let transpiledBytecode: EVMBytecode = [] - const errors: TranspilationError[] = [] - const jumpdestIndexesBefore: number[] = [] - let lastOpcode: EVMOpcode - let insideUnreachableCode: boolean = false - const substitutedOpcodes: Set = new Set() - - const [lastOpcodeAndConsumedBytes] = bytecode.slice(-1) - if ( - Opcode.isPUSHOpcode(lastOpcodeAndConsumedBytes.opcode) && - lastOpcodeAndConsumedBytes.consumedBytes.byteLength < - lastOpcodeAndConsumedBytes.opcode.programBytesConsumed - ) { - // todo: handle with warnings[] separate from errors[]? - const message: string = `Final input opcode: ${ - lastOpcodeAndConsumedBytes.opcode.name - } consumes ${ - lastOpcodeAndConsumedBytes.opcode.programBytesConsumed - }, but only has 0x${bufToHexString( - lastOpcodeAndConsumedBytes.consumedBytes - )} following it. Padding with zeros under the assumption that this arises from a constant at EOF...` - log.debug(message) - lastOpcodeAndConsumedBytes.consumedBytes = bufferUtils.padRight( - lastOpcodeAndConsumedBytes.consumedBytes, - lastOpcodeAndConsumedBytes.opcode.programBytesConsumed - ) - } - - let sizeIncreaseDueToOpcodeFunctionJUMPs: number = 0 // used for codesize stats - const bytecodeBuf: Buffer = bytecodeToBuffer(bytecode) - // todo remove once confirmed with Kevin? - let seenJump: boolean = false - // track the index in EVMBytecode we are on so that we can preserve metadata when we append it - // incrementing at the beginning of the loop so start at -1 - let indexOfOpcodeAndBytes: number = -1 - for (let pc = 0; pc < bytecodeBuf.length; pc++) { - indexOfOpcodeAndBytes += 1 - const currentTaggedOpcodeAndBytes: EVMOpcodeAndBytes = - bytecode[indexOfOpcodeAndBytes] - - let opcode = Opcode.parseByNumber(bytecodeBuf[pc]) - // If we are inside unreachable code, and see a JUMPDEST, the code is now reachable - if (insideUnreachableCode && seenJump && opcode === Opcode.JUMPDEST) { - insideUnreachableCode = false - } - if (!insideUnreachableCode) { - if ( - !TranspilerImpl.validOpcode( - opcode, - pc, - bytecodeBuf[pc], - lastOpcode, - errors - ) - ) { - log.debug( - `Deteced invalid opcode in reachable code: ${opcode}. at PC: 0x${pc.toString( - 16 - )} Skipping inclusion in transpilation output...` - ) - // skip, do not push anything to the transpilation output - lastOpcode = undefined - continue - } - lastOpcode = opcode - seenJump = seenJump || Opcode.JUMP_OP_CODES.includes(opcode) - insideUnreachableCode = Opcode.HALTING_OP_CODES.includes(opcode) - - if (opcode === Opcode.JUMPDEST) { - jumpdestIndexesBefore.push(pc) - } - if (!this.opcodeWhitelisted(opcode, pc, errors)) { - pc += opcode.programBytesConsumed - continue - } - } - if (insideUnreachableCode && !opcode) { - const unreachableCode: Buffer = bytecodeBuf.slice(pc, pc + 1) - opcode = { - name: `UNREACHABLE (${bufToHexString(unreachableCode)})`, - code: unreachableCode, - programBytesConsumed: 0, - } - } - - const tag = currentTaggedOpcodeAndBytes.tag - const opcodeAndBytes: EVMOpcodeAndBytes = { - opcode, - consumedBytes: !opcode.programBytesConsumed - ? undefined - : bytecodeBuf.slice(pc + 1, pc + 1 + opcode.programBytesConsumed), - tag, - } - - if (!!tag && tag.padPUSH) { - opcodeAndBytes.consumedBytes = Buffer.concat([ - Buffer.alloc(1), - opcodeAndBytes.consumedBytes, - ]) - // will break if we ever tagged a push32 because push33 doesn't exist. However we shouldn't be tagging any such val. - opcodeAndBytes.opcode = Opcode.parseByNumber( - Opcode.getCodeNumber(opcodeAndBytes.opcode) + 1 - ) - } - let transpiledBytecodeSubstitute: EVMBytecode - if ( - insideUnreachableCode || - !this.opcodeReplacer.shouldSubstituteOpcodeForFunction( - opcodeAndBytes.opcode - ) - ) { - transpiledBytecodeSubstitute = [opcodeAndBytes] - } else { - // record that we will need to add this opcode to the replacement table - substitutedOpcodes.add(opcodeAndBytes.opcode) - // jump to the footer where the logic of the replacement will be executed - transpiledBytecodeSubstitute = this.opcodeReplacer.getJUMPToOpcodeFunction( - opcodeAndBytes.opcode - ) - sizeIncreaseDueToOpcodeFunctionJUMPs += bytecodeToBuffer( - transpiledBytecodeSubstitute - ).byteLength - } - - transpiledBytecode.push(...transpiledBytecodeSubstitute) - pc += opcode.programBytesConsumed - } - - log.debug( - `Bytecode after substitution before JUMP table logic: \n${formatBytecode( - transpiledBytecode - )}` - ) - - const res: JumpReplacementResult = accountForJumps( - transpiledBytecode, - jumpdestIndexesBefore - ) - // TODO make sure accountForJumps STOPs after, should do - errors.push(...(res.errors || [])) - const bytecodeWithTranspiledJumpsPopulated = res.bytecode - - log.debug( - `Bytecode after substitution and JUMP table added: \n${formatBytecode( - bytecodeWithTranspiledJumpsPopulated - )}` - ) - - const inputSize: number = bytecodeToBuffer(bytecode).byteLength - const sizeDueToJUMPFixes: number = - bytecodeToBuffer(bytecodeWithTranspiledJumpsPopulated).byteLength - - sizeIncreaseDueToOpcodeFunctionJUMPs - statsLog.info( - `Codesize increase due to JUMP correction: ${inputSize} --> ${sizeDueToJUMPFixes} (factor of ${sizeDueToJUMPFixes / - inputSize}x vs input)` - ) - - const substitutedOpcodeFunctionFooter: EVMBytecode = this.opcodeReplacer.getOpcodeFunctionTable( - substitutedOpcodes - ) - log.debug( - `Inserting opcode substitution footer: ${formatBytecode( - substitutedOpcodeFunctionFooter - )}` - ) - transpiledBytecode = [ - ...bytecodeWithTranspiledJumpsPopulated, - ...substitutedOpcodeFunctionFooter, - ] - - transpiledBytecode = this.opcodeReplacer.populateOpcodeFunctionJUMPs( - transpiledBytecode - ) - - const finalSize: number = bytecodeToBuffer(transpiledBytecode).byteLength - const sizeDueToSubstitutions: number = - finalSize - sizeDueToJUMPFixes + sizeIncreaseDueToOpcodeFunctionJUMPs - statsLog.info( - `Codesize increase due to opcode substitutions: +${sizeDueToSubstitutions} bytes (factor of ${sizeDueToSubstitutions / - inputSize}x vs input). Of this, ${sizeIncreaseDueToOpcodeFunctionJUMPs} bytes were due to JUMPs, and ${sizeDueToSubstitutions - - sizeIncreaseDueToOpcodeFunctionJUMPs} bytes were due to the ${ - substitutedOpcodes.size - } opcode functions used.` - ) - statsLog.info( - `Total codesize increase: ${inputSize} --> ${finalSize} bytes (factor of ${finalSize / - inputSize}x)` - ) - - if (!!errors.length) { - return { - succeeded: false, - errors, - } - } - return { - succeeded: true, - bytecodeWithTags: transpiledBytecode, - } - } - - public transpileRawBytecode(bytecodeBuf: Buffer): TranspilationResult { - const rawBytecodeTyped: EVMBytecode = bufferToBytecode(bytecodeBuf) - const transpilationResult: TaggedTranspilationResult = this.transpileBytecodePreservingTags( - rawBytecodeTyped - ) - if (!transpilationResult.succeeded) { - return { - succeeded: false, - errors: transpilationResult.errors, - } - } - log.debug( - `successfully executed transpileRawBytecode, got result: \n${formatBytecode( - transpilationResult.bytecodeWithTags - )}` - ) - return { - succeeded: true, - bytecode: bytecodeToBuffer(transpilationResult.bytecodeWithTags), - } - } - - /** - * Returns whether or not the provided EVMOpcode is valid (not undefined). - * If it is not, it creates a new TranpilationError and appends it to the provided list. - * - * @param opcode The opcode in question. - * @param pc The current program counter value. - * @param code The code (decimal) of the opcode in question . - * @param lastOpcode The last Opcode seen before this one. - * @param errors The cumulative errors list. - * @returns True if valid, False otherwise. - */ - private static validOpcode( - opcode: EVMOpcode, - pc: number, - code: number, - lastOpcode: EVMOpcode, - errors: TranspilationError[] - ): boolean { - if (!opcode) { - let messageExtension: string = '' - if (!!lastOpcode && !!lastOpcode.programBytesConsumed) { - messageExtension = ` Was ${lastOpcode.name} at index ${pc - - lastOpcode.programBytesConsumed} provided exactly ${ - lastOpcode.programBytesConsumed - } bytes as expected?` - } - const message: string = `Cannot find opcode for: ${add0x( - code.toString(16) - )}.${messageExtension}` - log.debug(message) - errors.push( - TranspilerImpl.createError( - pc, - TranspilationErrors.UNSUPPORTED_OPCODE, - message - ) - ) - return false - } - return true - } - - /** - * Returns whether or not the provided EVMOpcode is whitelisted. - * If it is not, it creates a new TranpilationError and appends it to the provided list. - * - * @param opcode The opcode in question. - * @param pc The current program counter value. - * @param errors The cumulative errors list. - * @returns True if whitelisted, False otherwise. - */ - private opcodeWhitelisted( - opcode: EVMOpcode, - pc: number, - errors: TranspilationError[] - ): boolean { - if (!this.opcodeWhitelist.isOpcodeWhitelisted(opcode)) { - const message: string = `Opcode [${opcode.name}] is not on the whitelist.` - log.debug(message) - errors.push( - TranspilerImpl.createError( - pc, - TranspilationErrors.OPCODE_NOT_WHITELISTED, - message - ) - ) - return false - } - return true - } - - /** - * Util function to create TranspilationErrors. - * - * @param index The index of the byte in the input bytecode where the error originates. - * @param error The TranspilationErrors error type. - * @param message The error message. - * @returns The constructed TranspilationError - */ - private static createError( - index: number, - error: number, - message: string - ): TranspilationError { - return { - index, - error, - message, - } - } -} diff --git a/packages/rollup-dev-tools/src/tools/transpiler/util.ts b/packages/rollup-dev-tools/src/tools/transpiler/util.ts deleted file mode 100644 index 60b0e790cf384..0000000000000 --- a/packages/rollup-dev-tools/src/tools/transpiler/util.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { TranspilationError } from '../../types/transpiler' -import { - bufToHexString, - getLogger, - hexStrToBuf, - Logger, -} from '@eth-optimism/core-utils' - -export const BIG_ENOUGH_GAS_LIMIT: number = 100000000 - -const log: Logger = getLogger('transpiler-util') - -/** - * Util function to create TranspilationErrors. - * - * @param index The index of the byte in the input bytecode where the error originates. - * @param error The TranspilationError error type. - * @param message The error message. - * @returns The constructed TranspilationError - */ -export const createError = ( - index: number, - error: number, - message: string -): TranspilationError => { - return { - index, - error, - message, - } -} - -export const stripAuxData = ( - bytecode: Buffer, - buildJSON: any, - isDeployedBytecode: boolean -): Buffer => { - const auxDataObject = buildJSON.evm.legacyAssembly['.data'] - const auxData = auxDataObject['0']['.auxdata'] - let bytecodeWithoutAuxdata: Buffer - const auxdataObjectKeys = Object.keys(auxDataObject) - // deployed bytecode always has auxdata at the end, but constuction code may not. - if (auxdataObjectKeys.length <= 1 || isDeployedBytecode) { - log.debug(`Auxdata is at EOF, removing entirely from bytecode...`) - const split = bufToHexString(bytecode).split(auxData) - bytecodeWithoutAuxdata = hexStrToBuf(split[0]) - } else { - log.debug( - `Auxdata is not at EOF, replacing it with 0s to preserve remaining data...` - ) - const auxDataBuf: Buffer = hexStrToBuf(auxData) - const auxDataPosition = bytecode.indexOf(auxDataBuf) - log.debug( - `buf: ${bufToHexString( - auxDataBuf - )}, position: ${auxDataPosition}, length: ${auxDataBuf.byteLength}` - ) - bytecodeWithoutAuxdata = Buffer.from(bytecode) - bytecodeWithoutAuxdata.fill( - 0, - auxDataPosition, - auxDataPosition + auxDataBuf.byteLength - ) - } - - return bytecodeWithoutAuxdata -} diff --git a/packages/rollup-dev-tools/src/tools/vm/evm-introspection-util.ts b/packages/rollup-dev-tools/src/tools/vm/evm-introspection-util.ts deleted file mode 100644 index 6e64344aab730..0000000000000 --- a/packages/rollup-dev-tools/src/tools/vm/evm-introspection-util.ts +++ /dev/null @@ -1,494 +0,0 @@ -/* External Imports */ -import { - bufToHexString, - getLogger, - keccak256, - Logger, - add0x, - remove0x, - logError, - hexStrToBuf, - bufferUtils, -} from '@eth-optimism/core-utils' -import { Address, Opcode } from '@eth-optimism/rollup-core' - -import AsyncLock from 'async-lock' -import abi from 'ethereumjs-abi' - -import BN = require('bn.js') -import VM from 'ethereumjs-vm' -import { Transaction } from 'ethereumjs-tx' -import { ethers } from 'ethers' -import { promisify } from 'util' -import { EVMResult, ExecResult } from 'ethereumjs-vm/dist/evm/evm' -import { ERROR, VmError } from 'ethereumjs-vm/dist/exceptions' -import { - EvmError, - EvmErrors, - EvmIntrospectionUtil, - ExecutionResultComparison, - StepContext, - ExecutionComparison, - ExecutionResult, - CallContext, - InvalidCALLStackError, -} from '../../types/vm' - -const log: Logger = getLogger('evm-util') - -type StepCallback = (data, continueFn) => Promise -type StepContextCallback = (context: StepContext) => Promise - -const EMPTY_BUFFER: Buffer = Buffer.from('', 'hex') -const BIG_ENOUGH_GAS_LIMIT: any = new BN('ffffffff', 'hex') -const KEY = 'EvmIntrospectionUtilImpl_LOCK' -const DEFAULT_ACCOUNT_PK: string = - '0xe331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109' - -export class EvmIntrospectionUtilImpl implements EvmIntrospectionUtil { - private nonce: number - - private readonly vm: VM - private readonly lock: AsyncLock - private readonly wallet: ethers.Wallet - - public static async create( - accountPK: string = DEFAULT_ACCOUNT_PK - ): Promise { - const util: EvmIntrospectionUtilImpl = new EvmIntrospectionUtilImpl( - accountPK - ) - - await util.init() - return util - } - - private constructor(private readonly accountPK: string) { - this.vm = new VM() - this.lock = new AsyncLock() - this.wallet = new ethers.Wallet(add0x(accountPK)) - this.nonce = 0 - } - - private async init(): Promise { - // Give account 100 ETH - await promisify( - this.vm.stateManager.putAccount.bind(this.vm.stateManager) - )(Buffer.from(remove0x(this.wallet.address), 'hex'), { balance: 100e18 }) - } - - public async deployContract( - initcode: Buffer, - abiEncodedParameters?: Buffer - ): Promise { - log.debug( - `Deploy contract with bytecode ${bufToHexString( - initcode - )} and ABI encoded parameters ${ - !!abiEncodedParameters ? bufToHexString(abiEncodedParameters) : 'N/A' - }` - ) - const params: string = !!abiEncodedParameters - ? abiEncodedParameters.toString('hex') - : '' - const data: string = add0x(initcode.toString('hex') + params) - - const tx: Transaction = new Transaction({ - value: 0, - gasLimit: BIG_ENOUGH_GAS_LIMIT, - gasPrice: 1, - data, - nonce: this.nonce++, - }) - - tx.sign(Buffer.from(remove0x(this.wallet.privateKey), 'hex')) - const stepCallback: StepCallback = EvmIntrospectionUtilImpl.stepCallbackFactory() - const deployResult: EVMResult = await this.lock.acquire(KEY, async () => { - this.vm.on('step', stepCallback) - const res = this.vm.runTx({ tx }) - return res - }) - this.vm.removeListener('step', stepCallback) - - if (!!deployResult.execResult.exceptionError) { - const msg: string = `Error deploying contract [${initcode.toString( - 'hex' - )}] with params: [${params}]: ${ - deployResult.execResult.exceptionError.errorType - }` - log.info(msg) - return { - error: EvmIntrospectionUtilImpl.getEvmErrorFromVmError( - deployResult.execResult.exceptionError - ), - result: EMPTY_BUFFER, - } - } - - return { - result: deployResult.createdAddress, - } - } - - public async deployBytecodeToAddress( - deployedBytecode: Buffer, - address: Buffer - ): Promise { - const result: void = await this.lock.acquire(KEY, async () => { - return this.vm.stateManager.putContractCode( - address, - deployedBytecode, - () => { - return - } - ) - }) - return result - } - - public async getContractDeployedBytecode(address: Buffer): Promise { - return new Promise((resolve) => { - this.lock.acquire( - KEY, - async (done) => { - this.vm.stateManager.getContractCode(address, (err, res) => { - done(err, res) - }) - }, - (err, ret) => { - resolve(ret as Buffer) - } - ) - }) - } - - public async callContract( - address: Address, - method: string, - methodTypes: string[] = [], - abiEncodedParams: Buffer = EMPTY_BUFFER - ): Promise { - const data: Buffer = Buffer.concat([ - abi.methodID(method, methodTypes), - abiEncodedParams, - ]) - - const stepCallback: StepCallback = EvmIntrospectionUtilImpl.stepCallbackFactory() - const result: EVMResult = await this.lock.acquire(KEY, async () => { - this.vm.on('step', stepCallback) - const ret = this.vm.runCall({ - to: hexStrToBuf(address), - caller: hexStrToBuf(this.wallet.address), - origin: hexStrToBuf(this.wallet.address), - data, - }) - return ret - }) - this.vm.removeListener('step', stepCallback) - - if (result.execResult.exceptionError) { - const params: string = bufToHexString(abiEncodedParams) - const msg: string = `Error calling contract [${address}] method [${method}] with params: [${params}]: ${JSON.stringify( - result.execResult.exceptionError - )}` - log.info(msg) - return { - error: EvmIntrospectionUtilImpl.getEvmErrorFromVmError( - result.execResult.exceptionError - ), - result: EMPTY_BUFFER, - } - } - - return { - result: result.execResult.returnValue, - } - } - - public async getCallContext(bytecode: Buffer): Promise { - let contextBeforeCALL: StepContext - let hasCALLed: boolean - - const callback: StepCallback = EvmIntrospectionUtilImpl.stepCallbackFactory( - async (stepContext: StepContext) => { - if (stepContext.opcode.code.equals(Opcode.CALL.code) && !hasCALLed) { - contextBeforeCALL = stepContext - hasCALLed = true - } - } - ) - await this.runLocked(bytecode, callback) - - if (contextBeforeCALL.stackDepth < 7) { - throw new InvalidCALLStackError() - } - - const gas: Buffer = contextBeforeCALL.stack[0] - const addr: Address = bufferUtils.bufferToAddress( - contextBeforeCALL.stack[1] - ) - const value: Buffer = contextBeforeCALL.stack[2] - const argOffset = new BN(contextBeforeCALL.stack[3]).toNumber() - const argLength = new BN(contextBeforeCALL.stack[4]).toNumber() - const retOffset = new BN(contextBeforeCALL.stack[5]).toNumber() - const retLength = new BN(contextBeforeCALL.stack[6]).toNumber() - - let callData: Buffer = contextBeforeCALL.memory.slice( - argOffset, - argOffset + argLength - ) - callData = bufferUtils.padRight(callData, argLength) - - return { - input: { - gas, - addr, - value, - argOffset, - argLength, - retOffset, - retLength, - }, - callData, - stepContext: contextBeforeCALL, - } - } - - public async getExecutionResult(bytecode: Buffer): Promise { - const res: ExecResult = await this.runLocked(bytecode) - const error: EvmError = EvmIntrospectionUtilImpl.getEvmErrorFromVmError( - res.exceptionError - ) - - const toReturn: ExecutionResult = { - result: res.returnValue, - } - if (!!error) { - toReturn.error = error - } - return toReturn - } - public async getStepContextBeforeStep( - bytecode: Buffer, - bytecodeIndex: number - ): Promise { - let context: StepContext - let address: Address - const callback: StepCallback = EvmIntrospectionUtilImpl.stepCallbackFactory( - async (stepContext: StepContext) => { - if (!address) { - address = stepContext.address - } - - if ( - stepContext.address === address && - stepContext.pc === bytecodeIndex && - !context - ) { - context = stepContext - } - } - ) - - await this.runLocked(bytecode, callback) - - return context - } - - public async getExecutionResultComparison( - firstBytecode: Buffer, - secondBytecode: Buffer - ): Promise { - const [firstResult, secondResult]: [ - ExecutionResult, - ExecutionResult - ] = await Promise.all([ - this.getExecutionResult(firstBytecode), - this.getExecutionResult(secondBytecode), - ]) - - const resultsDiffer: boolean = !EvmIntrospectionUtilImpl.areExecutionResultsEqual( - firstResult, - secondResult - ) - return { - resultsDiffer, - firstResult, - secondResult, - } - } - - public async getExecutionComparisonBeforeStep( - firstBytecode: Buffer, - firstBytecodeIndex: number, - secondBytecode: Buffer, - secondBytecodeIndex: number - ): Promise { - const [firstContext, secondContext]: [ - StepContext, - StepContext - ] = await Promise.all([ - this.getStepContextBeforeStep(firstBytecode, firstBytecodeIndex), - this.getStepContextBeforeStep(secondBytecode, secondBytecodeIndex), - ]) - - return { - executionDiffers: !EvmIntrospectionUtilImpl.areStepContextsEqual( - firstContext, - secondContext - ), - firstContext, - secondContext, - } - } - - private async runLocked( - bytecode: Buffer, - stepCallback: StepCallback = EvmIntrospectionUtilImpl.stepCallbackFactory() - ): Promise { - const bytecodeHash: string = keccak256(bytecode.toString('hex')) - const hashBuffer: Buffer = Buffer.from(bytecodeHash, 'hex') - - return this.lock.acquire(KEY, async () => { - this.vm.on('step', stepCallback) - - try { - const res: ExecResult = await this.vm.runCode({ - code: bytecode, - gasLimit: BIG_ENOUGH_GAS_LIMIT, - address: hashBuffer, - }) - log.debug(`\nFinished executing ${bytecodeHash}\n`) - - return res - } catch (e) { - logError( - log, - `Error running bytecode ${add0x(bytecode.toString('hex'))}`, - e - ) - throw e - } finally { - // Always make sure to unsubscribe one-time step callback function - this.vm.removeListener('step', stepCallback) - } - }) - } - - private static parseStepContext(data: any): StepContext { - const stack: Buffer[] = data['stack'].map((x) => x.toBuffer()).reverse() - return { - address: bufToHexString(data['address']), - pc: data['pc'], - opcode: Opcode.parseByName(data['opcode']['name']), - stack, - stackDepth: stack.length, - memory: Buffer.from(data['memory']), - memoryWordCount: data['memoryWordCount'].toNumber(), - } - } - - private static stepCallbackFactory(fn?: StepContextCallback): StepCallback { - return async (data, continueFn) => { - try { - const stepContext: StepContext = EvmIntrospectionUtilImpl.parseStepContext( - data - ) - - if (!!fn) { - await fn(stepContext) - } - - log.debug( - `Step data: ${EvmIntrospectionUtilImpl.getStepContextString( - stepContext - )}` - ) - } finally { - continueFn() - } - } - } - - private static getEvmErrorFromVmError( - vmError: VmError - ): EvmError | undefined { - if (!vmError || !vmError.error) { - return undefined - } - switch (vmError.error) { - case ERROR.OUT_OF_GAS: - return EvmErrors.OUT_OF_GAS_ERROR - case ERROR.STACK_UNDERFLOW: - return EvmErrors.STACK_UNDERFLOW_ERROR - case ERROR.STACK_OVERFLOW: - return EvmErrors.STACK_OVERFLOW_ERROR - case ERROR.INVALID_JUMP: - return EvmErrors.INVALID_JUMP_ERROR - case ERROR.INVALID_OPCODE: - return EvmErrors.INVALID_OPCODE_ERROR - case ERROR.OUT_OF_RANGE: - return EvmErrors.OUT_OF_RANGE_ERROR - case ERROR.REVERT: - return EvmErrors.REVERT_ERROR - case ERROR.STATIC_STATE_CHANGE: - return EvmErrors.STATIC_STATE_CHANGE_ERROR - case ERROR.INTERNAL_ERROR: - return EvmErrors.INTERNAL_ERROR - case ERROR.CREATE_COLLISION: - return EvmErrors.CREATE_COLLISION_ERROR - case ERROR.STOP: - return EvmErrors.STOP_ERROR - case ERROR.REFUND_EXHAUSTED: - return EvmErrors.REFUND_EXHAUSTED_ERROR - default: - throw Error(`Unrecognized VmError: ${vmError.error}`) - } - } - - private static getStepContextString(stepContext: StepContext): string { - return `{pc: 0x${stepContext.pc.toString(16)}, opcode: ${ - stepContext.opcode.name - }, stackDepth: ${ - stepContext.stackDepth - }, stack: [${stepContext.stack - .map((x) => bufToHexString(x)) - .join(',')}], memoryWordCount: ${ - stepContext.memoryWordCount - }, memory: [${bufToHexString(stepContext.memory)}], address: ${ - stepContext.address - }` - } - - private static areExecutionResultsEqual( - first: ExecutionResult, - second: ExecutionResult - ): boolean { - return ( - (!first && !second) || - (!!first && - !!second && - first.result.equals(second.result) && - ((!first.error && !second.error) || - (!!first.error && !!second.error && first.error === second.error))) - ) - } - - private static areStepContextsEqual( - first: StepContext, - second: StepContext - ): boolean { - return ( - (!first && !second) || - (!!first && - !!second && - // first.pc === second.pc && -- This will probably not line up for different executions - first.opcode === second.opcode && - first.stackDepth === second.stackDepth && - first.memoryWordCount === second.memoryWordCount && - first.memory.equals(second.memory) && - first.stack.map((b) => b.toString('hex')).join() === - second.stack.map((b) => b.toString('hex')).join()) - ) - } -} diff --git a/packages/rollup-dev-tools/src/tools/vm/index.ts b/packages/rollup-dev-tools/src/tools/vm/index.ts deleted file mode 100644 index 78d3b4b7bdc25..0000000000000 --- a/packages/rollup-dev-tools/src/tools/vm/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './evm-introspection-util' diff --git a/packages/rollup-dev-tools/src/types/index.ts b/packages/rollup-dev-tools/src/types/index.ts deleted file mode 100644 index 1ec5f52fdca15..0000000000000 --- a/packages/rollup-dev-tools/src/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './transpiler' -export * from './vm' diff --git a/packages/rollup-dev-tools/src/types/transpiler/errors.ts b/packages/rollup-dev-tools/src/types/transpiler/errors.ts deleted file mode 100644 index bce3aef722a81..0000000000000 --- a/packages/rollup-dev-tools/src/types/transpiler/errors.ts +++ /dev/null @@ -1,50 +0,0 @@ -export class InvalidAddressError extends Error { - constructor(msg?: string) { - super( - msg || - 'An address was specified which is not a valid hex string of 20 bytes.' - ) - } -} - -export class InvalidBytesConsumedError extends Error { - constructor(msg?: string) { - super( - msg || - "The specified bytes consumed does not match the opcode's actual consumed bytes." - ) - } -} - -export class UnsupportedOpcodeError extends Error { - constructor(msg?: string) { - super(msg || 'Transpiler currently does not support the specified opcode.') - } -} - -export class InvalidSubstitutionError extends Error { - constructor(msg?: string) { - super( - msg || - 'The configured replacements for the transpiler have resulted in invalid bytecode.' - ) - } -} - -export class InvalidInitcodeError extends Error { - constructor(msg?: string) { - super(msg || 'The specified initcode is invalid.') - } -} - -export class TranspilationErrors { - public static readonly UNSUPPORTED_OPCODE: number = 0 - public static readonly OPCODE_NOT_WHITELISTED: number = 1 - public static readonly INVALID_BYTES_CONSUMED: number = 2 - public static readonly INVALID_SUBSTITUTION: number = 3 - public static readonly INVALID_INITCODE: number = 4 - public static readonly MISSING_CONSTANT_ERROR: number = 5 - public static readonly DETECTED_CONSTANT_OOB: number = 6 - public static readonly SUB_TRANSPILATION_ERROR: number = 7 - public static readonly MISSING_DEPLOYED_BYTECODE_ERROR: number = 8 -} diff --git a/packages/rollup-dev-tools/src/types/transpiler/index.ts b/packages/rollup-dev-tools/src/types/transpiler/index.ts deleted file mode 100644 index 724c6da2677e6..0000000000000 --- a/packages/rollup-dev-tools/src/types/transpiler/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './errors' -export * from './opcode-whitelist' -export * from './opcode-replacer' -export * from './transpiler' -export * from './types' diff --git a/packages/rollup-dev-tools/src/types/transpiler/opcode-replacer.ts b/packages/rollup-dev-tools/src/types/transpiler/opcode-replacer.ts deleted file mode 100644 index 17799ee023134..0000000000000 --- a/packages/rollup-dev-tools/src/types/transpiler/opcode-replacer.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* External Imports */ -import { - EVMOpcodeAndBytes, - EVMBytecode, - EVMOpcode, -} from '@eth-optimism/rollup-core' - -/** - * Interface defining the set of transpiled opcodes, and what bytecode to replace with. - */ -export interface OpcodeReplacer { - shouldSubstituteOpcodeForFunction(opcode: EVMOpcode): boolean - getJUMPToOpcodeFunction(opcode: EVMOpcode): EVMBytecode - getJUMPOnOpcodeFunctionReturn(opcode: EVMOpcode): EVMBytecode - getOpcodeFunctionTable(opcodes: Set): EVMBytecode - populateOpcodeFunctionJUMPs(taggedBytecode: EVMBytecode): EVMBytecode - getSubstituedFunctionFor(opcode: EVMOpcodeAndBytes): EVMBytecode -} diff --git a/packages/rollup-dev-tools/src/types/transpiler/opcode-whitelist.ts b/packages/rollup-dev-tools/src/types/transpiler/opcode-whitelist.ts deleted file mode 100644 index 5a187f4f52d85..0000000000000 --- a/packages/rollup-dev-tools/src/types/transpiler/opcode-whitelist.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* External Imports */ -import { EVMOpcode } from '@eth-optimism/rollup-core' - -/** - * Interface defining the available access operations for the OpCode Whitelist. - */ -export interface OpcodeWhitelist { - isOpcodeWhitelisted(opcode: EVMOpcode): boolean - - isOpcodeWhitelistedByName(opcodeName: string): boolean - - isOpcodeWhitelistedByCodeBuffer(opcode: Buffer): boolean - - isOpcodeWhitelistedByCodeValue(opcode: number): boolean -} diff --git a/packages/rollup-dev-tools/src/types/transpiler/transpiler.ts b/packages/rollup-dev-tools/src/types/transpiler/transpiler.ts deleted file mode 100644 index 9ca808e9b7742..0000000000000 --- a/packages/rollup-dev-tools/src/types/transpiler/transpiler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TranspilationResult } from './types' - -/** - * Interface defining the transpiler, which converts - * EVM bytecode into Optimistic Rollup compatible EVM bytecode. - */ -export interface Transpiler { - /** - * Function to transpile input bytecode & deployedBytecode according to configured Transpiler rules. - * Note: The resulting bytecode will work if used in CREATE or CREATE2 - * - * @param bytecode The bytecode (initcode + deployedBytecode) to transpile. - * @param deployedBytecode The deployedBytecode to transpile. - * @param originalDeployedBytecodeSize The size of the original initcode with auxdata if different than deployedBytecode length. - * @returns The TranspilationResult, containing the list of errors if there are any - * or the transpiled bytecode if successful. - */ - transpile( - bytecode: Buffer, - deployedBytecode: Buffer, - originalDeployedBytecodeSize?: number - ): TranspilationResult - - /** - * Function to transpile input rawBytecode according to configured Transpiler rules. - * Note: This function will work for raw bytecode but the resulting bytecode will fail if used in CREATE or CREATE2 - * - * @param rawBytecode The raw bytecode to transpile - * @returns The TranspilationResult, containing the list of errors if there are any - * or the transpiled bytecode if successful. - */ - transpileRawBytecode(rawBytecode: Buffer): TranspilationResult -} diff --git a/packages/rollup-dev-tools/src/types/transpiler/types.ts b/packages/rollup-dev-tools/src/types/transpiler/types.ts deleted file mode 100644 index fabfa35fd0ce4..0000000000000 --- a/packages/rollup-dev-tools/src/types/transpiler/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { EVMBytecode } from '@eth-optimism/rollup-core' - -export interface TranspilationError { - index: number - error: number - message: string -} - -export interface TranspilationResultBase { - succeeded: boolean -} - -export interface ErroredTranspilation extends TranspilationResultBase { - succeeded: false - errors: TranspilationError[] -} - -export interface SuccessfulTranspilation extends TranspilationResultBase { - succeeded: true - bytecode: Buffer -} - -export type TranspilationResult = ErroredTranspilation | SuccessfulTranspilation - -export interface JumpReplacementResult { - bytecode: EVMBytecode - errors?: TranspilationError[] -} - -export interface TaggedTranspilationResult { - succeeded: boolean - errors?: TranspilationError[] - bytecodeWithTags?: EVMBytecode -} - -export interface BinarySearchTreeNode { - value: { - jumpdestBefore: number - jumpdestAfter: number - } - left: BinarySearchTreeNode - right: BinarySearchTreeNode -} diff --git a/packages/rollup-dev-tools/src/types/vm/errors.ts b/packages/rollup-dev-tools/src/types/vm/errors.ts deleted file mode 100644 index 2302a65279637..0000000000000 --- a/packages/rollup-dev-tools/src/types/vm/errors.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class InvalidCALLStackError extends Error { - constructor() { - super('Stack before CALL is too small to correctly execute the CALL.') - } -} diff --git a/packages/rollup-dev-tools/src/types/vm/evm-introspection-util.ts b/packages/rollup-dev-tools/src/types/vm/evm-introspection-util.ts deleted file mode 100644 index 88254cdf6c3b8..0000000000000 --- a/packages/rollup-dev-tools/src/types/vm/evm-introspection-util.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* External Imports */ -import { Address } from '@eth-optimism/rollup-core' - -/* Internal Imports */ -import { - ExecutionComparison, - ExecutionResult, - ExecutionResultComparison, - StepContext, - CallContext, -} from './types' - -/** - * Interface defining an EVM utility allowing introspection of EVM - * state during bytecode processing. - */ -export interface EvmIntrospectionUtil { - /** - * Deploys a contract with the provided bytecode and returns its resulting address. - * - * @param initcode The initcode of the contract to deploy. - * @parameter abiEncodedParameters The ABI-encoded constructor args. - * @returns The ExecutionResult containing the deployed contract address or the deployment error. - */ - deployContract( - initcode: Buffer, - abiEncodedParameters?: Buffer - ): Promise - - /** - * Deploys a contract with the provided bytecode and to the specified address. - * - * @param bytecode The bytecode of the contract to deploy. - * @param address The address to deploy the bytecode to - */ - deployBytecodeToAddress( - deployedBytecode: Buffer, - address: Buffer - ): Promise - - /** - * Gets the deployed bytecode for a given contract address which has been deployed - * - * @param address The address of the contract to get bytecode of. - * @returns The deployed bytecode. - */ - getContractDeployedBytecode(address: Buffer): Promise - - /** - * Calls the provided method of the provided contract, passing in the - * provided parameters. - * - * @param address The address of the contract to call. - * @param method The method to call as a string. - * @param paramTypes The ordered array of parameter types - * @param abiEncodedParams The ABI-encoded parameters for the call. - * @returns The ExecutionResult of the call - */ - callContract( - address: Address, - method: string, - paramTypes?: string[], - abiEncodedParams?: Buffer - ): Promise - - /** - * Gets the call details and call context right before the execution of the - * provided bytecode at its first CALL. - * - * @param bytecode The bytecode leading to a CALL to execute. - * @returns The CallContext at the first CALL in the bytecode. - */ - getCallContext(bytecode: Buffer): Promise - - /** - * Gets the result from executing the provided bytecode. - * - * @param bytecode The bytecode to execute. - * @returns The ExecutionResult - */ - getExecutionResult(bytecode: Buffer): Promise - - /** - * Gets the execution context right before the execution of the - * provided bytecode at the provided index. - * - * @param bytecode The bytecode to execute. - * @param bytecodeIndex The index at which context will be captured. - * @returns The StepContext at the step in question. - */ - getStepContextBeforeStep( - bytecode: Buffer, - bytecodeIndex: number - ): Promise - - /** - * Gets the ExecutionResultComparison between two different sets of bytecode to run. - * - * @param firstBytecode The first EVM bytecode to run. - * @param secondBytecode The second EVM bytecode to run. - * @returns The ExecutionResultComparison comparing the execution results. - */ - getExecutionResultComparison( - firstBytecode: Buffer, - secondBytecode: Buffer - ): Promise - - /** - * Gets the ExecutionComparison, comparing the execution of the two provided bytecodes - * before the two provided indexes in the bytecode. - * - * @param firstBytecode The first EVM bytecode to run. - * @param firstBytecodeIndex The index in the second EVM bytecode at which execution will be compared. - * @param secondBytecode The second EVM bytecode to run. - * @param secondBytecodeIndex The index in the second EVM bytecode at which execution will be compared. - * @returns The ExecutionComparison. - */ - getExecutionComparisonBeforeStep( - firstBytecode: Buffer, - firstBytecodeIndex: number, - secondBytecode: Buffer, - secondBytecodeIndex: number - ): Promise -} diff --git a/packages/rollup-dev-tools/src/types/vm/index.ts b/packages/rollup-dev-tools/src/types/vm/index.ts deleted file mode 100644 index f95fcf761939b..0000000000000 --- a/packages/rollup-dev-tools/src/types/vm/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './evm-introspection-util' -export * from './types' -export * from './errors' diff --git a/packages/rollup-dev-tools/src/types/vm/types.ts b/packages/rollup-dev-tools/src/types/vm/types.ts deleted file mode 100644 index f184cc876b90c..0000000000000 --- a/packages/rollup-dev-tools/src/types/vm/types.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Address, EVMOpcode } from '@eth-optimism/rollup-core' - -export interface ExecutionResult { - result: Buffer - error?: EvmError -} - -export interface StepContext { - address: Address - pc: number - opcode: EVMOpcode - stack: Buffer[] - stackDepth: number - memory: Buffer - memoryWordCount: number -} - -export interface CallContext { - input: { - gas - addr: Address - value: Buffer - argOffset: number - argLength: number - retOffset: number - retLength: number - } - callData: Buffer - stepContext: StepContext -} - -export interface ExecutionResultComparison { - resultsDiffer: boolean - firstResult: ExecutionResult - secondResult: ExecutionResult -} - -export interface ExecutionComparison { - executionDiffers: boolean - firstContext: StepContext - secondContext: StepContext -} - -/* Right now duping ethereumjs-vm errors, but separated to isolate dependency */ -export class EvmErrors { - public static readonly OUT_OF_GAS_ERROR: EvmError = 'out of gas' - public static readonly STACK_UNDERFLOW_ERROR: EvmError = 'stack underflow' - public static readonly STACK_OVERFLOW_ERROR: EvmError = 'stack overflow' - public static readonly INVALID_JUMP_ERROR: EvmError = 'invalid JUMP' - public static readonly INVALID_OPCODE_ERROR: EvmError = 'invalid opcode' - public static readonly OUT_OF_RANGE_ERROR: EvmError = 'value out of range' - public static readonly REVERT_ERROR: EvmError = 'revert' - public static readonly STATIC_STATE_CHANGE_ERROR: EvmError = - 'static state change' - public static readonly INTERNAL_ERROR: EvmError = 'internal error' - public static readonly CREATE_COLLISION_ERROR: EvmError = 'create collision' - public static readonly STOP_ERROR: EvmError = 'stop' - public static readonly REFUND_EXHAUSTED_ERROR: EvmError = 'refund exhausted' -} - -export type EvmError = string diff --git a/packages/rollup-dev-tools/test/contracts/AssemblyReturnGetter.sol b/packages/rollup-dev-tools/test/contracts/AssemblyReturnGetter.sol deleted file mode 100644 index 0b527f0c326cb..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/AssemblyReturnGetter.sol +++ /dev/null @@ -1,27 +0,0 @@ -pragma solidity ^0.5.0; - -contract AssemblyReturnGetter { - bytes public someStorage; - - constructor(bytes memory _someParameter) public { - someStorage = _someParameter; - } - - function update(bytes memory _someParameter) public returns (bytes memory) { - bytes memory temp = someStorage; - someStorage = _someParameter; - return temp; - } - - // this getter uses inline assembly to return a NON-ABI-encoded byte array, - // so it's easier to work with on the recieving end (assembly). - function get() public view returns (bytes32) { - bytes32 valToReturn; - - for (uint i = 0; i < someStorage.length; i++) { - valToReturn |= bytes32(someStorage[i] & 0xFF) >> (i * 8); - } - - return valToReturn; - } -} diff --git a/packages/rollup-dev-tools/test/contracts/SimpleCallable.sol b/packages/rollup-dev-tools/test/contracts/SimpleCallable.sol deleted file mode 100644 index 6e5895695e8dd..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/SimpleCallable.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.5.0; - -contract SimpleCallable { - bytes public someStorage; - - constructor(bytes memory _someParameter) public { - someStorage = _someParameter; - } - - function update(bytes memory _someParameter) public returns (bytes memory) { - bytes memory temp = someStorage; - someStorage = _someParameter; - return temp; - } - - function get() public view returns (bytes memory) { - return someStorage; - } -} diff --git a/packages/rollup-dev-tools/test/contracts/constants-transpilation/AbiEncodedConstantInConstructor.sol b/packages/rollup-dev-tools/test/contracts/constants-transpilation/AbiEncodedConstantInConstructor.sol deleted file mode 100644 index 2150dfdb73771..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constants-transpilation/AbiEncodedConstantInConstructor.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity ^0.5.16; - -contract AbiEncodedConstantInConstructor { - bytes32 public hash; - - constructor() public { - hash = keccak256( - abi.encode( - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), - 1 - ) - ); - } - - function getConstant() external returns(bytes32) { - return hash; - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/constants-transpilation/ConstantGetter.sol b/packages/rollup-dev-tools/test/contracts/constants-transpilation/ConstantGetter.sol deleted file mode 100644 index 4b4347379edc6..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constants-transpilation/ConstantGetter.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstantGetter { - mapping(uint => bytes) public map; - - bytes32 public constant bytes32Constant = 0xABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34; - bytes public constant bytesMemoryConstantA = hex"AAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAA"; - bytes public constant bytesMemoryConstantB = "this should pass but the error message is much longer"; - - constructor(bytes memory _param) public { - map[420] = _param; - map[0] = bytesMemoryConstantA; - map[1] = bytesMemoryConstantB; - } - - function getBytes32Constant() public pure returns(bytes32) { - return(bytes32Constant); - } - - function getBytesMemoryConstantA() public pure returns(bytes memory) { - return(bytesMemoryConstantA); - } - - function getBytesMemoryConstantB() public pure returns(bytes memory) { - return(bytesMemoryConstantB); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstantsInConstructorAndBody.sol b/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstantsInConstructorAndBody.sol deleted file mode 100644 index 4f9e58268b289..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstantsInConstructorAndBody.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstantsInConstructorAndBody { - mapping(uint => uint) public map; - bytes public constant E = hex"BBBdeadbeefBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA"; - - constructor(bytes memory _a, bytes memory _b) public { - map[12] = E.length; - map[15] = _a.length + _b.length; - } - - function getConst() public returns(bytes memory) { - return(E); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorStoringMultipleParams.sol b/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorStoringMultipleParams.sol deleted file mode 100644 index 2291c8fb01c74..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorStoringMultipleParams.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -pragma experimental ABIEncoderV2; - - -contract ConstructorStoringMultipleParams { - bytes public constant E = hex"BBBdeadbeefBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA"; - mapping(uint => bytes) public map; - - constructor(bytes memory _param1, bytes memory _param2) public { - map[12] = E; - map[15] = _param2; - } - - function retrieveStoredVal() public returns(bytes memory) { - map[12] = E; - return(map[15]); - } -} diff --git a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorStoringParam.sol b/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorStoringParam.sol deleted file mode 100644 index ab0873e187a5f..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorStoringParam.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorStoringParam { - mapping(uint => bytes32) public map; - bytes public constant aConst = hex"BBBdeadbeefBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA"; - - constructor(bytes32 _param1) public { - map[15] = keccak256(aConst); - map[15] = _param1; - } - - function retrieveStoredVal() public returns(bytes32) { - return(map[15]); - } -} diff --git a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorUsingConstantWithMultipleParams.sol b/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorUsingConstantWithMultipleParams.sol deleted file mode 100644 index f68b7dc1cbf16..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorUsingConstantWithMultipleParams.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorUsingConstantWithMultipleParams { - mapping(uint => uint) public map; - bytes public constant aConst = hex"BBBdeadbeefBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA"; - - constructor(bytes memory _param1, bytes memory _param2) public { - map[15] = _param1.length + _param2.length + aConst.length; - } - - function incrementVal() public returns(uint) { - map[15] += 1; - return(map[15]); - } -} diff --git a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorWithMultipleParams.sol b/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorWithMultipleParams.sol deleted file mode 100644 index b4d3674615f0d..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorWithMultipleParams.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorWithMultipleParams { - mapping(uint => uint) public map; - - constructor(bytes memory _param1, bytes memory _param2) public { - map[15] = _param1.length + _param2.length; - } - - function incrementVal() public returns(uint) { - map[15] += 1; - return(map[15]); - } -} diff --git a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorWithSingleParam.sol b/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorWithSingleParam.sol deleted file mode 100644 index 0ddfa43699da1..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/ConstructorWithSingleParam.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorWithSingleParam { - mapping(uint => uint) public map; - - constructor(bytes memory _param) public { - map[15] = _param.length; - } - - function incrementVal() public returns(uint) { - map[15] += 1; - return(map[15]); - } -} diff --git a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/Counter.sol b/packages/rollup-dev-tools/test/contracts/constructor-transpilation/Counter.sol deleted file mode 100644 index 985cad1c1d88f..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/constructor-transpilation/Counter.sol +++ /dev/null @@ -1,21 +0,0 @@ -pragma solidity ^0.5.0; - -contract Counter { - event Increment (uint256 by); - - uint256 public value; - - constructor (uint256 initialValue) public { - value = initialValue; - } - - function increment (uint256 by) public { - // NOTE: You should use SafeMath in production code - value += by; - emit Increment(by); - } - - function getCount() public returns(uint256) { - return value; - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithBigParameter.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithBigParameter.sol deleted file mode 100644 index f1c68fe07bb54..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithBigParameter.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorWithBigParameter { - mapping(uint => uint) public map; - - constructor(bytes memory _a) public { - map[15] = _a.length; - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithSmallParameter.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithSmallParameter.sol deleted file mode 100644 index 4eb6421a2d3bf..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithSmallParameter.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorWithSmallParameter { - mapping(uint => uint) public map; - - constructor(uint _a) public { - map[15] = _a; - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithTwoBigParameters.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithTwoBigParameters.sol deleted file mode 100644 index 03513d6cf6d94..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithTwoBigParameters.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorWithTwoBigParameters { - mapping(uint => uint) public map; - - constructor(bytes memory _a, bytes memory _b) public { - map[15] = _a.length + _b.length; - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithTwoBigParametersAccessingConstantBefore.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithTwoBigParametersAccessingConstantBefore.sol deleted file mode 100644 index 802beda3e3da5..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorWithTwoBigParametersAccessingConstantBefore.sol +++ /dev/null @@ -1,10 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorWithTwoBigParametersAccessingConstantBefore { - mapping(uint => uint) public map; - bytes public constant E = hex"BBBdeadbeefBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA"; - - constructor(bytes memory _a, bytes memory _b) public { - map[12] = E.length; - map[15] = _a.length + _b.length; - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorlessWithConstants.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorlessWithConstants.sol deleted file mode 100644 index 0f1f7478a79d9..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/ConstructorlessWithConstants.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract ConstructorlessWithConstants { - bytes32 public constant A = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA; - bytes public constant B = hex"BBBdeadbeefBBB"; - // bytes public constant C = hex"CCCdeadbeefCCC"; - // bytes public constant D = hex"DDDdeadbeefDDD"; - mapping(bytes32 => bytes32) public map; - - // constructor() public { - // map[0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] = keccak256(D); - // } - - function getA() public pure returns(bytes32) { - return(A); - } - - function getB() public pure returns(bytes memory) { - return(B); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/StandaloneConstructorWithConstants.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/StandaloneConstructorWithConstants.sol deleted file mode 100644 index 403807a0ab467..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/StandaloneConstructorWithConstants.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract StandaloneConstructorWithConstants { - // bytes32 public constant A = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA; - bytes public constant E = hex"BBBdeadbeefBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA"; - bytes public constant F = hex"beedfeedbeedfeedbeedfeedbeedfeedbeedfeedbeedfeedbeedfeedbeedfeedCCCCCCCCCCCCCCCCCCCC"; - // bytes public constant C = hex"CCCdeadbeefCCC"; - // bytes public constant D = hex"DDDdeadbeefDDD"; - mapping(bytes32 => bytes32) public map; - - constructor() public { - map[keccak256(abi.encodePacked(E, F))] = bytes32(block.timestamp); - } - - function getVal() public pure returns(bytes32) { - return(0x567abcdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithConstants.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithConstants.sol deleted file mode 100644 index 8f3875aa7235d..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithConstants.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract SubcallConstructorWithConstants { - // bytes32 public constant A = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA; - bytes public constant B = hex"BBBdeadbeefBBB"; - // bytes public constant C = hex"CCCdeadbeefCCC"; - // bytes public constant D = hex"DDDdeadbeefDDD"; - mapping(bytes32 => bytes32) public map; - - constructor() public { - map[keccak256(B)] = getVal(); - } - - function getVal() public pure returns(bytes32) { - return(0x567abcdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithConstantsInBoth.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithConstantsInBoth.sol deleted file mode 100644 index aee77251cd05b..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithConstantsInBoth.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract SubcallConstructorWithConstantsInBoth { - // bytes32 public constant A = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA; - bytes public constant B = hex"BBBdeadbeefBBB"; - bytes public constant C = hex"CCCdeadbeefCCC"; - // bytes public constant D = hex"DDDdeadbeefDDD"; - mapping(bytes32 => bytes32) public map; - - constructor() public { - map[keccak256(B)] = keccak256(getVal()); - } - - function getVal() public pure returns(bytes memory) { - return(C); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithNestedSubcalls.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithNestedSubcalls.sol deleted file mode 100644 index fb81a793d55fb..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithNestedSubcalls.sol +++ /dev/null @@ -1,24 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract SubcallConstructorWithNestedSubcalls { - // bytes32 public constant A = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA; - bytes public constant B = hex"BBBdeadbeefBBB"; - bytes public constant C = hex"CCCdeadbeefCCC"; - // bytes public constant D = hex"DDDdeadbeefDDD"; - mapping(bytes32 => uint) public map; - - constructor() public { - map[keccak256(B)] = doSubcallUsingConstants(); - } - - function getB() public pure returns(bytes memory) { - return(B); - } - - function getC() public pure returns(bytes memory) { - return(C); - } - - function doSubcallUsingConstants() public pure returns(uint) { - return(getB().length + getC().length); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithoutConstants.sol b/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithoutConstants.sol deleted file mode 100644 index 451d9acc72be9..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/initcode-structure-check/SubcallConstructorWithoutConstants.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity >=0.4.22 <0.6.0; -contract SubcallConstructorWithoutConstants { - // bytes32 public constant A = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdeadbeefAAAAAAAAAAAAAAAAAAAAAAAAAA; - // bytes public constant B = hex"BBBdeadbeefBBB"; - // bytes public constant C = hex"CCCdeadbeefCCC"; - // bytes public constant D = hex"DDDdeadbeefDDD"; - mapping(bytes32 => bytes32) public map; - - constructor() public { - map[0x1234AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] = getVal(); - } - - function getVal() public pure returns(bytes32) { - return(0x567abcdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA); - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/jump-transpilation/SimpleJumper.sol b/packages/rollup-dev-tools/test/contracts/jump-transpilation/SimpleJumper.sol deleted file mode 100644 index 93a3c7e3a7dba..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/jump-transpilation/SimpleJumper.sol +++ /dev/null @@ -1,65 +0,0 @@ -pragma solidity ^0.5.0; - -contract SimpleJumper { - mapping(uint256 => uint256) public times; - function staticIfTrue() public returns(bool) { - if (true) { - times[block.timestamp] = 15; - return(true); - } else { - return(false); - } - } - - function staticIfFalseElse() public returns(bool) { - if (false) { - return(true); - } else { - times[block.timestamp] = 15; - return(false); - } - } - - function doForLoop() public returns(uint256) { - uint256 val = 29; - for (uint i=0; i<25; i++) { - times[block.timestamp] = 15; - val = val + 7*i; - } - return val; - } - - function doWhileLoop() public returns(uint256) { - uint256 val = 29; - while(val <= 200){ - times[block.timestamp] = 15; - val = val + 7; - } - return val; - } - - function doLoopingSubcalls() public returns(uint256) { - uint256 val = 29; - while(val <= 20000){ - times[block.timestamp] = 15; - val = val + doForLoop(); - } - return val; - } - - function doCrazyCombination(uint256 _input) public returns(uint256) { - times[block.timestamp] = 15; - if (_input == 0) { - times[block.timestamp] = 15; - return 0; - } else { - uint256 val = 29; - for (uint i=0; i<8; i++) { - times[block.timestamp] = doLoopingSubcalls(); - val = val + 7*i; - } - return val; - } - - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/contracts/library-transpilation/SafeMathUser.sol b/packages/rollup-dev-tools/test/contracts/library-transpilation/SafeMathUser.sol deleted file mode 100644 index f0ccf4ca97070..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/library-transpilation/SafeMathUser.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.5.0; - -import {SimpleSafeMath} from './SimpleSafeMath.sol'; - -contract SafeMathUser { - function use() public returns (uint) { - return SimpleSafeMath.addUint(2, 3); - } -} diff --git a/packages/rollup-dev-tools/test/contracts/library-transpilation/SimpleSafeMath.sol b/packages/rollup-dev-tools/test/contracts/library-transpilation/SimpleSafeMath.sol deleted file mode 100644 index 3ea2aa32da593..0000000000000 --- a/packages/rollup-dev-tools/test/contracts/library-transpilation/SimpleSafeMath.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity ^0.5.0; - -library SimpleSafeMath { - - function subUint(uint a, uint b) public returns(uint){ - - require(a >= b); // Make sure it doesn't return a negative value. - return a - b; - - } - function addUint(uint a , uint b) public pure returns(uint){ - - uint c = a + b; - - require(c >= a); // Makre sure the right computation was made - return c; - } -} \ No newline at end of file diff --git a/packages/rollup-dev-tools/test/helpers.ts b/packages/rollup-dev-tools/test/helpers.ts deleted file mode 100644 index a3c6079bf3cba..0000000000000 --- a/packages/rollup-dev-tools/test/helpers.ts +++ /dev/null @@ -1,551 +0,0 @@ -/* External Imports */ -import { - Address, - bufferToBytecode, - EVMBytecode, - EVMOpcode, - EVMOpcodeAndBytes, - formatBytecode, - Opcode, -} from '@eth-optimism/rollup-core' -import { - bufferUtils, - bufToHexString, - getLogger, - hexStrToBuf, - Logger, - remove0x, - ZERO_ADDRESS, -} from '@eth-optimism/core-utils' -import * as abi from 'ethereumjs-abi' -import { ethers } from 'ethers' - -/* Internal Imports */ -import { should } from './setup' -import { - EvmIntrospectionUtil, - ExecutionResultComparison, - TranspilationResult, - SuccessfulTranspilation, - ErroredTranspilation, - TranspilerImpl, - OpcodeReplacer, - OpcodeReplacerImpl, - stripAuxData, -} from '../src/' - -import { getPUSHBuffer, getPUSHIntegerOp } from '../src' - -const log: Logger = getLogger('helpers') - -export const emptyBuffer: Buffer = Buffer.from('', 'hex') -export const stateManagerAddress: Address = - '0x0000000000000000000000000000000000000000' -export const invalidOpcode: Buffer = Buffer.from('5d', 'hex') - -export const whitelistedOpcodes: EVMOpcode[] = [ - Opcode.PUSH1, - Opcode.PUSH2, - Opcode.PUSH4, - Opcode.PUSH29, - Opcode.PUSH32, - Opcode.MSTORE, - Opcode.CALLDATALOAD, - Opcode.SWAP1, - Opcode.SWAP2, - Opcode.SWAP3, - Opcode.DIV, - Opcode.DUP1, - Opcode.DUP2, - Opcode.DUP3, - Opcode.DUP4, - Opcode.EQ, - Opcode.JUMPI, - Opcode.JUMP, - Opcode.JUMPDEST, - Opcode.STOP, - Opcode.ADD, - Opcode.MUL, - Opcode.POP, - Opcode.MLOAD, - Opcode.SUB, - Opcode.RETURN, - Opcode.REVERT, - Opcode.INVALID, -] - -export const validBytecode: EVMBytecode = [ - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('00', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('01', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('02', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('03', 'hex') }, - { opcode: Opcode.ADD, consumedBytes: undefined }, - { opcode: Opcode.MUL, consumedBytes: undefined }, - { opcode: Opcode.EQ, consumedBytes: undefined }, -] - -export const singleNonWhitelisted: EVMBytecode = [ - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('00', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('01', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('02', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('03', 'hex') }, - - { opcode: Opcode.SSTORE, consumedBytes: undefined }, - - { opcode: Opcode.ADD, consumedBytes: undefined }, - { opcode: Opcode.MUL, consumedBytes: undefined }, - { opcode: Opcode.EQ, consumedBytes: undefined }, - { opcode: Opcode.RETURN, consumedBytes: undefined }, -] - -export const multipleNonWhitelisted: EVMBytecode = [ - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('00', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('01', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('02', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('03', 'hex') }, - - { opcode: Opcode.SSTORE, consumedBytes: undefined }, - - { opcode: Opcode.ADD, consumedBytes: undefined }, - { opcode: Opcode.MUL, consumedBytes: undefined }, - { opcode: Opcode.EQ, consumedBytes: undefined }, - - { opcode: Opcode.SLOAD, consumedBytes: undefined }, - - { opcode: Opcode.RETURN, consumedBytes: undefined }, -] - -export const invalidBytesConsumedBytecode: EVMBytecode = [ - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('00', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('01', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('02', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('03', 'hex') }, - { opcode: Opcode.ADD, consumedBytes: undefined }, - { opcode: Opcode.MUL, consumedBytes: undefined }, - { opcode: Opcode.EQ, consumedBytes: undefined }, - { opcode: Opcode.RETURN, consumedBytes: undefined }, - { opcode: Opcode.PUSH1, consumedBytes: undefined }, -] - -export const invalidBytesConsumedBytecodeNoReturn: EVMBytecode = [ - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('00', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('01', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('02', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('03', 'hex') }, - { opcode: Opcode.ADD, consumedBytes: undefined }, - { opcode: Opcode.MUL, consumedBytes: undefined }, - { opcode: Opcode.EQ, consumedBytes: undefined }, - { opcode: Opcode.POP, consumedBytes: undefined }, - { opcode: Opcode.PUSH1, consumedBytes: undefined }, -] - -export const multipleErrors: EVMBytecode = [ - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('00', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('01', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('02', 'hex') }, - { opcode: Opcode.PUSH1, consumedBytes: Buffer.from('03', 'hex') }, - { opcode: Opcode.ADD, consumedBytes: undefined }, - { opcode: Opcode.MUL, consumedBytes: undefined }, - { opcode: Opcode.EQ, consumedBytes: undefined }, - { opcode: Opcode.SLOAD, consumedBytes: undefined }, - { opcode: Opcode.PUSH1, consumedBytes: undefined }, -] - -export const assertExecutionEqual = async ( - evmUtil: EvmIntrospectionUtil, - firstBytecode: Buffer, - secondBytecode: Buffer -): Promise => { - const res: ExecutionResultComparison = await evmUtil.getExecutionResultComparison( - firstBytecode, - secondBytecode - ) - - const firstEvmBytecode: EVMBytecode = bufferToBytecode(firstBytecode) - const secondEvmBytecode: EVMBytecode = bufferToBytecode(secondBytecode) - should.exist( - res, - `Got undefined result checking for discrepancies between \n${formatBytecode( - firstEvmBytecode - )}\n\nand\n\n${formatBytecode(secondEvmBytecode)}.` - ) - - res.resultsDiffer.should.equal( - false, - `Execution result differs between\n${formatBytecode( - firstEvmBytecode - )}\n\nand\n\n${formatBytecode(secondEvmBytecode)}.\n${JSON.stringify(res)}` - ) -} - -export const returnNumberBytecode = (num: number = 1): EVMBytecode => { - return [ - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(32), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('60', 'hex'), - }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(num), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('60', 'hex'), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('80', 'hex'), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('40', 'hex'), - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - { - opcode: Opcode.RETURN, - consumedBytes: undefined, - }, - ] -} - -export const voidBytecode: EVMBytecode = [ - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('ff', 'hex'), - }, -] - -export const voidBytecodeWithPushPop: EVMBytecode = [ - ...voidBytecode, - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - ...voidBytecode, -] - -export const memoryAndStackBytecode: EVMBytecode = [ - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('ff', 'hex'), - }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(1), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('60', 'hex'), - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, -] - -export const memoryDiffersBytecode: EVMBytecode = [ - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('ff', 'hex'), - }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(2), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('60', 'hex'), - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, -] - -export const stackDiffersBytecode: EVMBytecode = [ - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('fe', 'hex'), - }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(1), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.from('60', 'hex'), - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, -] - -export const setupStackAndCALL = ( - gas: number, - callTarget: Address, - value: number, - argOffset: number, - argLength: number, - retOffset: number, - retLength: number -): EVMBytecode => { - return [ - getPUSHIntegerOp(retLength), // ret length - getPUSHIntegerOp(retOffset), // ret offset; must exceed 4 * 32, TODO: need to write new memory in a loop to fix this edge case? - getPUSHIntegerOp(argLength), // args length - getPUSHIntegerOp(argOffset), // args offset; must exceed 4 * 32, TODO: need to write new memory in a loop to fix this edge case? - getPUSHIntegerOp(value), // value - getPUSHBuffer(hexStrToBuf(callTarget)), // target address - getPUSHIntegerOp(gas), // gas - { - opcode: Opcode.CALL, - consumedBytes: undefined, - }, - ] -} - -export const getBytecodeCallingContractMethod = ( - address: Address, - methodName: string, - returnLength: number -): EVMBytecode => { - const methodData: Buffer = abi.methodID(methodName, []) - - const mStoreArgsOffset: Buffer = hexStrToBuf('0x60') - // last 4 bytes since method is only 4 bytes - const actualArgsOffset: Buffer = hexStrToBuf('0x7C') - const retOffset: Buffer = hexStrToBuf('0x80') - const retLengthBuffer: Buffer = bufferUtils.numberToBuffer(returnLength) - - return [ - // Store free memory index pointer - { - opcode: Opcode.PUSH1, - consumedBytes: hexStrToBuf('0xe0'), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: hexStrToBuf('0x40'), - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - // Store method ID - { - opcode: Opcode.PUSH4, - consumedBytes: methodData, - }, - { - opcode: Opcode.PUSH1, - consumedBytes: mStoreArgsOffset, - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - // CALL - // ret length - { - opcode: Opcode.PUSH32, - consumedBytes: retLengthBuffer, - }, - // ret offset - { - opcode: Opcode.PUSH1, - consumedBytes: retOffset, - }, - // args length - { - opcode: Opcode.PUSH1, - consumedBytes: hexStrToBuf('0x04'), - }, - // args offset - { - opcode: Opcode.PUSH1, - consumedBytes: actualArgsOffset, - }, - // value - { - opcode: Opcode.PUSH1, - consumedBytes: hexStrToBuf('0x00'), - }, - // address - { - opcode: Opcode.PUSH20, - consumedBytes: hexStrToBuf(address), - }, - // Gas - { - opcode: Opcode.PUSH32, - consumedBytes: Buffer.from('00'.repeat(16) + 'ff'.repeat(16), 'hex'), - }, - { - opcode: Opcode.CALL, - consumedBytes: undefined, - }, - // POP success - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - // RETURN - { - opcode: Opcode.PUSH32, - consumedBytes: retLengthBuffer, - }, - { - opcode: Opcode.PUSH1, - consumedBytes: retOffset, - }, - { - opcode: Opcode.RETURN, - consumedBytes: undefined, - }, - ] -} - -export const setMemory = (toSet: Buffer): EVMBytecode => { - const op: EVMBytecode = [] - const numWords = Math.ceil(toSet.byteLength / 32) - for (let i = 0; i < numWords; i++) { - op.push( - getPUSHBuffer(toSet.slice(i * 32, (i + 1) * 32)), - getPUSHIntegerOp(i * 32), - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - } - ) - } - return op -} - -export const transpileAndDeployInitcode = async ( - contractBuildJSON: any, - constructorParams: any[], - constructorParamsEncoding: string[], - transpiler: TranspilerImpl, - evmUtil: EvmIntrospectionUtil -): Promise => { - const abiCoder = new ethers.utils.AbiCoder() - const originalDeployedBytecodeLength: number = hexStrToBuf( - contractBuildJSON.evm.deployedBytecode.object - ).length - - const bytecodeStripped: Buffer = stripAuxData( - hexStrToBuf(contractBuildJSON.bytecode), - contractBuildJSON, - false - ) - const deployedBytecodeStripped: Buffer = stripAuxData( - hexStrToBuf(contractBuildJSON.evm.deployedBytecode.object), - contractBuildJSON, - true - ) - - const initcodeTranspilationResult: TranspilationResult = transpiler.transpile( - bytecodeStripped, - deployedBytecodeStripped, - originalDeployedBytecodeLength - ) - if (!initcodeTranspilationResult.succeeded) { - throw new Error( - `transpilation didn't work. Errors: ${JSON.stringify( - (initcodeTranspilationResult as ErroredTranspilation).errors - )}` - ) - } - const transpiledInitcode: Buffer = (initcodeTranspilationResult as SuccessfulTranspilation) - .bytecode - const encodedConstructorParams: Buffer = Buffer.from( - remove0x(abiCoder.encode(constructorParamsEncoding, constructorParams)), - 'hex' - ) - log.debug( - `deploying contract with bytecode: ${transpiledInitcode.toString('hex')}` - ) - - const deployedViaInitcode = await evmUtil.deployContract( - transpiledInitcode, - encodedConstructorParams - ) - const deployedViaInitcodeAddress = deployedViaInitcode.result - return deployedViaInitcodeAddress -} - -const defaultReplacer = new OpcodeReplacerImpl(ZERO_ADDRESS) -export const mockSSTOREReplacer: OpcodeReplacer = { - getJUMPToOpcodeFunction: defaultReplacer.getJUMPToOpcodeFunction, - getJUMPOnOpcodeFunctionReturn: defaultReplacer.getJUMPOnOpcodeFunctionReturn, - getOpcodeFunctionTable: defaultReplacer.getOpcodeFunctionTable, - populateOpcodeFunctionJUMPs: defaultReplacer.populateOpcodeFunctionJUMPs, - shouldSubstituteOpcodeForFunction(op: EVMOpcode): boolean { - return op === Opcode.SSTORE - }, - getSubstituedFunctionFor(opcodeAndBytes: EVMOpcodeAndBytes): EVMBytecode { - if (opcodeAndBytes.opcode === Opcode.SSTORE) { - return [ - // Do random PUSH POPs to increase codesize - getPUSHIntegerOp(1), - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - getPUSHIntegerOp(2), - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - getPUSHIntegerOp(3), - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - // get return dest to third in stack so original SSTORE args are first two stack vals - // expected stack: [pc to return to after replacement, key, val] - { - opcode: Opcode.SWAP2, - consumedBytes: undefined, - }, - // expected stack: [val, key, return PC] - { - opcode: Opcode.SWAP1, - consumedBytes: undefined, - }, - // expected stack: [key, val, return PC] - // now do the SSTORE - { - opcode: Opcode.SSTORE, - consumedBytes: undefined, - }, - ] - } else { - return [opcodeAndBytes] - } - }, -} diff --git a/packages/rollup-dev-tools/test/setup.ts b/packages/rollup-dev-tools/test/setup.ts deleted file mode 100644 index 5e3ce49e37fff..0000000000000 --- a/packages/rollup-dev-tools/test/setup.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* External Imports */ -import fs = require('fs') -import path = require('path') -import chai = require('chai') -import chaiAsPromised = require('chai-as-promised') - -/* Internal Imports */ -import { rootPath } from '../index' - -chai.use(chaiAsPromised) -const should = chai.should() - -export { should } diff --git a/packages/rollup-dev-tools/test/transpiler/abi-encoded-constants-transpilation.spec.ts b/packages/rollup-dev-tools/test/transpiler/abi-encoded-constants-transpilation.spec.ts deleted file mode 100644 index 7dc9ced77bce9..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/abi-encoded-constants-transpilation.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { bufToHexString, getLogger } from '@eth-optimism/core-utils' -import { - formatBytecode, - Opcode, - bufferToBytecode, -} from '@eth-optimism/rollup-core' -import * as AbiEncodedConstantInConstructor from '../contracts/build/AbiEncodedConstantInConstructor.json' - -/* Internal Imports */ -import { - EvmIntrospectionUtil, - ExecutionResult, - EvmIntrospectionUtilImpl, -} from '../../src' - -import { TranspilerImpl, OpcodeWhitelistImpl } from '../../src/tools/transpiler' -import { transpileAndDeployInitcode, mockSSTOREReplacer } from '../helpers' - -const log = getLogger(`test-constructor-params-new`) - -const getGetterReturnedVal = async ( - deployedAddress: Buffer, - methodId: string, - evmUtil: EvmIntrospectionUtil -): Promise => { - const callRes: ExecutionResult = await evmUtil.callContract( - bufToHexString(deployedAddress), - methodId - ) - if (!!callRes.error) { - throw new Error( - `call to ${methodId} failed with evmUtil Error: ${callRes.error}` - ) - } - return callRes.result -} - -describe('Solitity contracts should have hardcoded values correctly accessible in transpiled initcode', () => { - let evmUtil: EvmIntrospectionUtil - - const opcodeWhitelist = new OpcodeWhitelistImpl(Opcode.ALL_OP_CODES) - const transpiler = new TranspilerImpl(opcodeWhitelist, mockSSTOREReplacer) - let deployedGetterAddress: Buffer - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - log.debug( - `transpiling and deploying initcode which should store hash in constructor` - ) - deployedGetterAddress = await transpileAndDeployInitcode( - AbiEncodedConstantInConstructor, - [], - [], - transpiler, - evmUtil - ) - const code: Buffer = await evmUtil.getContractDeployedBytecode( - deployedGetterAddress - ) - log.debug( - `Initcode transpiled and deployed. The code is:\n${formatBytecode( - bufferToBytecode(code) - )}` - ) - }) - - it(`The hash of an abi.encode(hardcoded) should be correct and retrievable if stored during constructor()`, async () => { - const expectedStoredVal = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( - ['bytes32', 'uint256'], - [ - ethers.utils.keccak256( - Buffer.from( - ethers.utils.toUtf8Bytes( - 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' - ) - ) - ), - 1, - ] - ) - ) - const res = await getGetterReturnedVal( - deployedGetterAddress, - 'getConstant', - evmUtil - ) - bufToHexString(res).should.eq(expectedStoredVal) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/constants-transpilation.spec.ts b/packages/rollup-dev-tools/test/transpiler/constants-transpilation.spec.ts deleted file mode 100644 index 8b157d52a1796..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/constants-transpilation.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - getLogger, - hexStrToBuf, - deploy, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - EVMOpcode, - formatBytecode, - Opcode, - EVMOpcodeAndBytes, - bufferToBytecode, -} from '@eth-optimism/rollup-core' -import * as ethereumjsAbi from 'ethereumjs-abi' -import * as ConstantGetter from '../contracts/build/ConstantGetter.json' - -/* Internal Imports */ -import { - EvmIntrospectionUtil, - ExecutionResult, - StepContext, - CallContext, - EvmIntrospectionUtilImpl, - getPUSHBuffer, - getPUSHIntegerOp, -} from '../../src' -import { - ErroredTranspilation, - OpcodeReplacer, - OpcodeWhitelist, - TranspilationErrors, - TranspilationResult, - Transpiler, - SuccessfulTranspilation, -} from '../../src/types/transpiler' -import { - TranspilerImpl, - OpcodeReplacerImpl, - OpcodeWhitelistImpl, -} from '../../src/tools/transpiler' -import { transpileAndDeployInitcode, mockSSTOREReplacer } from '../helpers' - -const log = getLogger(`test-constructor-params`) -const abi = new ethers.utils.AbiCoder() - -const getGetterReturnedVal = async ( - deployedAddress: Buffer, - methodId: string, - evmUtil: EvmIntrospectionUtil -): Promise => { - const callRes: ExecutionResult = await evmUtil.callContract( - bufToHexString(deployedAddress), - methodId - ) - if (!!callRes.error) { - throw new Error( - `call to retrieveStoredVal() failed with evmUtil Error: ${callRes.error}` - ) - } - return callRes.result -} - -describe('Solitity contracts should have constants correctly accessible when using transpiled initcode', () => { - let evmUtil: EvmIntrospectionUtil - - const opcodeWhitelist = new OpcodeWhitelistImpl(Opcode.ALL_OP_CODES) - const transpiler = new TranspilerImpl(opcodeWhitelist, mockSSTOREReplacer) - let deployedGetterAddress: Buffer - const randomUncheckedParam = ['0x1234123412341234'] - const randomUncheckedParamEncoding = ['bytes'] - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - deployedGetterAddress = await transpileAndDeployInitcode( - ConstantGetter, - randomUncheckedParam, - randomUncheckedParamEncoding, - transpiler, - evmUtil - ) - }) - - const bytes32Const: Buffer = hexStrToBuf( - '0xABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34ABCDEF34' - ) - it('should work for a bytes32 constant', async () => { - const code: Buffer = await evmUtil.getContractDeployedBytecode( - deployedGetterAddress - ) - log.debug(`deployed code is: \n${formatBytecode(bufferToBytecode(code))}`) - const retrievedBytes32Val: Buffer = await getGetterReturnedVal( - deployedGetterAddress, - 'getBytes32Constant', - evmUtil - ) - retrievedBytes32Val.should.deep.equal(bytes32Const) - }) - - const bytesMemoryConstA: Buffer = hexStrToBuf( - '0xAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAAAAAdeadbeefAAA' - ) - it('should work for the first bytes memory constant', async () => { - const retrievedBytesMemoryAVal: Buffer = await getGetterReturnedVal( - deployedGetterAddress, - 'getBytesMemoryConstantA', - evmUtil - ) - const encodedBytesMemoryConstA: Buffer = hexStrToBuf( - abi.encode(['bytes'], [bufToHexString(bytesMemoryConstA)]) - ) - retrievedBytesMemoryAVal.should.deep.equal(encodedBytesMemoryConstA) - }) - - const bytesMemoryConstB: Buffer = Buffer.from( - `this should pass but the error message is much longer` - ) - it('should work for the second bytes memory constant', async () => { - const retrievedBytesMemoryBVal: Buffer = await getGetterReturnedVal( - deployedGetterAddress, - 'getBytesMemoryConstantB', - evmUtil - ) - const encodedbytesMemoryConstB: Buffer = hexStrToBuf( - abi.encode(['bytes'], [bufToHexString(bytesMemoryConstB)]) - ) - retrievedBytesMemoryBVal.should.deep.equal(encodedbytesMemoryConstB) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/constructor-transpilation.spec.ts b/packages/rollup-dev-tools/test/transpiler/constructor-transpilation.spec.ts deleted file mode 100644 index 0047e27b1828e..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/constructor-transpilation.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - getLogger, - hexStrToBuf, - bufferUtils, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - EVMOpcode, - formatBytecode, - Opcode, - EVMOpcodeAndBytes, - bufferToBytecode, -} from '@eth-optimism/rollup-core' -import * as ethereumjsAbi from 'ethereumjs-abi' -import * as ConstructorWithSingleParam from '../contracts/build/ConstructorWithSingleParam.json' -import * as ConstructorWithMultipleParams from '../contracts/build/ConstructorWithMultipleParams.json' -import * as ConstructorUsingConstantWithMultipleParams from '../contracts/build/ConstructorUsingConstantWithMultipleParams.json' -import * as ConstructorStoringParam from '../contracts/build/ConstructorStoringParam.json' -import * as ConstructorStoringMultipleParams from '../contracts/build/ConstructorStoringMultipleParams.json' -import * as Counter from '../contracts/build/Counter.json' -import * as SimpleSafeMath from '../contracts/build/SimpleSafeMath.json' - -/* Internal Imports */ -import { - EvmIntrospectionUtil, - ExecutionResult, - StepContext, - CallContext, - EvmIntrospectionUtilImpl, - getPUSHBuffer, - getPUSHIntegerOp, - stripAuxData, -} from '../../src' -import { - ErroredTranspilation, - OpcodeReplacer, - OpcodeWhitelist, - TranspilationErrors, - TranspilationResult, - Transpiler, - SuccessfulTranspilation, -} from '../../src/types/transpiler' -import { - TranspilerImpl, - OpcodeReplacerImpl, - OpcodeWhitelistImpl, -} from '../../src/tools/transpiler' -import { transpileAndDeployInitcode, mockSSTOREReplacer } from '../helpers' - -const abi = new ethers.utils.AbiCoder() -const log = getLogger(`constructor-transpilation`) - -describe('Solitity contracts with constructors that take inputs should be correctly deployed', () => { - let evmUtil: EvmIntrospectionUtil - const opcodeWhitelist = new OpcodeWhitelistImpl(Opcode.ALL_OP_CODES) - const transpiler = new TranspilerImpl(opcodeWhitelist, mockSSTOREReplacer) - - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - }) - - it('should work for a contract whose constructor accepts a single [bytes memory] param', async () => { - const constructorParams = ['0x1234123412341234'] - const constructorParamTypes = ['bytes'] - await assertTranspiledInitcodeDeploysManuallyTranspiledRawDeployedBytecode( - ConstructorWithSingleParam, - constructorParams, - constructorParamTypes, - transpiler, - evmUtil - ) - }) - it('should work for a contract whose constructor accepts two [bytes memory] params', async () => { - const constructorParams = ['0x1234123412341234', '0xadfadfadfadf'] - const constructorParamTypes = ['bytes', 'bytes'] - await assertTranspiledInitcodeDeploysManuallyTranspiledRawDeployedBytecode( - ConstructorWithMultipleParams, - constructorParams, - constructorParamTypes, - transpiler, - evmUtil - ) - }) - it('should work for a library construction', async () => { - const constructorParams = [] - const constructorParamTypes = [] - const resultsToCompare = await getManuallyTranspiledAndInitcodeTranspiledDeployedBytecode( - SimpleSafeMath, - constructorParams, - constructorParamTypes, - transpiler, - evmUtil - ) - const deployedViaInitcode: Buffer = - resultsToCompare.deployedViaTranspiledInitcode - const manuallyTranspiled: Buffer = - resultsToCompare.manuallyTranspiledDeployedBytecode - // deployed libraries should have their deployed address subbed in as the first thing being pushed to the stack. - // copy it over from the deployed version before checking equality - deployedViaInitcode.copy(manuallyTranspiled, 1, 1, 21) - - manuallyTranspiled.should.deep.equal(deployedViaInitcode) - }) - it(`should work for waffle's counter example`, async () => { - const constructorParams = [12345] - const constructorParamTypes = ['uint256'] - const deployedAddress: Buffer = await transpileAndDeployInitcode( - Counter, - constructorParams, - constructorParamTypes, - transpiler, - evmUtil - ) - const callRes: ExecutionResult = await evmUtil.callContract( - bufToHexString(deployedAddress), - 'getCount' - ) - if (!!callRes.error) { - throw new Error( - `call to getCount() failed with evmUtil Error: ${callRes.error}` - ) - } - const retrievedVal: Buffer = callRes.result - log.debug(`retrieved ${bufToHexString(retrievedVal)}`) - log.debug(`should be ${constructorParams[0]}`) - retrievedVal.should.deep.equal( - bufferUtils.padLeft( - bufferUtils.numberToBuffer(constructorParams[0]), - retrievedVal.byteLength - ) - ) - }) - // TODO: FIX THIS TEST. - // The reason it breaks is because accessing constants causes the `padPUSH: boolean` flag to be triggered within the deployed bytecode during transpile(). - // This means that the check here does not pass, because padding the PUSHes makes the two results not equal. - // I've manually looked at the outputs and confirmed it's still doing what we want--just not sure how to test that yet. - it.skip('should work for a contract whose constructor accepts two [bytes memory] params and accesses a constant', async () => { - const constructorParams = ['0x1234123412341234', '0xadfadfadfadf'] - const constructorParamTypes = ['bytes', 'bytes'] - await assertTranspiledInitcodeDeploysManuallyTranspiledRawDeployedBytecode( - ConstructorUsingConstantWithMultipleParams, - constructorParams, - constructorParamTypes, - transpiler, - evmUtil - ) - }) - it('a contract who stores a constructor param should be able to successfully retrieve it', async () => { - const valToStore: Buffer = hexStrToBuf( - '0x1234123412341234123412341234123412341234123412341234123412341234' - ) - const constructorParams = [bufToHexString(valToStore)] - const constructorParamTypes = ['bytes32'] - const deployedAddress: Buffer = await transpileAndDeployInitcode( - ConstructorStoringParam, - constructorParams, - constructorParamTypes, - transpiler, - evmUtil - ) - const callRes: ExecutionResult = await evmUtil.callContract( - bufToHexString(deployedAddress), - 'retrieveStoredVal' - ) - if (!!callRes.error) { - throw new Error( - `call to retrieveStoredVal() failed with evmUtil Error: ${callRes.error}` - ) - } - const retrievedVal: Buffer = callRes.result - retrievedVal.should.deep.equal(valToStore) - }) - it('a contract which stores multiple bytes memory params in the constructor should be retrievable', async () => { - const constructorParams = [ - '0x1234123412341234123412341234123412341234123412341234123412341234', - '0x34563456345631234456345634563456345634563456345634563456345634563456', - ] - const constructorParamTypes = ['bytes', 'bytes'] - const deployedAddress: Buffer = await transpileAndDeployInitcode( - ConstructorStoringMultipleParams, - constructorParams, - constructorParamTypes, - transpiler, - evmUtil - ) - const callRes: ExecutionResult = await evmUtil.callContract( - bufToHexString(deployedAddress), - 'retrieveStoredVal' - ) - if (!!callRes.error) { - throw new Error( - `call to retrieveStoredVal() failed with evmUtil Error: ${callRes.error}` - ) - } - const retrievedVal: Buffer = callRes.result - - const expectedResult = abi.encode(['bytes'], [constructorParams[1]]) - retrievedVal.should.deep.equal(hexStrToBuf(expectedResult)) - }) -}) - -const assertTranspiledInitcodeDeploysManuallyTranspiledRawDeployedBytecode = async ( - contractBuildJSON: any, - constructorParams: any[], - constructorParamsEncoding: string[], - transpiler: TranspilerImpl, - evmUtil: EvmIntrospectionUtil -): Promise => { - const resultsToCompare = await getManuallyTranspiledAndInitcodeTranspiledDeployedBytecode( - contractBuildJSON, - constructorParams, - constructorParamsEncoding, - transpiler, - evmUtil - ) - const successfullyDeployedBytecode = - resultsToCompare.deployedViaTranspiledInitcode - const transpiledDeployedBytecode = - resultsToCompare.manuallyTranspiledDeployedBytecode - - successfullyDeployedBytecode.should.deep.equal(transpiledDeployedBytecode) -} - -const getManuallyTranspiledAndInitcodeTranspiledDeployedBytecode = async ( - contractBuildJSON: any, - constructorParams: any[], - constructorParamsEncoding: string[], - transpiler: TranspilerImpl, - evmUtil: EvmIntrospectionUtil -): Promise<{ - deployedViaTranspiledInitcode: Buffer - manuallyTranspiledDeployedBytecode: Buffer -}> => { - // ****** - // TRANSPILE AND DEPLOY INITCODE via transpiler.transpile() - // ****** - const deployedViaInitcodeAddress = await transpileAndDeployInitcode( - contractBuildJSON, - constructorParams, - constructorParamsEncoding, - transpiler, - evmUtil - ) - - const successfullyDeployedBytecode = await evmUtil.getContractDeployedBytecode( - deployedViaInitcodeAddress - ) - // ****** - // TRANSPILE DEPLOYED INITCODE via transpiler.transpileRawBytecode() - // ****** - const deployedBytecode: Buffer = hexStrToBuf( - contractBuildJSON.evm.deployedBytecode.object - ) - - // pad because this is currently done by the transpiler - const deployedBytecodeTranspilationResult: TranspilationResult = transpiler.transpileRawBytecode( - stripAuxData(deployedBytecode, contractBuildJSON, true) - ) - if (!deployedBytecodeTranspilationResult.succeeded) { - throw new Error( - `transpilation didn't work. Errors: ${JSON.stringify( - (deployedBytecodeTranspilationResult as ErroredTranspilation).errors - )}` - ) - } - const transpiledDeployedBytecode: Buffer = (deployedBytecodeTranspilationResult as SuccessfulTranspilation) - .bytecode - - log.debug( - `succesfully deplpoyed: ${formatBytecode( - bufferToBytecode(successfullyDeployedBytecode) - )}` - ) - log.debug( - `transpiled deployed: ${formatBytecode( - bufferToBytecode(transpiledDeployedBytecode) - )}` - ) - - return { - deployedViaTranspiledInitcode: successfullyDeployedBytecode, - manuallyTranspiledDeployedBytecode: transpiledDeployedBytecode, - } -} diff --git a/packages/rollup-dev-tools/test/transpiler/contract-creation-opcodes.spec.ts b/packages/rollup-dev-tools/test/transpiler/contract-creation-opcodes.spec.ts deleted file mode 100644 index 4a34490bcb5c1..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/contract-creation-opcodes.spec.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - getLogger, - hexStrToBuf, - bufferUtils, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - Opcode, -} from '@eth-optimism/rollup-core' -import * as abiForMethod from 'ethereumjs-abi' - -/* Internal Imports */ -import { - EvmIntrospectionUtil, - ExecutionResult, - CallContext, - EvmIntrospectionUtilImpl, - getCREATESubstitute, - getCREATE2Substitute, - getPUSHBuffer, - getPUSHIntegerOp, -} from '../../src' -import { setMemory } from '../helpers' - -const log = getLogger(`test-static-memory-opcodes`) - -const abi = new ethers.utils.AbiCoder() - -/* Contracts */ -import * as AssemblyReturnGetter from '../contracts/build/AssemblyReturnGetter.json' - -const valToReturn = - '0xbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeed' -const contractDeployParams: Buffer = Buffer.from( - remove0x(abi.encode(['bytes'], [valToReturn])), - 'hex' -) - -describe('Contract Creation Opcode Replacements', () => { - let evmUtil: EvmIntrospectionUtil - const getMethodName: string = 'get' - - const deployGetterContract = async ( - util: EvmIntrospectionUtil - ): Promise
=> { - const contractBytecode: Buffer = Buffer.from( - AssemblyReturnGetter.bytecode, - 'hex' - ) - const result: ExecutionResult = await util.deployContract( - contractBytecode, - contractDeployParams - ) - return bufToHexString(result.result) - } - - let getterAddress: Address - let mockCREATEReplacement: EVMBytecode - let mockCREATE2Replacement: EVMBytecode - const PCtoReturnTo: Buffer = hexStrToBuf('0x696969') - const mockMemory: Buffer = Buffer.alloc(32 * 10).fill(28) - const initcodeOffset: number = 1 + 32 * 2 // must exceed 32 * 2 to do CREATE2 word prepending (methodId and salt) - const initcodeLength: number = 2 - const salt: Buffer = hexStrToBuf( - '0xaaaaacbdefacbdefaaaaacbdefacbdefaaaaacbdefacbdefaaaaacbdefacbdef' - ) - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - getterAddress = await deployGetterContract(evmUtil) - // mock a transpiler-output replaced CREATE - mockCREATEReplacement = [ - // fill memory with some random data so that we can confirm it was not modified - ...setMemory(mockMemory), - getPUSHIntegerOp(initcodeLength), - getPUSHIntegerOp(initcodeOffset), - getPUSHIntegerOp(0), // value input, will be ignored by transpiled bytecode - getPUSHBuffer(PCtoReturnTo), - ...getCREATESubstitute(getterAddress, getMethodName), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - // mock a transpiler-output replaced CREATE - mockCREATE2Replacement = [ - // fill memory with some random data so that we can confirm it was not modified - ...setMemory(mockMemory), - getPUSHBuffer(salt), - getPUSHIntegerOp(initcodeLength), - getPUSHIntegerOp(initcodeOffset), - getPUSHIntegerOp(0), // value input, will be ignored by transpiled bytecode - getPUSHBuffer(PCtoReturnTo), - ...getCREATE2Substitute(getterAddress, getMethodName), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - }) - - describe('CREATE replacement', () => { - it('should pass the right calldata', async () => { - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(mockCREATEReplacement) - ) - // check we generated the correct calldata - const expectedCallData: Buffer = Buffer.concat([ - abiForMethod.methodID(getMethodName, []), // prepended methodId - mockMemory.slice(initcodeOffset, initcodeOffset + initcodeLength), // original initcode data - ]) - - callContext.callData.equals(expectedCallData).should.be.true - }) - it('Should end up with the right stack and memory after the CALL', async () => { - // make sure the end state of memory is unaffected - const finalContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(mockCREATEReplacement), - bytecodeToBuffer(mockCREATEReplacement).length - 1 - ) - finalContext.memory.equals(mockMemory).should.be.true - - // check that returned address is only thing left on the stack - finalContext.stackDepth.should.equal(2) - finalContext.stack[0].should.deep.equal(PCtoReturnTo) - finalContext.stack[1].should.deep.equal(hexStrToBuf(valToReturn)) - }) - }) - - describe('CREATE2 replacement', () => { - it('should pass the right calldata', async () => { - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(mockCREATE2Replacement) - ) - // check we generated the correct calldata - const expectedCallData: Buffer = Buffer.concat([ - abiForMethod.methodID(getMethodName, []), // prepended methodId - bufferUtils.padLeft(salt, 32), // prepended salt - mockMemory.slice(initcodeOffset, initcodeOffset + initcodeLength), // original initcode data - ]) - - callContext.callData.equals(expectedCallData).should.be.true - }) - it('Should end up with the right stack and memory after the CALL', async () => { - // make sure the end state of memory is unaffected - const finalContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(mockCREATE2Replacement), - bytecodeToBuffer(mockCREATE2Replacement).length - 1 - ) - finalContext.memory.equals(mockMemory).should.be.true - - // check that returned address is only thing left on the stack - finalContext.stackDepth.should.equal(2) - finalContext.stack[0].should.deep.equal(PCtoReturnTo) - finalContext.stack[1].should.deep.equal(hexStrToBuf(valToReturn)) - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/dynamic-memory-opcodes.spec.ts b/packages/rollup-dev-tools/test/transpiler/dynamic-memory-opcodes.spec.ts deleted file mode 100644 index 4443cfd4f2b8c..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/dynamic-memory-opcodes.spec.ts +++ /dev/null @@ -1,241 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - getLogger, - hexStrToBuf, - bufferUtils, - hexStrToNumber, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - Opcode, - formatBytecode, -} from '@eth-optimism/rollup-core' -import * as abiForMethod from 'ethereumjs-abi' - -/* Internal Imports */ -import { - EvmIntrospectionUtil, - ExecutionResult, - CallContext, -} from '../../src/types/vm' -import { EvmIntrospectionUtilImpl } from '../../src/tools/vm' -import { setMemory, setupStackAndCALL } from '../helpers' -import { - getCALLSubstitute, - getSTATICCALLSubstitute, - getEXTCODECOPYSubstitute, - BIG_ENOUGH_GAS_LIMIT, -} from '../../src' - -const log = getLogger(`static-memory-opcodes`, true) - -const abi = new ethers.utils.AbiCoder() - -/* Contracts */ -import * as AssemblyReturnGetter from '../contracts/build/AssemblyReturnGetter.json' -import { getPUSHBuffer, getPUSHIntegerOp } from '../../src/' - -const valToReturn = - '0xbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeed' -const contractDeployParams: Buffer = Buffer.from( - remove0x(abi.encode(['bytes'], [valToReturn])), - 'hex' -) - -describe('Memory-dynamic Opcode Replacement', () => { - let evmUtil: EvmIntrospectionUtil - const getMethodName: string = 'get' - - // mock up a CALL with random inputs - const originalAddress: Address = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' - const retLength: number = 5 - const retoffset: number = 8 * 32 - const originalArgOffset: number = 4 * 32 + 17 // must exceed 4 * 32 for prepend to be possible - const originalArgLength: number = 15 - const PCtoReturnTo: Buffer = hexStrToBuf('0x696969') - - const setupStackForCALL: EVMBytecode = setupStackAndCALL( - BIG_ENOUGH_GAS_LIMIT, - originalAddress, - 0, - originalArgOffset, - originalArgLength, - retoffset, - retLength - ) - setupStackForCALL.pop() // pop the CALL itself - setupStackForCALL.push(getPUSHBuffer(PCtoReturnTo)) // push a val to represent the PC to jump back to - - const deployGetterContract = async ( - util: EvmIntrospectionUtil - ): Promise
=> { - const contractBytecode: Buffer = Buffer.from( - AssemblyReturnGetter.bytecode, - 'hex' - ) - const result: ExecutionResult = await util.deployContract( - contractBytecode, - contractDeployParams - ) - return bufToHexString(result.result) - } - - let getterAddress: Address - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - getterAddress = await deployGetterContract(evmUtil) - log.debug(`deployed getter contract to EVM introspection util.`) - }) - - describe('Call-type opcode replacements', () => { - it('should parse a CALL replacement', async () => { - // mock a transpiler-output replaced CALL - const mockMemory: Buffer = Buffer.alloc(32 * 10).fill(25) - const mockCallReplacement: EVMBytecode = [ - // fill memory with some random data so that we can confirm it was not modified - ...setMemory(mockMemory), - ...setupStackForCALL, - ...getCALLSubstitute(getterAddress, getMethodName), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(mockCallReplacement) - ) - // check we generated the correct calldata - const expectedCallData: Buffer = Buffer.concat([ - abiForMethod.methodID(getMethodName, []), // prepended methodId - Buffer.alloc(32 - 20), // prepended address 32-byte word padding - hexStrToBuf(originalAddress), // prepended Addreess - mockMemory.slice( - originalArgOffset, - originalArgOffset + originalArgLength - ), // original calldata - ]) - - callContext.callData.equals(expectedCallData).should.be.true - - // make sure the end state of memory is unaffectedx - const finalContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(mockCallReplacement), - bytecodeToBuffer(mockCallReplacement).length - 1 - ) - const expectedFinalMemory: Buffer = Buffer.concat([ - mockMemory.slice(0, retoffset), - hexStrToBuf(valToReturn).slice(0, retLength), - mockMemory.slice(retoffset + retLength), - ]) - - finalContext.memory.equals(expectedFinalMemory).should.be.true - - // check that the remaining stack is (PC to return to), (success) - finalContext.stackDepth.should.equal(2) - finalContext.stack[0].should.deep.equal(PCtoReturnTo) - finalContext.stack[1].should.deep.equal(hexStrToBuf('0x01')) - }) - it('should parse a STATICCALL replacement', async () => { - // mock a transpiler-output replaced CALL - const mockMemory: Buffer = Buffer.alloc(32 * 10).fill(25) - // remove the VALUE param from the call - setupStackForCALL.splice(4, 1) - const mockCallReplacement: EVMBytecode = [ - // fill memory with some random data so that we can confirm it was not modified - ...setMemory(mockMemory), - ...setupStackForCALL, - ...getSTATICCALLSubstitute(getterAddress, getMethodName), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(mockCallReplacement) - ) - - // check we generated the correct calldata - const expectedCallData: Buffer = Buffer.concat([ - abiForMethod.methodID(getMethodName, []), // prepended methodId - Buffer.alloc(32 - 20), // prepended address 32-byte word padding - hexStrToBuf(originalAddress), // prepended Addreess - mockMemory.slice( - originalArgOffset, - originalArgOffset + originalArgLength - ), // original calldata - ]) - - callContext.callData.equals(expectedCallData).should.be.true - - // make sure the end state of memory is unaffectedx - const finalContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(mockCallReplacement), - bytecodeToBuffer(mockCallReplacement).length - 1 - ) - const expectedFinalMemory: Buffer = Buffer.concat([ - mockMemory.slice(0, retoffset), - hexStrToBuf(valToReturn).slice(0, retLength), - mockMemory.slice(retoffset + retLength), - ]) - finalContext.memory.equals(expectedFinalMemory).should.be.true - - // check that the remaining stack is (PC to return to), (success) - finalContext.stackDepth.should.equal(2) - finalContext.stack[0].should.deep.equal(PCtoReturnTo) - finalContext.stack[1].should.deep.equal(hexStrToBuf('0x01')) - }) - }) - describe('EXTCODECOPY replacement', () => { - const addressToRequest: Address = - '0xbeeebeeebeeebeeebeeebeeebeeebeeeeeeeeeee' - const length: number = 4 - const offset: number = 3 - const destOffset: number = 2 - const setupStackForEXTCODECOPY: EVMBytecode = [ - // fill memory with some random data so that we can confirm it was not modified - ...setMemory(Buffer.alloc(32 * 10).fill(25)), - getPUSHIntegerOp(length), - getPUSHIntegerOp(offset), - getPUSHIntegerOp(destOffset), - getPUSHBuffer(hexStrToBuf(addressToRequest)), // address - getPUSHBuffer(PCtoReturnTo), // PC to return to - ] - - it('should correctly parse an EXTCODECOPY replacement', async () => { - const extcodesizeReplacement: EVMBytecode = [ - ...setupStackForEXTCODECOPY, - ...getEXTCODECOPYSubstitute(getterAddress, getMethodName), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(extcodesizeReplacement) - ) - - // Should pass calldata in the form that the execution manager expects: - // * [methodID (bytes4)] - // * [targetOvmContractAddress (address as bytes32 (big-endian))] - // * [index (uint (32)] - // * [length (uint (32))] - const expectedCalldata: Buffer = Buffer.concat([ - abiForMethod.methodID(getMethodName, []), - Buffer.alloc(12), // padding for 20-byte address - hexStrToBuf(addressToRequest), - bufferUtils.numberToBuffer(offset), - bufferUtils.numberToBuffer(length), - ]) - callContext.callData.equals(expectedCalldata).should.be.true - - // should call with the correct return memory values - callContext.input.retOffset.should.equal(destOffset) - callContext.input.retLength.should.equal(length) - - // check stack has preserved the PC to jump back to - const finalContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(extcodesizeReplacement), - bytecodeToBuffer(extcodesizeReplacement).length - 1 - ) - finalContext.stackDepth.should.equal(1) - finalContext.stack[0].should.deep.equal(PCtoReturnTo) - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/initcode-structure-check.spec.ts b/packages/rollup-dev-tools/test/transpiler/initcode-structure-check.spec.ts deleted file mode 100644 index 9a0005f9e975c..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/initcode-structure-check.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - getLogger, - hexStrToBuf, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - EVMOpcode, - formatBytecode, - Opcode, - EVMOpcodeAndBytes, - bufferToBytecode, -} from '@eth-optimism/rollup-core' - -// constants related -import * as ethereumjsAbi from 'ethereumjs-abi' -import * as ConstructorlessWithConstants from '../contracts/build/ConstructorlessWithConstants.json' -import * as StandaloneConstructorWithConstants from '../contracts/build/StandaloneConstructorWithConstants.json' -import * as SubcallConstructorWithoutConstants from '../contracts/build/SubcallConstructorWithoutConstants.json' -import * as SubcallConstructorWithConstants from '../contracts/build/SubcallConstructorWithConstants.json' -import * as SubcallConstructorWithConstantsInBoth from '../contracts/build/SubcallConstructorWithConstantsInBoth.json' -import * as SubcallConstructorWithNestedSubcalls from '../contracts/build/SubcallConstructorWithNestedSubcalls.json' - -// constructor params related -import * as ConstructorWithSmallParameter from '../contracts/build/ConstructorWithSmallParameter.json' -import * as ConstructorWithBigParameter from '../contracts/build/ConstructorWithBigParameter.json' -import * as ConstructorWithTwoBigParameters from '../contracts/build/ConstructorWithTwoBigParameters.json' -import * as ConstantsInConstructorAndBody from '../contracts/build/ConstantsInConstructorAndBody.json' - -// constructor params accessing constant before accessing those params -import * as ConstructorWithTwoBigParametersAccessingConstantBefore from '../contracts/build/ConstructorWithTwoBigParametersAccessingConstantBefore.json' - -/* - ******** - CONSTRUCTOR STRUCTURE CHECKS - ******** - These tests were used to investigate how solc creates initcode: specifically, the relation between init logic used in CREATE and the deployed bytecode. - The results were that initcode consists of the following: - [deploy logic] [bytecode to deploy] [constants used in the deploy logic] - Which is now the format assumed for transpiler.transpile(). - The test asserting the above structure is the only one not .skip()ped, but the remainder have been left for posterity. - They basically just logged various combinations of Solidity contracts with constructors, subcalls, and constants, so that they could be assesed visually. -*/ - -const log = getLogger(`constructor-exploration`) -const abi = new ethers.utils.AbiCoder() - -interface BytecodeInspection { - contractName: string - initcode: Buffer - deployedBytecode: Buffer -} - -const getBytecodeInspection = (json: any, name: string): BytecodeInspection => { - return { - contractName: name, - initcode: hexStrToBuf(json.bytecode), - deployedBytecode: hexStrToBuf(json.evm.deployedBytecode.object), - } -} - -const constructorContracts: BytecodeInspection[] = [ - getBytecodeInspection( - ConstructorlessWithConstants, - 'ConstructorlessWithConstants' - ), - getBytecodeInspection( - StandaloneConstructorWithConstants, - 'StandaloneConstructorWithConstants' - ), - getBytecodeInspection( - SubcallConstructorWithoutConstants, - 'SubcallConstructorWithoutConstants' - ), - getBytecodeInspection( - SubcallConstructorWithConstants, - 'SubcallConstructorWithConstants' - ), - getBytecodeInspection( - SubcallConstructorWithConstantsInBoth, - 'SubcallConstructorWithConstantsInBoth' - ), - getBytecodeInspection( - SubcallConstructorWithNestedSubcalls, - 'SubcallConstructorWithNestedSubcalls' - ), -] - -const getInitcodePrefix = (cont: BytecodeInspection): EVMBytecode => { - const deployedBytecode: Buffer = cont.deployedBytecode - const deployedBytecodeLength: number = deployedBytecode.byteLength - const initcodeLength: number = cont.initcode.byteLength - const initcodePrefix: Buffer = cont.initcode.slice( - 0, - initcodeLength - deployedBytecodeLength - ) - return bufferToBytecode(initcodePrefix) -} - -describe('Constructor/initcode positioning test', () => { - it.skip(`let's just look at the hex bytes,`, async () => { - for (const cont of constructorContracts) { - log.debug( - `contract ${cont.contractName} has: \n initcode: ${bufToHexString( - cont.initcode - )}\n deployed bytecode: ${bufToHexString(cont.deployedBytecode)} \n` - ) - } - }) - it(`in all cases the initcode should be a prefixed version of deployedBytecode`, async () => { - for (const cont of constructorContracts) { - const deployedBytecode: Buffer = cont.deployedBytecode - const deployedBytecodeLength: number = deployedBytecode.byteLength - const initLogicLength: number = cont.initcode.indexOf(deployedBytecode) - const constantsUsedInConstructorStart: number = - cont.initcode.indexOf(deployedBytecode) + deployedBytecodeLength - const sliceOfDeployedBytecodeInInitcode: Buffer = cont.initcode.slice( - initLogicLength, - constantsUsedInConstructorStart - ) - if ( - Buffer.compare(deployedBytecode, sliceOfDeployedBytecodeInInitcode) !== - 0 - ) { - log.debug(`failed to hold for ${cont.contractName}.`) - log.debug(`Raw initcode: ${bufToHexString(cont.initcode)}`) - log.debug(`Raw deployedBytecode: ${bufToHexString(deployedBytecode)}`) - log.debug( - `The deployed bytecode is: ${formatBytecode( - bufferToBytecode(deployedBytecode) - )}` - ) - log.debug( - `The slice of initcode between constructor logic and constants accessed in initcode is: ${formatBytecode( - bufferToBytecode(sliceOfDeployedBytecodeInInitcode) - )}` - ) - } - deployedBytecode.should.deep.equal(sliceOfDeployedBytecodeInInitcode) - } - }) - - const constructorParameterContracts: BytecodeInspection[] = [ - getBytecodeInspection( - ConstructorWithSmallParameter, - 'ConstructorWithSmallParameter' - ), - getBytecodeInspection( - ConstructorWithBigParameter, - 'ConstructorWithBigParameter' - ), - getBytecodeInspection( - ConstructorWithTwoBigParameters, - 'ConstructorWithTwoBigParameters' - ), - ] - - it.skip(`let's look at some initcode prefixes which utilize different params`, async () => { - for (const cont of constructorParameterContracts) { - const initcodePrefix: EVMBytecode = getInitcodePrefix(cont) - log.debug(`The initcode prefix for contract ${cont.contractName}.sol is:`) - log.debug(formatBytecode(initcodePrefix)) - } - }) - it.skip(`let's make sure that accessing a constant before accessing a constructor input doesn't trigger out JUMPDEST...CODECOPY prematurely`, async () => { - const crazyConstructorPrefix: EVMBytecode = getInitcodePrefix( - getBytecodeInspection( - ConstructorWithTwoBigParametersAccessingConstantBefore, - 'ConstructorWithTwoBigParametersAccessingConstantBefore' - ) - ) - log.debug( - `solidity with const accessed in constructor before accessing constructor params gives us the following initcode prefix:` - ) - log.debug(formatBytecode(crazyConstructorPrefix)) - }) - it.skip(`let's look at a constructor which recieves no input but accesses a constant`, async () => { - const standaloneConstructorWithConstantsPrefix: EVMBytecode = getInitcodePrefix( - getBytecodeInspection( - StandaloneConstructorWithConstants, - 'StandaloneConstructorWithConstants' - ) - ) - log.debug( - `solidity with const accessed in constructor and no constructor params gives us the following initcode prefix:` - ) - log.debug(formatBytecode(standaloneConstructorWithConstantsPrefix)) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/jump-integration.spec.ts b/packages/rollup-dev-tools/test/transpiler/jump-integration.spec.ts deleted file mode 100644 index d5998c421ce78..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/jump-integration.spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - getLogger, - hexStrToBuf, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - EVMOpcode, - formatBytecode, - Opcode, - EVMOpcodeAndBytes, - bufferToBytecode, -} from '@eth-optimism/rollup-core' -import * as ethereumjsAbi from 'ethereumjs-abi' -import * as SimpleJumper from '../contracts/build/SimpleJumper.json' - -/* Internal Imports */ -import { - EvmIntrospectionUtil, - ExecutionResult, - StepContext, - CallContext, - EvmIntrospectionUtilImpl, - getPUSHBuffer, - getPUSHIntegerOp, - duplicateStackAt, - callContractWithStackElementsAndReturnWordToMemory, - storeStackElementsAsMemoryWords, - callContractWithStackElementsAndReturnWordToStack, -} from '../../src' -import { - ErroredTranspilation, - OpcodeReplacer, - OpcodeWhitelist, - TranspilationErrors, - TranspilationResult, - Transpiler, - SuccessfulTranspilation, -} from '../../src/types/transpiler' -import { - TranspilerImpl, - OpcodeReplacerImpl, - OpcodeWhitelistImpl, -} from '../../src/tools/transpiler' -import { mockSSTOREReplacer } from '../helpers' - -const log = getLogger(`test-solidity-JUMPs`) -const abi = new ethers.utils.AbiCoder() - -describe('JUMP table solidity integration', () => { - let evmUtil: EvmIntrospectionUtil - const opcodeWhitelist = new OpcodeWhitelistImpl(Opcode.ALL_OP_CODES) - const transpiler = new TranspilerImpl(opcodeWhitelist, mockSSTOREReplacer) - - const originalJumperAddr: Address = - '0x1234123412341234123412341234123412341234' - const transpiledJumperAddr: Address = - '0x3456345634563456345634563456345634563456' - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - const originalJumperDeployedBytecde: Buffer = hexStrToBuf( - SimpleJumper.evm.deployedBytecode.object - ) - await evmUtil.deployBytecodeToAddress( - originalJumperDeployedBytecde, - hexStrToBuf(originalJumperAddr) - ) - const transpiledJumperDeployedBytecode: Buffer = (transpiler.transpileRawBytecode( - originalJumperDeployedBytecde - ) as SuccessfulTranspilation).bytecode - log.debug( - `Transpiled jumper output with BST jump table: \n${formatBytecode( - bufferToBytecode(transpiledJumperDeployedBytecode) - )}` - ) - await evmUtil.deployBytecodeToAddress( - transpiledJumperDeployedBytecode, - hexStrToBuf(transpiledJumperAddr) - ) - }) - it('should handle an if(true)', async () => { - await assertCallsProduceSameResult( - evmUtil, - originalJumperAddr, - transpiledJumperAddr, - 'staticIfTrue' - ) - }) - it('should handle an if(false)', async () => { - await assertCallsProduceSameResult( - evmUtil, - originalJumperAddr, - transpiledJumperAddr, - 'staticIfFalseElse' - ) - }) - it('should handle for loops', async () => { - await assertCallsProduceSameResult( - evmUtil, - originalJumperAddr, - transpiledJumperAddr, - 'doForLoop' - ) - }) - it('should handle while loops', async () => { - await assertCallsProduceSameResult( - evmUtil, - originalJumperAddr, - transpiledJumperAddr, - 'doWhileLoop' - ) - }) - it('should handle a while loop whose inner function calls another method with a for loop', async () => { - await assertCallsProduceSameResult( - evmUtil, - originalJumperAddr, - transpiledJumperAddr, - 'doLoopingSubcalls' - ) - }) - it('should handle a combination of a ton of these conditionals, subcalls, and loops', async () => { - const nonzeroInput = '0x123456' - const paramTypes = ['uint256'] - const callParams: Buffer = Buffer.from( - remove0x(abi.encode(paramTypes, [nonzeroInput])), - 'hex' - ) - await assertCallsProduceSameResult( - evmUtil, - originalJumperAddr, - transpiledJumperAddr, - 'doCrazyCombination', - paramTypes, - callParams - ) - }).timeout(35000) -}) - -const assertCallsProduceSameResult = async ( - util: EvmIntrospectionUtil, - addr1: Address, - addr2: Address, - methodName?: string, - paramTypes?: string[], - abiEncodedParams?: Buffer -) => { - log.debug(`Asked to compare the result of two calls.`) - log.debug(`Calling first contract at address ${addr1}...`) - const res1 = await util.callContract( - addr1, - methodName, - paramTypes, - abiEncodedParams - ) - if (res1.error) { - log.debug(`oh, erroring`) - throw new Error( - `TEST ERROR: failed to execute callContract() for contract address: ${addr1}. Error was: \n${res1.error}` - ) - } - log.debug( - `Completed first call successfully. Calling second contract at address ${addr2}...` - ) - const res2 = await util.callContract( - addr2, - methodName, - paramTypes, - abiEncodedParams - ) - if (res2.error) { - throw new Error( - `TEST ERROR: failed to execute callContract() for contract address: ${addr2}. Error was: \n${res2.error}` - ) - } - log.debug(`Completed second call successfully. Comparing results...`) - res2.result.should.deep.equal(res1.result) - return -} diff --git a/packages/rollup-dev-tools/test/transpiler/memory-substitution.spec.ts b/packages/rollup-dev-tools/test/transpiler/memory-substitution.spec.ts deleted file mode 100644 index e34ffbb1c790f..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/memory-substitution.spec.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { should } from '../setup' - -/* External Imports */ -import { - getLogger, - Logger, - hexStrToBuf, - BigNumber, - bufferUtils, -} from '@eth-optimism/core-utils' -import { - Opcode, - EVMOpcode, - EVMBytecode, - bytecodeToBuffer, - bufferToBytecode, - formatBytecode, -} from '@eth-optimism/rollup-core' - -/* Internal imports */ -import { SuccessfulTranspilation } from '../../src/types/transpiler' -import { - TranspilerImpl, - OpcodeReplacerImpl, - OpcodeWhitelistImpl, - pushMemoryAtIndexOntoStack, - storeStackInMemoryAtIndex, - getPUSHIntegerOp, - getSWAPNOp, -} from '../../src/tools/transpiler' -import { stateManagerAddress, whitelistedOpcodes } from '../helpers' -import { EvmIntrospectionUtil, StepContext } from '../../src/types/vm' -import { EvmIntrospectionUtilImpl } from '../../src/tools/vm' - -const log: Logger = getLogger('test-memory-sub') - -const overwritingString: string = '69' // nice. -const overwritingByte: Buffer = Buffer.from(overwritingString, 'hex') -const overwritingBytes32: Buffer = Buffer.from( - overwritingString.repeat(32), - 'hex' -) - -const pointlessOperation: EVMBytecode = [ - { - opcode: Opcode.PUSH1, - consumedBytes: hexStrToBuf('0xab'), - }, - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, -] - -const storeNWordsInMemorySequential = (numWords: number): EVMBytecode => { - const storageBytecode: EVMBytecode[] = [] - for (let i = 0; i < numWords; i++) { - storageBytecode.push([ - { - opcode: Opcode.PUSH32, - consumedBytes: Buffer.alloc(32).fill(i), - }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(i * 32), - }, - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - ]) - } - return [].concat(...storageBytecode) -} - -const getExpectedMemoryAfterSequentialStore = (numWords): Buffer => { - const expectedMemory: Buffer = Buffer.alloc(numWords * 32) - for (let i = 0; i < numWords; i++) { - expectedMemory.fill(i, i * 32, (i + 1) * 32) - } - return expectedMemory -} - -const overwriteNWordsInMemoryWithOffset = ( - numWords: number, - offset: number -): EVMBytecode => { - const overwriteBytecode: EVMBytecode[] = [] - for (let i = 0; i < numWords; i++) { - overwriteBytecode.push([ - { - opcode: Opcode.PUSH32, - consumedBytes: overwritingBytes32, - }, - getPUSHIntegerOp(offset + i * 32), - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - ]) - } - return [].concat(...overwriteBytecode) -} - -describe('Memory Replacement Operations', () => { - let evmUtil: EvmIntrospectionUtil - - before(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - }) - - describe('Testing Memory Utils', () => { - it('should correctly storeNWordsInMemorySequential', async () => { - const numSequentialWordsToStore: number = 10 - const operationBytecode: EVMBytecode = [ - ...storeNWordsInMemorySequential(numSequentialWordsToStore), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const operationBuffer: Buffer = bytecodeToBuffer(operationBytecode) - - const indexOfReturnOp: number = operationBuffer.length - 1 - const memoryStoredResult: StepContext = await evmUtil.getStepContextBeforeStep( - operationBuffer, - indexOfReturnOp - ) - memoryStoredResult.stackDepth.should.equal(0) - memoryStoredResult.memoryWordCount.should.equal(numSequentialWordsToStore) - memoryStoredResult.memory.should.eql( - getExpectedMemoryAfterSequentialStore(numSequentialWordsToStore) - ) - }) - - it('should correctly overwriteNWordsInMemoryWithOffset', async () => { - const numWordsToStore: number = 10 - const numWordsToOverwrite: number = 3 - const overwriteOffset: number = 15 - const operationBytecode: EVMBytecode = [ - ...storeNWordsInMemorySequential(numWordsToStore), - ...overwriteNWordsInMemoryWithOffset( - numWordsToOverwrite, - overwriteOffset - ), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const operationBuffer: Buffer = bytecodeToBuffer(operationBytecode) - - const indexOfReturnOp: number = operationBuffer.length - 1 - const memoryModifiedResult: StepContext = await evmUtil.getStepContextBeforeStep( - operationBuffer, - indexOfReturnOp - ) - memoryModifiedResult.stackDepth.should.equal(0) - memoryModifiedResult.memoryWordCount.should.equal(numWordsToStore) - - const expectedMemory: Buffer = getExpectedMemoryAfterSequentialStore( - numWordsToStore - ) - const bytesOverwritten = 32 * numWordsToOverwrite - expectedMemory.fill( - overwritingByte, - overwriteOffset, - overwriteOffset + bytesOverwritten - ) - - memoryModifiedResult.memory.should.eql(expectedMemory) - }) - }) - - describe('Memory/stack swapping', () => { - it('Correctly pushes multiple words of memory onto the stack', async () => { - const numWords: number = 3 - const mloadIndex: number = 3 - const storeAndPushToStack: EVMBytecode = [ - ...storeNWordsInMemorySequential(9), // random exceeding numWords + index - ...pushMemoryAtIndexOntoStack(mloadIndex, numWords), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const binary: Buffer = bytecodeToBuffer(storeAndPushToStack) - const indexOfReturnOp: number = binary.length - 1 - const finalContext: StepContext = await evmUtil.getStepContextBeforeStep( - binary, - indexOfReturnOp - ) - log.debug(`Final step context was: ${JSON.stringify(finalContext)}`) - - finalContext.stackDepth.should.equal( - numWords, - 'The stack should only contain the loaded words' - ) - // The stack should contain the loaded words in order - for (let i = 0; i < numWords; i++) { - const wordIndex: number = mloadIndex + 32 * i - const wordFromMemory: Buffer = finalContext.memory.slice( - wordIndex, - wordIndex + 32 - ) - - const wordOnStack: Buffer = finalContext.stack[i] - // check equality, ethereumjs-vm removes unnecessary zeroes. - wordFromMemory.should.eql(bufferUtils.padLeft(wordOnStack, 32)) - } - }) - - it('Correctly stores multiple words from the stack back into memory', async () => { - const fourRandomWords: Buffer = hexStrToBuf( - '0x0111030ffffa0a0a11103040a0a0a0a011103040a0a0a0a1110232323a0a0a0d011103040a0a0a0a11103040a555555011103040a0a0a0a11103040a0a0a0ab0111030ffffa0a0a11103040a0a0a0a011103040adddddd1110232323a0a0a07011103040a0a0a5858699040a555555011103040a0a0a0a11103040a0a0a0abbb' - ) - const pushWordsToStack: EVMBytecode = [] - for (let i = 3; i >= 0; i--) { - pushWordsToStack.push({ - opcode: Opcode.PUSH32, - consumedBytes: fourRandomWords.slice(i * 32, (i + 1) * 32), - }) - } - const pushWordsToStackAndRestore: EVMBytecode = [ - ...pushWordsToStack, - ...storeStackInMemoryAtIndex(0, 4), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const binary: Buffer = bytecodeToBuffer(pushWordsToStackAndRestore) - const indexOfReturnOp: number = binary.length - 1 - const finalContext: StepContext = await evmUtil.getStepContextBeforeStep( - binary, - indexOfReturnOp - ) - finalContext.stackDepth.should.equal(0) - finalContext.memory.should.deep.equal(fourRandomWords) - }) - - it('Memory operations between a pushtoStack and storeInMemory operation should not have any effect', async () => { - const numWordsToStore = 10 - const memoryModifyingBytecode: EVMBytecode = [ - ...storeNWordsInMemorySequential(numWordsToStore), - ...pointlessOperation, // to be transpiled - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const memoryModifyingBytecodeBuf: Buffer = bytecodeToBuffer( - memoryModifyingBytecode - ) - - const memoryIndexToModify: number = 2 - const numWordsToModify: number = 2 - // push memory to stack, overwrite memory, store stack back to memory - const pushModifyLoad: EVMBytecode = [ - ...pushMemoryAtIndexOntoStack(memoryIndexToModify, numWordsToModify), - ...overwriteNWordsInMemoryWithOffset( - numWordsToModify, - memoryIndexToModify - ), - ...storeStackInMemoryAtIndex(memoryIndexToModify, numWordsToModify), - ] - - const replaceMap: Map = new Map< - EVMOpcode, - EVMBytecode - >().set(Opcode.POP, [ - // SWAP the PC to be returned to with the element originally to be popped - getSWAPNOp(1), - // still do the POP we will be replacing so that the PUSH POP still has no effect - { - opcode: Opcode.POP, - consumedBytes: undefined, - }, - // do the operation which should have no effect - ...pushModifyLoad, - ]) - - const opcodeWhitelist = new OpcodeWhitelistImpl(whitelistedOpcodes) - const replacer = new OpcodeReplacerImpl(stateManagerAddress, replaceMap) - const transpiler = new TranspilerImpl(opcodeWhitelist, replacer) - const transpilation = transpiler.transpileRawBytecode( - memoryModifyingBytecodeBuf - ) as SuccessfulTranspilation - const transpiledMemoryModifyingBytecodeBuf: Buffer = - transpilation.bytecode - - log.debug( - `The memory modifying untranspiled bytecode is as follows: \n${formatBytecode( - memoryModifyingBytecode - )}\nAnd the transpiled version is: \n${formatBytecode( - bufferToBytecode(transpiledMemoryModifyingBytecodeBuf) - )}` - ) - - const comparisonBeforeReturns = await evmUtil.getExecutionComparisonBeforeStep( - memoryModifyingBytecodeBuf, - 673, - transpiledMemoryModifyingBytecodeBuf, - 679 - ) - - comparisonBeforeReturns.firstContext.memory.should.deep.equal( - comparisonBeforeReturns.secondContext.memory - ) - comparisonBeforeReturns.firstContext.stack.should.deep.equal( - comparisonBeforeReturns.secondContext.stack - ) - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/opcode-replacer.spec.ts b/packages/rollup-dev-tools/test/transpiler/opcode-replacer.spec.ts deleted file mode 100644 index 1770507cc557a..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/opcode-replacer.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import '../setup' - -/* External Imports */ -import { hexStrToBuf, TestUtils, ZERO_ADDRESS } from '@eth-optimism/core-utils' -import { Opcode, EVMOpcode, EVMBytecode } from '@eth-optimism/rollup-core' - -/* Internal imports */ -import { OpcodeReplacer, InvalidBytesConsumedError } from '../../src/types' -import { OpcodeReplacerImpl } from '../../src/tools/transpiler' - -const zeroAddrBuf: Buffer = hexStrToBuf(ZERO_ADDRESS) - -describe('OpcodeReplacer', () => { - describe('Initialization', () => { - it('Should throw if given invalid execution manager address', () => { - try { - new OpcodeReplacerImpl('0xnotAnAddr', new Map()) - } catch (err) { - // Success we threw an error! - return - } - throw new Error('Did not throw when expected!') - }) - it('Should not throw if given a valid execution manager address and no Opcodes Replacements Map', () => { - try { - new OpcodeReplacerImpl(ZERO_ADDRESS, new Map()) - } catch (err) { - throw new Error( - 'Should not throw with a valid execution manager address' - ) - } - }) - }) - - describe('Mandatory Substitutions and Substitutions Checking', () => { - let replacer: OpcodeReplacer - beforeEach(() => { - replacer = new OpcodeReplacerImpl(ZERO_ADDRESS) - }) - - const assertSubstituted = (r: OpcodeReplacer, opcode: EVMOpcode): void => { - const shouldReplace = r.shouldSubstituteOpcodeForFunction(opcode) - shouldReplace.should.eq(true) - - const res = r.getSubstituedFunctionFor({ - opcode, - consumedBytes: undefined, - }) - - const callCount: number = res.filter((x) => x.opcode === Opcode.CALL) - .length - const pushEMAddrCount: number = res.filter( - (x) => x.opcode === Opcode.PUSH20 && x.consumedBytes.equals(zeroAddrBuf) - ).length - - res.length.should.be.gt(0, 'Should return replacement!') - callCount.should.eq(1, 'Should call EM!') - pushEMAddrCount.should.eq(1, 'Should push EM address for call!') - if (opcode !== Opcode.CALL) { - const origOpcodeCount: number = res.filter( - (x) => x.opcode === Opcode.ADDRESS - ).length - origOpcodeCount.should.eq(0, 'Should replace opcode!') - } - } - - it('substitutes ADDRESS', async () => { - assertSubstituted(replacer, Opcode.ADDRESS) - }) - - it('substitutes CALL', async () => { - assertSubstituted(replacer, Opcode.CALL) - }) - - it('substitutes CALLER', async () => { - assertSubstituted(replacer, Opcode.CALLER) - }) - - it('substitutes CHAINID', async () => { - assertSubstituted(replacer, Opcode.CHAINID) - }) - - it('substitutes CREATE', async () => { - assertSubstituted(replacer, Opcode.CREATE) - }) - - it('substitutes CREATE2', async () => { - assertSubstituted(replacer, Opcode.CREATE2) - }) - - it('substitutes DELEGATECALL', async () => { - assertSubstituted(replacer, Opcode.DELEGATECALL) - }) - - it('substitutes EXTCODECOPY', async () => { - assertSubstituted(replacer, Opcode.EXTCODECOPY) - }) - - it('substitutes EXTCODEHASH', async () => { - assertSubstituted(replacer, Opcode.EXTCODEHASH) - }) - - it('substitutes EXTCODESIZE', async () => { - assertSubstituted(replacer, Opcode.EXTCODESIZE) - }) - - it('substitutes ORIGIN', async () => { - assertSubstituted(replacer, Opcode.ORIGIN) - }) - - it('substitutes SLOAD', async () => { - assertSubstituted(replacer, Opcode.SLOAD) - }) - - it('substitutes SSTORE', async () => { - assertSubstituted(replacer, Opcode.SSTORE) - }) - - it('substitutes STATICCALL', async () => { - assertSubstituted(replacer, Opcode.STATICCALL) - }) - - it('substitutes TIMESTAMP', async () => { - assertSubstituted(replacer, Opcode.TIMESTAMP) - }) - }) - - describe('Discretionary Replacements', () => { - it('returns the EVMOpcode as EVMBytecode if no replacement specified', () => { - const cfg: Map = new Map< - EVMOpcode, - EVMBytecode - >().set(Opcode.ADD, [{ opcode: Opcode.MUL, consumedBytes: undefined }]) - - const replacer = new OpcodeReplacerImpl(ZERO_ADDRESS, cfg) - - const replacedBytecode: EVMBytecode = replacer.getSubstituedFunctionFor({ - opcode: Opcode.MUL, // different opcode - consumedBytes: undefined, - }) - const expected: EVMBytecode = [ - { - opcode: Opcode.MUL, - consumedBytes: undefined, - }, - ] - replacedBytecode.should.deep.equal(expected) - }) - - it('correctly parses and substitutes a single opcode with another', () => { - const cfg: Map = new Map< - EVMOpcode, - EVMBytecode - >().set(Opcode.ADD, [{ opcode: Opcode.MUL, consumedBytes: undefined }]) - - const replacer = new OpcodeReplacerImpl(ZERO_ADDRESS, cfg) - - const replacedBytecode: EVMBytecode = replacer.getSubstituedFunctionFor({ - opcode: Opcode.ADD, - consumedBytes: undefined, - }) - - replacedBytecode.should.deep.equal(cfg.get(Opcode.ADD)) - }) - - it('correctly parses and substitutes a single opcode with two others', () => { - const cfg: Map = new Map< - EVMOpcode, - EVMBytecode - >().set(Opcode.ADD, [ - { opcode: Opcode.MUL, consumedBytes: undefined }, - { opcode: Opcode.MUL, consumedBytes: undefined }, - ]) - const replacer = new OpcodeReplacerImpl(ZERO_ADDRESS, cfg) - - const replacedBytecode: EVMBytecode = replacer.getSubstituedFunctionFor({ - opcode: Opcode.ADD, - consumedBytes: undefined, - }) - - replacedBytecode.should.deep.equal(cfg.get(Opcode.ADD)) - }) - - it('correctly parses and substitutes a single PUSH1', () => { - const cfg: Map = new Map< - EVMOpcode, - EVMBytecode - >().set(Opcode.ADD, [ - { opcode: Opcode.PUSH1, consumedBytes: hexStrToBuf('0x00') }, - ]) - const replacer = new OpcodeReplacerImpl(ZERO_ADDRESS, cfg) - - const replacedBytecode: EVMBytecode = replacer.getSubstituedFunctionFor({ - opcode: Opcode.ADD, - consumedBytes: undefined, - }) - - replacedBytecode.should.deep.equal(cfg.get(Opcode.ADD)) - }) - - it('correctly identifies when a PUSH2 is followed by wrong num bytes and throws', () => { - const cfg: Map = new Map< - EVMOpcode, - EVMBytecode - >().set(Opcode.ADD, [ - { opcode: Opcode.PUSH2, consumedBytes: hexStrToBuf('0x00') }, - ]) - TestUtils.assertThrows(() => { - new OpcodeReplacerImpl(ZERO_ADDRESS, cfg) - }, InvalidBytesConsumedError) - }) - - it('correctly parses and substitutes a push for the execution manager', () => { - const cfg: Map = new Map< - EVMOpcode, - EVMBytecode - >().set(Opcode.ADD, [ - { - opcode: Opcode.PUSH20, - consumedBytes: OpcodeReplacerImpl.EX_MGR_PLACEHOLDER, - }, - ]) - const executionManagerAddress = ZERO_ADDRESS - const replacer = new OpcodeReplacerImpl(executionManagerAddress, cfg) - - const replacedBytecode: EVMBytecode = replacer.getSubstituedFunctionFor({ - opcode: Opcode.ADD, - consumedBytes: undefined, - }) - const expected: EVMBytecode = [ - { - opcode: Opcode.PUSH20, - consumedBytes: hexStrToBuf(executionManagerAddress), - }, - ] - replacedBytecode.should.deep.equal(expected) - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/opcode-whitelist.spec.ts b/packages/rollup-dev-tools/test/transpiler/opcode-whitelist.spec.ts deleted file mode 100644 index 31d4bea920fab..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/opcode-whitelist.spec.ts +++ /dev/null @@ -1,136 +0,0 @@ -import '../setup' - -/* External Imports */ -import { Opcode, EVMOpcode } from '@eth-optimism/rollup-core' - -/* Internal imports */ -import { OpcodeWhitelist } from '../../src/types/transpiler' -import { OpcodeWhitelistImpl } from '../../src/tools/transpiler' - -describe('OpcodeWhitelist', () => { - let opcodeWhitelist: OpcodeWhitelist - const opcodes: EVMOpcode[] = [ - Opcode.ADD, - Opcode.SUB, - Opcode.MLOAD, - Opcode.BALANCE, - Opcode.BLOCKHASH, - Opcode.INVALID, - Opcode.CREATE, - Opcode.PUSH1, - Opcode.TIMESTAMP, - Opcode.CHAINID, - Opcode.DUP1, - Opcode.POP, - Opcode.SSTORE, - Opcode.MSTORE, - ] - - beforeEach(() => { - opcodeWhitelist = new OpcodeWhitelistImpl(opcodes) - }) - - describe('Identifies included / excluded opcodes', () => { - it('correctly classifies included', () => { - for (const op of opcodes) { - opcodeWhitelist - .isOpcodeWhitelisted(op) - .should.eq(true, `OP: ${op.name} should be whitelisted but was not!`) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeBuffer(op.code) - .should.eq( - true, - `OP: ${op.name} should be whitelisted by code buffer but was not!` - ) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeValue(parseInt(op.code.toString('hex'), 16)) - .should.eq( - true, - `OP: ${op.name} should be whitelisted by code value but was not!` - ) - - opcodeWhitelist - .isOpcodeWhitelistedByName(op.name) - .should.eq( - true, - `OP: ${op.name} should be whitelisted by name but was not!` - ) - } - }) - - it('correctly classifies excluded', () => { - for (const op of Opcode.ALL_OP_CODES.filter( - (x) => opcodes.indexOf(x) < 0 - )) { - opcodeWhitelist - .isOpcodeWhitelisted(op) - .should.eq(false, `OP: ${op.name} should not be whitelisted but was!`) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeBuffer(op.code) - .should.eq( - false, - `OP: ${op.name} should not be whitelisted by code buffer but was!` - ) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeValue(parseInt(op.code.toString('hex'), 16)) - .should.eq( - false, - `OP: ${op.name} should not be whitelisted by code value but was!` - ) - - opcodeWhitelist - .isOpcodeWhitelistedByName(op.name) - .should.eq( - false, - `OP: ${op.name} should not be whitelisted by name but was!` - ) - } - }) - }) - - describe('Edge cases', () => { - it('correctly handles invalid', () => { - opcodeWhitelist - .isOpcodeWhitelistedByName('derp') - .should.eq(false, `'derp' should not be whitelisted but was!`) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeBuffer(Buffer.from('derp')) - .should.eq(false, `'derp' buffer should not be whitelisted but was!`) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeValue(999) - .should.eq(false, `Code value 999 should not be whitelisted but was!`) - }) - - it('correctly handles undefined / empty', () => { - opcodeWhitelist - .isOpcodeWhitelistedByName('') - .should.eq(false, `'' opcode string should not be whitelisted but was!`) - opcodeWhitelist - .isOpcodeWhitelistedByName(undefined) - .should.eq( - false, - `undefined string opcode should not be whitelisted but was!` - ) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeBuffer(Buffer.from('')) - .should.eq(false, `'' buffer should not be whitelisted but was!`) - opcodeWhitelist - .isOpcodeWhitelistedByCodeBuffer(undefined) - .should.eq(false, `undefined buffer should not be whitelisted but was!`) - - opcodeWhitelist - .isOpcodeWhitelistedByCodeValue(undefined) - .should.eq( - false, - `Code value of undefined should not be whitelisted but was!` - ) - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/static-memory-opcodes.spec.ts b/packages/rollup-dev-tools/test/transpiler/static-memory-opcodes.spec.ts deleted file mode 100644 index 77ee6177b69cf..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/static-memory-opcodes.spec.ts +++ /dev/null @@ -1,321 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - getLogger, - hexStrToBuf, - bufferUtils, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - formatBytecode, - Opcode, - EVMOpcodeAndBytes, -} from '@eth-optimism/rollup-core' -import * as ethereumjsAbi from 'ethereumjs-abi' - -/* Internal Imports */ -import { - EvmIntrospectionUtil, - ExecutionResult, - StepContext, - CallContext, - EvmIntrospectionUtilImpl, - getPUSHBuffer, - getPUSHIntegerOp, - duplicateStackAt, - callContractWithStackElementsAndReturnWordToMemory, - storeStackElementsAsMemoryWords, - callContractWithStackElementsAndReturnWordToStack, -} from '../../src' - -const log = getLogger(`test-static-memory-opcodes`, true) -const abi = new ethers.utils.AbiCoder() - -/* Contracts */ -import * as AssemblyReturnGetter from '../contracts/build/AssemblyReturnGetter.json' - -describe('Static Memory Opcode Replacement', () => { - let evmUtil: EvmIntrospectionUtil - const contractBytecode: Buffer = Buffer.from( - AssemblyReturnGetter.bytecode, - 'hex' - ) - const getterFunctionName: string = 'get' - const getterMethodId: Buffer = ethereumjsAbi.methodID(getterFunctionName, []) - const valToReturn: Buffer = hexStrToBuf( - '0xbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeedbeadfeed' - ) - const contractDeployParams: Buffer = Buffer.from( - remove0x(abi.encode(['bytes'], [bufToHexString(valToReturn)])), - 'hex' - ) - - let getterAddress: Address - - const deployAssemblyReturningContract = async ( - util: EvmIntrospectionUtil - ): Promise
=> { - const result: ExecutionResult = await util.deployContract( - contractBytecode, - contractDeployParams - ) - return bufToHexString(result.result) - } - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - // deploy the contract whose function `get` returns raw non ABI-encoded bytes - getterAddress = await deployAssemblyReturningContract(evmUtil) - }) - describe('Some helpers', () => { - it('Should correctly duplicateStackAt', async () => { - const initialStackSize: number = 7 - const offsetToDuplicate: number = 3 - const elementsToDuplicate: number = 2 - - const op: EVMBytecode = [ - ...pushStackElements(initialStackSize), - ...duplicateStackAt(offsetToDuplicate, elementsToDuplicate), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - const finalContext: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(op), - 233 - ) - - finalContext.stackDepth.should.equal( - initialStackSize + elementsToDuplicate - ) - const finalStack: Buffer[] = finalContext.stack - finalStack - .slice(0, elementsToDuplicate) - .should.deep.equal( - finalStack.slice( - elementsToDuplicate + offsetToDuplicate, - offsetToDuplicate + 2 * elementsToDuplicate - ) - ) - }) - }) - - describe('storeStackElementsAsMemoryWords', () => { - it('Should store three stack elements in the memory, including the first stack element', async () => { - const stackElements: Buffer[] = [ - Buffer.alloc(32).fill(hexStrToBuf('0xaa')), - Buffer.alloc(32).fill(hexStrToBuf('0xbb')), - Buffer.alloc(32).fill(hexStrToBuf('0xcc')), - ] - // bytecode to push the `stackElements` array to the stack, done in reverse order so stack is [aa, bb, cc] - const pushAndStore: EVMBytecode = [ - getPUSHBuffer(stackElements[2]), - getPUSHBuffer(stackElements[1]), - getPUSHBuffer(stackElements[0]), - ...storeStackElementsAsMemoryWords(3), - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - log.debug( - `running the following storeStackElementsAsMemoryWords bytecode: \n${formatBytecode( - pushAndStore - )}` - ) - const finalContext: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(pushAndStore), - bytecodeToBuffer(pushAndStore).length - 1 - ) - // memory should be the concatenation of the 32 byte words we previously pushed to the stack - finalContext.memory.should.deep.equal(Buffer.concat(stackElements)) - }) - }) - - describe('callContractWithStackElementsAndReturnWordToMemory', () => { - it('Should return the result of a simple contract getter with 0 stack params to memory successfully', async () => { - // get bytecode which calls contract, passing stack elements, and returning the word to memory - const callGetterAndStore: EVMBytecode = callContractWithStackElementsAndReturnWordToMemory( - getterAddress, - getterFunctionName, - 0 - ) - callGetterAndStore.push({ - opcode: Opcode.RETURN, - consumedBytes: undefined, - }) - - const finalContext: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(callGetterAndStore), - 73 - ) - - const methodBytes: Buffer = ethereumjsAbi.methodID(getterFunctionName, []) - // the resulting memory should be: [0000s proceeding method Id, methodId], [valToReturn] - // where [] above indicates a 32 byte word. - const expectedMemorySlice: Buffer = Buffer.concat([ - Buffer.alloc(32 - 4), - methodBytes, - valToReturn, - ]) - - finalContext.memory.should.deep.equal(expectedMemorySlice) - }) - it('Should return the result of a simple contract getter with with stack params 0 and 1 to memory successfully', async () => { - const numStackElementsToPass: number = 3 - - let callGetterAndStoreWithStackParams: EVMBytecode = callContractWithStackElementsAndReturnWordToMemory( - getterAddress, - getterFunctionName, - numStackElementsToPass - ) - - callGetterAndStoreWithStackParams = [ - ...pushStackElements(numStackElementsToPass), - ...callGetterAndStoreWithStackParams, - { opcode: Opcode.RETURN, consumedBytes: undefined }, - ] - log.debug( - `Running getter-storing bytecode which pushes elements to stack: \n${formatBytecode( - callGetterAndStoreWithStackParams - )}` - ) - - const finalContext: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(callGetterAndStoreWithStackParams), - 181 - ) - // the resulting memory should be: [0000s proceeding method Id, methodId], [stack param 0], [stack param 1], [stack param 2] [deadbeef, 0000...] - // where [] above indicates a 32 byte word. - const expectedMemorySlice: Buffer = hexStrToBuf( - '0x000000000000000000000000000000000000000000000000000000006d4ce63c000000000000000000000000000000000000000000000000000000000000000001010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202' + - remove0x(bufToHexString(valToReturn)) - ) - - finalContext.memory.should.deep.equal(expectedMemorySlice) - }) - }) - describe('callContractWithStackElementsAndReturnWordToStack', () => { - const initialMemory: Buffer = Buffer.alloc(32 * 10).fill(25) - const aBigStack: Buffer[] = Array.from({ length: 10 }, (v, k) => - Buffer.from(new Array(32).fill(k)) - ) // this whole thing gets us [0x0000, 0x010101, 0x020202, ...] as 32 byte words - - for (const numStackElsToPass of [0, 1, 2]) { - for (const numWordsToReturn of [0, 1]) { - const thisStack: Buffer[] = aBigStack.slice(0, numStackElsToPass + 1) // +1 since first stack element preserved - it(`Should successfully pass ${numStackElsToPass} concatenated stack elements and methodId as calldata and return ${numWordsToReturn} words to the stack`, async () => { - const setupContextAndExecuteCall: EVMBytecode = setupAndExecuteStaticMemoryCall( - getterAddress, - getterFunctionName, - initialMemory, - thisStack, - numWordsToReturn as 0 | 1 - ) - - log.debug( - `setupAndExecuteStaticMemoryCall(...) bytecode for ${numStackElsToPass} numStackElsToPass and ${numWordsToReturn} numWordsToReturn is: \n${formatBytecode( - setupContextAndExecuteCall - )}` - ) - - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(setupContextAndExecuteCall) - ) - // make sure the calldata is [methodId, thisStack[1], thisStack[2], ...] - callContext.callData.should.deep.equal( - Buffer.concat([getterMethodId, ...thisStack.slice(1)]), - 'Calldata should always be [bytes4 methodId], [stack el 1], [stack el 2]' - ) - - log.debug(`getting final cntexts`) - - const finalContext: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(setupContextAndExecuteCall), - bytecodeToBuffer(setupContextAndExecuteCall).length - 1 - ) - // make sure the memory was not at all affected - finalContext.memory.should.deep.equal( - initialMemory, - 'Memory should not change over the course of memory-static opcode replacements.' - ) - // make sure the returnData was pushed to stack if needed - finalContext.stackDepth.should.equal( - numWordsToReturn + 1, - `Stack does not match requested number of words returned(${numWordsToReturn}) + 1 for preserved first stack element` - ) - bufferUtils - .padLeft(finalContext.stack[0], 32) - .should.deep.eq( - thisStack[0], - 'Operation did not preserve the first stack element correctly' - ) - if (numWordsToReturn === 1) { - finalContext.stack[1].should.deep.equal( - valToReturn, - 'Word returned to stack was not what the getter was told to return!' - ) - } - }) - } - } - }) -}) - -// helper function to generate bytecode which: -// 1. Fills memory as requested -// 2. Sets up the stack as requested -// 3. Executes a setupContextCALLandReturnBuf -// 4. Returns -const setupAndExecuteStaticMemoryCall = ( - callTarget: Address, - targetMethodName: string, - preOperationMemory: Buffer, - initialStack: Buffer[], - numWordsToBeReturned: 0 | 1 -) => { - const replacementOperation: EVMBytecode = callContractWithStackElementsAndReturnWordToStack( - callTarget, - targetMethodName, - initialStack.length - 1, // since we are not passing the first stack element - numWordsToBeReturned - ) - // push to stack in reverse order so that we stack[0] is pushed last - const setStack: EVMBytecode = initialStack - .slice() // slice so we don't reverse original array (reused in testing) - .reverse() - .map((stackEl: Buffer): EVMOpcodeAndBytes => getPUSHBuffer(stackEl)) - return [ - ...setMemory(preOperationMemory), - ...setStack, - ...replacementOperation, - { - opcode: Opcode.RETURN, - consumedBytes: undefined, - }, - ] -} - -// Helper function, sets the memory to the given buffer -const setMemory = (toSet: Buffer): EVMBytecode => { - let op: EVMBytecode = [] - const numWords = Math.ceil(toSet.byteLength / 32) - for (let i = 0; i < numWords; i++) { - op = op.concat([ - getPUSHBuffer(toSet.slice(i * 32, (i + 1) * 32)), - getPUSHIntegerOp(i * 32), - { - opcode: Opcode.MSTORE, - consumedBytes: undefined, - }, - ]) - } - return op -} - -// helper function, sets stack to [00 00 00 00 ...] [01 01 01 01 ...] ... [0N 0N 0N ...] -const pushStackElements = (numElements: number): EVMBytecode => { - const op: EVMBytecode = [] - for (let i = 0; i < numElements; i++) { - op.push(getPUSHBuffer(Buffer.alloc(32).fill(numElements - i - 1))) - } - return op -} diff --git a/packages/rollup-dev-tools/test/transpiler/transpile.jump.spec.ts b/packages/rollup-dev-tools/test/transpiler/transpile.jump.spec.ts deleted file mode 100644 index d957f78bcf2d3..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/transpile.jump.spec.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { should } from '../setup' - -/* External Imports */ -import { bufferUtils, bufToHexString } from '@eth-optimism/core-utils' -import { - Opcode, - EVMOpcode, - EVMBytecode, - bytecodeToBuffer, - bufferToBytecode, - EVMOpcodeAndBytes, - formatBytecode, -} from '@eth-optimism/rollup-core' - -/* Internal imports */ -import { - ErroredTranspilation, - OpcodeReplacer, - OpcodeWhitelist, - SuccessfulTranspilation, - TranspilationResult, - Transpiler, -} from '../../src/types/transpiler' -import { - TranspilerImpl, - OpcodeReplacerImpl, - OpcodeWhitelistImpl, -} from '../../src/tools/transpiler' -import { - assertExecutionEqual, - stateManagerAddress, - whitelistedOpcodes, -} from '../helpers' -import { EvmIntrospectionUtil } from '../../src/types/vm' -import { EvmIntrospectionUtilImpl } from '../../src/tools/vm' - -/** - * Validates transpiled JUMP bytecode provided via the TranspilationResult parameter. - * - * @param successResult The transpilation result in question. - */ -const validateJumpBytecode = (successResult: SuccessfulTranspilation): void => { - const outputBytecode: EVMBytecode = bufferToBytecode(successResult.bytecode) - - let pc = 0 - let lastOpcode: EVMOpcodeAndBytes - const jumpdestIndexes: number[] = [] - const opcodesBeforeJump: Map = new Map< - number, - EVMOpcodeAndBytes - >() - // Build map of index => opcode immediately before JUMP and get index of footer switch - for (const opcodeAndBytes of outputBytecode) { - if (opcodeAndBytes.opcode === Opcode.JUMPDEST) { - jumpdestIndexes.push(pc) - } - if ( - opcodeAndBytes.opcode === Opcode.JUMP || - opcodeAndBytes.opcode === Opcode.JUMPI - ) { - opcodesBeforeJump.set( - pc - 1 - lastOpcode.opcode.programBytesConsumed, - lastOpcode - ) - } - lastOpcode = opcodeAndBytes - pc += 1 + opcodeAndBytes.opcode.programBytesConsumed - } - - jumpdestIndexes.length.should.be.greaterThan( - 0, - 'There should be JUMPDESTs, but there are not!' - ) - - const switchJumpdestIndex: number = jumpdestIndexes.pop() - const switchJumpdest: Buffer = successResult.bytecode.slice( - switchJumpdestIndex, - switchJumpdestIndex + 1 - ) - switchJumpdest.should.eql( - Opcode.JUMPDEST.code, - `Switch JUMPDEST index is ${switchJumpdestIndex}, but byte at that index is ${bufToHexString( - switchJumpdest - )}, not ${bufToHexString(Opcode.JUMPDEST.code)}` - ) - - const switchSuccessJumpdestIndex: number = jumpdestIndexes.pop() - const switchSuccessJumpdest: Buffer = successResult.bytecode.slice( - switchSuccessJumpdestIndex, - switchSuccessJumpdestIndex + 1 - ) - switchSuccessJumpdest.should.eql( - Opcode.JUMPDEST.code, - `Switch success JUMPDEST index is ${switchJumpdestIndex}, but byte at that index is ${bufToHexString( - switchJumpdest - )}, not ${bufToHexString(Opcode.JUMPDEST.code)}` - ) - - opcodesBeforeJump.size.should.be.greaterThan( - 0, - 'opcodesBeforeJump should have entries but does not!' - ) -} - -const getSuccessfulTranspilationResult = ( - transpiler: Transpiler, - bytecode: Buffer -): SuccessfulTranspilation => { - const result: TranspilationResult = transpiler.transpileRawBytecode(bytecode) - result.succeeded.should.equal( - true, - `${JSON.stringify((result as ErroredTranspilation).errors)}` - ) - return result as SuccessfulTranspilation -} - -describe('Transpile - JUMPs', () => { - let opcodeWhitelist: OpcodeWhitelist - let transpiler: Transpiler - let replacer: OpcodeReplacer - let evmUtil: EvmIntrospectionUtil - - beforeEach(async () => { - opcodeWhitelist = new OpcodeWhitelistImpl(whitelistedOpcodes) - replacer = new OpcodeReplacerImpl( - stateManagerAddress, - new Map() - ) - transpiler = new TranspilerImpl(opcodeWhitelist, replacer) - evmUtil = await EvmIntrospectionUtilImpl.create() - }) - - it('handles simple JUMPs properly', async () => { - const evmBytecode: EVMBytecode = [ - { - opcode: Opcode.PUSH2, - consumedBytes: bufferUtils.numberToBufferPacked(4, 2), - }, - { opcode: Opcode.JUMP, consumedBytes: undefined }, - { opcode: Opcode.JUMPDEST, consumedBytes: undefined }, - { opcode: Opcode.STOP, consumedBytes: undefined }, - ] - const initialBytecode: Buffer = bytecodeToBuffer(evmBytecode) - - const successResult: SuccessfulTranspilation = getSuccessfulTranspilationResult( - transpiler, - initialBytecode - ) - - validateJumpBytecode(successResult) - await assertExecutionEqual(evmUtil, initialBytecode, successResult.bytecode) - }) - - it('handles simple JUMPIs properly', async () => { - const evmBytecode: EVMBytecode = [ - { opcode: Opcode.PUSH32, consumedBytes: bufferUtils.numberToBuffer(1) }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(67), - }, - { opcode: Opcode.JUMPI, consumedBytes: undefined }, - { opcode: Opcode.JUMPDEST, consumedBytes: undefined }, - { opcode: Opcode.STOP, consumedBytes: undefined }, - ] - const initialBytecode: Buffer = bytecodeToBuffer(evmBytecode) - - const successResult: SuccessfulTranspilation = getSuccessfulTranspilationResult( - transpiler, - initialBytecode - ) - validateJumpBytecode(successResult) - await assertExecutionEqual(evmUtil, initialBytecode, successResult.bytecode) - }) - - it('handles complex JUMP(I)s properly', async () => { - const evmBytecode: EVMBytecode = [ - { opcode: Opcode.PUSH32, consumedBytes: bufferUtils.numberToBuffer(1) }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(103), - }, - { opcode: Opcode.JUMPI, consumedBytes: undefined }, - { opcode: Opcode.PUSH1, consumedBytes: bufferUtils.numberToBuffer(1) }, - { opcode: Opcode.DUP1, consumedBytes: undefined }, - { opcode: Opcode.SWAP1, consumedBytes: undefined }, - { opcode: Opcode.DIV, consumedBytes: undefined }, - { opcode: Opcode.JUMPDEST, consumedBytes: undefined }, - { opcode: Opcode.STOP, consumedBytes: undefined }, - { opcode: Opcode.JUMPDEST, consumedBytes: undefined }, - { opcode: Opcode.RETURN, consumedBytes: undefined }, - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(107), - }, - { opcode: Opcode.JUMP, consumedBytes: bufferUtils.numberToBuffer(1) }, - ] - const initialBytecode: Buffer = bytecodeToBuffer(evmBytecode) - - const successResult: SuccessfulTranspilation = getSuccessfulTranspilationResult( - transpiler, - initialBytecode - ) - validateJumpBytecode(successResult) - await assertExecutionEqual(evmUtil, initialBytecode, successResult.bytecode) - }) - - it('handles code without JUMPs properly', async () => { - const evmBytecode: EVMBytecode = [ - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(67), - }, - { opcode: Opcode.JUMPDEST, consumedBytes: undefined }, - { opcode: Opcode.STOP, consumedBytes: undefined }, - ] - const initialBytecode: Buffer = bytecodeToBuffer(evmBytecode) - - const successResult: SuccessfulTranspilation = getSuccessfulTranspilationResult( - transpiler, - initialBytecode - ) - validateJumpBytecode(successResult) - await assertExecutionEqual(evmUtil, initialBytecode, successResult.bytecode) - }) - - it('handles code without JUMPs or JUMPDESTs properly', async () => { - const evmBytecode: EVMBytecode = [ - { - opcode: Opcode.PUSH32, - consumedBytes: bufferUtils.numberToBuffer(67), - }, - ] - const initialBytecode: Buffer = bytecodeToBuffer(evmBytecode) - - const result: TranspilationResult = transpiler.transpileRawBytecode( - initialBytecode - ) - result.succeeded.should.equal( - true, - 'Transpilation should have succeeded but did not!' - ) - - const successResult: SuccessfulTranspilation = result as SuccessfulTranspilation - - successResult.bytecode.should.eql( - initialBytecode, - 'Transpilation should not have changed anything but did!' - ) - - await assertExecutionEqual(evmUtil, initialBytecode, successResult.bytecode) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/transpile.spec.ts b/packages/rollup-dev-tools/test/transpiler/transpile.spec.ts deleted file mode 100644 index 6b3ee5ff7dfeb..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/transpile.spec.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { should } from '../setup' - -/* External Imports */ -import { - bufferUtils, - bufToHexString, - getLogger, -} from '@eth-optimism/core-utils' - -import { - Opcode, - EVMOpcode, - EVMBytecode, - bytecodeToBuffer, -} from '@eth-optimism/rollup-core' - -/* Internal imports */ -import { - SuccessfulTranspilation, - ErroredTranspilation, - OpcodeReplacer, - OpcodeWhitelist, - TranspilationErrors, - TranspilationResult, - Transpiler, -} from '../../src/types/transpiler' -import { - TranspilerImpl, - OpcodeReplacerImpl, - OpcodeWhitelistImpl, -} from '../../src/tools/transpiler' -import { - invalidBytesConsumedBytecodeNoReturn, - invalidOpcode, - multipleErrors, - multipleNonWhitelisted, - singleNonWhitelisted, - stateManagerAddress, - validBytecode, - whitelistedOpcodes, -} from '../helpers' - -const log = getLogger(`transpile`) -const haltingOpcodes: EVMOpcode[] = Opcode.HALTING_OP_CODES -const haltingOpcodesNoJump: EVMOpcode[] = haltingOpcodes.filter( - (x) => x.name !== 'JUMP' -) -const jumps: EVMOpcode[] = Opcode.JUMP_OP_CODES - -describe('Transpile', () => { - let opcodeWhitelist: OpcodeWhitelist - let transpiler: Transpiler - let replacer: OpcodeReplacer - - beforeEach(() => { - opcodeWhitelist = new OpcodeWhitelistImpl(whitelistedOpcodes) - replacer = new OpcodeReplacerImpl( - stateManagerAddress, - new Map() - ) - transpiler = new TranspilerImpl(opcodeWhitelist, replacer) - }) - - describe('Valid input', () => { - it('correctly accepts valid bytecode input', () => { - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecodeToBuffer(validBytecode) - ) - result.succeeded.should.equal(true) - }) - }) - - describe('Unsupported Opcodes', () => { - it('flags unsupported opcode', () => { - const inputBytecode: Buffer = Buffer.concat([ - bytecodeToBuffer(validBytecode), - invalidOpcode, - ]) - - const result: TranspilationResult = transpiler.transpileRawBytecode( - inputBytecode - ) - - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(inputBytecode.length - 1) - error.errors[0].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - }) - - it('flags multiple unsupported opcodes', () => { - const inputBytecode: Buffer = Buffer.concat([ - invalidOpcode, - bytecodeToBuffer(validBytecode), - invalidOpcode, - ]) - - const result: TranspilationResult = transpiler.transpileRawBytecode( - inputBytecode - ) - - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(2) - error.errors[0].index.should.equal(0) - error.errors[0].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - error.errors[1].index.should.equal(inputBytecode.length - 1) - error.errors[1].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - }) - }) - - describe('Whitelist Enforcement', () => { - it('flags non-whitelisted opcode', () => { - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecodeToBuffer(singleNonWhitelisted) - ) - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(8) - error.errors[0].error.should.equal( - TranspilationErrors.OPCODE_NOT_WHITELISTED - ) - }) - - it('flags multiple non-whitelisted opcode', () => { - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecodeToBuffer(multipleNonWhitelisted) - ) - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(2) - error.errors[0].index.should.equal(8) - error.errors[0].error.should.equal( - TranspilationErrors.OPCODE_NOT_WHITELISTED - ) - - error.errors[1].index.should.equal(12) - error.errors[1].error.should.equal( - TranspilationErrors.OPCODE_NOT_WHITELISTED - ) - }) - }) - - describe('Enforces Invalid Bytes Consumed', () => { - // TODO: Handle this as a warning, since we now pad instead of throwing - it.skip('flags invalid bytes consumed', () => { - const bytecode: Buffer = bytecodeToBuffer( - invalidBytesConsumedBytecodeNoReturn - ) - - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(bytecode.length - 1) - error.errors[0].error.should.equal( - TranspilationErrors.INVALID_BYTES_CONSUMED - ) - }) - - it('flags invalid (less) bytes consumed as unrecognized opcode', () => { - const bytecode: Buffer = bytecodeToBuffer([ - { - opcode: Opcode.PUSH1, - consumedBytes: undefined, - }, - { - opcode: Opcode.PUSH1, - consumedBytes: invalidOpcode, - }, - ]) - - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(2) - error.errors[0].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - error.errors[0].message.endsWith('?').should.equal(true) - }) - - it('flags invalid (more) bytes consumed as unrecognized opcode', () => { - const bytecode: Buffer = bytecodeToBuffer([ - { - opcode: Opcode.PUSH1, - consumedBytes: Buffer.concat([ - Buffer.from('00', 'hex'), - invalidOpcode, - ]), - }, - { - opcode: Opcode.PUSH1, - consumedBytes: invalidOpcode, - }, - ]) - - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(2) - error.errors[0].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - error.errors[0].message.endsWith('?').should.equal(true) - }) - }) - - describe('Multiple Errors', () => { - it('flags all error types at once', () => { - const bytecode: Buffer = Buffer.concat([ - invalidOpcode, - bytecodeToBuffer(multipleErrors), - ]) - - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal(false) - - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(2) - error.errors[0].index.should.equal(0) - error.errors[0].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - error.errors[1].index.should.equal(bytecode.length - 2) - error.errors[1].error.should.equal( - TranspilationErrors.OPCODE_NOT_WHITELISTED - ) - }) - }) - - describe('handles unreachable code', async () => { - it(`skips unreachable bytecode after a halting opcode`, async () => { - for (const haltingOp of haltingOpcodes) { - const bytecode: Buffer = Buffer.concat([haltingOp.code, invalidOpcode]) - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal( - true, - `Bytecode containing invalid opcodes in unreachable code after a ${haltingOp.name} should not have failed!` - ) - const success = result as SuccessfulTranspilation - success.bytecode.should.eql( - bytecode, - `Bytecode containing invalid opcodes in unreachable code after a ${haltingOp.name} should not have changed any bytecode!` - ) - } - }) - it('skips bytecode after an unreachable JUMPDEST', async () => { - for (const haltingOp of haltingOpcodesNoJump) { - const bytecode: Buffer = Buffer.concat([ - haltingOp.code, - invalidOpcode, - Opcode.JUMPDEST.code, - invalidOpcode, - ]) - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal( - true, - `Bytecode containing invalid opcodes in unreachable code after unreachable JUMPDEST (after a ${haltingOp.name})should not have failed!` - ) - const success = result as SuccessfulTranspilation - success.bytecode.should.eql( - bytecode, - `Bytecode containing invalid opcodes in unreachable code after unreachable JUMPDEST (after a ${haltingOp.name}) should not have changed any bytecode!` - ) - } - }) - it('parses opcodes after a reachable JUMPDEST', async () => { - for (const haltingOp of haltingOpcodesNoJump) { - for (const jump of jumps) { - const bytecode: Buffer = Buffer.concat([ - jump.code, - // JUMPDEST here so that the haltingOp is reachable - Opcode.JUMPDEST.code, - haltingOp.code, - Opcode.JUMPDEST.code, - invalidOpcode, - ]) - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal( - false, - `Bytecode containing invalid opcodes after reachable JUMPDEST preceded by a ${haltingOp.name} should have failed!` - ) - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(bytecode.length - 1) - error.errors[0].error.should.equal( - TranspilationErrors.UNSUPPORTED_OPCODE - ) - } - } - }) - it('parses opcodes after first JUMP and JUMPDEST', async () => { - const bytecode: Buffer = Buffer.concat([ - Opcode.JUMP.code, - Opcode.JUMPDEST.code, - invalidOpcode, - ]) - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal( - false, - `Bytecode containing invalid opcodes after reachable JUMPDEST should have failed!` - ) - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(bytecode.length - 1) - error.errors[0].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - }) - - it('parses opcodes after JUMPI', async () => { - const bytecode: Buffer = Buffer.concat([Opcode.JUMPI.code, invalidOpcode]) - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal( - false, - `Bytecode containing invalid opcodes after reachable JUMPI should have failed!` - ) - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(bytecode.length - 1) - error.errors[0].error.should.equal(TranspilationErrors.UNSUPPORTED_OPCODE) - }) - - it('should correctly handle alternating reachable/uncreachable code ending in reachable, valid code', async () => { - for (const haltingOp of haltingOpcodesNoJump) { - for (const jump of jumps) { - let bytecode: Buffer = Buffer.concat([ - jump.code, - // JUMPDEST here so that the haltingOp is reachable - Opcode.JUMPDEST.code, - ]) - for (let i = 0; i < 3; i++) { - bytecode = Buffer.concat([ - bytecode, - haltingOp.code, - // Unreachable, invalid code - invalidOpcode, - Opcode.JUMPDEST.code, - // Reachable, valid code - Opcode.ADD.code, - ]) - } - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal( - true, - `Long bytecode containing alternating valid reachable and invalid unreachable code failed!` - ) - } - } - }) - - it('should correctly handle alternating reachable/uncreachable code ending in reachable, invalid code', async () => { - for (const haltingOp of haltingOpcodesNoJump) { - for (const jump of jumps) { - let bytecode: Buffer = Buffer.concat([ - jump.code, - // JUMPDEST here so that the haltingOp is reachable - Opcode.JUMPDEST.code, - ]) - for (let i = 0; i < 3; i++) { - bytecode = Buffer.concat([ - bytecode, - haltingOp.code, - // Unreachable, invalid code - invalidOpcode, - Opcode.JUMPDEST.code, - // Reachable, valid code - Opcode.ADD.code, - ]) - } - bytecode = Buffer.concat([ - bytecode, - // Reachable, invalid code - invalidOpcode, - ]) - const result: TranspilationResult = transpiler.transpileRawBytecode( - bytecode - ) - result.succeeded.should.equal( - false, - `Long bytecode ending in reachable, invalid code should have failed!` - ) - const error: ErroredTranspilation = result as ErroredTranspilation - error.errors.length.should.equal(1) - error.errors[0].index.should.equal(bytecode.length - 1) - error.errors[0].error.should.equal( - TranspilationErrors.UNSUPPORTED_OPCODE - ) - } - } - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/transpiler/util/generate-bytecode-files.spec.ts b/packages/rollup-dev-tools/test/transpiler/util/generate-bytecode-files.spec.ts deleted file mode 100644 index 4a58d8afa8ea7..0000000000000 --- a/packages/rollup-dev-tools/test/transpiler/util/generate-bytecode-files.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* External Imports */ -import { bufToHexString } from '@eth-optimism/core-utils' -import { - bytecodeToBuffer, - EVMBytecode, - Opcode, -} from '@eth-optimism/rollup-core' -import * as fs from 'fs' -import { resolve } from 'path' - -// describe('Used to generate transpilation input files', () => { -// it('Generates a file to test/transpiler/util/generated/input.bytecode', () => { -// const bytecode: EVMBytecode = [ -// { opcode: Opcode.PUSH1, consumedBytes: undefined }, -// { opcode: Opcode.SLOAD, consumedBytes: undefined }, -// { opcode: Opcode.SLOAD, consumedBytes: undefined }, -// { opcode: Opcode.PUSH1, consumedBytes: undefined }, -// ] -// -// const bytes: Buffer = bytecodeToBuffer(bytecode) -// console.log(`Hex Bytes: ${bufToHexString(bytes)}`) -// -// const generatedDir: string = resolve( -// __dirname, -// '../../../test/transpiler/util/generated' -// ) -// -// if (!fs.existsSync(generatedDir)) { -// fs.mkdirSync(generatedDir) -// } -// -// fs.writeFileSync(`${generatedDir}/input.bytecode`, bytes) -// }) -// }) diff --git a/packages/rollup-dev-tools/test/vm/evm-introspection-util.call-inspection.spec.ts b/packages/rollup-dev-tools/test/vm/evm-introspection-util.call-inspection.spec.ts deleted file mode 100644 index 5cf891cd8681a..0000000000000 --- a/packages/rollup-dev-tools/test/vm/evm-introspection-util.call-inspection.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - remove0x, - hexStrToBuf, - TestUtils, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, - Opcode, -} from '@eth-optimism/rollup-core' -import * as ethereumjsAbi from 'ethereumjs-abi' - -/* Internal Imports */ -import { should } from '../setup' -import { - EvmIntrospectionUtil, - ExecutionResult, - CallContext, - InvalidCALLStackError, -} from '../../src/types/vm' -import { EvmIntrospectionUtilImpl } from '../../src/tools/vm' -import { setMemory, setupStackAndCALL } from '../helpers' - -import * as AssemblyReturnGetter from '../contracts/build/AssemblyReturnGetter.json' -const abi = new ethers.utils.AbiCoder() - -const getRandomWordsWithMethodIdAtOffset = ( - methodName: string, - offset: number, - totalWords: number -): Buffer => { - const totalSizeBytes: number = 32 * totalWords - return Buffer.alloc(totalSizeBytes) - .fill(25, 0, offset) - .fill(ethereumjsAbi.methodID(methodName, []), offset, offset + 4) - .fill(69, offset + 4) -} - -const valToReturn: Buffer = hexStrToBuf('0xdeadbeef') -const deployGetterContract = async ( - util: EvmIntrospectionUtil -): Promise
=> { - const getterBytecode: Buffer = Buffer.from( - AssemblyReturnGetter.bytecode, - 'hex' - ) - const result: ExecutionResult = await util.deployContract( - getterBytecode, - Buffer.from(remove0x(abi.encode(['bytes'], [valToReturn])), 'hex') - ) - - return bufToHexString(result.result) -} - -const gas: number = 10001 -const value: number = 0 -const argOffset: number = 38 -const argLength: number = 4 -const retOffset: number = 7 -const retLength: number = 4 - -describe('EvmIntrospectionUtil', () => { - let evmUtil: EvmIntrospectionUtil - let returnerAddress: Address - const getterMethodName = 'get' - - describe('CallContext', () => { - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - returnerAddress = await deployGetterContract(evmUtil) - }) - - it('should successfully parse a CALL to a simple returner contract', async () => { - const memoryToFill: Buffer = getRandomWordsWithMethodIdAtOffset( - getterMethodName, - argOffset, - 7 // random val - ) - - const fillMemoryAndCall: EVMBytecode = [ - ...setMemory(memoryToFill), - ...setupStackAndCALL( - gas, - returnerAddress, - value, - argOffset, - argLength, - retOffset, - retLength - ), - { - opcode: Opcode.RETURN, - consumedBytes: undefined, - }, - ] - - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(fillMemoryAndCall) - ) - - callContext.stepContext.memory.should.deep.equal( - memoryToFill, - 'context should have the pre-RETURNed memory' - ) - callContext.stepContext.opcode.should.equal( - Opcode.CALL, - 'context should be a CALL' - ) - callContext.input.argLength.should.equal( - argLength, - 'argLength should be the same as provided to call' - ) - callContext.input.argOffset.should.equal( - argOffset, - 'argOffset should be the same as provided to call' - ) - const methodId: Buffer = ethereumjsAbi.methodID(getterMethodName, []) - callContext.callData.should.deep.equal( - methodId, - 'calldata should be the methodId' - ) - callContext.input.addr.should.deep.equal( - returnerAddress, - 'CALL should be headed to the returner address specified' - ) - callContext.input.retOffset.should.equal( - retOffset, - 'retOffset should be the same as provided to call' - ) - callContext.input.retLength.should.equal( - retLength, - 'retLength should be the same as provided to call' - ) - }) - - it('Should pad calldata with 0s if exceeding memory size', async () => { - const callWitOutOfBoundsMemory: EVMBytecode = setupStackAndCALL( - gas, - returnerAddress, - value, - 1000, // since we set no memory beforehand this should be bigger than context.memory - argLength, - retOffset, - retLength - ) - const callContext: CallContext = await evmUtil.getCallContext( - bytecodeToBuffer(callWitOutOfBoundsMemory) - ) - callContext.callData.equals(Buffer.alloc(argLength)).should.be.true - }) - - it('Should throw if too few stack arguments', async () => { - const callWithEmptyStack: EVMBytecode = [ - { opcode: Opcode.CALL, consumedBytes: undefined }, - ] - TestUtils.assertThrowsAsync(async () => { - await evmUtil.getCallContext(bytecodeToBuffer(callWithEmptyStack)) - }, InvalidCALLStackError) - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/vm/evm-introspection-util.multi-tx.spec.ts b/packages/rollup-dev-tools/test/vm/evm-introspection-util.multi-tx.spec.ts deleted file mode 100644 index 08a827c5dfddd..0000000000000 --- a/packages/rollup-dev-tools/test/vm/evm-introspection-util.multi-tx.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -/* External Imports */ -import { ethers } from 'ethers' -import { - bufToHexString, - isValidHexAddress, - remove0x, -} from '@eth-optimism/core-utils' -import { - Address, - bytecodeToBuffer, - EVMBytecode, -} from '@eth-optimism/rollup-core' - -/* Internal Imports */ -import { should } from '../setup' -import { - EvmErrors, - EvmIntrospectionUtil, - ExecutionResult, -} from '../../src/types/vm' -import { EvmIntrospectionUtilImpl } from '../../src/tools/vm' -import { - emptyBuffer, - getBytecodeCallingContractMethod, - invalidBytesConsumedBytecode, -} from '../helpers' - -const abi = new ethers.utils.AbiCoder() - -/* Contracts */ -import * as SimpleCallable from '../contracts/build/SimpleCallable.json' - -describe('EvmIntrospectionUtil', () => { - let evmUtil: EvmIntrospectionUtil - let contractBytecode: Buffer - const updateFunctionName: string = 'update' - const getterFunctionName: string = 'get' - const contractDeployParams: Buffer = Buffer.from( - remove0x(abi.encode(['bytes'], ['0xdeadbeef'])), - 'hex' - ) - - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - contractBytecode = Buffer.from(SimpleCallable.bytecode, 'hex') - }) - - const deployContract = async ( - util: EvmIntrospectionUtil - ): Promise
=> { - const result: ExecutionResult = await util.deployContract( - contractBytecode, - contractDeployParams - ) - - should.exist(result, 'Result should always exist!') - should.not.exist(result.error, 'Result error mismatch!') - should.exist(result.result, 'Result result mismatch!') - - const address: Address = bufToHexString(result.result) - isValidHexAddress(address).should.equal(true, 'Invalid address!') - return address - } - - describe('deployContract', () => { - it('should deploy simple contract successfully', async () => { - await deployContract(evmUtil) - }) - - it('should fail on invalid bytecode', async () => { - const result: ExecutionResult = await evmUtil.deployContract( - bytecodeToBuffer(invalidBytesConsumedBytecode) - ) - - result.result.should.eql(emptyBuffer, 'Result should be empty!') - result.error.should.eql( - EvmErrors.STACK_UNDERFLOW_ERROR, - 'Invalid deploy should revert!' - ) - }) - }) - - describe('callContract', () => { - let address: Address - - beforeEach(async () => { - address = await deployContract(evmUtil) - }) - - it('should call deployed simple contract successfully -- without parameters', async () => { - const result: ExecutionResult = await evmUtil.callContract( - address, - getterFunctionName - ) - - should.exist(result, 'Result should never be empty!') - should.not.exist(result.error, 'Error mismatch!') - result.result.should.eql(contractDeployParams, 'Result mismatch!') - }) - - it('should call deployed simple contract successfully -- With parameters', async () => { - const result: ExecutionResult = await evmUtil.callContract( - address, - updateFunctionName, - ['bytes'], - contractDeployParams - ) - - should.exist(result, 'Result should never be empty!') - should.not.exist(result.error, 'Error mismatch!') - result.result.should.eql(contractDeployParams, 'Result mismatch!') - }) - - it('should fail to call invalid contract method', async () => { - const result: ExecutionResult = await evmUtil.callContract( - address, - 'derp' - ) - - should.exist(result, 'Result should never be empty!') - should.exist(result.result, 'Result should always exist!') - result.result.should.eql(emptyBuffer, 'Result mismatch!') - should.exist(result.error, 'Error mismatch!') - result.error.should.eql(EvmErrors.REVERT_ERROR, 'Result mismatch!') - }) - - it('should fail to call contract method with invalid param type', async () => { - const result: ExecutionResult = await evmUtil.callContract( - address, - updateFunctionName, - ['bytes32'], - contractDeployParams - ) - - should.exist(result, 'Result should never be empty!') - should.exist(result.result, 'Result should always exist!') - result.result.should.eql(emptyBuffer, 'Result mismatch!') - should.exist(result.error, 'Error mismatch!') - result.error.should.eql(EvmErrors.REVERT_ERROR, 'Result mismatch!') - }) - - it('should fail to call contract method with invalid parameter', async () => { - const result: ExecutionResult = await evmUtil.callContract( - address, - updateFunctionName, - ['bytes'], - emptyBuffer - ) - - should.exist(result, 'Result should never be empty!') - should.exist(result.result, 'Result should always exist!') - result.result.should.eql(emptyBuffer, 'Result mismatch!') - should.exist(result.error, 'Error mismatch!') - result.error.should.eql(EvmErrors.REVERT_ERROR, 'Result mismatch!') - }) - - it('should fail to call contract method expecting params without params', async () => { - const result: ExecutionResult = await evmUtil.callContract( - address, - updateFunctionName - ) - - should.exist(result, 'Result should never be empty!') - should.exist(result.result, 'Result should always exist!') - result.result.should.eql(emptyBuffer, 'Result mismatch!') - should.exist(result.error, 'Error mismatch!') - result.error.should.eql(EvmErrors.REVERT_ERROR, 'Result mismatch!') - }) - }) - - describe('Deploy + Execute bytecode calling deployed contract', () => { - let address: Address - - beforeEach(async () => { - address = await deployContract(evmUtil) - }) - - it('should call deployed contract and return value', async () => { - const bytecode: EVMBytecode = getBytecodeCallingContractMethod( - address, - getterFunctionName, - contractDeployParams.length - ) - const res: ExecutionResult = await evmUtil.getExecutionResult( - bytecodeToBuffer(bytecode) - ) - - should.exist(res, 'Result should always exist!') - should.not.exist(res.error, 'Error mismatch!') - should.exist(res.result, 'Result should exist!') - res.result.should.eql(contractDeployParams, 'Result mismatch!') - }) - }) -}) diff --git a/packages/rollup-dev-tools/test/vm/evm-introspection-util.single-tx.spec.ts b/packages/rollup-dev-tools/test/vm/evm-introspection-util.single-tx.spec.ts deleted file mode 100644 index f697020b299d3..0000000000000 --- a/packages/rollup-dev-tools/test/vm/evm-introspection-util.single-tx.spec.ts +++ /dev/null @@ -1,559 +0,0 @@ -/* External Imports */ -import { bytecodeToBuffer } from '@eth-optimism/rollup-core' -import { bufferUtils } from '@eth-optimism/core-utils' - -/* Internal Imports */ -import { should } from '../setup' -import { - EvmErrors, - EvmIntrospectionUtil, - ExecutionComparison, - ExecutionResult, - ExecutionResultComparison, - StepContext, -} from '../../src/types/vm' -import { EvmIntrospectionUtilImpl } from '../../src/tools/vm' -import { - emptyBuffer, - invalidBytesConsumedBytecode, - memoryAndStackBytecode, - memoryDiffersBytecode, - returnNumberBytecode, - stackDiffersBytecode, - voidBytecode, - voidBytecodeWithPushPop, -} from '../helpers' - -describe('EvmIntrospectionUtil', () => { - let evmUtil: EvmIntrospectionUtil - - beforeEach(async () => { - evmUtil = await EvmIntrospectionUtilImpl.create() - }) - - describe('getExecutionResult', () => { - it('handles emptyBuffer case', async () => { - const res: ExecutionResult = await evmUtil.getExecutionResult(emptyBuffer) - - should.not.exist( - res.error, - 'Simple bytecode to return a number yielded error!' - ) - res.result.should.eql(emptyBuffer, 'Got unexpected result!') - }) - - it('gets execution result of simple bytecode to return a number', async () => { - const num: number = 1 - const res: ExecutionResult = await evmUtil.getExecutionResult( - bytecodeToBuffer(returnNumberBytecode(num)) - ) - - should.not.exist( - res.error, - 'Simple bytecode to return a number yielded error!' - ) - res.result.should.eql( - bufferUtils.numberToBuffer(num), - 'Got unexpected result!' - ) - }) - - it('handles void case', async () => { - const res: ExecutionResult = await evmUtil.getExecutionResult( - bytecodeToBuffer(voidBytecode) - ) - - should.not.exist( - res.error, - 'Simple bytecode to return a number yielded error!' - ) - res.result.should.eql(emptyBuffer, 'Got unexpected result!') - }) - - it('handles errors', async () => { - const res: ExecutionResult = await evmUtil.getExecutionResult( - bytecodeToBuffer(invalidBytesConsumedBytecode) - ) - - should.exist(res.error, 'Invalid bytecode should yield error!') - res.error.should.equal(EvmErrors.STACK_UNDERFLOW_ERROR) - - res.result.should.eql(emptyBuffer, 'Got unexpected result!') - }) - }) - - describe('getExecutionResultComparison', () => { - it('handles emptyBuffer case', async () => { - const res: ExecutionResultComparison = await evmUtil.getExecutionResultComparison( - emptyBuffer, - emptyBuffer - ) - - res.resultsDiffer.should.equal(false, 'Results differ mismatch!') - res.firstResult.result.should.eql( - res.secondResult.result, - 'Result mismatch!' - ) - }) - - it('handles different bytecode with same output case', async () => { - const res: ExecutionResultComparison = await evmUtil.getExecutionResultComparison( - emptyBuffer, - bytecodeToBuffer(voidBytecode) - ) - - res.resultsDiffer.should.equal(false, 'Results differ mismatch!') - res.firstResult.result.should.eql( - res.secondResult.result, - 'Result mismatch!' - ) - }) - - it('ensures results differ when they should', async () => { - const res: ExecutionResultComparison = await evmUtil.getExecutionResultComparison( - bytecodeToBuffer(returnNumberBytecode(1)), - bytecodeToBuffer(voidBytecode) - ) - - res.resultsDiffer.should.equal(true, 'Results differ mismatch!') - res.firstResult.result.should.eql( - bufferUtils.numberToBuffer(1), - 'first result mismatch!' - ) - res.secondResult.result.should.eql(emptyBuffer, 'Result mismatch!') - }) - - it('ensures non-void results match', async () => { - const res: ExecutionResultComparison = await evmUtil.getExecutionResultComparison( - bytecodeToBuffer(returnNumberBytecode(1)), - bytecodeToBuffer(returnNumberBytecode(1)) - ) - - res.resultsDiffer.should.equal(false, 'Results differ mismatch!') - res.firstResult.should.eql(res.secondResult, 'Result mismatch!') - }) - - it('ensures results match on error', async () => { - const res: ExecutionResultComparison = await evmUtil.getExecutionResultComparison( - bytecodeToBuffer(invalidBytesConsumedBytecode), - bytecodeToBuffer(invalidBytesConsumedBytecode) - ) - - res.resultsDiffer.should.equal(false, 'Results differ mismatch!') - res.firstResult.should.eql(res.secondResult, 'Result mismatch!') - }) - }) - - describe('getStepContextBeforeStep', () => { - it('handles emptyBuffer case', async () => { - const ctx: StepContext = await evmUtil.getStepContextBeforeStep( - emptyBuffer, - 1 - ) - - should.not.exist( - ctx, - 'Context should not exist before emptyBuffer bytecode execution!' - ) - }) - - it('is undefined if step is not hit', async () => { - const ctx: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(memoryAndStackBytecode), - 3 - ) - - should.not.exist( - ctx, - 'Context should not exist since PC index is not hit!' - ) - }) - - it('works for emptyBuffer memory & stack', async () => { - const ctx: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(voidBytecode), - 0 - ) - - should.exist(ctx, 'Empty memory and stack context should exist!') - - ctx.pc.should.equal(0, 'PC mismatch!') - ctx.opcode.should.equal(voidBytecode[0].opcode, 'Opcode mismatch!') - ctx.stackDepth.should.equal(0, 'Stack depth mismatch!') - ctx.stack.should.eql([], 'Stack mismatch!') - ctx.memoryWordCount.should.equal(0, 'Memory word count mismatch!') - ctx.memory.should.eql(emptyBuffer, 'Memory mismatch!') - }) - - it('works for populated memory & stack', async () => { - const ctx: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(memoryAndStackBytecode), - 38 - ) - - should.exist(ctx, 'Memory and stack context should exist!') - - ctx.pc.should.equal(38, 'PC mismatch!') - ctx.opcode.should.equal( - memoryAndStackBytecode[4].opcode, - 'Opcode mismatch!' - ) - ctx.stackDepth.should.equal(1, 'Stack depth mismatch!') - ctx.stack[0].should.eql( - memoryAndStackBytecode[0].consumedBytes, - 'Stack mismatch!' - ) - ctx.memoryWordCount.should.equal(4, 'Memory word count mismatch!') - ctx.memory.should.eql( - Buffer.from('00'.repeat(127) + '01', 'hex'), - 'Memory mismatch!' - ) - }) - - it('handles case where code errors after step', async () => { - const ctx: StepContext = await evmUtil.getStepContextBeforeStep( - bytecodeToBuffer(invalidBytesConsumedBytecode), - 2 - ) - - should.exist(ctx, 'Context should exist!') - - ctx.pc.should.equal(2, 'PC mismatch!') - ctx.opcode.should.equal( - invalidBytesConsumedBytecode[1].opcode, - 'Opcode mismatch!' - ) - ctx.stackDepth.should.equal(1, 'Stack depth mismatch!') - ctx.stack[0].should.eql( - invalidBytesConsumedBytecode[0].consumedBytes, - 'Stack mismatch!' - ) - ctx.memoryWordCount.should.equal(0, 'Memory word count mismatch!') - ctx.memory.should.eql(emptyBuffer, 'Memory mismatch!') - }) - }) - - describe('getExecutionComparisonBeforeStep', () => { - it('handles emptyBuffer case', async () => { - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - emptyBuffer, - 1, - emptyBuffer, - 1 - ) - - should.exist( - comparison, - 'Comparison should exist between emptyBuffer bytecode executions!' - ) - comparison.executionDiffers.should.equal( - false, - 'Executions should not differ!' - ) - should.not.exist(comparison.firstContext, 'First context mismatch!') - should.not.exist(comparison.secondContext, 'Second context mismatch!') - }) - - it('shows same execution if step is not hit', async () => { - const buff: Buffer = bytecodeToBuffer(memoryAndStackBytecode) - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - buff, - 3, - buff, - 3 - ) - - should.exist( - comparison, - 'Comparison should exist between bytecode executions!' - ) - comparison.executionDiffers.should.equal( - false, - 'Executions should not differ!' - ) - should.not.exist(comparison.firstContext, 'First context mismatch!') - should.not.exist(comparison.secondContext, 'Second context mismatch!') - }) - - it('shows same execution for emptyBuffer memory & stack', async () => { - const buff: Buffer = bytecodeToBuffer(voidBytecode) - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - buff, - 0, - buff, - 0 - ) - - should.exist( - comparison, - 'Comparison should exist between bytecode executions!' - ) - comparison.executionDiffers.should.equal( - false, - 'Executions should not differ!' - ) - should.exist(comparison.firstContext, 'First context mismatch!') - should.exist(comparison.secondContext, 'Second context mismatch!') - comparison.firstContext.should.eql( - comparison.secondContext, - 'First and second do not match!' - ) - - comparison.firstContext.pc.should.equal(0, 'PC mismatch!') - comparison.firstContext.opcode.should.equal( - voidBytecode[0].opcode, - 'Opcode mismatch!' - ) - comparison.firstContext.stackDepth.should.equal( - 0, - 'Stack depth mismatch!' - ) - comparison.firstContext.stack.should.eql([], 'Stack mismatch!') - comparison.firstContext.memoryWordCount.should.equal( - 0, - 'Memory word count mismatch!' - ) - comparison.firstContext.memory.should.eql(emptyBuffer, 'Memory mismatch!') - }) - - it('shows same execution for different pc values', async () => { - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - bytecodeToBuffer(voidBytecode), - 0, - bytecodeToBuffer(voidBytecodeWithPushPop), - 3 - ) - - should.exist( - comparison, - 'Comparison should exist between bytecode executions!' - ) - comparison.executionDiffers.should.equal( - false, - 'Executions should not differ!' - ) - should.exist(comparison.firstContext, 'First context mismatch!') - should.exist(comparison.secondContext, 'Second context mismatch!') - - comparison.firstContext.pc.should.equal(0, 'PC mismatch!') - comparison.secondContext.pc.should.equal(3, 'PC mismatch!') - comparison.firstContext.opcode.should.equal( - comparison.secondContext.opcode, - 'Opcode mismatch!' - ) - comparison.firstContext.stackDepth.should.equal( - comparison.secondContext.stackDepth, - 'Stack depth mismatch!' - ) - comparison.firstContext.stack.should.eql( - comparison.secondContext.stack, - 'Stack mismatch!' - ) - comparison.firstContext.memoryWordCount.should.equal( - comparison.secondContext.memoryWordCount, - 'Memory word count mismatch!' - ) - comparison.firstContext.memory.should.eql( - comparison.secondContext.memory, - 'Memory mismatch!' - ) - }) - - it('works for populated memory & stack', async () => { - const buff: Buffer = bytecodeToBuffer(memoryAndStackBytecode) - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - buff, - 38, - buff, - 38 - ) - - should.exist( - comparison, - 'Comparison should exist between bytecode executions!' - ) - comparison.executionDiffers.should.equal( - false, - 'Executions should not differ!' - ) - should.exist(comparison.firstContext, 'First context mismatch!') - should.exist(comparison.secondContext, 'Second context mismatch!') - comparison.firstContext.should.eql( - comparison.secondContext, - 'First and second do not match!' - ) - - comparison.firstContext.pc.should.equal(38, 'PC mismatch!') - comparison.firstContext.opcode.should.equal( - memoryAndStackBytecode[4].opcode, - 'Opcode mismatch!' - ) - comparison.firstContext.stackDepth.should.equal( - 1, - 'Stack depth mismatch!' - ) - comparison.firstContext.stack[0].should.eql( - memoryAndStackBytecode[0].consumedBytes, - 'Stack mismatch!' - ) - comparison.firstContext.memoryWordCount.should.equal( - 4, - 'Memory word count mismatch!' - ) - comparison.firstContext.memory.should.eql( - Buffer.from('00'.repeat(127) + '01', 'hex'), - 'Memory mismatch!' - ) - }) - - it('differs for different memory', async () => { - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - bytecodeToBuffer(memoryAndStackBytecode), - 38, - bytecodeToBuffer(memoryDiffersBytecode), // <--- Different - 38 - ) - - should.exist( - comparison, - 'Comparison should exist between bytecode executions!' - ) - comparison.executionDiffers.should.equal( - true, - 'Executions should differ!' - ) - should.exist(comparison.firstContext, 'First context mismatch!') - should.exist(comparison.secondContext, 'Second context mismatch!') - comparison.firstContext.should.not.eql( - comparison.secondContext, - 'First and second match!' - ) - - comparison.firstContext.pc.should.equal( - comparison.secondContext.pc, - 'PC mismatch!' - ) - comparison.firstContext.opcode.should.equal( - comparison.secondContext.opcode, - 'Opcode mismatch!' - ) - comparison.firstContext.stackDepth.should.equal( - comparison.secondContext.stackDepth, - 'Stack depth mismatch!' - ) - comparison.firstContext.stack[0].should.eql( - comparison.secondContext.stack[0], - 'Stack mismatch!' - ) - comparison.firstContext.memoryWordCount.should.equal( - comparison.secondContext.memoryWordCount, - 'Memory word count mismatch!' - ) - comparison.firstContext.memory.should.eql( - Buffer.from('00'.repeat(127) + '01', 'hex'), - 'Memory mismatch!' - ) - comparison.secondContext.memory.should.eql( - Buffer.from('00'.repeat(127) + '02', 'hex'), - 'Memory mismatch!' - ) - }) - - it('differs for different stack', async () => { - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - bytecodeToBuffer(memoryAndStackBytecode), - 38, - bytecodeToBuffer(stackDiffersBytecode), // <--- Different - 38 - ) - - should.exist( - comparison, - 'Comparison should exist between bytecode executions!' - ) - comparison.executionDiffers.should.equal( - true, - 'Executions should differ!' - ) - should.exist(comparison.firstContext, 'First context mismatch!') - should.exist(comparison.secondContext, 'Second context mismatch!') - comparison.firstContext.should.not.eql( - comparison.secondContext, - 'First and second match!' - ) - - comparison.firstContext.pc.should.equal( - comparison.secondContext.pc, - 'PC mismatch!' - ) - comparison.firstContext.opcode.should.equal( - comparison.secondContext.opcode, - 'Opcode mismatch!' - ) - comparison.firstContext.stackDepth.should.equal( - comparison.secondContext.stackDepth, - 'Stack depth mismatch!' - ) - comparison.firstContext.stack[0].should.eql( - memoryAndStackBytecode[0].consumedBytes, - 'Stack mismatch!' - ) - comparison.secondContext.stack[0].should.eql( - stackDiffersBytecode[0].consumedBytes, - 'Stack mismatch!' - ) - comparison.firstContext.memoryWordCount.should.equal( - comparison.secondContext.memoryWordCount, - 'Memory word count mismatch!' - ) - comparison.firstContext.memory.should.eql( - comparison.secondContext.memory, - 'Memory mismatch!' - ) - }) - - it('handles case where code errors after step', async () => { - const buff: Buffer = bytecodeToBuffer(invalidBytesConsumedBytecode) - const comparison: ExecutionComparison = await evmUtil.getExecutionComparisonBeforeStep( - buff, - 2, - buff, - 2 - ) - - should.exist( - comparison, - 'Comparison should exist between bytecode executions!' - ) - comparison.executionDiffers.should.equal( - false, - 'Executions should not differ!' - ) - should.exist(comparison.firstContext, 'First context mismatch!') - should.exist(comparison.secondContext, 'Second context mismatch!') - comparison.firstContext.should.eql( - comparison.secondContext, - 'First and second do not match!' - ) - - comparison.firstContext.pc.should.equal(2, 'PC mismatch!') - comparison.firstContext.opcode.should.equal( - invalidBytesConsumedBytecode[1].opcode, - 'Opcode mismatch!' - ) - comparison.firstContext.stackDepth.should.equal( - 1, - 'Stack depth mismatch!' - ) - comparison.firstContext.stack[0].should.eql( - invalidBytesConsumedBytecode[0].consumedBytes, - 'Stack mismatch!' - ) - comparison.firstContext.memoryWordCount.should.equal( - 0, - 'Memory word count mismatch!' - ) - comparison.firstContext.memory.should.eql(emptyBuffer, 'Memory mismatch!') - }) - }) -}) diff --git a/packages/rollup-dev-tools/tsconfig.json b/packages/rollup-dev-tools/tsconfig.json deleted file mode 100644 index 04e1436f7d8c8..0000000000000 --- a/packages/rollup-dev-tools/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./../../tsconfig.json", - "compilerOptions": { - "outDir": "./build", - "baseUrl": "./", - "resolveJsonModule": true, - "esModuleInterop": true - }, - "include": ["*.ts", "**/*.ts"] -} diff --git a/packages/rollup-dev-tools/tslint.json b/packages/rollup-dev-tools/tslint.json deleted file mode 100644 index 3f4b3e6147c6c..0000000000000 --- a/packages/rollup-dev-tools/tslint.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": ["./../../tslint.json"], - "rules": { - "prettier": [true, "../../prettier-config.json"], - "no-console": false - } -} diff --git a/packages/rollup-dev-tools/waffle-config.json b/packages/rollup-dev-tools/waffle-config.json deleted file mode 100644 index 57e54ad142882..0000000000000 --- a/packages/rollup-dev-tools/waffle-config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "sourcesPath": "./test/contracts", - "targetPath": "./test/contracts/build", - "npmPath": "../../node_modules", - "compilerOptions": { - "outputSelection": { - "*": { - "*": ["*"] - } - } - } -} diff --git a/packages/solc-transpiler/README.md b/packages/solc-transpiler/README.md deleted file mode 100644 index 202521ccc1a19..0000000000000 --- a/packages/solc-transpiler/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# TODO: this. -# USAGE -Note: solc input settings expects a `executionManagerAddress` parameter or an `EXECUTION_MANAGER_ADDRESS` environment variable (read in that order of preference) or compilation will fail. diff --git a/packages/solc-transpiler/index.ts b/packages/solc-transpiler/index.ts deleted file mode 100644 index 37df423bed749..0000000000000 --- a/packages/solc-transpiler/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -const rootPath = __dirname - -export { rootPath } -import { wrapper } from './src/index' -module.exports = wrapper diff --git a/packages/solc-transpiler/package.json b/packages/solc-transpiler/package.json deleted file mode 100644 index 2c0d1e85c2058..0000000000000 --- a/packages/solc-transpiler/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@eth-optimism/solc-transpiler", - "version": "0.0.1-alpha.27", - "description": "Optimism Transpiler Solc Compiler Wrapper", - "main": "build/index.js", - "files": [ - "build/**/*.js" - ], - "scripts": { - "all": "yarn clean && yarn build && yarn test && yarn fix && yarn lint", - "build": "mkdir -p test/contracts/build && waffle test/waffle-config.json && tsc -p .", - "clean": "rimraf build/ && rimraf test/contracts/build", - "fix": "prettier --config ../../prettier-config.json --write 'index.ts' '{src,test}/**/*.ts'", - "lint": "tslint --format stylish --project .", - "test": "mkdir -p test/contracts/build && waffle test/waffle-config.json && mocha --require ts-node/register 'test/**/*.spec.ts' --timeout 5000 --exit" - }, - "keywords": [ - "optimism", - "rollup", - "optimistic", - "ethereum", - "client" - ], - "homepage": "https://github.com/ethereum-optimism/optimism-monorepo/tree/master/packages/solc-transpiler#readme", - "license": "MIT", - "author": "Optimism PBC", - "repository": { - "type": "git", - "url": "https://github.com/ethereum-optimism/optimism-monorepo.git" - }, - "dependencies": { - "@eth-optimism/core-utils": "^0.0.1-alpha.26", - "@eth-optimism/rollup-dev-tools": "^0.0.1-alpha.27", - "ethers": "^4.0.45", - "require-from-string": "^2.0.2", - "solc": "^0.5.12" - }, - "devDependencies": { - "@types/chai": "^4.1.7", - "@types/mocha": "^5.2.7", - "@types/node": "^12.0.7", - "chai": "^4.2.0", - "chai-as-promised": "^7.1.1", - "ethereum-waffle": "2.1.0", - "mocha": "^6.1.4", - "rimraf": "^2.6.3", - "ts-node": "^8.2.0", - "typescript": "^3.5.1" - }, - "publishConfig": { - "access": "public" - }, - "gitHead": "ccce366645fca6bad46c5cf7f7ff2f407c6ba5fd" -} diff --git a/packages/solc-transpiler/src/compiler.ts b/packages/solc-transpiler/src/compiler.ts deleted file mode 100644 index 49bb305e07b04..0000000000000 --- a/packages/solc-transpiler/src/compiler.ts +++ /dev/null @@ -1,654 +0,0 @@ -/* External Imports */ -import { - ErroredTranspilation, - OpcodeReplacerImpl, - OpcodeWhitelistImpl, - SuccessfulTranspilation, - TranspilationError, - Transpiler, - TranspilerImpl, - stripAuxData, -} from '@eth-optimism/rollup-dev-tools' -import { - bufToHexString, - getLogger, - hexStrToBuf, - isValidHexAddress, - Logger, - remove0x, - objectsEqual, -} from '@eth-optimism/core-utils' -import * as solc from 'solc' -import { execSync } from 'child_process' -import * as requireFromString from 'require-from-string' -import { ethers } from 'ethers' - -const log: Logger = getLogger('solc-transpiler') - -interface TranspilationOutput { - bytecode?: any - deployedBytecode?: any - errors?: any[] -} - -/** - * Solc-compatible entrypoint into compilation. This function expects Solc-formatted input (with the addition of the - * `executionManagerAddress` field), and it will compile and transpile any contracts provided, resulting in - * Solc-compatible output. - * - * @param configJsonString The Solc input as a string. - * @param callbacks Optional callback(s) that are just passed to solc's compile function. - * @returns The Solc output as a string. - */ -export const compile = (configJsonString: string, callbacks?: any): string => { - const compiler = getCompiler(process.env.SOLC_VERSION) - log.debug(`Trying to transpile with config: ${configJsonString}`) - let json: any - try { - json = JSON.parse(configJsonString) - } catch (e) { - log.debug(`Cannot parse JSON: ${JSON.stringify(e)}`) - // todo: populate some errors - return compiler.compile(configJsonString) - } - - const inputErrors: string = getInputErrors(json) - if (!!inputErrors) { - return inputErrors - } - - const solcConfig: string = getSolcConfig(json) - const resString = !!callbacks - ? compiler.compile(solcConfig, callbacks) - : compiler.compile(solcConfig) - - const res = JSON.parse(resString) - if ( - 'errors' in res && - !!res.errors && - !!res.errors.filter((x) => x.severity === 'error').length - ) { - log.debug(`ERROR: ${JSON.stringify(res)}`) - return resString - } - - const transpiler: Transpiler = new TranspilerImpl( - new OpcodeWhitelistImpl(), - new OpcodeReplacerImpl(getExecutionManagerAddress(json)) - ) - - for (const [filename, fileJson] of Object.entries(res.contracts)) { - log.debug(`Transpiling file: ${filename}`) - for (const [contractName, contractJson] of Object.entries(fileJson)) { - if ( - !contractJson || - !contractJson.evm || - !contractJson.evm.bytecode || - !contractJson.evm.deployedBytecode - ) { - continue - } - // Library links in bytecode strings have invalid hex: they are of the form __$asdfasdf$__. - // Because __$ is not a valid hex string, we replace with a valid hex string during transpilation, - // storing the links re-substituting the __$* strings afterwards - const originalRefStrings = getOriginalLinkRefStringsAndSubstituteValidHex( - contractJson - ) - log.debug( - `Transpiling contract: ${contractName} with valid hex strings for link placeholders.` - ) - const output = transpileContract( - transpiler, - contractJson, - filename, - contractName - ) - log.debug(`Transpiled contract ${contractName}.`) - - res.contracts[filename][contractName].evm.bytecode.object = remove0x( - output.bytecode || '' - ) - res.contracts[filename][ - contractName - ].evm.deployedBytecode.object = remove0x(output.deployedBytecode || '') - res.contracts[filename][contractName].evm.bytecode.object = remove0x( - output.bytecode || '' - ) - res.contracts[filename][ - contractName - ].evm.deployedBytecode.object = remove0x(output.deployedBytecode || '') - - log.debug( - `Updating links for all libraries by putting back original invalid hex (__$...$__) strings and updating link .start's.` - ) - updateLinkRefsAndSubstituteOriginalStrings( - res.contracts[filename][contractName], - originalRefStrings - ) - - if (!!output.errors) { - if (!res.errors) { - res.errors = [] - } - - res.errors.push(...output.errors) - } - } - } - - return formatOutput(res, json) -} - -/** - * Gets the requested version of the solc module. solc-js provides downloadable - * versions of itself which can be downloaded and used to compile contracts that - * require different compiler versions. This function must be synchronous so it - * can be used in the compilation process which is also synchronous. To achieve - * this we construct a string of JavaScript which downloads the latest version - * of solc and run that code using `execSync` - * - * @param versionString The requested version of solc - * @returns The requested version of the `solc` module or the latest version - */ -const getCompiler = (versionString: string): typeof solc => { - if (!versionString) { - return solc - } - - const getCompilerString = ` - function httpsRequest(params, postData) { - return new Promise(function(resolve, reject) { - var req = https.request(params, function(res) { - var body = []; - res.on("data", function(chunk) { - body.push(chunk); - }); - res.on("end", function() { - try { - body = Buffer.concat(body).toString(); - } catch(e) { - reject(e); - } - resolve(body); - }); - }); - req.on("error", function(err) { - reject(err); - }); - req.end(); - }); - } - - async function getSolcVersion(versionString) { - const listUrl = "https://ethereum.github.io/solc-bin/bin/list.json"; - const {releases} = JSON.parse(await httpsRequest(listUrl)) - const solcUrl = "https://ethereum.github.io/solc-bin/bin/" + releases[versionString]; - return await httpsRequest(solcUrl); - } - - (async () => { - await process.stdout.write(await getSolcVersion("${versionString}")); - })(); - ` - return solc.setupMethods( - requireFromString( - execSync(`${process.argv[0]} -e '${getCompilerString}'`).toString() - ) - ) -} - -const getExecutionManagerAddress = (configObject: any): string => { - return ( - configObject.settings.executionManagerAddress || - process.env.EXECUTION_MANAGER_ADDRESS - ) -} - -/** - * Validates the input jsonObject by checking to see that it's formatted properly. If not, it will return - * an errors as properly-formatted solc output. - * - * @param configObject The config object being checked for validity - * @returns undefined if valid, formatted error(s) as a string if invalid. - */ -const getInputErrors = (configObject: any): string => { - if (!configObject.settings || typeof configObject.settings !== 'object') { - return getFormattedSolcErrorOutput( - 'Input must include "settings" object in top-level object.' - ) - } - - const executionManagerAddress: string = getExecutionManagerAddress( - configObject - ) - if (!executionManagerAddress || !isValidHexAddress(executionManagerAddress)) { - return getFormattedSolcErrorOutput( - 'Input must include "executionManagerAddress" field in the "settings" object or there must be an "EXECUTION_MANAGER_ADDRESS" environment variable, and it must be a valid Ethereum address as a hex string (case insensitive).' - ) - } - log.info(`Compiling with executionManagerAddress ${executionManagerAddress}`) - - if ( - !configObject.settings.outputSelection || - typeof configObject.settings.outputSelection !== 'object' || - !Object.entries(configObject.settings.outputSelection).length - ) { - return getFormattedSolcErrorOutput( - 'Input must include a populated "outputSelection" object in "settings"' - ) - } - - for (const [filename, fileConfig] of Object.entries( - configObject.settings.outputSelection - )) { - if (typeof fileConfig !== 'object' || Array.isArray(fileConfig)) { - return getFormattedSolcErrorOutput( - '"outputSelection" configuration in "settings" must be of the form: { "filename": { "contractName": [] }, ... }' - ) - } - for (const contractConfig of Object.values( - configObject.settings.outputSelection[filename] - )) { - if (!Array.isArray(contractConfig)) { - return getFormattedSolcErrorOutput( - '"outputSelection" configuration in "settings" must be of the form: { "filename": { "contractName": [] }, ... }' - ) - } - } - } -} - -/** - * Takes the provided config and converts it into solc-js input. - * This mainly entails: - * * removing the `executionManagerAddress` from settings - * * making sure `evm.legacyAssembly` is listed as an output selection - * @param config The config object - * @returns the formatted solc config - */ -const getSolcConfig = (config: any): string => { - // Just deep cloning the config json - const solcConfig = JSON.parse(JSON.stringify(config)) - delete solcConfig.settings.executionManagerAddress - - for (const [filename, fileConfig] of Object.entries( - solcConfig.settings.outputSelection - )) { - for (const [contractName, contractConfig] of Object.entries(fileConfig)) { - const lowerConfig = contractConfig.map((x) => x.toLowerCase()) - if (!('evm.legacyAssembly' in lowerConfig) && !('*' in lowerConfig)) { - solcConfig.settings.outputSelection[filename][contractName].push( - 'evm.legacyAssembly' - ) - } - } - } - - return JSON.stringify(solcConfig) -} - -/** - * Takes the transpilation output object and formats it to be returned. - * This mainly entails: - * * Removing `legacyAssemby` output if it was not specified on the input config - * * Formatting the resulting object as a string - * - * @param transpiledOutput The transpilation result. - * @params inputConfig The input config that indicates how the output should be formatted. - * @returns The formatted output. - */ -const formatOutput = (transpiledOutput: any, inputConfig: any): string => { - for (const [filename, fileObj] of Object.entries( - transpiledOutput.contracts - )) { - for (const [contractName, contractObj] of Object.entries(fileObj)) { - const filenameConfig = - filename in inputConfig.settings.outputSelection ? filename : '*' - const contractNameConfig = - contractName in inputConfig.settings.outputSelection[filenameConfig] - ? contractName - : '*' - const outputConfig = - inputConfig.settings.outputSelection[filenameConfig][contractNameConfig] - if (!('evm.legacyAssembly' in outputConfig) && !('*' in outputConfig)) { - delete transpiledOutput.contracts[filename][contractName].evm - .legacyAssembly - } - } - } - - return JSON.stringify(transpiledOutput) -} - -/** - * Gets Solc-formatted errors from the provided transpilation errors. - * - * @param transpilationErrors. - * @param file The file in which the errors occurred. - * @param contract The contract in qhich the errors occurred - * @param isDeployedBytecode. - * @returns The Solc-formatted errors - */ -const getSolcErrorsFromTranspilerErrors = ( - transpilationErrors: TranspilationError[], - file: string, - contract: string, - isDeployedBytecode: boolean = false -): any[] => { - return transpilationErrors.map((x) => { - const message: string = `${file}:${contract} error [${ - x.message - }] at index ${x.index} of ${ - isDeployedBytecode ? 'deployed bytecode' : 'bytecode' - }` - return { - component: 'general', - formattedMessage: message, - message, - severity: 'error', - type: 'CompilerError', - } - }) -} - -/** - * Creates a formatted solc-js error output string from the provided params. - * - * @param message The error message. - * @param severity The severity of the error. - * @param component The component of the error - * @param type The type of the error - * @returns The formatted string. - */ -const getFormattedSolcErrorOutput = ( - message: string, - severity: string = 'error', - component: string = 'general', - type: string = 'JSONError' -): string => { - return JSON.stringify({ - errors: [ - { - component, - formattedMessage: message, - message, - severity, - type, - }, - ], - }) -} - -/** - * Gets the bytecode or the deployedBytecode from the solc output for a specific contract. - * - * @param contractSolcOutput The solc output for the contract in question. - * @param isDeployedBytecode Whether we're getting the deployed bytecode or the bytecode. - * @returns The bytecode if it exists in the solc output. - */ -const getBytecode = ( - contractSolcOutput: any, - isDeployedBytecode: boolean -): string => { - try { - const code: string = isDeployedBytecode - ? contractSolcOutput.evm.deployedBytecode.object - : contractSolcOutput.evm.bytecode.object - const strippedCode: Buffer = stripAuxData( - hexStrToBuf(code), - contractSolcOutput, - isDeployedBytecode - ) - return bufToHexString(strippedCode) - } catch (e) { - return undefined - } -} - -/** - * Gets the auxdata from the compiler output. - * This is the fingerprint of the bytecode that may depend on compiler and version and therefore should be removed - * from bytecode if strictly analyzing the bytecode. - * - * @param contractSolcOutput The solc-js compile(...) output. - * @returns The auxdata in question. - */ -const getAuxData = (contractSolcOutput: any): string => { - try { - return contractSolcOutput.evm.legacyAssembly['.data']['0']['.auxdata'] - } catch (e) { - return undefined - } -} - -/** - * Transpiles the provided solc output, overwriting the `bytecode` and `deployedBytecode`. - * - * @param transpiler The transpiler to use. - * @param contractSolcOutput The contract solc output. - * @param filename The file being transpiled. - * @param contractName The contract being transpiled. - * @returns The updated solc output where the bytecode and deployedBytecode are overwritten with transpiled bytecode if present. - */ -const transpileContract = ( - transpiler: Transpiler, - contractSolcOutput: any, - filename: string, - contractName: string -): TranspilationOutput => { - let bytecode: string = getBytecode(contractSolcOutput, false) - let deployedBytecode: string = getBytecode(contractSolcOutput, true) - - if (!bytecode && !deployedBytecode) { - return contractSolcOutput - } - - if (!!bytecode) { - const originalBytecodeSize: number = hexStrToBuf( - contractSolcOutput.evm.bytecode.object - ).byteLength - - const transpilationResult = transpiler.transpile( - hexStrToBuf(bytecode), - hexStrToBuf(deployedBytecode), - originalBytecodeSize - ) - if (!transpilationResult.succeeded) { - const errorResult: ErroredTranspilation = transpilationResult as ErroredTranspilation - return { - errors: getSolcErrorsFromTranspilerErrors( - errorResult.errors, - filename, - contractName - ), - } - } - - bytecode = bufToHexString( - (transpilationResult as SuccessfulTranspilation).bytecode - ) - } - - if (!!deployedBytecode) { - // log.debug(`replacing (DEPLOYED) bytecode library linkReferences with valid hex strings... input: \n${contractSolcOutput.evm.deployedBytecode.object.hex}`) - // const placeholders = substituteLinkPlaceholdersForValidBytes(contractSolcOutput.evm.deployedBytecode.object) - // log.debug(`replaced (DEPLOYED) bytecode library linkReferences with valid hex strings... output: \n${contractSolcOutput.evm.deployedBytecode.object.hex}`) - - const transpilationResult = transpiler.transpileRawBytecode( - hexStrToBuf(deployedBytecode) - ) - if (!transpilationResult.succeeded) { - const errorResult: ErroredTranspilation = transpilationResult as ErroredTranspilation - return { - errors: getSolcErrorsFromTranspilerErrors( - errorResult.errors, - filename, - contractName, - true - ), - } - } - deployedBytecode = bufToHexString( - (transpilationResult as SuccessfulTranspilation).bytecode - ) - } - - return { - bytecode, - deployedBytecode, - } -} - -/** - * Iterates over all library links, replacing the temporary valid hex strings with their original valid hex strings, and updating the new byte locations for the links. - * - * @param contractSolcOutput The contract solc output. - * @param originalPlaceholderStrings A mapping from library name string to __$*$__ string (invalid hex) which was substituted for a different placeholder so that transpilation works. - */ -const updateLinkRefsAndSubstituteOriginalStrings = ( - contractSolcOutput: any, - originalPlaceholderStrings: Map -): void => { - let placeholderIndex: number = 0 - const updatePlaceholderStartAndSubstituteOriginalString = ( - bytecodeObject: any, - linkLocation: any, - fileName: string, - libraryName: string - ) => { - const replacedHexString: string = getPlaceholderHexString(libraryName) - const newPlaceholderStart = bytecodeObject.object.indexOf(replacedHexString) - linkLocation.start = newPlaceholderStart / 2 // /2 because we found this from a hex string, but we operate on - - const placeholderLength = linkLocation.length * 2 // 2x because this is expressed in bytes but we will operate on hex string - const newPlaceholderEnd = newPlaceholderStart + placeholderLength - const originalPlaceholderString = originalPlaceholderStrings.get( - libraryName - ) - const prevBytecodeString: string = bytecodeObject.object - log.debug( - `Rebuilding ${placeholderIndex}th link for file ${fileName}, AKA library ${libraryName} with original placeholder ${originalPlaceholderString} at new location (${newPlaceholderStart},${newPlaceholderEnd}).` - ) - bytecodeObject.object = - prevBytecodeString.slice(0, newPlaceholderStart) + - originalPlaceholderString + - prevBytecodeString.slice(newPlaceholderEnd) - placeholderIndex++ - } - executeOnAllLinks( - contractSolcOutput.evm.bytecode, - updatePlaceholderStartAndSubstituteOriginalString, - 'PUT ORIGINAL __$ PLACEHOLDER STRINGS BACK INTO BYTECODE' - ) - executeOnAllLinks( - contractSolcOutput.evm.deployedBytecode, - updatePlaceholderStartAndSubstituteOriginalString, - 'PUT ORIGINAL __$ PLACEHOLDER STRINGS BACK INTO DEPLOYED BYTECODE' - ) -} - -/** - * Takes a contract solc output, and iterates over all library links, replacing the invalid hex __$*$__ strings with temporary valid hex strings so that they pass through the transpiler. - * - * @param contractSolcOutput The contract solc output. - * @returns A mapping from library name string to original __$*$__ string which was replaced with a valid hex string - */ -const getOriginalLinkRefStringsAndSubstituteValidHex = ( - solcOutput: any -): Map => { - const originalPlaceholderStrings = new Map() - let placeholderIndex: number = 0 - - const pushOriginalPlaceholderAndSubstituteValidHex = ( - bytecodeObject: any, - linkLocation: any, - fileName: string, - libraryName: string - ) => { - const placeholderStart: number = linkLocation.start * 2 // 2x because this is expressed in bytes but we will operate on hex string - const placeholderLength: number = linkLocation.length * 2 // 2x because this is expressed in bytes but we will operate on hex string - const placeholderEnd: number = placeholderLength + placeholderStart - const prevBytecodeString: string = bytecodeObject.object - const originalPlaceholderString: string = prevBytecodeString.slice( - placeholderStart, - placeholderEnd - ) - log.debug( - `Parsed ${placeholderIndex}th link for file ${fileName}, AKA library ${libraryName} with placeholder ${originalPlaceholderString} at elements (${placeholderStart},${placeholderEnd}) bytecode string.` - ) - bytecodeObject.object = - prevBytecodeString.slice(0, placeholderStart) + - getPlaceholderHexString(libraryName) + - prevBytecodeString.slice(placeholderEnd) - originalPlaceholderStrings.set(libraryName, originalPlaceholderString) - placeholderIndex++ - } - - executeOnAllLinks( - solcOutput.evm.bytecode, - pushOriginalPlaceholderAndSubstituteValidHex, - 'REPLACE BYTECODE INVALID HEX STRINGS WITH VALID' - ) - executeOnAllLinks( - solcOutput.evm.deployedBytecode, - pushOriginalPlaceholderAndSubstituteValidHex, - 'REPLACE DEPLOYED BYTECODE INVALID HEX STRINGS WITH VALID' - ) - - return originalPlaceholderStrings -} - -/** - * Executes the given callback on each linkReference (library metadata) for a given contractJSONOutput.bytecode or .deployedBytecode object - * - * @param bytecodeObjectFromSolcOutput The given contractJSONOutput.bytecode or .deployedBytecode object - * @param callback The callback to execute - * @param callbackDescription Optional logging string describing the functionality this callback will perform - */ -const executeOnAllLinks = ( - bytecodeObjectFromSolcOutput: any, - callback: ( - bytecodeObject: any, - linkLocation: any, - fileName: string, - libraryName: string - ) => void, - callbackDescription: string = '(UNSPECIFIED)' -) => { - log.debug( - `asked to execute callback performing functionality: [${callbackDescription}] on all JSON links...` - ) - const linkRefs = bytecodeObjectFromSolcOutput.linkReferences - for (const fileName in linkRefs) { - // tslint:disable - const libraryName: string = Object.keys(linkRefs[fileName])[0] - log.debug(`Parsing links for ${libraryName}...`) - for (const linkLocation of linkRefs[fileName][libraryName]) { - callback( - bytecodeObjectFromSolcOutput, - linkLocation, - fileName, - libraryName - ) - } - } -} - -/** - * Gets a deterministic, collision-resistant valid hex string placeholder for a given input string - * - * @param libraryName The string for which to get a deterministic hex string. - * @param byteLength Number of bytes worth of hex string to return. - */ -const getPlaceholderHexString = ( - libraryName: string, - bytelength: number = 20 -): string => { - const randomHexString: string = ethers.utils.keccak256( - '0x' + - Buffer.from( - `${libraryName}PLACEHOLDERPLACEHOLDERPLACEHOLDERPLACEHOLDERPLACEHOLDEROPTIMSIMPBC` - ).toString('hex') - ) - return remove0x(randomHexString).slice(0, 2 * bytelength) -} diff --git a/packages/solc-transpiler/src/index.ts b/packages/solc-transpiler/src/index.ts deleted file mode 100644 index 94bff4c1801ad..0000000000000 --- a/packages/solc-transpiler/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './compiler' -export * from './wrapper' diff --git a/packages/solc-transpiler/src/wrapper.ts b/packages/solc-transpiler/src/wrapper.ts deleted file mode 100644 index 24b71e1890418..0000000000000 --- a/packages/solc-transpiler/src/wrapper.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* Internal Imports */ -import { compile } from './compiler' - -const version = process.env.npm_package_version - ? process.env.npm_package_version - : 'default.version' - -export const wrapper = { - version: () => version, - semver: () => version, - license: () => version, - compile, - compileStandard: compile, - compileStandardWrapper: compile, -} - -/* SOLC JS Interface - -{ - version: version, - semver: versionToSemver, - license: license, - lowlevel: { - compileSingle: compileJSON, - compileMulti: compileJSONMulti, - compileCallback: compileJSONCallback, - compileStandard: compileStandard - }, - features: { - legacySingleInput: compileJSON !== null, - multipleInputs: compileJSONMulti !== null || compileStandard !== null, - importCallback: compileJSONCallback !== null || compileStandard !== null, - nativeStandardJSON: compileStandard !== null - }, - compile: compileStandardWrapper, - // Temporary wrappers to minimise breaking with other projects. - // NOTE: to be removed in 0.5.2 - compileStandard: compileStandardWrapper, - compileStandardWrapper: compileStandardWrapper, - // Loads the compiler of the given version from the github repository - // instead of from the local filesystem. - loadRemoteVersion: function (versionString, cb) { - var mem = new MemoryStream(null, {readable: false}); - var url = 'https://ethereum.github.io/solc-bin/bin/soljson-' + versionString + '.js'; - https.get(url, function (response) { - if (response.statusCode !== 200) { - cb(new Error('Error retrieving binary: ' + response.statusMessage)); - } else { - response.pipe(mem); - response.on('end', function () { - cb(null, setupMethods(requireFromString(mem.toString(), 'soljson-' + versionString + '.js'))); - }); - } - }).on('error', function (error) { - cb(error); - }); - }, - // Use this if you want to add wrapper functions around the pure module. - setupMethods: setupMethods -}; - - */ diff --git a/packages/solc-transpiler/test/contracts/dummy/Dummy.sol b/packages/solc-transpiler/test/contracts/dummy/Dummy.sol deleted file mode 100644 index d9ab8f6ac59e1..0000000000000 --- a/packages/solc-transpiler/test/contracts/dummy/Dummy.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.5.0; - -contract Dummy { - bytes32 someState; - - constructor() public { - someState = keccak256("Wooooooo!"); - } - - function getState() public view returns(bytes32) { - return someState; - } - - function updateState(bytes32 newValue) public { - someState = newValue; - } -} \ No newline at end of file diff --git a/packages/solc-transpiler/test/contracts/dummy/Dummy2.sol b/packages/solc-transpiler/test/contracts/dummy/Dummy2.sol deleted file mode 100644 index 795df60590403..0000000000000 --- a/packages/solc-transpiler/test/contracts/dummy/Dummy2.sol +++ /dev/null @@ -1,33 +0,0 @@ -pragma solidity ^0.5.0; - -contract Dummy2 { - bytes32 someState; - - constructor() public { - someState = keccak256("Derp Derp Derp!"); - } - - function getState() public view returns(bytes32) { - return someState; - } - - function updateState(bytes32 newValue) public { - someState = newValue; - } -} - -contract Dummy3 { - bytes32 someState; - - constructor() public { - someState = keccak256("!!!!!!!!!!!"); - } - - function getState() public view returns(bytes32) { - return someState; - } - - function updateState(bytes32 newValue) public { - someState = newValue; - } -} \ No newline at end of file diff --git a/packages/solc-transpiler/test/contracts/library/SafeMathUser.sol b/packages/solc-transpiler/test/contracts/library/SafeMathUser.sol deleted file mode 100644 index 347c9a836fb52..0000000000000 --- a/packages/solc-transpiler/test/contracts/library/SafeMathUser.sol +++ /dev/null @@ -1,13 +0,0 @@ -pragma solidity ^0.5.0; - -import {SimpleSafeMath} from './SimpleSafeMath.sol'; -import {SimpleUnsafeMath} from './SimpleUnsafeMath.sol'; - -contract SafeMathUser { - function useLib() public pure returns (uint) { - return SimpleSafeMath.addUint(2, 3); - } - function use2Libs() public pure returns(uint) { - return SimpleUnsafeMath.addUint(SimpleSafeMath.addUint(1, 2), 3); - } -} diff --git a/packages/solc-transpiler/test/contracts/library/SimpleSafeMath.sol b/packages/solc-transpiler/test/contracts/library/SimpleSafeMath.sol deleted file mode 100644 index 3ea2aa32da593..0000000000000 --- a/packages/solc-transpiler/test/contracts/library/SimpleSafeMath.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity ^0.5.0; - -library SimpleSafeMath { - - function subUint(uint a, uint b) public returns(uint){ - - require(a >= b); // Make sure it doesn't return a negative value. - return a - b; - - } - function addUint(uint a , uint b) public pure returns(uint){ - - uint c = a + b; - - require(c >= a); // Makre sure the right computation was made - return c; - } -} \ No newline at end of file diff --git a/packages/solc-transpiler/test/contracts/library/SimpleUnsafeMath.sol b/packages/solc-transpiler/test/contracts/library/SimpleUnsafeMath.sol deleted file mode 100644 index 21eae455d8395..0000000000000 --- a/packages/solc-transpiler/test/contracts/library/SimpleUnsafeMath.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.5.0; - -library SimpleUnsafeMath { - - function subUint(uint a, uint b) public returns(uint){ - - return a - b; - - } - function addUint(uint a , uint b) public pure returns(uint){ - uint c = a + b; - return c; - } -} \ No newline at end of file diff --git a/packages/solc-transpiler/test/libraries.spec.ts b/packages/solc-transpiler/test/libraries.spec.ts deleted file mode 100644 index 477d6f8d5fa0e..0000000000000 --- a/packages/solc-transpiler/test/libraries.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import './setup' - -/* External Imports */ -import { getLogger } from '@eth-optimism/core-utils' - -import * as path from 'path' -import * as fs from 'fs' - -/* Internal Imports */ -import { compile } from '../src' - -const log = getLogger('library-use-compilation') - -const safeMathUserPath = path.resolve( - __dirname, - './contracts/library/SafeMathUser.sol' -) -const simpleSafeMathPath = path.resolve( - __dirname, - './contracts/library/SimpleSafeMath.sol' -) -const simpleUnsafeMathPath = path.resolve( - __dirname, - './contracts/library/SimpleUnsafeMath.sol' -) - -const config = { - language: 'Solidity', - sources: { - 'SafeMathUser.sol': { - content: fs.readFileSync(safeMathUserPath, 'utf8'), - }, - 'SimpleSafeMath.sol': { - content: fs.readFileSync(simpleSafeMathPath, 'utf8'), - }, - 'SimpleUnsafeMath.sol': { - content: fs.readFileSync(simpleUnsafeMathPath, 'utf8'), - }, - }, - settings: { - outputSelection: { - '*': { - '*': ['*'], - }, - }, - executionManagerAddress: '0x6454c9d69a4721feba60e26a367bd4d56196ee7c', - }, -} - -describe('Library usage tests', () => { - it('should compile with libraries', async () => { - const wrappedSolcResult = compile(JSON.stringify(config)) - const wrappedSolcJson = JSON.parse(wrappedSolcResult) - - wrappedSolcJson.contracts.should.not.equal( - undefined, - 'No compiled contracts found!' - ) - - wrappedSolcJson.contracts['SimpleSafeMath.sol'].should.not.equal( - undefined, - 'SimpleSafeMath file not found!' - ) - wrappedSolcJson.contracts['SimpleSafeMath.sol'][ - 'SimpleSafeMath' - ].should.not.equal(undefined, 'SimpleSafeMath contract not found!') - - wrappedSolcJson.contracts['SimpleUnsafeMath.sol'].should.not.equal( - undefined, - 'SimpleUnsafeMath file not found!' - ) - wrappedSolcJson.contracts['SimpleUnsafeMath.sol'][ - 'SimpleUnsafeMath' - ].should.not.equal(undefined, 'SimpleUnsafeMath contract not found!') - - wrappedSolcJson.contracts['SafeMathUser.sol'].should.not.equal( - undefined, - 'SafeMathUser file not found!' - ) - wrappedSolcJson.contracts['SafeMathUser.sol'][ - 'SafeMathUser' - ].should.not.equal(undefined, 'SafeMathUser contract not found!') - }).timeout(10_000) -}) diff --git a/packages/solc-transpiler/test/setup.ts b/packages/solc-transpiler/test/setup.ts deleted file mode 100644 index 975dacd6cf91c..0000000000000 --- a/packages/solc-transpiler/test/setup.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* External Imports */ -import chai = require('chai') -import chaiAsPromised = require('chai-as-promised') - -/* Internal Imports */ -import { rootPath } from '../index' - -chai.use(chaiAsPromised) -const should = chai.should() - -export { should } diff --git a/packages/solc-transpiler/test/waffle-config.json b/packages/solc-transpiler/test/waffle-config.json deleted file mode 100644 index 9d31c68ec665c..0000000000000 --- a/packages/solc-transpiler/test/waffle-config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "sourcesPath": "./test/contracts", - "targetPath": "./test/contracts/build", - "npmPath": "../../node_modules", - "solcVersion": "../../node_modules/solc", - "compilerOptions": { - "outputSelection": { - "*": { - "*": ["*"] - } - } - } -} diff --git a/packages/solc-transpiler/test/wrapper.spec.ts b/packages/solc-transpiler/test/wrapper.spec.ts deleted file mode 100644 index 63ea554105f3c..0000000000000 --- a/packages/solc-transpiler/test/wrapper.spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -import './setup' - -/* External Imports */ -import { - OpcodeReplacerImpl, - OpcodeWhitelistImpl, - SuccessfulTranspilation, - TranspilationResult, - Transpiler, - TranspilerImpl, -} from '@eth-optimism/rollup-dev-tools' -import { - bufToHexString, - hexStrToBuf, - remove0x, - ZERO_ADDRESS, -} from '@eth-optimism/core-utils' -import * as path from 'path' -import * as fs from 'fs' - -/* Internal Imports */ -import { compile } from '../src' -import * as DummyContract from './contracts/build/Dummy.json' -import * as Dummy2Contract from './contracts/build/Dummy2.json' -import * as Dummy3Contract from './contracts/build/Dummy3.json' - -const dummyPath = path.resolve(__dirname, './contracts/dummy/Dummy.sol') -const dummy2Path = path.resolve(__dirname, './contracts/dummy/Dummy2.sol') - -describe('Wrapper tests', () => { - let config - let configWithoutLegacyAssembly - let multiConfig - before(() => { - config = { - language: 'Solidity', - sources: { - 'Dummy.sol': { - content: fs.readFileSync(dummyPath, 'utf8'), - }, - }, - settings: { - outputSelection: { - '*': { - '*': ['*'], - }, - }, - executionManagerAddress: ZERO_ADDRESS, - }, - } - - multiConfig = { ...config } - multiConfig.sources['Dummy2.sol'] = { - content: fs.readFileSync(dummy2Path, 'utf8'), - } - - configWithoutLegacyAssembly = { ...config } - configWithoutLegacyAssembly.settings.outputSelection['*']['*'] = [ - 'abi', - 'evm.bytecode', - 'evm.deployedBytecode', - ] - }) - - const transpiler: Transpiler = new TranspilerImpl( - new OpcodeWhitelistImpl(), - new OpcodeReplacerImpl(ZERO_ADDRESS) - ) - - const getContractTranspiledBytecode = ( - compiledJson: any, - isDeployedBytecode: boolean = false - ): string => { - const auxData = compiledJson.evm.legacyAssembly['.data']['0']['.auxdata'] - const bytecode = isDeployedBytecode - ? compiledJson.evm.deployedBytecode.object - : compiledJson.evm.bytecode.object - - // Remove the AuxData at the end of the contract bytecode because this may be different even if it's the exact same contract - const bytecodeWithoutAuxdata: string = bytecode.split(auxData)[0] - const transpilationResult: TranspilationResult = transpiler.transpileRawBytecode( - hexStrToBuf(bytecodeWithoutAuxdata) - ) - - transpilationResult.succeeded.should.eq( - true, - 'Cannot evaluate test because transpiling solc bytecode failed.' - ) - - return remove0x( - bufToHexString((transpilationResult as SuccessfulTranspilation).bytecode) - ) - } - - it('should match deployed bytecode transpilation result if compiled and transpiled separately', () => { - const wrappedSolcResult = compile(JSON.stringify(config)) - const wrappedSolcJson = JSON.parse(wrappedSolcResult) - - const waffleTranspiledDeployedBytecode: string = getContractTranspiledBytecode( - DummyContract, - true - ) - const wrappedSolcTranspiledDeployedBytecode: string = - wrappedSolcJson['contracts']['Dummy.sol']['Dummy'].evm.deployedBytecode - .object - - waffleTranspiledDeployedBytecode.should.eq( - wrappedSolcTranspiledDeployedBytecode, - 'Transpiled deployed bytecode mismatch!' - ) - }).timeout(10000) - - it('should work for multiple sources', () => { - const wrappedSolcResult = compile(JSON.stringify(multiConfig)) - const wrappedSolcJson = JSON.parse(wrappedSolcResult) - - const dummyWaffleTranspiledDeployedBytecode: string = getContractTranspiledBytecode( - DummyContract, - true - ) - const dummyWrappedSolcTranspiledDeployedBytecode: string = - wrappedSolcJson['contracts']['Dummy.sol']['Dummy'].evm.deployedBytecode - .object - - dummyWaffleTranspiledDeployedBytecode.should.eq( - dummyWrappedSolcTranspiledDeployedBytecode, - 'Dummy transpiled deployed bytecode mismatch!' - ) - - const dummy2WaffleTranspiledDeployedBytecode: string = getContractTranspiledBytecode( - Dummy2Contract, - true - ) - const dummy2WrappedSolcTranspiledDeployedBytecode: string = - wrappedSolcJson['contracts']['Dummy2.sol']['Dummy2'].evm.deployedBytecode - .object - - dummy2WaffleTranspiledDeployedBytecode.should.eq( - dummy2WrappedSolcTranspiledDeployedBytecode, - 'Dummy2 transpiled deployed bytecode mismatch!' - ) - - const dummy3WaffleTranspiledDeployedBytecode: string = getContractTranspiledBytecode( - Dummy3Contract, - true - ) - const dummy3WrappedSolcTranspiledDeployedBytecode: string = - wrappedSolcJson['contracts']['Dummy2.sol']['Dummy3'].evm.deployedBytecode - .object - - dummy3WaffleTranspiledDeployedBytecode.should.eq( - dummy3WrappedSolcTranspiledDeployedBytecode, - 'Dummy3 transpiled deployed bytecode mismatch!' - ) - }) - - it('should work without `evm.legacyAssembly` outputSelection', () => { - const wrappedSolcResult = compile( - JSON.stringify(configWithoutLegacyAssembly) - ) - const wrappedSolcJson = JSON.parse(wrappedSolcResult) - - const waffleTranspiledDeployedBytecode: string = getContractTranspiledBytecode( - DummyContract, - true - ) - const wrappedSolcTranspiledDeployedBytecode: string = - wrappedSolcJson['contracts']['Dummy.sol']['Dummy'].evm.deployedBytecode - .object - - waffleTranspiledDeployedBytecode.should.eq( - wrappedSolcTranspiledDeployedBytecode, - 'Transpiled deployed bytecode mismatch!' - ) - - const hasLegacyAssembly: boolean = - 'legacyAssembly' in wrappedSolcJson['contracts']['Dummy.sol']['Dummy'].evm - hasLegacyAssembly.should.equal( - false, - 'Legacy assembly should not be present in output!' - ) - }) -}) diff --git a/packages/solc-transpiler/tsconfig.json b/packages/solc-transpiler/tsconfig.json deleted file mode 100644 index 552952412a746..0000000000000 --- a/packages/solc-transpiler/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./../../tsconfig.json", - "compilerOptions": { - "outDir": "./build", - "resolveJsonModule": true - }, - "include": ["*.ts", "**/*.ts"] -} diff --git a/packages/solc-transpiler/tslint.json b/packages/solc-transpiler/tslint.json deleted file mode 100644 index 7348d4d168dd5..0000000000000 --- a/packages/solc-transpiler/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": ["./../../tslint.json"], - "rules": { - "prettier": [true, "../../prettier-config.json"] - } -} diff --git a/yarn.lock b/yarn.lock index a29504eef20e9..0ca7a94c4211b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -101,7 +101,7 @@ ethereumjs-vm "^4.1.3" ethers "^4.0.42" -"@eth-optimism/solc-transpiler@^0.0.1-alpha.28": +"@eth-optimism/solc-transpiler@^0.0.1-alpha.27", "@eth-optimism/solc-transpiler@^0.0.1-alpha.28": version "0.0.1-alpha.28" resolved "https://registry.yarnpkg.com/@eth-optimism/solc-transpiler/-/solc-transpiler-0.0.1-alpha.28.tgz#327743c7c0271dac13243fb4de22e1f0ee81b31c" integrity sha512-UyQK6AseKfG1h1kRW+Atu9z0z0Ym3MW+ncoiRK+7TUbKyrDcKuBlLst+FXuo6fChHkw25oDfHpb3t/oTLDLlCg==