From d46e81a6a0d4877f0369e0c535e0c55c37c25ca7 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 13:08:41 -0400 Subject: [PATCH 1/9] port SequencerEntrypoint to use ovm solc --- .../predeploys/OVM_SequencerEntrypoint.sol | 51 +++--- .../libraries/codec/Lib_OVMCodec.sol | 6 +- .../wrappers/Lib_ExecutionManagerWrapper.sol | 148 ++++++++++++++++++ .../test-libraries/codec/TestLib_OVMCodec.sol | 8 +- packages/contracts/package.json | 1 + .../src/contract-deployment/config.ts | 2 +- packages/contracts/src/contract-dumps.ts | 10 +- .../OVM_ProxySequencerEntrypoint.spec.ts | 7 +- .../OVM_SequencerEntrypoint.spec.ts | 119 ++++++++++---- .../libraries/codec/Lib_OVMCodec.test.json | 3 +- packages/hardhat-ovm/src/index.ts | 3 + yarn.lock | 2 +- 12 files changed, 296 insertions(+), 64 deletions(-) create mode 100644 packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol index bba7dd59c90fc..8a151dee389e0 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol @@ -1,11 +1,15 @@ // SPDX-License-Identifier: MIT +// @unsupported: evm pragma solidity >0.5.0 <0.8.0; +/* Interface Imports */ +import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContractAccount.sol"; + /* Library Imports */ import { Lib_BytesUtils } from "../../libraries/utils/Lib_BytesUtils.sol"; import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_ECDSAUtils } from "../../libraries/utils/Lib_ECDSAUtils.sol"; -import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_SafeExecutionManagerWrapper.sol"; +import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol"; /** * @title OVM_SequencerEntrypoint @@ -15,7 +19,7 @@ import { Lib_SafeExecutionManagerWrapper } from "../../libraries/wrappers/Lib_Sa * This contract is the implementation referenced by the Proxy Sequencer Entrypoint, thus enabling * the Optimism team to upgrade the decompression of calldata from the Sequencer. * - * Compiler used: solc + * Compiler used: optimistic-solc * Runtime target: OVM */ contract OVM_SequencerEntrypoint { @@ -61,41 +65,48 @@ contract OVM_SequencerEntrypoint { // Need to decompress and then re-encode the transaction based on the original encoding. bytes memory encodedTx = Lib_OVMCodec.encodeEIP155Transaction( - Lib_OVMCodec.decompressEIP155Transaction(compressedTx), + Lib_OVMCodec.decompressEIP155Transaction( + compressedTx, + Lib_ExecutionManagerWrapper.ovmCHAINID() + ), isEthSignedMessage ); address target = Lib_ECDSAUtils.recover( encodedTx, isEthSignedMessage, - uint8(v), + v, r, s ); - if (Lib_SafeExecutionManagerWrapper.safeEXTCODESIZE(target) == 0) { + bool isEmptyContract; + assembly { + isEmptyContract := iszero(extcodesize(target)) + } + + if (isEmptyContract) { // ProxyEOA has not yet been deployed for this EOA. bytes32 messageHash = Lib_ECDSAUtils.getMessageHash(encodedTx, isEthSignedMessage); - Lib_SafeExecutionManagerWrapper.safeCREATEEOA(messageHash, uint8(v), r, s); + Lib_ExecutionManagerWrapper.ovmCREATEEOA(messageHash, v, r, s); + } + + Lib_OVMCodec.EOASignatureType sigtype; + if (isEthSignedMessage) { + sigtype = Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE; + } else { + sigtype = Lib_OVMCodec.EOASignatureType.EIP155_TRANSACTION; } - // ProxyEOA has been deployed for this EOA, continue to CALL. - bytes memory callbytes = abi.encodeWithSignature( - "execute(bytes,uint8,uint8,bytes32,bytes32)", + iOVM_ECDSAContractAccount(target).execute( encodedTx, - isEthSignedMessage, - uint8(v), + sigtype, + v, r, s ); - - Lib_SafeExecutionManagerWrapper.safeCALL( - gasleft(), - target, - callbytes - ); } - + /********************** * Internal Functions * @@ -119,9 +130,7 @@ contract OVM_SequencerEntrypoint { } if (_transactionType == 2) { return TransactionType.ETH_SIGNED_MESSAGE; } else { - Lib_SafeExecutionManagerWrapper.safeREVERT( - "Transaction type must be 0 or 2" - ); + revert("Transaction type must be 0 or 2"); } } } diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol index 0cb7245956a5b..42c9d2e8b614d 100644 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/codec/Lib_OVMCodec.sol @@ -155,10 +155,12 @@ library Lib_OVMCodec { /** * Decompresses a compressed EIP155 transaction. * @param _transaction Compressed EIP155 transaction bytes. + * @param _chainId Chain ID this transaction was signed with. * @return Transaction parsed into a struct. */ function decompressEIP155Transaction( - bytes memory _transaction + bytes memory _transaction, + uint256 _chainId ) internal returns ( @@ -171,7 +173,7 @@ library Lib_OVMCodec { nonce: Lib_BytesUtils.toUint24(_transaction, 6), to: Lib_BytesUtils.toAddress(_transaction, 9), data: Lib_BytesUtils.slice(_transaction, 29), - chainId: Lib_SafeExecutionManagerWrapper.safeCHAINID(), + chainId: _chainId, value: 0 }); } diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol new file mode 100644 index 0000000000000..ae2c0189f1033 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT +// @unsupported: evm +pragma solidity >0.5.0 <0.8.0; + +/* Library Imports */ +import { Lib_ErrorUtils } from "../utils/Lib_ErrorUtils.sol"; + +/** + * @title Lib_ExecutionManagerWrapper + * @dev The Safe Execution Manager Wrapper provides functions which facilitate writing OVM safe + * code using the standard solidity compiler, by routing all its operations through the Execution + * Manager. + * + * Compiler used: solc + * Runtime target: OVM + */ +library Lib_ExecutionManagerWrapper { + + /********************** + * Internal Functions * + **********************/ + + /** + * Performs a safe ovmCHAINID call. + * @return _CHAINID Result of calling ovmCHAINID. + */ + function ovmCHAINID() + internal + returns ( + uint256 _CHAINID + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + abi.encodeWithSignature( + "ovmCHAINID()" + ) + ); + + return abi.decode(returndata, (uint256)); + } + + /** + * Performs a safe ovmGETNONCE call. + * @return _nonce Result of calling ovmGETNONCE. + */ + function ovmGETNONCE() + internal + returns ( + uint256 _nonce + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + abi.encodeWithSignature( + "ovmGETNONCE()" + ) + ); + + return abi.decode(returndata, (uint256)); + } + + /** + * Performs a safe ovmINCREMENTNONCE call. + */ + function ovmINCREMENTNONCE() + internal + { + _safeExecutionManagerInteraction( + abi.encodeWithSignature( + "ovmINCREMENTNONCE()" + ) + ); + } + + /** + * Performs a safe ovmCREATEEOA call. + * @param _messageHash Message hash which was signed by EOA + * @param _v v value of signature (0 or 1) + * @param _r r value of signature + * @param _s s value of signature + */ + function ovmCREATEEOA( + bytes32 _messageHash, + uint8 _v, + bytes32 _r, + bytes32 _s + ) + internal + { + _safeExecutionManagerInteraction( + abi.encodeWithSignature( + "ovmCREATEEOA(bytes32,uint8,bytes32,bytes32)", + _messageHash, + _v, + _r, + _s + ) + ); + } + + /** + * Calls the ovmL1TXORIGIN opcode. + * @return Address that sent this message from L1. + */ + function ovmL1TXORIGIN() + internal + returns ( + address + ) + { + bytes memory returndata = _safeExecutionManagerInteraction( + abi.encodeWithSignature( + "ovmL1TXORIGIN()" + ) + ); + + return abi.decode(returndata, (address)); + } + + + /********************* + * Private Functions * + *********************/ + + /** + * Performs an ovm interaction and the necessary safety checks. + * @param _calldata Data to send to the OVM_ExecutionManager (encoded with sighash). + * @return _returndata Data sent back by the OVM_ExecutionManager. + */ + function _safeExecutionManagerInteraction( + bytes memory _calldata + ) + private + returns ( + bytes memory + ) + { + bytes memory returndata; + assembly { + kall(add(_calldata, 0x20), mload(_calldata), 0x0, 0x0) + let size := returndatasize() + returndata := mload(0x40) + mstore(0x40, add(returndata, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + mstore(returndata, size) + returndatacopy(add(returndata, 0x20), 0x0, size) + } + return returndata; + } +} diff --git a/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol b/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol index b9046ac8a7766..d162c1216e0e0 100644 --- a/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol +++ b/packages/contracts/contracts/test-libraries/codec/TestLib_OVMCodec.sol @@ -48,13 +48,17 @@ contract TestLib_OVMCodec { } function decompressEIP155Transaction( - bytes memory _transaction + bytes memory _transaction, + uint256 _chainId ) public returns ( Lib_OVMCodec.EIP155Transaction memory _decompressed ) { - return Lib_OVMCodec.decompressEIP155Transaction(_transaction); + return Lib_OVMCodec.decompressEIP155Transaction( + _transaction, + _chainId + ); } } diff --git a/packages/contracts/package.json b/packages/contracts/package.json index b9ac7a2d79092..5f163ea73f03b 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -63,6 +63,7 @@ "directory-tree": "^2.2.7", "dotenv": "^8.2.0", "ethereum-waffle": "^3.3.0", + "ethereumjs-util": "^7.0.10", "ethers": "^5.0.31", "hardhat": "^2.0.8", "hardhat-deploy": "^0.7.4", diff --git a/packages/contracts/src/contract-deployment/config.ts b/packages/contracts/src/contract-deployment/config.ts index 5ddfc87b88ef7..db5c10b6b4d34 100644 --- a/packages/contracts/src/contract-deployment/config.ts +++ b/packages/contracts/src/contract-deployment/config.ts @@ -220,7 +220,7 @@ export const makeContractDeployConfig = async ( factory: getContractFactory('OVM_ECDSAContractAccount'), }, OVM_SequencerEntrypoint: { - factory: getContractFactory('OVM_SequencerEntrypoint'), + factory: getContractFactory('OVM_SequencerEntrypoint', undefined, true), }, OVM_ProxySequencerEntrypoint: { factory: getContractFactory('OVM_ProxySequencerEntrypoint'), diff --git a/packages/contracts/src/contract-dumps.ts b/packages/contracts/src/contract-dumps.ts index 697f35f1206d5..baffa384cffd9 100644 --- a/packages/contracts/src/contract-dumps.ts +++ b/packages/contracts/src/contract-dumps.ts @@ -164,6 +164,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise => { const ovmCompiled = [ 'OVM_L2ToL1MessagePasser', 'OVM_L2CrossDomainMessenger', + 'OVM_SequencerEntrypoint', 'Lib_AddressManager', 'OVM_ETH', ] @@ -211,12 +212,19 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise => { predeploys[name] || `0xdeaddeaddeaddeaddeaddeaddeaddeaddead${i.toString(16).padStart(4, '0')}` + let def: any + try { + def = getContractDefinition(name.replace('Proxy__', '')) + } catch (err) { + def = getContractDefinition(name.replace('Proxy__', ''), true) + } + dump.accounts[name] = { address: deadAddress, code, codeHash: keccak256(code), storage: await getStorageDump(cStateManager, contract.address), - abi: getContractDefinition(name.replace('Proxy__', '')).abi, + abi: def.abi, } } diff --git a/packages/contracts/test/contracts/OVM/precompiles/OVM_ProxySequencerEntrypoint.spec.ts b/packages/contracts/test/contracts/OVM/precompiles/OVM_ProxySequencerEntrypoint.spec.ts index ec1e40fac3736..4407ff157140f 100644 --- a/packages/contracts/test/contracts/OVM/precompiles/OVM_ProxySequencerEntrypoint.spec.ts +++ b/packages/contracts/test/contracts/OVM/precompiles/OVM_ProxySequencerEntrypoint.spec.ts @@ -8,6 +8,7 @@ import { remove0x } from '@eth-optimism/core-utils' /* Internal Imports */ import { decodeSolidityError } from '../../../helpers' +import { getContractFactory } from '../../../../src' const callPredeploy = async ( Helper_PredeployCaller: Contract, @@ -59,8 +60,10 @@ describe('OVM_ProxySequencerEntrypoint', () => { Helper_PredeployCaller.setTarget(Mock__OVM_ExecutionManager.address) - OVM_SequencerEntrypoint = await ( - await ethers.getContractFactory('OVM_SequencerEntrypoint') + OVM_SequencerEntrypoint = await getContractFactory( + 'OVM_SequencerEntrypoint', + wallet, + true ).deploy() }) diff --git a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts index 2979ad3bc33eb..4046eaf7abdf2 100644 --- a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts +++ b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts @@ -2,11 +2,13 @@ import { expect } from '../../../setup' /* External Imports */ import { waffle, ethers } from 'hardhat' -import { ContractFactory, Wallet, Contract } from 'ethers' +import { ContractFactory, Wallet, Contract, BigNumber } from 'ethers' import { smockit, MockContract } from '@eth-optimism/smock' +import { fromHexString, toHexString } from '@eth-optimism/core-utils' +import { ecrecover, publicToAddress } from 'ethereumjs-util' /* Internal Imports */ -import { getContractInterface } from '../../../../src' +import { getContractInterface, getContractFactory } from '../../../../src' import { encodeSequencerCalldata, signNativeTransaction, @@ -31,7 +33,38 @@ describe('OVM_SequencerEntrypoint', () => { ) Mock__OVM_ExecutionManager.smocked.ovmCHAINID.will.return.with(420) - Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with([true, '0x']) + Mock__OVM_ExecutionManager.smocked.ovmCALL.will.return.with( + (gasLimit, target, data) => { + if (target === wallet.address) { + return [ + true, + iOVM_ECDSAContractAccount.encodeFunctionResult('execute', [ + true, + '0x', + ]), + ] + } else { + return [true, '0x'] + } + } + ) + Mock__OVM_ExecutionManager.smocked.ovmSTATICCALL.will.return.with( + (gasLimit, target, data) => { + if (target === '0x0000000000000000000000000000000000000001') { + const databuf = fromHexString(data) + const msghash = databuf.slice(0, 32) + const v = databuf.slice(32, 64) + const r = databuf.slice(64, 96) + const s = databuf.slice(96, 128) + const pubkey = ecrecover(msghash, BigNumber.from(v).toNumber(), r, s) + const addr = toHexString(publicToAddress(pubkey)) + const ret = ethers.utils.defaultAbiCoder.encode(['address'], [addr]) + return [true, ret] + } else { + return [true, '0x'] + } + } + ) Helper_PredeployCaller = await ( await ethers.getContractFactory('Helper_PredeployCaller') @@ -42,11 +75,18 @@ describe('OVM_SequencerEntrypoint', () => { let OVM_SequencerEntrypointFactory: ContractFactory before(async () => { - OVM_SequencerEntrypointFactory = await ethers.getContractFactory( - 'OVM_SequencerEntrypoint' + OVM_SequencerEntrypointFactory = getContractFactory( + 'OVM_SequencerEntrypoint', + wallet, + true ) }) + const iOVM_ECDSAContractAccount = getContractInterface( + 'OVM_ECDSAContractAccount', + true + ) + let OVM_SequencerEntrypoint: Contract beforeEach(async () => { OVM_SequencerEntrypoint = await OVM_SequencerEntrypointFactory.deploy() @@ -69,15 +109,16 @@ describe('OVM_SequencerEntrypoint', () => { const encodedTx = serializeNativeTransaction(DEFAULT_EIP155_TX) const sig = await signNativeTransaction(wallet, DEFAULT_EIP155_TX) - const expectedEOACalldata = getContractInterface( - 'OVM_ECDSAContractAccount' - ).encodeFunctionData('execute', [ - encodedTx, - 0, //isEthSignedMessage - `0x${sig.v}`, //v - `0x${sig.r}`, //r - `0x${sig.s}`, //s - ]) + const expectedEOACalldata = iOVM_ECDSAContractAccount.encodeFunctionData( + 'execute', + [ + encodedTx, + 0, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0] expect(ovmCALL._address).to.equal(await wallet.getAddress()) expect(ovmCALL._calldata).to.equal(expectedEOACalldata) @@ -94,15 +135,16 @@ describe('OVM_SequencerEntrypoint', () => { const encodedTx = serializeNativeTransaction(createTx) const sig = await signNativeTransaction(wallet, createTx) - const expectedEOACalldata = getContractInterface( - 'OVM_ECDSAContractAccount' - ).encodeFunctionData('execute', [ - encodedTx, - 0, //isEthSignedMessage - `0x${sig.v}`, //v - `0x${sig.r}`, //r - `0x${sig.s}`, //s - ]) + const expectedEOACalldata = iOVM_ECDSAContractAccount.encodeFunctionData( + 'execute', + [ + encodedTx, + 0, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0] expect(ovmCALL._address).to.equal(await wallet.getAddress()) expect(ovmCALL._calldata).to.equal(expectedEOACalldata) @@ -110,7 +152,17 @@ describe('OVM_SequencerEntrypoint', () => { for (let i = 0; i < 3; i += 2) { it(`should call ovmCreateEOA when tx type is ${i} and ovmEXTCODESIZE returns 0`, async () => { - Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with(0) + let firstCheck = true + Mock__OVM_ExecutionManager.smocked.ovmEXTCODESIZE.will.return.with( + () => { + if (firstCheck) { + firstCheck = false + return 0 + } else { + return 1 + } + } + ) const calldata = await encodeSequencerCalldata( wallet, DEFAULT_EIP155_TX, @@ -145,15 +197,16 @@ describe('OVM_SequencerEntrypoint', () => { const encodedTx = serializeEthSignTransaction(DEFAULT_EIP155_TX) const sig = await signEthSignMessage(wallet, DEFAULT_EIP155_TX) - const expectedEOACalldata = getContractInterface( - 'OVM_ECDSAContractAccount' - ).encodeFunctionData('execute', [ - encodedTx, - 1, //isEthSignedMessage - `0x${sig.v}`, //v - `0x${sig.r}`, //r - `0x${sig.s}`, //s - ]) + const expectedEOACalldata = iOVM_ECDSAContractAccount.encodeFunctionData( + 'execute', + [ + encodedTx, + 1, //isEthSignedMessage + `0x${sig.v}`, //v + `0x${sig.r}`, //r + `0x${sig.s}`, //s + ] + ) const ovmCALL: any = Mock__OVM_ExecutionManager.smocked.ovmCALL.calls[0] expect(ovmCALL._address).to.equal(await wallet.getAddress()) expect(ovmCALL._calldata).to.equal(expectedEOACalldata) diff --git a/packages/contracts/test/data/json/libraries/codec/Lib_OVMCodec.test.json b/packages/contracts/test/data/json/libraries/codec/Lib_OVMCodec.test.json index b442f4e07c8cf..6808dcef02d4d 100644 --- a/packages/contracts/test/data/json/libraries/codec/Lib_OVMCodec.test.json +++ b/packages/contracts/test/data/json/libraries/codec/Lib_OVMCodec.test.json @@ -3,7 +3,8 @@ "decompressEIP155Transaction": { "decompression": { "in": [ - "0x0001f4000064000064121212121212121212121212121212121212121299999999999999999999" + "0x0001f4000064000064121212121212121212121212121212121212121299999999999999999999", + 420 ], "out": [ [ diff --git a/packages/hardhat-ovm/src/index.ts b/packages/hardhat-ovm/src/index.ts index 40d7212839056..277893411a08b 100644 --- a/packages/hardhat-ovm/src/index.ts +++ b/packages/hardhat-ovm/src/index.ts @@ -27,6 +27,9 @@ const DEFAULT_OVM_SOLC_VERSION = '0.7.6' * @return Path to the downloaded soljson.js file. */ const getOvmSolcPath = async (version: string): Promise => { + // TEMPORARY FOR DEVELOPMENT + version = '0.7.6-allow_kall_2' + // If __DANGEROUS_OVM_IGNORE_ERRORS__ env var is not undefined we append the -no-errors suffix to the solc version. if (process.env.__DANGEROUS_OVM_IGNORE_ERRORS__) { console.log('\n\n__DANGEROUS_OVM_IGNORE_ERRORS__ IS ENABLED!\n\n') diff --git a/yarn.lock b/yarn.lock index a8f182b80cbd4..8c293bb164a7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5485,7 +5485,7 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.8: +ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.8: version "7.0.10" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz#5fb7b69fa1fda0acc59634cf39d6b0291180fc1f" integrity sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw== From ef5c08595a08a3414a539b9cd069d021c8525bd9 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 13:37:47 -0400 Subject: [PATCH 2/9] Minor tweaks and comments --- .../predeploys/OVM_SequencerEntrypoint.sol | 8 ++++++- .../wrappers/Lib_ExecutionManagerWrapper.sol | 24 ++----------------- .../OVM_SequencerEntrypoint.spec.ts | 1 + 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol b/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol index 8a151dee389e0..2b2911013d877 100644 --- a/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol +++ b/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol @@ -63,11 +63,17 @@ contract OVM_SequencerEntrypoint { bytes memory compressedTx = Lib_BytesUtils.slice(msg.data, 66); bool isEthSignedMessage = transactionType == TransactionType.ETH_SIGNED_MESSAGE; + // Grab the chain ID for the current network. + uint256 chainId; + assembly { + chainId := chainid() + } + // Need to decompress and then re-encode the transaction based on the original encoding. bytes memory encodedTx = Lib_OVMCodec.encodeEIP155Transaction( Lib_OVMCodec.decompressEIP155Transaction( compressedTx, - Lib_ExecutionManagerWrapper.ovmCHAINID() + chainId ), isEthSignedMessage ); diff --git a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol index ae2c0189f1033..1546922a6fc32 100644 --- a/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol +++ b/packages/contracts/contracts/optimistic-ethereum/libraries/wrappers/Lib_ExecutionManagerWrapper.sol @@ -7,9 +7,6 @@ import { Lib_ErrorUtils } from "../utils/Lib_ErrorUtils.sol"; /** * @title Lib_ExecutionManagerWrapper - * @dev The Safe Execution Manager Wrapper provides functions which facilitate writing OVM safe - * code using the standard solidity compiler, by routing all its operations through the Execution - * Manager. * * Compiler used: solc * Runtime target: OVM @@ -20,25 +17,6 @@ library Lib_ExecutionManagerWrapper { * Internal Functions * **********************/ - /** - * Performs a safe ovmCHAINID call. - * @return _CHAINID Result of calling ovmCHAINID. - */ - function ovmCHAINID() - internal - returns ( - uint256 _CHAINID - ) - { - bytes memory returndata = _safeExecutionManagerInteraction( - abi.encodeWithSignature( - "ovmCHAINID()" - ) - ); - - return abi.decode(returndata, (uint256)); - } - /** * Performs a safe ovmGETNONCE call. * @return _nonce Result of calling ovmGETNONCE. @@ -136,6 +114,8 @@ library Lib_ExecutionManagerWrapper { { bytes memory returndata; assembly { + // kall is a custom yul builtin within optimistic-solc that allows us to directly call + // the execution manager (since `call` would be compiled). kall(add(_calldata, 0x20), mload(_calldata), 0x0, 0x0) let size := returndatasize() returndata := mload(0x40) diff --git a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts index 4046eaf7abdf2..1cec1b994a79a 100644 --- a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts +++ b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts @@ -50,6 +50,7 @@ describe('OVM_SequencerEntrypoint', () => { ) Mock__OVM_ExecutionManager.smocked.ovmSTATICCALL.will.return.with( (gasLimit, target, data) => { + // Duplicating the behavior of the ecrecover precompile. if (target === '0x0000000000000000000000000000000000000001') { const databuf = fromHexString(data) const msghash = databuf.slice(0, 32) From 783be8cba24ace6fdf2f73c0ddcdedf399474520 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 13:53:10 -0400 Subject: [PATCH 3/9] have hardhat-ovm invalidate cache on commit to solc-bin --- packages/hardhat-ovm/src/index.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/hardhat-ovm/src/index.ts b/packages/hardhat-ovm/src/index.ts index 277893411a08b..1de0eb8408844 100644 --- a/packages/hardhat-ovm/src/index.ts +++ b/packages/hardhat-ovm/src/index.ts @@ -13,6 +13,9 @@ import { /* Imports: Internal */ import './type-extensions' +const OPTIMISM_SOLC_VERSION_URL = + 'https://api.github.com/repos/ethereum-optimism/solc-bin/git/refs/heads/gh-pages' + const OPTIMISM_SOLC_BIN_URL = 'https://raw.githubusercontent.com/ethereum-optimism/solc-bin/gh-pages/bin' @@ -44,10 +47,29 @@ const getOvmSolcPath = async (version: string): Promise => { if (!fs.existsSync(ovmCompilersCache)) [fs.mkdirSync(ovmCompilersCache, { recursive: true })] + // Pull information about the latest commit in the solc-bin repo. We'll use this to invalidate + // our compiler cache if necessary. + const remoteCompilerVersion = await ( + await fetch(OPTIMISM_SOLC_VERSION_URL) + ).text() + + // Pull the locally stored info about the latest commit. If this differs from the remote info + // then we know to invalidate our cache. + let cachedCompilerVersion = '' + const cachedCompilerVersionPath = path.join(ovmCompilersCache, 'version.json') + if (fs.existsSync(cachedCompilerVersionPath)) { + cachedCompilerVersion = fs + .readFileSync(cachedCompilerVersionPath) + .toString() + } + // Check to see if we already have this compiler version downloaded. We store the cached files at // `X.Y.Z.js`. If it already exists, just return that instead of downloading a new one. const cachedCompilerPath = path.join(ovmCompilersCache, `${version}.js`) - if (fs.existsSync(cachedCompilerPath)) { + if ( + remoteCompilerVersion === cachedCompilerVersion && + fs.existsSync(cachedCompilerPath) + ) { return cachedCompilerPath } @@ -71,6 +93,7 @@ const getOvmSolcPath = async (version: string): Promise => { // figure out how to properly extend and/or hack Hardat's CompilerDownloader class. const compilerContent = await compilerContentResponse.text() fs.writeFileSync(cachedCompilerPath, compilerContent) + fs.writeFileSync(cachedCompilerVersionPath, remoteCompilerVersion) return cachedCompilerPath } From 5f081b41f06f7546b7db4d35bb0d7659d5ae4914 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 14:34:31 -0400 Subject: [PATCH 4/9] remove temporary solc version override --- packages/hardhat-ovm/src/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/hardhat-ovm/src/index.ts b/packages/hardhat-ovm/src/index.ts index 1de0eb8408844..f7f00ddf6dcff 100644 --- a/packages/hardhat-ovm/src/index.ts +++ b/packages/hardhat-ovm/src/index.ts @@ -30,9 +30,6 @@ const DEFAULT_OVM_SOLC_VERSION = '0.7.6' * @return Path to the downloaded soljson.js file. */ const getOvmSolcPath = async (version: string): Promise => { - // TEMPORARY FOR DEVELOPMENT - version = '0.7.6-allow_kall_2' - // If __DANGEROUS_OVM_IGNORE_ERRORS__ env var is not undefined we append the -no-errors suffix to the solc version. if (process.env.__DANGEROUS_OVM_IGNORE_ERRORS__) { console.log('\n\n__DANGEROUS_OVM_IGNORE_ERRORS__ IS ENABLED!\n\n') From d88008810377a931743f245c95ecd4fed921e735 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 14:59:52 -0400 Subject: [PATCH 5/9] have cache invalidation on a per-version basis --- packages/hardhat-ovm/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/hardhat-ovm/src/index.ts b/packages/hardhat-ovm/src/index.ts index f7f00ddf6dcff..727fd40014dee 100644 --- a/packages/hardhat-ovm/src/index.ts +++ b/packages/hardhat-ovm/src/index.ts @@ -53,7 +53,10 @@ const getOvmSolcPath = async (version: string): Promise => { // Pull the locally stored info about the latest commit. If this differs from the remote info // then we know to invalidate our cache. let cachedCompilerVersion = '' - const cachedCompilerVersionPath = path.join(ovmCompilersCache, 'version.json') + const cachedCompilerVersionPath = path.join( + ovmCompilersCache, + `version-info-${version}.json` + ) if (fs.existsSync(cachedCompilerVersionPath)) { cachedCompilerVersion = fs .readFileSync(cachedCompilerVersionPath) From 3687018c59579b85f2fa43f21ef7265e4db3343f Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 15:12:51 -0400 Subject: [PATCH 6/9] remove ethereumjs-util dep --- packages/contracts/package.json | 1 - .../OVM/precompiles/OVM_SequencerEntrypoint.spec.ts | 11 +++++------ yarn.lock | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 5f163ea73f03b..b9ac7a2d79092 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -63,7 +63,6 @@ "directory-tree": "^2.2.7", "dotenv": "^8.2.0", "ethereum-waffle": "^3.3.0", - "ethereumjs-util": "^7.0.10", "ethers": "^5.0.31", "hardhat": "^2.0.8", "hardhat-deploy": "^0.7.4", diff --git a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts index 1cec1b994a79a..72a4f0bf5ef98 100644 --- a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts +++ b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts @@ -53,12 +53,11 @@ describe('OVM_SequencerEntrypoint', () => { // Duplicating the behavior of the ecrecover precompile. if (target === '0x0000000000000000000000000000000000000001') { const databuf = fromHexString(data) - const msghash = databuf.slice(0, 32) - const v = databuf.slice(32, 64) - const r = databuf.slice(64, 96) - const s = databuf.slice(96, 128) - const pubkey = ecrecover(msghash, BigNumber.from(v).toNumber(), r, s) - const addr = toHexString(publicToAddress(pubkey)) + const addr = ethers.utils.recoverAddress(databuf.slice(0, 32), { + v: BigNumber.from(databuf.slice(32, 64)).toNumber(), + r: toHexString(databuf.slice(64, 96)), + s: toHexString(databuf.slice(96, 128)), + }) const ret = ethers.utils.defaultAbiCoder.encode(['address'], [addr]) return [true, ret] } else { diff --git a/yarn.lock b/yarn.lock index 8c293bb164a7a..a8f182b80cbd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5485,7 +5485,7 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.8: +ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.8: version "7.0.10" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz#5fb7b69fa1fda0acc59634cf39d6b0291180fc1f" integrity sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw== From 047eb335c631f68fc6252c8c70ce43db4d3b644d Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 15:18:18 -0400 Subject: [PATCH 7/9] fix lint error --- .../contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts index 72a4f0bf5ef98..02f613ce3aa8f 100644 --- a/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts +++ b/packages/contracts/test/contracts/OVM/precompiles/OVM_SequencerEntrypoint.spec.ts @@ -5,7 +5,6 @@ import { waffle, ethers } from 'hardhat' import { ContractFactory, Wallet, Contract, BigNumber } from 'ethers' import { smockit, MockContract } from '@eth-optimism/smock' import { fromHexString, toHexString } from '@eth-optimism/core-utils' -import { ecrecover, publicToAddress } from 'ethereumjs-util' /* Internal Imports */ import { getContractInterface, getContractFactory } from '../../../../src' From 795d87a5ce8f7c12cbd84bde9a53189f3b0c75ee Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 15:24:16 -0400 Subject: [PATCH 8/9] use allow_kall_2 compiler until build is fixed --- packages/contracts/hardhat.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index c13241a43516b..701b3511b32e9 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -48,6 +48,9 @@ const config: HardhatUserConfig = { }, }, }, + ovm: { + solcVersion: '0.7.6-allow_kall_2', // temporary until we fix the build for 0.7.6 + }, typechain: { outDir: 'dist/types', target: 'ethers-v5', From 2f2459e5dd8ff04b54ffe52f7a12c7964ac4a80e Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Wed, 21 Apr 2021 15:30:51 -0400 Subject: [PATCH 9/9] add changeset --- .changeset/pink-cameras-reflect.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/pink-cameras-reflect.md diff --git a/.changeset/pink-cameras-reflect.md b/.changeset/pink-cameras-reflect.md new file mode 100644 index 0000000000000..6790e00c1dd3f --- /dev/null +++ b/.changeset/pink-cameras-reflect.md @@ -0,0 +1,6 @@ +--- +"@eth-optimism/contracts": patch +"@eth-optimism/hardhat-ovm": patch +--- + +Use optimistic-solc to compile the SequencerEntrypoint. Also introduces a cache invalidation mechanism for hardhat-ovm so that we can push new compiler versions.