Commit c75a0fc8 authored by smartcontracts's avatar smartcontracts Committed by GitHub

Port SequencerEntrypoint to use ovm solc (#535)

* port SequencerEntrypoint to use ovm solc

* Minor tweaks and comments

* have hardhat-ovm invalidate cache on commit to solc-bin

* remove temporary solc version override

* have cache invalidation on a per-version basis

* remove ethereumjs-util dep

* fix lint error

* use allow_kall_2 compiler until build is fixed

* add changeset
parent c7bc0cef
---
"@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.
// 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 {
......@@ -59,41 +63,54 @@ 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_OVMCodec.decompressEIP155Transaction(
compressedTx,
chainId
),
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
);
}
......@@ -119,9 +136,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");
}
}
}
......@@ -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
});
}
......
// 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
*
* Compiler used: solc
* Runtime target: OVM
*/
library Lib_ExecutionManagerWrapper {
/**********************
* Internal Functions *
**********************/
/**
* 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 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)
mstore(0x40, add(returndata, and(add(add(size, 0x20), 0x1f), not(0x1f))))
mstore(returndata, size)
returndatacopy(add(returndata, 0x20), 0x0, size)
}
return returndata;
}
}
......@@ -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
);
}
}
......@@ -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',
......
......@@ -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'),
......
......@@ -164,6 +164,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
const ovmCompiled = [
'OVM_L2ToL1MessagePasser',
'OVM_L2CrossDomainMessenger',
'OVM_SequencerEntrypoint',
'Lib_AddressManager',
'OVM_ETH',
]
......@@ -211,12 +212,19 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
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,
}
}
......
......@@ -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()
})
......
......@@ -2,11 +2,12 @@ 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'
/* Internal Imports */
import { getContractInterface } from '../../../../src'
import { getContractInterface, getContractFactory } from '../../../../src'
import {
encodeSequencerCalldata,
signNativeTransaction,
......@@ -31,7 +32,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) => {
// Duplicating the behavior of the ecrecover precompile.
if (target === '0x0000000000000000000000000000000000000001') {
const databuf = fromHexString(data)
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 {
return [true, '0x']
}
}
)
Helper_PredeployCaller = await (
await ethers.getContractFactory('Helper_PredeployCaller')
......@@ -42,11 +74,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 +108,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', [
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 +134,16 @@ describe('OVM_SequencerEntrypoint', () => {
const encodedTx = serializeNativeTransaction(createTx)
const sig = await signNativeTransaction(wallet, createTx)
const expectedEOACalldata = getContractInterface(
'OVM_ECDSAContractAccount'
).encodeFunctionData('execute', [
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 +151,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 +196,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', [
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)
......
......@@ -3,7 +3,8 @@
"decompressEIP155Transaction": {
"decompression": {
"in": [
"0x0001f4000064000064121212121212121212121212121212121212121299999999999999999999"
"0x0001f4000064000064121212121212121212121212121212121212121299999999999999999999",
420
],
"out": [
[
......
......@@ -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'
......@@ -41,10 +44,32 @@ const getOvmSolcPath = async (version: string): Promise<string> => {
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-info-${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
}
......@@ -68,6 +93,7 @@ const getOvmSolcPath = async (version: string): Promise<string> => {
// 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
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment