Commit b799caab authored by Karl Floersch's avatar Karl Floersch Committed by GitHub

feat: v0.3.0 release candidate (#603)

* feat: Attempt to decode txs as RLP first (#563)
Co-authored-by: default avatarsmartcontracts <smartcontracts@doge.org>

* l2geth: remove eth_sendRawEthSignTransaction endpoint (#589)

* feat[contracts]: Use standard RLP transaction format (#566)

* feat[contracts]: Use standard RLP transaction format

* fix[l2geth]: Encode transaction as RLP

* fix: Correct gas estimation in integration tests

* fix: Correct gas estimation in integration tests

* Update packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol
Co-authored-by: default avatarben-chain <ben@pseudonym.party>

* fix[contracts]: Use isCreate instead of checking target address

* fix[contracts]: Minor optimization in SequencerEntrypoint

* fix[contracts]: Pass max gas to contract call in EOA contract
Co-authored-by: default avatarben-chain <ben@pseudonym.party>

* feat[contracts]: Make ProxyEOA compatible with eip1967 (#592)

* feat[contracts]: Make ProxyEOA compatible with eip1967

* fix[contracts]: Fix bug introduced by indirect constant

* chore[contracts]: Add changeset

* Update .changeset/old-cycles-invite.md
Co-authored-by: default avatarGeorgios Konstantopoulos <me@gakonst.com>

* l2geth: remove ovmsigner (#591)

* l2geth: remove ovmsigner

Also reduce the diff

Co-authored-by: smartcontracts

* l2geth: add changeset

* l2geth: set rlp encoded tx in txmeta in RPC layer (#644)

* l2geth: set rlp encoded tx in txmeta in RPC layer

* l2geth: remove extra setter of txmeta

* chore: add changeset

* feat: Have ExecutionManager pass data upwards (#643)

* feat[contracts]: Make ExecutionManager return data

* fix[l2geth]: fix linting error

* fix[contracts]: Fix build error

* fix[contracts]: fix failing unit tests

* Add changeset
Co-authored-by: default avatarKarl Floersch <karl@karlfloersch.com>

* rpc: only allow txs with no calldata when there is value (#645)

* l2geth: api checks for 0 value

* chore: add changeset

* l2geth: remove check for specific gasprice

* feat[contracts]: Add value transfer support to ECDSAContractAccount (#619)

* feat[contracts]: Use standard RLP transaction format (#566)

* feat[contracts]: Use standard RLP transaction format

* fix[l2geth]: Encode transaction as RLP

* fix: Correct gas estimation in integration tests

* fix: Correct gas estimation in integration tests

* Update packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol
Co-authored-by: default avatarben-chain <ben@pseudonym.party>

* fix[contracts]: Use isCreate instead of checking target address

* fix[contracts]: Minor optimization in SequencerEntrypoint

* fix[contracts]: Pass max gas to contract call in EOA contract
Co-authored-by: default avatarben-chain <ben@pseudonym.party>

* feat[contracts]: Add value transfer to contract account

* fix[contracts]: Tweak transfer logic and add tests

* fix[geth]: Remove logic that rejects value gt 0 txs

* fix: nonce issue in rpc tests

* fix: use correct wallet in rpc value tests

* Update rpc.spec.ts

* cleanup: remove double definition

* chore: add changeset

* chore: add changeset

* tests: delete dead test

* l2geth: log the tx value

* l2geth: pass through zero value at top level

* test: receipt passes

* test: more specifically set balance
Co-authored-by: default avatarben-chain <ben@pseudonym.party>
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>

* dtl: remove legacy encoding (#618)

* dtl: remove legacy decoding

* tests: remove dead test

* chore: add changeset

* Add Goerli v3 deployment (#651)

* Add Goerli v3 deployment

* Add Goerli v3 to README

* dtlL fix syncing off by one (#687)

* dtl: syncing off by one error

* chore: add changeset

* dtl: index the value field (#686)

* chore: add changeset

* chore: add changeset

* dtl: pass through value field

* core-utils: update and test toRpcString

* lint: fix

* l2geth: parse value fields

* chore: add changeset

* rpc: gas fixes (#695)

* l2geth: prevent fees lower than 21000

* l2geth: remove old check for too high tx gaslimit

* tests: update to use new min gas estimated value

* chore: add changeset

* test: update expected values

* test: remove dead test

* examples: fix waffle example + gas changes in tests (#724)

* examples: fix waffle example

* tests: update gas price in assertion

* chore: add changeset

* l2geth: estimate gas assertion in decimal

* test: use configurable key

* ops: delete extra whitespace (#731)

* fix: prevent eth sendtransaction (#725)

* api: prevent unsafe calls

* api: fill in txmeta

* chore: add changeset

* chore: add changeset

* l2geth + contracts:  standard interface for systems contracts and userland contracts (#721)

* l2geth: fix call returndata parsing

* contracts: standardize simulateMessage and run to return bytes

* chore: add changeset

* chore: add changeset

* l2geth: more simple decoding

* contracts: remove named arguments

* chore: fix linter errors

* Add contract deployment to Kovan (#715)

* fix: remove type check in rollup client (#750)

* l2geth: remove tx type check in client

* chore: add changeset

* dtl: prevent null reference in L1 handler (#757)

* dtl: prevent reference of null value

* chore: add changeset

* test: eth_call exceptions (#800)

* feat[l2geth]: Pass up contract revert reasons during DoEstimateGas (#774)

* wip: Starting work on geth revert reasons during estimate gas

fix: error in comment

fix: I got things backwards

fix: Use UnpackValues instead of Unpack

Update l2geth/accounts/abi/abi.go
Co-authored-by: default avatarGeorgios Konstantopoulos <me@gakonst.com>

* Add integration test for reverts

fix: build error

* chore: Add changeset
Co-authored-by: default avatarGeorgios Konstantopoulos <me@gakonst.com>

* chore: add changeset (#831)

* Migrate ETH between gateways (#778)

* add migrate ETH functionality

* contracts: add eth gateway docstring (#832)
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
Co-authored-by: default avatarsmartcontracts <smartcontracts@doge.org>
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
Co-authored-by: default avatarsmartcontracts <kelvinfichter@gmail.com>
Co-authored-by: default avatarben-chain <ben@pseudonym.party>
Co-authored-by: default avatarGeorgios Konstantopoulos <me@gakonst.com>
Co-authored-by: default avatarMaurelian <maurelian@protonmail.ch>
Co-authored-by: default avatarKevin Ho <kevinjho1996@gmail.com>
parent 20747fd5
---
"@eth-optimism/data-transport-layer": patch
---
Parse and index the value field in the data transport layer
---
"@eth-optimism/data-transport-layer": patch
---
Account for the off by one with regards to the l2geth block number and the CTC index
---
"@eth-optimism/l2geth": patch
---
Add value parsing to the rollup client
---
"@eth-optimism/l2geth": patch
---
Removes the extra setting of the txmeta in the syncservice and instead sets the raw tx in the txmeta at the rpc layer
---
'@eth-optimism/l2geth': patch
---
Fill in the raw transaction into the txmeta in the `eth_sendTransaction` codepath
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/l2geth': patch
---
Add support for parsed revert reasons in DoEstimateGas
---
"@eth-optimism/integration-tests": patch
"@eth-optimism/l2geth": patch
---
Update minimum response from estimate gas
---
"@eth-optimism/integration-tests": patch
"@eth-optimism/l2geth": patch
"@eth-optimism/contracts": patch
---
Add value transfer support to ECDSAContractAccount
---
'@eth-optimism/l2geth': patch
---
Ignore the deprecated type field in the API
---
"@eth-optimism/l2geth": patch
---
Return bytes from both ExecutionManager.run and ExecutionManager.simulateMessage and be sure to properly ABI decode the return values and the nested (bool, returndata)
---
"@eth-optimism/data-transport-layer": patch
---
Remove legacy transaction deserialization to support RLP batch encoding
---
'@eth-optimism/l2geth': patch
---
Block access to RPCs related to signing transactions
---
'@eth-optimism/integration-tests': patch
---
Update expected gas prices based on minimum of 21k value
---
"@eth-optimism/l2geth": patch
"@eth-optimism/contracts": patch
---
Add ExecutionManager return data & RLP encoding
---
"@eth-optimism/contracts": patch
---
Makes ProxyEOA compatible with EIP1967, not backwards compatible since the storage slot changes.
---
"@eth-optimism/l2geth": patch
---
Update gas related things in the RPC to allow transactions with high gas limits and prevent gas estimations from being too small
---
'@eth-optimism/l2geth': minor
'@eth-optimism/contracts': minor
'@eth-optimism/data-transport-layer': minor
'@eth-optimism/batch-submitter': minor
'@eth-optimism/hardhat-ovm': minor
'@eth-optimism/message-relayer': minor
---
Updates to use RLP encoded transactions in batches for the `v0.3.0` release
---
"@eth-optimism/l2geth": patch
---
Remove the OVMSigner
---
"@eth-optimism/l2geth": patch
---
Prevent 0 value transactions with calldata via RPC
---
"@eth-optimism/core-utils": patch
---
Update toRpcHexString to accept ethers.BigNumber and add tests
---
'@eth-optimism/data-transport-layer': patch
---
Prevent access of null value in L1 transaction deserialization
---
"@eth-optimism/contracts": patch
---
Update ABI of simulateMessage to match run
......@@ -9,30 +9,26 @@ const { getArtifact } = require('./getArtifact')
use(solidity)
const config = {
l2Url: process.env.L2_URL || 'http://127.0.0.1:8545',
l1Url: process.env.L1_URL || 'http://127.0.0.1:9545',
useL2: process.env.TARGET === 'OVM',
privateKey: process.env.PRIVATE_KEY || '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
}
describe('ERC20 smart contract', () => {
let ERC20,
provider,
wallet,
walletTo,
walletEmpty,
walletAddress,
walletToAddress,
walletEmptyAddress
const privateKey = ethers.Wallet.createRandom().privateKey
const privateKeyEmpty = ethers.Wallet.createRandom().privateKey
const useL2 = process.env.TARGET === 'OVM'
if (useL2 == true) {
provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545')
provider
if (config.useL2) {
provider = new ethers.providers.JsonRpcProvider(config.l2Url)
provider.pollingInterval = 100
provider.getGasPrice = async () => ethers.BigNumber.from(0)
} else {
provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:9545')
provider = new ethers.providers.JsonRpcProvider(config.l1Url)
}
walletTo = new ethers.Wallet(privateKey, provider)
walletEmpty = new ethers.Wallet(privateKeyEmpty, provider)
const wallet = new ethers.Wallet(config.privateKey).connect(provider)
// parameters to use for our test coin
const COIN_NAME = 'OVM Test Coin'
......@@ -41,12 +37,7 @@ describe('ERC20 smart contract', () => {
describe('when using a deployed contract instance', () => {
before(async () => {
wallet = await provider.getSigner(0)
walletAddress = await wallet.getAddress()
walletToAddress = await walletTo.getAddress()
walletEmptyAddress = await walletEmpty.getAddress()
const Artifact__ERC20 = getArtifact(useL2)
const Artifact__ERC20 = getArtifact(config.useL2)
const Factory__ERC20 = new ethers.ContractFactory(
Artifact__ERC20.abi,
Artifact__ERC20.bytecode,
......@@ -64,7 +55,8 @@ describe('ERC20 smart contract', () => {
})
it('should assigns initial balance', async () => {
expect(await ERC20.balanceOf(walletAddress)).to.equal(1000)
const address = await wallet.getAddress()
expect(await ERC20.balanceOf(address)).to.equal(1000)
})
it('should correctly set vanity information', async () => {
......@@ -79,29 +71,35 @@ describe('ERC20 smart contract', () => {
})
it('should transfer amount to destination account', async () => {
const tx = await ERC20.connect(wallet).transfer(walletToAddress, 7)
const freshWallet = ethers.Wallet.createRandom()
const destination = await freshWallet.getAddress()
const tx = await ERC20.connect(wallet).transfer(destination, 7)
await tx.wait()
const walletToBalance = await ERC20.balanceOf(walletToAddress)
const walletToBalance = await ERC20.balanceOf(destination)
expect(walletToBalance.toString()).to.equal('7')
})
it('should emit Transfer event', async () => {
const tx = ERC20.connect(wallet).transfer(walletToAddress, 7)
const address = await wallet.getAddress()
const tx = ERC20.connect(wallet).transfer(address, 7)
await expect(tx)
.to.emit(ERC20, 'Transfer')
.withArgs(walletAddress, walletToAddress, 7)
.withArgs(address, address, 7)
})
it('should not transfer above the amount', async () => {
const walletToBalanceBefore = await ERC20.balanceOf(walletToAddress)
await expect(ERC20.transfer(walletToAddress, 1007)).to.be.reverted
const walletToBalanceAfter = await ERC20.balanceOf(walletToAddress)
const address = await wallet.getAddress()
const walletToBalanceBefore = await ERC20.balanceOf(address)
await expect(ERC20.transfer(address, 1007)).to.be.reverted
const walletToBalanceAfter = await ERC20.balanceOf(address)
expect(walletToBalanceBefore).to.eq(walletToBalanceAfter)
})
it('should not transfer from empty account', async () => {
const ERC20FromOtherWallet = ERC20.connect(walletEmpty)
await expect(ERC20FromOtherWallet.transfer(walletEmptyAddress, 1)).to.be
const emptyWallet = ethers.Wallet.createRandom()
const address = await emptyWallet.getAddress()
const ERC20FromOtherWallet = ERC20.connect(emptyWallet)
await expect(ERC20FromOtherWallet.transfer(address, 1)).to.be
.reverted
})
})
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0;
import { Reverter } from './Reverter.sol';
contract ConstructorReverter is Reverter {
constructor() {
doRevert();
}
}
......@@ -33,7 +33,7 @@ contract ERC20 {
}
function transfer(address _to, uint256 _value) public returns (bool success) {
require(balances[msg.sender] >= _value);
require(balances[msg.sender] >= _value, "insufficient balance");
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
......
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0;
contract Reverter {
string constant public revertMessage = "This is a simple reversion.";
function doRevert() public pure {
revert(revertMessage);
}
}
......@@ -2,6 +2,7 @@ import { HardhatUserConfig } from 'hardhat/types'
// Hardhat plugins
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
import '@eth-optimism/hardhat-ovm'
import 'hardhat-gas-reporter'
......
......@@ -21,6 +21,7 @@
"@eth-optimism/hardhat-ovm": "^0.1.1",
"@ethersproject/providers": "^5.0.24",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/chai-as-promised": "^7.1.3",
"@types/chai": "^4.2.17",
"@types/mocha": "^8.2.2",
......
import { Contract, ContractFactory, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from 'chai'
import chai, { expect } from 'chai'
import { GWEI } from './shared/utils'
import { OptimismEnv } from './shared/env'
import { solidity } from 'ethereum-waffle'
chai.use(solidity)
describe('Basic ERC20 interactions', async () => {
const initialAmount = 1000
......@@ -79,4 +82,10 @@ describe('Basic ERC20 interactions', async () => {
expect(receiverBalance.toNumber()).to.equal(100)
expect(senderBalance.toNumber()).to.equal(900)
})
it('should revert if trying to transfer too much', async () => {
await expect(
ERC20.transfer(other.address, initialAmount * 2)
).to.be.revertedWith('insufficient balance')
})
})
......@@ -25,13 +25,4 @@ describe('Fee Payment Integration Tests', async () => {
tx.gasPrice.mul(tx.gasLimit).add(amount)
)
})
it('sequencer rejects transaction with a non-multiple-of-1M gasPrice', async () => {
const gasPrice = BigNumber.from(1_000_000 - 1)
await expect(
env.ovmEth.transfer(other, 0, { gasPrice })
).to.be.eventually.rejectedWith(
'Gas price must be a multiple of 1,000,000 wei'
)
})
})
......@@ -45,13 +45,13 @@ describe('Native ETH Integration Tests', async () => {
const amount = utils.parseEther('0.5')
const addr = '0x' + '1234'.repeat(10)
const gas = await env.ovmEth.estimateGas.transfer(addr, amount)
expect(gas).to.be.deep.eq(BigNumber.from(17344))
expect(gas).to.be.deep.eq(BigNumber.from(21000))
})
it('Should estimate gas for ETH withdraw', async () => {
const amount = utils.parseEther('0.5')
const gas = await env.ovmEth.estimateGas.withdraw(amount)
expect(gas).to.be.deep.eq(BigNumber.from(14400))
expect(gas).to.be.deep.eq(BigNumber.from(21000))
})
})
......
import { injectL2Context } from '@eth-optimism/core-utils'
import { Wallet, BigNumber } from 'ethers'
import { Wallet, BigNumber, Contract } from 'ethers'
import { ethers } from 'hardhat'
import chai, { expect } from 'chai'
import { sleep, l2Provider, GWEI } from './shared/utils'
import {
sleep,
l2Provider,
GWEI,
encodeSolidityRevertMessage,
} from './shared/utils'
import chaiAsPromised from 'chai-as-promised'
import { OptimismEnv } from './shared/env'
import {
TransactionReceipt,
TransactionRequest,
} from '@ethersproject/providers'
import { solidity } from 'ethereum-waffle'
chai.use(chaiAsPromised)
chai.use(solidity)
describe('Basic RPC tests', () => {
let env: OptimismEnv
let wallet: Wallet
const DEFAULT_TRANSACTION = {
to: '0x' + '1234'.repeat(10),
......@@ -18,10 +31,33 @@ describe('Basic RPC tests', () => {
}
const provider = injectL2Context(l2Provider)
const wallet = Wallet.createRandom().connect(provider)
let Reverter: Contract
let revertMessage: string
let revertingTx: TransactionRequest
let revertingDeployTx: TransactionRequest
before(async () => {
env = await OptimismEnv.new()
wallet = env.l2Wallet
const Factory__Reverter = await ethers.getContractFactory(
'Reverter',
wallet
)
Reverter = await Factory__Reverter.connect(env.l2Wallet).deploy()
await Reverter.deployTransaction.wait()
revertMessage = await Reverter.revertMessage()
revertingTx = {
to: Reverter.address,
data: Reverter.interface.encodeFunctionData('doRevert'),
}
const Factory__ConstructorReverter = await ethers.getContractFactory(
'ConstructorReverter',
wallet
)
revertingDeployTx = {
data: Factory__ConstructorReverter.bytecode,
}
})
describe('eth_sendRawTransaction', () => {
......@@ -59,20 +95,124 @@ describe('Basic RPC tests', () => {
).to.be.rejectedWith('Cannot submit unprotected transaction')
})
it('should not accept a transaction with a value', async () => {
it('should accept a transaction with a value', async () => {
const tx = {
...DEFAULT_TRANSACTION,
chainId: await wallet.getChainId(),
value: 100,
chainId: await env.l2Wallet.getChainId(),
data: '0x',
value: ethers.utils.parseEther('5'),
}
await expect(
provider.sendTransaction(await wallet.signTransaction(tx))
).to.be.rejectedWith(
'Cannot send transaction with non-zero value. Use WETH.transfer()'
const balanceBefore = await provider.getBalance(env.l2Wallet.address)
const result = await env.l2Wallet.sendTransaction(tx)
const receipt = await result.wait()
expect(receipt.status).to.deep.equal(1)
expect(await provider.getBalance(env.l2Wallet.address)).to.deep.equal(
balanceBefore.sub(ethers.utils.parseEther('5'))
)
})
it('should reject a transaction with higher value than user balance', async () => {
const balance = await env.l2Wallet.getBalance()
const tx = {
...DEFAULT_TRANSACTION,
chainId: await env.l2Wallet.getChainId(),
data: '0x',
value: balance.add(ethers.utils.parseEther('1')),
}
await expect(env.l2Wallet.sendTransaction(tx)).to.be.rejectedWith(
'invalid transaction: insufficient funds for gas * price + value'
)
})
})
describe('eth_call', () => {
let expectedReverterRevertData: string
before(async () => {
expectedReverterRevertData = encodeSolidityRevertMessage(revertMessage)
})
it('should correctly return solidity revert data from a call', async () => {
const revertData = await provider.call(revertingTx)
const expectedRevertData = encodeSolidityRevertMessage(revertMessage)
expect(revertData).to.eq(expectedRevertData)
})
it('should produce error when called from ethers', async () => {
await expect(Reverter.doRevert()).to.be.revertedWith(revertMessage)
})
it('should correctly return revert data from contract creation', async () => {
const revertData = await provider.call(revertingDeployTx)
expect(revertData).to.eq(expectedReverterRevertData)
})
it('should return the correct error message when attempting to deploy unsafe initcode', async () => {
// PUSH1 0x00 PUSH1 0x00 SSTORE
const unsafeCode = '0x6000600055'
const tx: TransactionRequest = {
data: unsafeCode,
}
const result = await provider.call(tx)
const expected = encodeSolidityRevertMessage(
'Contract creation code contains unsafe opcodes. Did you use the right compiler or pass an unsafe constructor argument?'
)
expect(result).to.eq(expected)
})
})
describe('eth_getTransactionReceipt', () => {
it('correctly exposes revert data for contract calls', async () => {
const req: TransactionRequest = {
...revertingTx,
gasLimit: 8_999_999, // override gas estimation
}
const tx = await wallet.sendTransaction(req)
let errored = false
try {
await tx.wait()
} catch (e) {
errored = true
}
expect(errored).to.be.true
const receipt: TransactionReceipt = await provider.getTransactionReceipt(
tx.hash
)
expect(receipt.status).to.eq(0)
})
it('correctly exposes revert data for contract creations', async () => {
const req: TransactionRequest = {
...revertingDeployTx,
gasLimit: 8_999_999, // override gas estimation
}
const tx = await wallet.sendTransaction(req)
let errored = false
try {
await tx.wait()
} catch (e) {
errored = true
}
expect(errored).to.be.true
const receipt: TransactionReceipt = await provider.getTransactionReceipt(
tx.hash
)
expect(receipt.status).to.eq(0)
})
})
describe('eth_getTransactionByHash', () => {
it('should be able to get all relevant l1/l2 transaction data', async () => {
const tx = DEFAULT_TRANSACTION
......@@ -168,10 +308,23 @@ describe('Basic RPC tests', () => {
// we normalize by gwei here because the RPC does it as well, since the
// user provides a 1gwei gas price when submitting txs via the eth_gasPrice
// rpc call
const expected = expectedCost[i].mul(l1GasPrice).div(GWEI)
// rpc call. The smallest possible value for the expected cost is 21000
let expected = expectedCost[i].mul(l1GasPrice).div(GWEI)
if (expected.lt(BigNumber.from(21000))) {
expected = BigNumber.from(21000)
}
expect(estimate).to.be.deep.eq(expected)
}
})
it('should fail for a reverting call transaction', async () => {
await expect(provider.send('eth_estimateGas', [revertingTx])).to.be
.reverted
})
it('should fail for a reverting deploy transaction', async () => {
await expect(provider.send('eth_estimateGas', [revertingDeployTx])).to.be
.reverted
})
})
})
......@@ -4,7 +4,7 @@ import {
getContractFactory,
getContractInterface,
} from '@eth-optimism/contracts'
import { Watcher } from '@eth-optimism/core-utils'
import { remove0x, Watcher } from '@eth-optimism/core-utils'
import {
Contract,
Wallet,
......@@ -12,6 +12,7 @@ import {
providers,
BigNumberish,
BigNumber,
utils,
} from 'ethers'
import { cleanEnv, str, num } from 'envalid'
......@@ -100,3 +101,8 @@ export const fundUser = async (
}
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
const abiCoder = new utils.AbiCoder()
export const encodeSolidityRevertMessage = (_reason: string): string => {
return '0x08c379a0' + remove0x(abiCoder.encode(['string'], [_reason]))
}
......@@ -19,10 +19,12 @@ package abi
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
// The ABI holds information about a contract's context and available
......@@ -192,3 +194,25 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
}
return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
}
// RevertSelector is a special function selector for revert reason unpacking.
var RevertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
// docs https://docs.soliditylang.org/en/v0.8.4/control-structures.html#revert,
// the provided revert reason is abi-encoded as if it were a call to a function
// `Error(string)`. So it's a special tool for it.
func UnpackRevert(data []byte) (string, error) {
if len(data) < 4 {
return "", errors.New("invalid data for unpacking")
}
if !bytes.Equal(data[:4], RevertSelector) {
return "", errors.New("invalid data for unpacking")
}
typ, _ := NewType("string", "", nil)
unpacked, err := (Arguments{{Type: typ}}).UnpackValues(data[4:])
if err != nil {
return "", err
}
return unpacked[0].(string), nil
}
......@@ -252,7 +252,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
l1MessageSender = msg.L1MessageSender().Hex()
}
if st.evm.EthCallSender == nil {
log.Debug("Applying transaction", "ID", st.evm.Id, "from", sender.Address().Hex(), "to", to, "nonce", msg.Nonce(), "gasPrice", msg.GasPrice().Uint64(), "gasLimit", msg.Gas(), "l1MessageSender", l1MessageSender, "data", hexutil.Encode(msg.Data()))
log.Debug("Applying transaction", "ID", st.evm.Id, "from", sender.Address().Hex(), "to", to, "nonce", msg.Nonce(), "gasPrice", msg.GasPrice().Uint64(), "gasLimit", msg.Gas(), "value", msg.Value().Uint64(), "l1MessageSender", l1MessageSender, "data", hexutil.Encode(msg.Data()))
}
}
......
......@@ -148,7 +148,7 @@ func modMessage(
from,
to,
msg.Nonce(),
msg.Value(),
common.Big0,
gasLimit,
msg.GasPrice(),
data,
......
......@@ -17,67 +17,16 @@
package types
import (
"bytes"
"crypto/ecdsa"
"encoding/binary"
"errors"
"fmt"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"golang.org/x/crypto/sha3"
)
var codec abi.ABI
func init() {
const abidata = `
[
{
"type": "function",
"name": "encode",
"constant": true,
"inputs": [
{
"name": "nonce",
"type": "uint256"
},
{
"name": "gasLimit",
"type": "uint256"
},
{
"name": "gasPrice",
"type": "uint256"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "to",
"type": "address"
},
{
"name": "data",
"type": "bytes"
}
]
}
]
`
var err error
codec, err = abi.JSON(strings.NewReader(abidata))
if err != nil {
panic(fmt.Errorf("unable to create Eth Sign abi reader: %v", err))
}
}
var (
ErrInvalidChainId = errors.New("invalid chain id for signer")
)
......@@ -91,7 +40,16 @@ type sigCache struct {
// MakeSigner returns a Signer based on the given chain config and block number.
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
return NewOVMSigner(config.ChainID)
var signer Signer
switch {
case config.IsEIP155(blockNumber):
signer = NewEIP155Signer(config.ChainID)
case config.IsHomestead(blockNumber):
signer = HomesteadSigner{}
default:
signer = FrontierSigner{}
}
return signer
}
// SignTx signs the transaction using the given signer and private key
......@@ -144,97 +102,6 @@ type Signer interface {
Equal(Signer) bool
}
// OVMSigner implements Signers using the EIP155 rules along with a new
// `eth_sign` based signature hash.
type OVMSigner struct {
EIP155Signer
}
func NewOVMSigner(chainId *big.Int) OVMSigner {
signer := NewEIP155Signer(chainId)
return OVMSigner{signer}
}
func (s OVMSigner) Equal(s2 Signer) bool {
ovm, ok := s2.(OVMSigner)
return ok && ovm.chainId.Cmp(s.chainId) == 0
}
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s OVMSigner) Hash(tx *Transaction) common.Hash {
if tx.IsEthSignSighash() {
msg := s.OVMSignerTemplateSighashPreimage(tx)
hasher := sha3.NewLegacyKeccak256()
hasher.Write(msg[:])
digest := hasher.Sum(nil)
return common.BytesToHash(digest)
}
return rlpHash([]interface{}{
tx.data.AccountNonce,
tx.data.Price,
tx.data.GasLimit,
tx.data.Recipient,
tx.data.Amount,
tx.data.Payload,
s.chainId, uint(0), uint(0),
})
}
// Sender will ecrecover the public key that created the signature
// and then hash the public key to create an address. In the
// case of L1ToL2 transactions, Layer One did the authentication
// for us so there is no signature involved. The concept of a "from"
// is only required for bookkeeping within this codebase
func (s OVMSigner) Sender(tx *Transaction) (common.Address, error) {
qo := tx.QueueOrigin()
if qo != nil && qo.Uint64() == uint64(QueueOriginL1ToL2) {
return common.Address{}, nil
}
if !tx.Protected() {
return HomesteadSigner{}.Sender(tx)
}
if tx.ChainId().Cmp(s.chainId) != 0 {
return common.Address{}, ErrInvalidChainId
}
V := new(big.Int).Sub(tx.data.V, s.chainIdMul)
V.Sub(V, big8)
return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true)
}
// OVMSignerTemplateSighashPreimage creates the preimage for the `eth_sign` like
// signature hash. The transaction is `ABI.encodePacked`.
func (s OVMSigner) OVMSignerTemplateSighashPreimage(tx *Transaction) []byte {
data := []interface{}{
big.NewInt(int64(tx.data.AccountNonce)),
big.NewInt(int64(tx.data.GasLimit)),
tx.data.Price,
s.chainId,
*tx.data.Recipient,
tx.data.Payload,
}
ret, err := codec.Pack("encode", data...)
if err != nil {
panic(fmt.Errorf("unable to pack Eth Sign data: %v", err))
}
hasher := sha3.NewLegacyKeccak256()
// Slice off the function selector before hashing
hasher.Write(ret[4:])
digest := hasher.Sum(nil)
preimage := new(bytes.Buffer)
prefix := []byte("\x19Ethereum Signed Message:\n32")
binary.Write(preimage, binary.BigEndian, prefix)
binary.Write(preimage, binary.BigEndian, digest)
return preimage.Bytes()
}
// EIP155Transaction implements Signer using the EIP155 rules.
type EIP155Signer struct {
chainId, chainIdMul *big.Int
......
......@@ -136,100 +136,3 @@ func TestChainId(t *testing.T) {
t.Error("expected no error")
}
}
func TestOVMSigner(t *testing.T) {
key, _ := defaultTestKey()
tx := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil)
txMeta := NewTransactionMeta(nil, 0, nil, SighashEthSign, QueueOriginSequencer, nil, nil, nil)
tx.SetTransactionMeta(txMeta)
var err error
tx, err = SignTx(tx, NewOVMSigner(big.NewInt(1)), key)
if err != nil {
t.Fatal(err)
}
_, err = Sender(NewOVMSigner(big.NewInt(2)), tx)
if err != ErrInvalidChainId {
t.Error("expected error:", ErrInvalidChainId)
}
_, err = Sender(NewOVMSigner(big.NewInt(1)), tx)
if err != nil {
t.Error("expected no error")
}
}
func TestOVMSignerHash(t *testing.T) {
signer := NewOVMSigner(big.NewInt(1))
txNil := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil)
txEIP155 := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil)
hashNil := signer.Hash(txNil)
hashEIP155 := signer.Hash(txEIP155)
if hashNil != hashEIP155 {
t.Errorf("Signature hashes should be equal: %s != %s", hashNil.Hex(), hashEIP155.Hex())
}
// The signature hash should be different when using `SighashEthSign`
txEthSign := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil)
txMeta := NewTransactionMeta(nil, 0, nil, SighashEthSign, QueueOriginSequencer, nil, nil, nil)
txEthSign.SetTransactionMeta(txMeta)
hashEthSign := signer.Hash(txEthSign)
if hashEIP155 == hashEthSign {
t.Errorf("Signature hashes should not be equal: %s == %s", hashEIP155.Hex(), hashEthSign.Hex())
}
}
func TestOVMSignerSender(t *testing.T) {
// Create a keypair to sign transactions with and the corresponding address
// from the public key.
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
// This test makes sure that both the EIP155 and EthSign signature hash
// codepaths work when using the OVMSigner.
signer := NewOVMSigner(big.NewInt(1))
var err error
// Create a transaction with EIP155 signature hash, sign the transaction,
// recover the address and assert that the address matches the key.
txEIP155 := NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil)
txEIP155, err = SignTx(txEIP155, signer, key)
if err != nil {
t.Errorf("No error expected")
}
recEIP155, err := signer.Sender(txEIP155)
if err != nil {
t.Errorf("No error expected")
}
if addr != recEIP155 {
t.Errorf("Recovered address doesn't match. Got %s, expected %s", recEIP155.Hex(), addr.Hex())
}
// Create a transaction with EthSign signature hash, sign the transaction,
// recover the address and assert that the address matches the key.
txEthSign := NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil)
txMeta := NewTransactionMeta(nil, 0, nil, SighashEthSign, QueueOriginSequencer, nil, nil, nil)
txEthSign.SetTransactionMeta(txMeta)
txEthSign, err = SignTx(txEthSign, signer, key)
if err != nil {
t.Errorf("No error expected")
}
recEthSign, err := signer.Sender(txEthSign)
if err != nil {
t.Errorf("No error expected")
}
if addr != recEthSign {
t.Errorf("Recovered address doesn't match. Got %s, expected %s", recEthSign.Hex(), addr.Hex())
}
}
......@@ -208,7 +208,7 @@ func TestTransactionJSON(t *testing.T) {
if err != nil {
t.Fatalf("could not generate key: %v", err)
}
signer := NewOVMSigner(common.Big1)
signer := NewEIP155Signer(common.Big1)
transactions := make([]*Transaction, 0, 50)
for i := uint64(0); i < 25; i++ {
......
......@@ -17,13 +17,15 @@
package vm
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"math/big"
"strings"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
......@@ -32,10 +34,66 @@ import (
"github.com/ethereum/go-ethereum/rollup/dump"
)
// codec is a decoder for the return values of the execution manager. It decodes
// (bool, bytes) from the bytes that are returned from both
// `ExecutionManager.run()` and `ExecutionManager.simulateMessage()`
var codec abi.ABI
// innerData represents the results returned from the ExecutionManager
// that are wrapped in `bytes`
type innerData struct {
Success bool `abi:"_success"`
ReturnData []byte `abi:"_returndata"`
}
// runReturnData represents the actual return data of the ExecutionManager.
// It wraps (bool, bytes) in an ABI encoded bytes
type runReturnData struct {
ReturnData []byte `abi:"_returndata"`
}
// Will be removed when we update EM to return data in `run`.
var deadPrefix, fortyTwoPrefix, zeroPrefix []byte
func init() {
const abidata = `
[
{
"type": "function",
"name": "call",
"constant": true,
"inputs": [],
"outputs": [
{
"name": "_success",
"type": "bool"
},
{
"name": "_returndata",
"type": "bytes"
}
]
},
{
"type": "function",
"name": "blob",
"constant": true,
"inputs": [],
"outputs": [
{
"name": "_returndata",
"type": "bytes"
}
]
}
]
`
var err error
codec, err = abi.JSON(strings.NewReader(abidata))
if err != nil {
panic(fmt.Errorf("unable to create abi decoder: %v", err))
}
deadPrefix = hexutil.MustDecode("0xdeaddeaddeaddeaddeaddeaddeaddeaddead")
zeroPrefix = hexutil.MustDecode("0x000000000000000000000000000000000000")
fortyTwoPrefix = hexutil.MustDecode("0x420000000000000000000000000000000000")
......@@ -134,9 +192,6 @@ type Context struct {
// OVM_ADDITION
EthCallSender *common.Address
OriginalTargetAddress *common.Address
OriginalTargetResult []byte
OriginalTargetReached bool
OvmExecutionManager dump.OvmDumpAccount
OvmStateManager dump.OvmDumpAccount
OvmSafetyChecker dump.OvmDumpAccount
......@@ -251,34 +306,6 @@ func (evm *EVM) Interpreter() Interpreter {
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
var isTarget = false
if UsingOVM {
// OVM_ENABLED
if evm.depth == 0 {
// We're inside a new transaction, so make sure to wipe these variables beforehand.
evm.Context.OriginalTargetAddress = nil
evm.Context.OriginalTargetResult = []byte("00")
evm.Context.OriginalTargetReached = false
}
if caller.Address() == evm.Context.OvmExecutionManager.Address &&
!bytes.HasPrefix(addr.Bytes(), deadPrefix) &&
!bytes.HasPrefix(addr.Bytes(), zeroPrefix) &&
!bytes.HasPrefix(addr.Bytes(), fortyTwoPrefix) &&
evm.Context.OriginalTargetAddress == nil {
// Whew. Okay, so: we consider ourselves to be at a "target" as long as we were called
// by the execution manager, and we're not a precompile or "dead" address.
evm.Context.OriginalTargetAddress = &addr
evm.Context.OriginalTargetReached = true
isTarget = true
}
// Handle eth_call
if evm.Context.EthCallSender != nil && (caller.Address() == common.Address{}) {
evm.Context.OriginalTargetReached = true
isTarget = true
}
}
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
......@@ -356,63 +383,41 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if UsingOVM {
// OVM_ENABLED
if isTarget {
// If this was our target contract, store the result so that it can be later re-inserted
// into the user-facing return data (as seen below).
evm.Context.OriginalTargetResult = ret
}
if evm.depth == 0 {
// We're back at the root-level message call, so we'll need to modify the return data
// sent to us by the OVM_ExecutionManager to instead be the intended return data.
if !evm.Context.OriginalTargetReached {
// If we didn't get to the target contract, then our execution somehow failed
// (perhaps due to insufficient gas). Just return an error that represents this.
ret = common.FromHex("0x")
err = ErrOvmExecutionFailed
} else if len(evm.Context.OriginalTargetResult) >= 96 {
// We expect that EOA contracts return at least 96 bytes of data, where the first
// 32 bytes are the boolean success value and the next 64 bytes are unnecessary
// ABI encoding data. The actual return data starts at the 96th byte and can be
// empty.
success := evm.Context.OriginalTargetResult[:32]
ret = evm.Context.OriginalTargetResult[96:]
if !bytes.Equal(success, AbiBytesTrue) && !bytes.Equal(success, AbiBytesFalse) {
// If the first 32 bytes not either are the ABI encoding of "true" or "false",
// then the user hasn't correctly ABI encoded the result. We return the null
// hex string as a default here (an annoying default that would convince most
// people to just use the standard form).
ret = common.FromHex("0x")
} else if bytes.Equal(success, AbiBytesFalse) {
// If the first 32 bytes are the ABI encoding of "false", then we need to add an
// artificial error that represents the revert.
// Attempt to decode the returndata as as ExecutionManager.run when
// it is not an `eth_call` and as ExecutionManager.simulateMessage
// when it is an `eth_call`. If the data is not decodable as ABI
// encoded bytes, then return nothing. If the data is able to be
// decoded as bytes, then attempt to decode as (bool, bytes)
isDecodable := true
returnData := runReturnData{}
if err := codec.Unpack(&returnData, "blob", ret); err != nil {
isDecodable = false
}
switch isDecodable {
case true:
inner := innerData{}
// If this fails to decode, the nil values will be set in
// `inner`, meaning that it will be interpreted as reverted
// execution with empty returndata
_ = codec.Unpack(&inner, "call", returnData.ReturnData)
if !inner.Success {
err = errExecutionReverted
// We also currently need to add an extra four empty bytes to the return data
// to appease ethers.js. Our return correctly inserts the four specific bytes
// that represent a "string error" to clients, but somehow the returndata size
// is a multiple of 32 (when we expect size % 32 == 4). ethers.js checks that
// [size % 32 == 4] before trying to decode a string error result. Adding these
// four empty bytes tricks ethers into correctly decoding the error string.
// ovmTODO: Figure out how to actually deal with this.
// ovmTODO: This may actually be completely broken if the first four bytes of
// the return data are **not** the specific "string error" bytes.
ret = append(ret, make([]byte, 4)...)
}
} else {
// User hasn't conformed the standard format, just return "null" for the success
// (with no return data) to convince them to use the standard.
ret = common.FromHex("0x")
ret = inner.ReturnData
case false:
ret = []byte{}
}
}
if evm.Context.EthCallSender == nil {
log.Debug("Reached the end of an OVM execution", "ID", evm.Id, "Return Data", hexutil.Encode(ret), "Error", err)
}
}
}
return ret, contract.Gas, err
}
......
......@@ -297,20 +297,11 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
// a lock can be used around the remotes for when the sequencer is reorganizing.
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
if b.UsingOVM {
// The value field is not rolled up so it must be set to 0
if signedTx.Value().Cmp(new(big.Int)) != 0 {
return fmt.Errorf("Cannot send transaction with non-zero value. Use WETH.transfer()")
}
to := signedTx.To()
if to != nil {
if *to == (common.Address{}) {
return errors.New("Cannot send transaction to zero address")
}
// Prevent transactions from being submitted if the gas limit too high
if signedTx.Gas() >= b.gasLimit {
return fmt.Errorf("Transaction gasLimit (%d) is greater than max gasLimit (%d)", signedTx.Gas(), b.gasLimit)
}
// Prevent QueueOriginSequencer transactions that are too large to
// be included in a batch. The `MaxCallDataSize` should be set to
// the layer one consensus max transaction size in bytes minus the
......@@ -320,6 +311,14 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
if len(signedTx.Data()) > b.MaxCallDataSize {
return fmt.Errorf("Calldata cannot be larger than %d, sent %d", b.MaxCallDataSize, len(signedTx.Data()))
}
// If there is a value field set then reject transactions that
// contain calldata. The feature of sending transactions with value
// and calldata will be added in the future.
if signedTx.Value().Cmp(common.Big0) != 0 {
if len(signedTx.Data()) > 0 {
return errors.New("Cannot send transactions with value and calldata")
}
}
}
return b.eth.syncService.ApplyTransaction(signedTx)
}
......
package eth
import (
"context"
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
func TestGasLimit(t *testing.T) {
backend := &EthAPIBackend{
extRPCEnabled: false,
eth: nil,
gpo: nil,
verifier: false,
gasLimit: 0,
UsingOVM: true,
}
nonce := uint64(0)
to := common.HexToAddress("0x5A0b54D5dc17e0AadC383d2db43B0a0D3E029c4c")
value := big.NewInt(0)
gasPrice := big.NewInt(0)
data := []byte{}
// Set the gas limit to 1 so that the transaction will not be
// able to be added.
gasLimit := uint64(1)
tx := types.NewTransaction(nonce, to, value, gasLimit, gasPrice, data)
err := backend.SendTx(context.Background(), tx)
if err == nil {
t.Fatal("Transaction with too large of gas limit accepted")
}
if err.Error() != fmt.Sprintf("Transaction gasLimit (%d) is greater than max gasLimit (%d)", gasLimit, backend.GasLimit()) {
t.Fatalf("Unexpected error type: %s", err)
}
}
......@@ -27,6 +27,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
"github.com/ethereum/go-ethereum/common"
......@@ -52,6 +53,8 @@ const (
defaultGasPrice = params.GWei
)
var errOVMUnsupported = errors.New("OVM: Unsupported RPC Method")
// PublicEthereumAPI provides an API to access Ethereum related information.
// It offers only methods that operate on public data that is freely available to anyone.
type PublicEthereumAPI struct {
......@@ -1043,6 +1046,9 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
// 3. calculate the fee and normalize by the default gas price
fee := core.CalculateRollupFee(*args.Data, uint64(gasUsed), dataPrice, executionPrice).Uint64() / defaultGasPrice
if fee < 21000 {
fee = 21000
}
return (hexutil.Uint64)(fee), nil
}
......@@ -1082,19 +1088,21 @@ func legacyDoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrO
args.From = &common.Address{}
}
// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) bool {
executable := func(gas uint64) (bool, []byte) {
args.Gas = (*hexutil.Uint64)(&gas)
_, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
res, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
if err != nil || failed {
return false
return false, res
}
return true
return true, res
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
if !executable(mid) {
ok, _ := executable(mid)
if !ok {
lo = mid
} else {
hi = mid
......@@ -1102,7 +1110,16 @@ func legacyDoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrO
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
if !executable(hi) {
ok, res := executable(hi)
if !ok {
if len(res) >= 4 && bytes.Equal(res[:4], abi.RevertSelector) {
reason, errUnpack := abi.UnpackRevert(res)
err := errors.New("execution reverted")
if errUnpack == nil {
err = fmt.Errorf("execution reverted: %v", reason)
}
return 0, err
}
return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap)
}
}
......@@ -1286,7 +1303,10 @@ type RPCTransaction struct {
// newRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
var signer types.Signer = types.NewOVMSigner(tx.ChainId())
var signer types.Signer = types.FrontierSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
......@@ -1514,7 +1534,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
var signer types.Signer = types.FrontierSigner{}
if tx.Protected() {
signer = types.NewOVMSigner(tx.ChainId())
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
......@@ -1648,12 +1668,14 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
}
if args.To == nil {
tx := types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
txMeta := types.NewTransactionMeta(args.L1BlockNumber, 0, nil, types.SighashEIP155, types.QueueOriginSequencer, nil, nil, nil)
raw, _ := rlp.EncodeToBytes(tx)
txMeta := types.NewTransactionMeta(args.L1BlockNumber, 0, nil, types.SighashEIP155, types.QueueOriginSequencer, nil, nil, raw)
tx.SetTransactionMeta(txMeta)
return tx
}
tx := types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
txMeta := types.NewTransactionMeta(args.L1BlockNumber, 0, args.L1MessageSender, args.SignatureHashType, types.QueueOriginSequencer, nil, nil, nil)
raw, _ := rlp.EncodeToBytes(tx)
txMeta := types.NewTransactionMeta(args.L1BlockNumber, 0, args.L1MessageSender, args.SignatureHashType, types.QueueOriginSequencer, nil, nil, raw)
tx.SetTransactionMeta(txMeta)
return tx
}
......@@ -1683,10 +1705,9 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
if s.b.IsVerifier() {
return common.Hash{}, errors.New("Cannot send transaction in verifier mode")
if vm.UsingOVM {
return common.Hash{}, errOVMUnsupported
}
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.From}
......@@ -1719,6 +1740,9 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen
// FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction,
// and returns it to the caller for further processing (signing + broadcast)
func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
if vm.UsingOVM {
return nil, errOVMUnsupported
}
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err
......@@ -1747,39 +1771,8 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err
}
if new(big.Int).Mod(tx.GasPrice(), big.NewInt(1000000)).Cmp(big.NewInt(0)) != 0 {
return common.Hash{}, errors.New("Gas price must be a multiple of 1,000,000 wei")
}
// L1Timestamp and L1BlockNumber will be set by the miner
txMeta := types.NewTransactionMeta(nil, 0, nil, types.SighashEIP155, types.QueueOriginSequencer, nil, nil, nil)
tx.SetTransactionMeta(txMeta)
return SubmitTransaction(ctx, s.b, tx)
}
// SendRawEthSignTransaction will add the signed transaction to the mempool.
// The signature hash was computed with `eth_sign`, meaning that the
// `abi.encodedPacked` transaction was prefixed with the string
// "Ethereum Signed Message".
func (s *PublicTransactionPoolAPI) SendRawEthSignTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
if s.b.IsVerifier() {
return common.Hash{}, errors.New("Cannot send raw ethsign transaction in verifier mode")
}
if s.b.IsSyncing() {
return common.Hash{}, errors.New("Cannot send raw transaction while syncing")
}
tx := new(types.Transaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err
}
if new(big.Int).Mod(tx.GasPrice(), big.NewInt(1000000)).Cmp(big.NewInt(0)) != 0 {
return common.Hash{}, errors.New("Gas price must be a multiple of 1,000,000 wei")
}
// L1Timestamp and L1BlockNumber will be set by the miner
txMeta := types.NewTransactionMeta(nil, 0, nil, types.SighashEthSign, types.QueueOriginSequencer, nil, nil, nil)
// L1Timestamp and L1BlockNumber will be set right before execution
txMeta := types.NewTransactionMeta(nil, 0, nil, types.SighashEIP155, types.QueueOriginSequencer, nil, nil, encodedTx)
tx.SetTransactionMeta(txMeta)
return SubmitTransaction(ctx, s.b, tx)
}
......@@ -1794,6 +1787,9 @@ func (s *PublicTransactionPoolAPI) SendRawEthSignTransaction(ctx context.Context
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
if vm.UsingOVM {
return nil, errOVMUnsupported
}
// Look up the wallet containing the requested signer
account := accounts.Account{Address: addr}
......@@ -1819,6 +1815,9 @@ type SignTransactionResult struct {
// The node needs to have the private key of the account corresponding with
// the given from address and it needs to be unlocked.
func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
if vm.UsingOVM {
return nil, errOVMUnsupported
}
if args.Gas == nil {
return nil, fmt.Errorf("gas not specified")
}
......@@ -1859,7 +1858,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err
for _, tx := range pending {
var signer types.Signer = types.HomesteadSigner{}
if tx.Protected() {
signer = types.NewOVMSigner(tx.ChainId())
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
if _, exists := accounts[from]; exists {
......@@ -1887,7 +1886,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
for _, p := range pending {
var signer types.Signer = types.HomesteadSigner{}
if p.Protected() {
signer = types.NewOVMSigner(p.ChainId())
signer = types.NewEIP155Signer(p.ChainId())
}
wantSigHash := signer.Hash(matchTx)
......
......@@ -646,7 +646,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
return err
}
env := &environment{
signer: types.NewOVMSigner(w.chainConfig.ChainID),
signer: types.NewEIP155Signer(w.chainConfig.ChainID),
state: state,
ancestors: mapset.NewSet(),
family: mapset.NewSet(),
......
......@@ -132,7 +132,7 @@ type RollupClient interface {
// Client is an HTTP based RollupClient
type Client struct {
client *resty.Client
signer *types.OVMSigner
signer *types.EIP155Signer
}
// TransactionResponse represents the response from the remote server when
......@@ -154,7 +154,7 @@ func NewClient(url string, chainID *big.Int) *Client {
client := resty.New()
client.SetHostURL(url)
client.SetHeader("User-Agent", "sequencer")
signer := types.NewOVMSigner(chainID)
signer := types.NewEIP155Signer(chainID)
return &Client{
client: client,
......@@ -274,7 +274,7 @@ func (c *Client) GetLatestEnqueue() (*types.Transaction, error) {
// batchedTransactionToTransaction converts a transaction into a
// types.Transaction that can be consumed by the SyncService
func batchedTransactionToTransaction(res *transaction, signer *types.OVMSigner) (*types.Transaction, error) {
func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signer) (*types.Transaction, error) {
// `nil` transactions are not found
if res == nil {
return nil, errElementNotFound
......@@ -289,18 +289,7 @@ func batchedTransactionToTransaction(res *transaction, signer *types.OVMSigner)
} else {
return nil, fmt.Errorf("Unknown queue origin: %s", res.QueueOrigin)
}
// The transaction type must be EIP155 or EthSign. Throughout this
// codebase, it is referred to as "sighash type" but it could actually
// be generalized to transaction type. Right now the only different
// types use a different signature hashing scheme.
var sighashType types.SignatureHashType
if res.Type == EIP155 {
sighashType = types.SighashEIP155
} else if res.Type == ETH_SIGN {
sighashType = types.SighashEthSign
} else {
return nil, fmt.Errorf("Unknown transaction type: %s", res.Type)
}
sighashType := types.SighashEIP155
// Transactions that have been decoded are
// Queue Origin Sequencer transactions
if res.Decoded != nil {
......@@ -542,7 +531,7 @@ func (c *Client) GetTransactionBatch(index uint64) (*Batch, []*types.Transaction
// parseTransactionBatchResponse will turn a TransactionBatchResponse into a
// Batch and its corresponding types.Transactions
func parseTransactionBatchResponse(txBatch *TransactionBatchResponse, signer *types.OVMSigner) (*Batch, []*types.Transaction, error) {
func parseTransactionBatchResponse(txBatch *TransactionBatchResponse, signer *types.EIP155Signer) (*Batch, []*types.Transaction, error) {
if txBatch == nil {
return nil, nil, nil
}
......
package rollup
import (
"bytes"
"context"
"errors"
"fmt"
......@@ -11,7 +10,6 @@ import (
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
......@@ -707,7 +705,6 @@ func (s *SyncService) maybeApplyTransaction(tx *types.Transaction) error {
// Lower level API used to apply a transaction, must only be used with
// transactions that came from L1.
func (s *SyncService) applyTransaction(tx *types.Transaction) error {
tx = fixType(tx)
txs := types.Transactions{tx}
s.txFeed.Send(core.NewTxsEvent{Txs: txs})
return nil
......@@ -745,114 +742,5 @@ func (s *SyncService) ApplyTransaction(tx *types.Transaction) error {
tx.SetL1Timestamp(ts)
tx.SetL1BlockNumber(bn)
}
// Set the raw transaction data in the meta
txRaw, err := getRawTransaction(tx)
if err != nil {
return fmt.Errorf("invalid transaction: %w", err)
}
meta := tx.GetMeta()
newMeta := types.NewTransactionMeta(
meta.L1BlockNumber,
meta.L1Timestamp,
meta.L1MessageSender,
meta.SignatureHashType,
types.QueueOrigin(meta.QueueOrigin.Uint64()),
meta.Index,
meta.QueueIndex,
txRaw,
)
tx.SetTransactionMeta(newMeta)
return s.applyTransaction(tx)
}
func getRawTransaction(tx *types.Transaction) ([]byte, error) {
if tx == nil {
return nil, errors.New("Cannot process nil transaction")
}
v, r, s := tx.RawSignatureValues()
// V parameter here will include the chain ID, so we need to recover the original V. If the V
// does not equal zero or one, we have an invalid parameter and need to throw an error.
// This is technically a duplicate check because it happens inside of
// `tx.AsMessage` as well.
v = new(big.Int).SetUint64(v.Uint64() - 35 - 2*tx.ChainId().Uint64())
if v.Uint64() != 0 && v.Uint64() != 1 {
return nil, fmt.Errorf("invalid signature v parameter: %d", v.Uint64())
}
// Since we use a fixed encoding, we need to insert some placeholder address to represent that
// the user wants to create a contract (in this case, the zero address).
var target common.Address
if tx.To() == nil {
target = common.Address{}
} else {
target = *tx.To()
}
// Divide the gas price by one million to compress it
// before it is send to the sequencer entrypoint. This is to save
// space on calldata.
gasPrice := new(big.Int).Div(tx.GasPrice(), new(big.Int).SetUint64(1000000))
// Sequencer uses a custom encoding structure --
// We originally receive sequencer transactions encoded in this way, but we decode them before
// inserting into Geth so we can make transactions easily parseable. However, this means that
// we need to re-encode the transactions before executing them.
var data = new(bytes.Buffer)
data.WriteByte(getSignatureType(tx)) // 1 byte: 00 == EIP 155, 02 == ETH Sign Message
data.Write(fillBytes(r, 32)) // 32 bytes: Signature `r` parameter
data.Write(fillBytes(s, 32)) // 32 bytes: Signature `s` parameter
data.Write(fillBytes(v, 1)) // 1 byte: Signature `v` parameter
data.Write(fillBytes(new(big.Int).SetUint64(tx.Gas()), 3)) // 3 bytes: Gas limit
data.Write(fillBytes(gasPrice, 3)) // 3 bytes: Gas price
data.Write(fillBytes(new(big.Int).SetUint64(tx.Nonce()), 3)) // 3 bytes: Nonce
data.Write(target.Bytes()) // 20 bytes: Target address
data.Write(tx.Data())
return data.Bytes(), nil
}
func fillBytes(x *big.Int, size int) []byte {
b := x.Bytes()
switch {
case len(b) > size:
panic("math/big: value won't fit requested size")
case len(b) == size:
return b
default:
buf := make([]byte, size)
copy(buf[size-len(b):], b)
return buf
}
}
func getSignatureType(tx *types.Transaction) uint8 {
if tx.SignatureHashType() == 0 {
return 0
} else if tx.SignatureHashType() == 1 {
return 2
} else {
return 1
}
}
// This is a temporary fix to patch the enums being used in the raw data
func fixType(tx *types.Transaction) *types.Transaction {
meta := tx.GetMeta()
raw := meta.RawTransaction
if len(raw) == 0 {
log.Error("Transaction with no raw detected")
return tx
}
if raw[0] == 0x00 {
return tx
} else if raw[0] == 0x01 {
raw[0] = 0x02
}
queueOrigin := types.QueueOrigin(meta.QueueOrigin.Uint64())
fixed := types.NewTransactionMeta(meta.L1BlockNumber, meta.L1Timestamp, meta.L1MessageSender, meta.SignatureHashType, queueOrigin, meta.Index, meta.QueueIndex, raw)
tx.SetTransactionMeta(fixed)
return tx
}
......@@ -7,8 +7,7 @@ pragma experimental ABIEncoderV2;
import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContractAccount.sol";
/* Library Imports */
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_ECDSAUtils } from "../../libraries/utils/Lib_ECDSAUtils.sol";
import { Lib_EIP155Tx } from "../../libraries/codec/Lib_EIP155Tx.sol";
import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol";
/* Contract Imports */
......@@ -21,13 +20,20 @@ import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
* @title OVM_ECDSAContractAccount
* @dev The ECDSA Contract Account can be used as the implementation for a ProxyEOA deployed by the
* ovmCREATEEOA operation. It enables backwards compatibility with Ethereum's Layer 1, by
* providing eth_sign and EIP155 formatted transaction encodings.
* providing EIP155 formatted transaction encodings.
*
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
/*************
* Libraries *
*************/
using Lib_EIP155Tx for Lib_EIP155Tx.EIP155Tx;
/*************
* Constants *
*************/
......@@ -44,20 +50,12 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
/**
* Executes a signed transaction.
* @param _transaction Signed EOA transaction.
* @param _signatureType Hashing scheme used for the transaction (e.g., ETH signed message).
* @param _v Signature `v` parameter.
* @param _r Signature `r` parameter.
* @param _s Signature `s` parameter.
* @param _encodedTransaction Signed EIP155 transaction.
* @return Whether or not the call returned (rather than reverted).
* @return Data returned by the call.
*/
function execute(
bytes memory _transaction,
Lib_OVMCodec.EOASignatureType _signatureType,
uint8 _v,
bytes32 _r,
bytes32 _s
bytes memory _encodedTransaction
)
override
public
......@@ -66,50 +64,30 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
bytes memory
)
{
bool isEthSign = _signatureType == Lib_OVMCodec.EOASignatureType.ETH_SIGNED_MESSAGE;
// Attempt to decode the transaction.
Lib_EIP155Tx.EIP155Tx memory transaction = Lib_EIP155Tx.decode(
_encodedTransaction,
Lib_ExecutionManagerWrapper.ovmCHAINID()
);
// Address of this contract within the ovm (ovmADDRESS) should be the same as the
// recovered address of the user who signed this message. This is how we manage to shim
// account abstraction even though the user isn't a contract.
// Need to make sure that the transaction nonce is right and bump it if so.
require(
Lib_ECDSAUtils.recover(
_transaction,
isEthSign,
_v,
_r,
_s
) == address(this),
transaction.sender() == address(this),
"Signature provided for EOA transaction execution is invalid."
);
Lib_OVMCodec.EIP155Transaction memory decodedTx = Lib_OVMCodec.decodeEIP155Transaction(
_transaction,
isEthSign
);
// Grab the chain ID of the current network.
uint256 chainId;
assembly {
chainId := chainid()
}
// Need to make sure that the transaction chainId is correct.
require(
decodedTx.chainId == chainId,
"Transaction chainId does not match expected OVM chainId."
);
// Need to make sure that the transaction nonce is right.
require(
decodedTx.nonce == Lib_ExecutionManagerWrapper.ovmGETNONCE(),
transaction.nonce == Lib_ExecutionManagerWrapper.ovmGETNONCE(),
"Transaction nonce does not match the expected nonce."
);
// TEMPORARY: Disable gas checks for mainnet.
// // Need to make sure that the gas is sufficient to execute the transaction.
// require(
// gasleft() >= SafeMath.add(decodedTx.gasLimit, EXECUTION_VALIDATION_GAS_OVERHEAD),
// gasleft() >= SafeMath.add(transaction.gasLimit, EXECUTION_VALIDATION_GAS_OVERHEAD),
// "Gas is not sufficient to execute the transaction."
// );
......@@ -117,15 +95,21 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
require(
ovmETH.transfer(
msg.sender,
SafeMath.mul(decodedTx.gasLimit, decodedTx.gasPrice)
SafeMath.mul(transaction.gasLimit, transaction.gasPrice)
),
"Fee was not transferred to relayer."
);
// Contract creations are signalled by sending a transaction to the zero address.
if (decodedTx.to == address(0)) {
if (transaction.isCreate) {
// TEMPORARY: Disable value transfer for contract creations.
require(
transaction.value == 0,
"Value transfer in contract creation not supported."
);
(address created, bytes memory revertdata) = Lib_ExecutionManagerWrapper.ovmCREATE(
decodedTx.data
transaction.data
);
// Return true if the contract creation succeeded, false w/ revertdata otherwise.
......@@ -140,7 +124,25 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// cases, but since this is a contract we'd end up bumping the nonce twice.
Lib_ExecutionManagerWrapper.ovmINCREMENTNONCE();
return decodedTx.to.call(decodedTx.data);
// Value transfer currently only supported for CALL but not for CREATE.
if (transaction.value > 0) {
// TEMPORARY: Block value transfer if the transaction has input data.
require(
transaction.data.length == 0,
"Value is nonzero but input data was provided."
);
require(
ovmETH.transfer(
transaction.to,
transaction.value
),
"Value could not be transferred to recipient."
);
return (true, bytes(""));
} else {
return transaction.to.call(transaction.data);
}
}
}
}
......@@ -16,12 +16,21 @@ import { Lib_Bytes32Utils } from "../../libraries/utils/Lib_Bytes32Utils.sol";
*/
contract OVM_ProxyEOA {
/**********
* Events *
**********/
event Upgraded(
address indexed implementation
);
/*************
* Constants *
*************/
address constant DEFAULT_IMPLEMENTATION = 0x4200000000000000000000000000000000000003;
bytes32 constant IMPLEMENTATION_KEY = 0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead;
bytes32 constant IMPLEMENTATION_KEY = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; //bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
/*********************
......@@ -68,6 +77,7 @@ contract OVM_ProxyEOA {
);
_setImplementation(_implementation);
emit Upgraded(_implementation);
}
/**
......
......@@ -174,4 +174,25 @@ contract OVM_L1ETHGateway is iOVM_L1ETHGateway, OVM_CrossDomainEnabled, Lib_Addr
(bool success, ) = _to.call{value: _value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
/*****************************
* Temporary - Migrating ETH *
*****************************/
/**
* @dev Migrates entire ETH balance to another gateway
* @param _to Gateway Proxy address to migrate ETH to
*/
function migrateEth(address payable _to) external {
address owner = Lib_AddressManager(libAddressManager).owner();
require(msg.sender == owner, "Only the owner can migrate ETH");
uint256 balance = address(this).balance;
OVM_L1ETHGateway(_to).donateETH{value:balance}();
}
/**
* @dev Adds ETH balance to the account. This is meant to allow for ETH
* to be migrated from an old gateway to a new gateway
*/
function donateETH() external payable {}
}
......@@ -162,11 +162,14 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
)
override
external
returns (
bytes memory
)
{
// Make sure that run() is not re-enterable. This condition should always be satisfied
// Once run has been called once, due to the behavior of _isValidInput().
if (transactionContext.ovmNUMBER != DEFAULT_UINT256) {
return;
return bytes("");
}
// Store our OVM_StateManager instance (significantly easier than attempting to pass the
......@@ -194,7 +197,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// reverts for INVALID_STATE_ACCESS.
if (_isValidInput(_transaction) == false) {
_resetContext();
return;
return bytes("");
}
// TEMPORARY: Gas metering is disabled for minnet.
......@@ -202,7 +205,7 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// uint256 gasProvided = gasleft();
// Run the transaction, make sure to meter the gas usage.
ovmCALL(
(, bytes memory returndata) = ovmCALL(
_transaction.gasLimit - gasMeterConfig.minTransactionGasLimit,
_transaction.entrypoint,
_transaction.data
......@@ -215,6 +218,8 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
// Wipe the execution context.
_resetContext();
return returndata;
}
......@@ -1877,7 +1882,6 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
)
external
returns (
bool,
bytes memory
)
{
......@@ -1894,18 +1898,19 @@ contract OVM_ExecutionManager is iOVM_ExecutionManager, Lib_AddressResolver {
if (isCreate) {
(address created, bytes memory revertData) = ovmCREATE(_transaction.data);
if (created == address(0)) {
return (false, revertData);
return abi.encode(false, revertData);
} else {
// The eth_call RPC endpoint for to = undefined will return the deployed bytecode
// in the success case, differing from standard create messages.
return (true, Lib_EthUtils.getCode(created));
return abi.encode(true, Lib_EthUtils.getCode(created));
}
} else {
return ovmCALL(
(bool success, bytes memory returndata) = ovmCALL(
_transaction.gasLimit,
_transaction.entrypoint,
_transaction.data
);
return abi.encode(success, returndata);
}
}
}
......@@ -2,13 +2,8 @@
// @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_EIP155Tx } from "../../libraries/codec/Lib_EIP155Tx.sol";
import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol";
/**
......@@ -16,22 +11,16 @@ import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_Execut
* @dev The Sequencer Entrypoint is a predeploy which, despite its name, can in fact be called by
* any account. It accepts a more efficient compressed calldata format, which it decompresses and
* encodes to the standard EIP155 transaction format.
* 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: optimistic-solc
* Runtime target: OVM
*/
contract OVM_SequencerEntrypoint {
/*********
* Enums *
*********/
/*************
* Libraries *
*************/
enum TransactionType {
NATIVE_ETH_TRANSACTION,
ETH_SIGNED_MESSAGE
}
using Lib_EIP155Tx for Lib_EIP155Tx.EIP155Tx;
/*********************
......@@ -39,104 +28,56 @@ contract OVM_SequencerEntrypoint {
*********************/
/**
* Uses a custom "compressed" format to save on calldata gas:
* calldata[00:01]: transaction type (0 == EIP 155, 2 == Eth Sign Message)
* calldata[01:33]: signature "r" parameter
* calldata[33:65]: signature "s" parameter
* calldata[65:66]: signature "v" parameter
* calldata[66:69]: transaction gas limit
* calldata[69:72]: transaction gas price
* calldata[72:75]: transaction nonce
* calldata[75:95]: transaction target address
* calldata[95:XX]: transaction data
* Expects an RLP-encoded EIP155 transaction as input. See the EIP for a more detailed
* description of this transaction format:
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
*/
fallback()
external
{
TransactionType transactionType = _getTransactionType(Lib_BytesUtils.toUint8(msg.data, 0));
bytes32 r = Lib_BytesUtils.toBytes32(Lib_BytesUtils.slice(msg.data, 1, 32));
bytes32 s = Lib_BytesUtils.toBytes32(Lib_BytesUtils.slice(msg.data, 33, 32));
uint8 v = Lib_BytesUtils.toUint8(msg.data, 65);
// Remainder is the transaction to execute.
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()
}
// We use this twice, so it's more gas efficient to store a copy of it (barely).
bytes memory encodedTx = msg.data;
// Need to decompress and then re-encode the transaction based on the original encoding.
bytes memory encodedTx = Lib_OVMCodec.encodeEIP155Transaction(
Lib_OVMCodec.decompressEIP155Transaction(
compressedTx,
chainId
),
isEthSignedMessage
);
address target = Lib_ECDSAUtils.recover(
// Decode the tx with the correct chain ID.
Lib_EIP155Tx.EIP155Tx memory transaction = Lib_EIP155Tx.decode(
encodedTx,
isEthSignedMessage,
v,
r,
s
Lib_ExecutionManagerWrapper.ovmCHAINID()
);
// Value is computed on the fly. Keep it in the stack to save some gas.
address target = transaction.sender();
bool isEmptyContract;
assembly {
isEmptyContract := iszero(extcodesize(target))
}
// If the account is empty, deploy the default EOA to that address.
if (isEmptyContract) {
// ProxyEOA has not yet been deployed for this EOA.
bytes32 messageHash = Lib_ECDSAUtils.getMessageHash(encodedTx, isEthSignedMessage);
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;
}
iOVM_ECDSAContractAccount(target).execute(
encodedTx,
sigtype,
v,
r,
s
Lib_ExecutionManagerWrapper.ovmCREATEEOA(
transaction.hash(),
transaction.recoveryParam,
transaction.r,
transaction.s
);
}
/**********************
* Internal Functions *
**********************/
/**
* Converts a uint256 into a TransactionType enum.
* @param _transactionType Transaction type index.
* @return _txType Transaction type enum value.
*/
function _getTransactionType(
uint8 _transactionType
)
internal
returns (
TransactionType _txType
// Forward the transaction over to the EOA.
(bool success, bytes memory returndata) = target.call(
abi.encodeWithSignature(
"execute(bytes)",
encodedTx
)
{
if (_transactionType == 0) {
return TransactionType.NATIVE_ETH_TRANSACTION;
} if (_transactionType == 2) {
return TransactionType.ETH_SIGNED_MESSAGE;
);
if (success) {
assembly {
return(add(returndata, 0x20), mload(returndata))
}
} else {
revert("Transaction type must be 0 or 2");
assembly {
revert(add(returndata, 0x20), mload(returndata))
}
}
}
}
......@@ -15,10 +15,11 @@ interface iOVM_ECDSAContractAccount {
********************/
function execute(
bytes memory _transaction,
Lib_OVMCodec.EOASignatureType _signatureType,
uint8 _v,
bytes32 _r,
bytes32 _s
) external returns (bool _success, bytes memory _returndata);
bytes memory _encodedTransaction
)
external
returns (
bool,
bytes memory
);
}
......@@ -75,7 +75,7 @@ interface iOVM_ExecutionManager {
function run(
Lib_OVMCodec.Transaction calldata _transaction,
address _txStateManager
) external;
) external returns (bytes memory);
/*******************
......
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_RLPReader } from "../rlp/Lib_RLPReader.sol";
import { Lib_RLPWriter } from "../rlp/Lib_RLPWriter.sol";
/**
* @title Lib_EIP155Tx
* @dev A simple library for dealing with the transaction type defined by EIP155:
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
*/
library Lib_EIP155Tx {
/***********
* Structs *
***********/
// Struct representing an EIP155 transaction. See EIP link above for more information.
struct EIP155Tx {
// These fields correspond to the actual RLP-encoded fields specified by EIP155.
uint256 nonce;
uint256 gasPrice;
uint256 gasLimit;
address to;
uint256 value;
bytes data;
uint8 v;
bytes32 r;
bytes32 s;
// Chain ID to associate this transaction with. Used all over the place, seemed easier to
// set this once when we create the transaction rather than providing it as an input to
// each function. I don't see a strong need to have a transaction with a mutable chain ID.
uint256 chainId;
// The ECDSA "recovery parameter," should always be 0 or 1. EIP155 specifies that:
// `v = {0,1} + CHAIN_ID * 2 + 35`
// Where `{0,1}` is a stand in for our `recovery_parameter`. Now computing our formula for
// the recovery parameter:
// 1. `v = {0,1} + CHAIN_ID * 2 + 35`
// 2. `v = recovery_parameter + CHAIN_ID * 2 + 35`
// 3. `v - CHAIN_ID * 2 - 35 = recovery_parameter`
// So we're left with the final formula:
// `recovery_parameter = v - CHAIN_ID * 2 - 35`
// NOTE: This variable is a uint8 because `v` is inherently limited to a uint8. If we
// didn't use a uint8, then recovery_parameter would always be a negative number for chain
// IDs greater than 110 (`255 - 110 * 2 - 35 = 0`). So we need to wrap around to support
// anything larger.
uint8 recoveryParam;
// Whether or not the transaction is a creation. Necessary because we can't make an address
// "nil". Using the zero address creates a potential conflict if the user did actually
// intend to send a transaction to the zero address.
bool isCreate;
}
// Lets us use nicer syntax.
using Lib_EIP155Tx for EIP155Tx;
/**********************
* Internal Functions *
**********************/
/**
* Decodes an EIP155 transaction and attaches a given Chain ID.
* Transaction *must* be RLP-encoded.
* @param _encoded RLP-encoded EIP155 transaction.
* @param _chainId Chain ID to assocaite with this transaction.
* @return Parsed transaction.
*/
function decode(
bytes memory _encoded,
uint256 _chainId
)
internal
pure
returns (
EIP155Tx memory
)
{
Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_encoded);
// Note formula above about how recoveryParam is computed.
uint8 v = uint8(Lib_RLPReader.readUint256(decoded[6]));
uint8 recoveryParam = uint8(v - 2 * _chainId - 35);
// Recovery param being anything other than 0 or 1 indicates that we have the wrong chain
// ID.
require(
recoveryParam < 2,
"Lib_EIP155Tx: Transaction signed with wrong chain ID"
);
// Creations can be detected by looking at the byte length here.
bool isCreate = Lib_RLPReader.readBytes(decoded[3]).length == 0;
return EIP155Tx({
nonce: Lib_RLPReader.readUint256(decoded[0]),
gasPrice: Lib_RLPReader.readUint256(decoded[1]),
gasLimit: Lib_RLPReader.readUint256(decoded[2]),
to: Lib_RLPReader.readAddress(decoded[3]),
value: Lib_RLPReader.readUint256(decoded[4]),
data: Lib_RLPReader.readBytes(decoded[5]),
v: v,
r: Lib_RLPReader.readBytes32(decoded[7]),
s: Lib_RLPReader.readBytes32(decoded[8]),
chainId: _chainId,
recoveryParam: recoveryParam,
isCreate: isCreate
});
}
/**
* Encodes an EIP155 transaction into RLP.
* @param _transaction EIP155 transaction to encode.
* @param _includeSignature Whether or not to encode the signature.
* @return RLP-encoded transaction.
*/
function encode(
EIP155Tx memory _transaction,
bool _includeSignature
)
internal
pure
returns (
bytes memory
)
{
bytes[] memory raw = new bytes[](9);
raw[0] = Lib_RLPWriter.writeUint(_transaction.nonce);
raw[1] = Lib_RLPWriter.writeUint(_transaction.gasPrice);
raw[2] = Lib_RLPWriter.writeUint(_transaction.gasLimit);
// We write the encoding of empty bytes when the transaction is a creation, *not* the zero
// address as one might assume.
if (_transaction.isCreate) {
raw[3] = Lib_RLPWriter.writeBytes('');
} else {
raw[3] = Lib_RLPWriter.writeAddress(_transaction.to);
}
raw[4] = Lib_RLPWriter.writeUint(_transaction.value);
raw[5] = Lib_RLPWriter.writeBytes(_transaction.data);
if (_includeSignature) {
raw[6] = Lib_RLPWriter.writeUint(_transaction.v);
raw[7] = Lib_RLPWriter.writeBytes32(_transaction.r);
raw[8] = Lib_RLPWriter.writeBytes32(_transaction.s);
} else {
// Chain ID *is* included in the unsigned transaction.
raw[6] = Lib_RLPWriter.writeUint(_transaction.chainId);
raw[7] = Lib_RLPWriter.writeBytes('');
raw[8] = Lib_RLPWriter.writeBytes('');
}
return Lib_RLPWriter.writeList(raw);
}
/**
* Computes the hash of an EIP155 transaction. Assumes that you don't want to include the
* signature in this hash because that's a very uncommon usecase. If you really want to include
* the signature, just encode with the signature and take the hash yourself.
*/
function hash(
EIP155Tx memory _transaction
)
internal
pure
returns (
bytes32
)
{
return keccak256(
_transaction.encode(false)
);
}
/**
* Computes the sender of an EIP155 transaction.
* @param _transaction EIP155 transaction to get a sender for.
* @return Address corresponding to the private key that signed this transaction.
*/
function sender(
EIP155Tx memory _transaction
)
internal
pure
returns (
address
)
{
return ecrecover(
_transaction.hash(),
_transaction.recoveryParam + 27,
_transaction.r,
_transaction.s
);
}
}
......@@ -17,11 +17,6 @@ library Lib_OVMCodec {
* Enums *
*********/
enum EOASignatureType {
EIP155_TRANSACTION,
ETH_SIGNED_MESSAGE
}
enum QueueOrigin {
SEQUENCER_QUEUE,
L1TOL2_QUEUE
......@@ -85,144 +80,11 @@ library Lib_OVMCodec {
uint40 blockNumber;
}
struct EIP155Transaction {
uint256 nonce;
uint256 gasPrice;
uint256 gasLimit;
address to;
uint256 value;
bytes data;
uint256 chainId;
}
/**********************
* Internal Functions *
**********************/
/**
* Decodes an EOA transaction (i.e., native Ethereum RLP encoding).
* @param _transaction Encoded EOA transaction.
* @return Transaction decoded into a struct.
*/
function decodeEIP155Transaction(
bytes memory _transaction,
bool _isEthSignedMessage
)
internal
pure
returns (
EIP155Transaction memory
)
{
if (_isEthSignedMessage) {
(
uint256 _nonce,
uint256 _gasLimit,
uint256 _gasPrice,
uint256 _chainId,
address _to,
bytes memory _data
) = abi.decode(
_transaction,
(uint256, uint256, uint256, uint256, address ,bytes)
);
return EIP155Transaction({
nonce: _nonce,
gasPrice: _gasPrice,
gasLimit: _gasLimit,
to: _to,
value: 0,
data: _data,
chainId: _chainId
});
} else {
Lib_RLPReader.RLPItem[] memory decoded = Lib_RLPReader.readList(_transaction);
return EIP155Transaction({
nonce: Lib_RLPReader.readUint256(decoded[0]),
gasPrice: Lib_RLPReader.readUint256(decoded[1]),
gasLimit: Lib_RLPReader.readUint256(decoded[2]),
to: Lib_RLPReader.readAddress(decoded[3]),
value: Lib_RLPReader.readUint256(decoded[4]),
data: Lib_RLPReader.readBytes(decoded[5]),
chainId: Lib_RLPReader.readUint256(decoded[6])
});
}
}
/**
* 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,
uint256 _chainId
)
internal
returns (
EIP155Transaction memory
)
{
return EIP155Transaction({
gasLimit: Lib_BytesUtils.toUint24(_transaction, 0),
gasPrice: uint256(Lib_BytesUtils.toUint24(_transaction, 3)) * 1000000,
nonce: Lib_BytesUtils.toUint24(_transaction, 6),
to: Lib_BytesUtils.toAddress(_transaction, 9),
data: Lib_BytesUtils.slice(_transaction, 29),
chainId: _chainId,
value: 0
});
}
/**
* Encodes an EOA transaction back into the original transaction.
* @param _transaction EIP155transaction to encode.
* @param _isEthSignedMessage Whether or not this was an eth signed message.
* @return Encoded transaction.
*/
function encodeEIP155Transaction(
EIP155Transaction memory _transaction,
bool _isEthSignedMessage
)
internal
pure
returns (
bytes memory
)
{
if (_isEthSignedMessage) {
return abi.encode(
_transaction.nonce,
_transaction.gasLimit,
_transaction.gasPrice,
_transaction.chainId,
_transaction.to,
_transaction.data
);
} else {
bytes[] memory raw = new bytes[](9);
raw[0] = Lib_RLPWriter.writeUint(_transaction.nonce);
raw[1] = Lib_RLPWriter.writeUint(_transaction.gasPrice);
raw[2] = Lib_RLPWriter.writeUint(_transaction.gasLimit);
if (_transaction.to == address(0)) {
raw[3] = Lib_RLPWriter.writeBytes('');
} else {
raw[3] = Lib_RLPWriter.writeAddress(_transaction.to);
}
raw[4] = Lib_RLPWriter.writeUint(0);
raw[5] = Lib_RLPWriter.writeBytes(_transaction.data);
raw[6] = Lib_RLPWriter.writeUint(_transaction.chainId);
raw[7] = Lib_RLPWriter.writeBytes(bytes(''));
raw[8] = Lib_RLPWriter.writeBytes(bytes(''));
return Lib_RLPWriter.writeList(raw);
}
}
/**
* Encodes a standard OVM transaction.
* @param _transaction OVM transaction to encode.
......
......@@ -89,6 +89,23 @@ library Lib_RLPWriter {
return writeBytes(abi.encodePacked(_in));
}
/**
* RLP encodes a bytes32 value.
* @param _in The bytes32 to encode.
* @return _out The RLP encoded bytes32 in bytes.
*/
function writeBytes32(
bytes32 _in
)
internal
pure
returns (
bytes memory _out
)
{
return writeBytes(abi.encodePacked(_in));
}
/**
* RLP encodes a uint.
* @param _in The uint256 to encode.
......
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/**
* @title Lib_ECDSAUtils
*/
library Lib_ECDSAUtils {
/**************************************
* Internal Functions: ECDSA Recovery *
**************************************/
/**
* Recovers a signed address given a message and signature.
* @param _message Message that was originally signed.
* @param _isEthSignedMessage Whether or not the user used the `Ethereum Signed Message` prefix.
* @param _v Signature `v` parameter.
* @param _r Signature `r` parameter.
* @param _s Signature `s` parameter.
* @return _sender Signer address.
*/
function recover(
bytes memory _message,
bool _isEthSignedMessage,
uint8 _v,
bytes32 _r,
bytes32 _s
)
internal
pure
returns (
address _sender
)
{
bytes32 messageHash = getMessageHash(_message, _isEthSignedMessage);
return ecrecover(
messageHash,
_v + 27,
_r,
_s
);
}
function getMessageHash(
bytes memory _message,
bool _isEthSignedMessage
)
internal
pure
returns (bytes32) {
if (_isEthSignedMessage) {
return getEthSignedMessageHash(_message);
}
return getNativeMessageHash(_message);
}
/*************************************
* Private Functions: ECDSA Recovery *
*************************************/
/**
* Gets the native message hash (simple keccak256) for a message.
* @param _message Message to hash.
* @return _messageHash Native message hash.
*/
function getNativeMessageHash(
bytes memory _message
)
private
pure
returns (
bytes32 _messageHash
)
{
return keccak256(_message);
}
/**
* Gets the hash of a message with the `Ethereum Signed Message` prefix.
* @param _message Message to hash.
* @return _messageHash Prefixed message hash.
*/
function getEthSignedMessageHash(
bytes memory _message
)
private
pure
returns (
bytes32 _messageHash
)
{
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 messageHash = keccak256(_message);
return keccak256(abi.encodePacked(prefix, messageHash));
}
}
\ No newline at end of file
......@@ -118,6 +118,25 @@ library Lib_ExecutionManagerWrapper {
return abi.decode(returndata, (address));
}
/**
* Calls the ovmCHAINID opcode.
* @return Chain ID of the current network.
*/
function ovmCHAINID()
internal
returns (
uint256
)
{
bytes memory returndata = _safeExecutionManagerInteraction(
abi.encodeWithSignature(
"ovmCHAINID()"
)
);
return abi.decode(returndata, (uint256));
}
/*********************
* Private Functions *
......
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { Lib_EIP155Tx } from "../../optimistic-ethereum/libraries/codec/Lib_EIP155Tx.sol";
/**
* @title TestLib_EIP155Tx
*/
contract TestLib_EIP155Tx {
function decode(
bytes memory _encoded,
uint256 _chainId
)
public
pure
returns (
Lib_EIP155Tx.EIP155Tx memory
)
{
return Lib_EIP155Tx.decode(
_encoded,
_chainId
);
}
function encode(
Lib_EIP155Tx.EIP155Tx memory _transaction,
bool _includeSignature
)
public
pure
returns (
bytes memory
)
{
return Lib_EIP155Tx.encode(
_transaction,
_includeSignature
);
}
function hash(
Lib_EIP155Tx.EIP155Tx memory _transaction
)
public
pure
returns (
bytes32
)
{
return Lib_EIP155Tx.hash(
_transaction
);
}
function sender(
Lib_EIP155Tx.EIP155Tx memory _transaction
)
public
pure
returns (
address
)
{
return Lib_EIP155Tx.sender(
_transaction
);
}
}
......@@ -9,20 +9,6 @@ import { Lib_OVMCodec } from "../../optimistic-ethereum/libraries/codec/Lib_OVMC
* @title TestLib_OVMCodec
*/
contract TestLib_OVMCodec {
function decodeEIP155Transaction(
bytes memory _transaction,
bool _isEthSignedMessage
)
public
pure
returns (
Lib_OVMCodec.EIP155Transaction memory _decoded
)
{
return Lib_OVMCodec.decodeEIP155Transaction(_transaction, _isEthSignedMessage);
}
function encodeTransaction(
Lib_OVMCodec.Transaction memory _transaction
)
......@@ -46,19 +32,4 @@ contract TestLib_OVMCodec {
{
return Lib_OVMCodec.hashTransaction(_transaction);
}
function decompressEIP155Transaction(
bytes memory _transaction,
uint256 _chainId
)
public
returns (
Lib_OVMCodec.EIP155Transaction memory _decompressed
)
{
return Lib_OVMCodec.decompressEIP155Transaction(
_transaction,
_chainId
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;
/* Library Imports */
import { Lib_ECDSAUtils } from "../../optimistic-ethereum/libraries/utils/Lib_ECDSAUtils.sol";
/**
* @title TestLib_ECDSAUtils
*/
contract TestLib_ECDSAUtils {
function recover(
bytes memory _message,
bool _isEthSignedMessage,
uint8 _v,
bytes32 _r,
bytes32 _s
)
public
pure
returns (
address _sender
)
{
return Lib_ECDSAUtils.recover(
_message,
_isEthSignedMessage,
_v,
_r,
_s
);
}
}
......@@ -49,6 +49,7 @@ Network : __mainnet (chain id: 1)__
|Proxy__OVM_L1ETHGateway|[0xF20C38fCdDF0C790319Fd7431d17ea0c2bC9959c](https://etherscan.io/address/0xF20C38fCdDF0C790319Fd7431d17ea0c2bC9959c)|
|mockOVM_BondManager|[0x99EDa8472E93Aa28E5470eEDEc6e32081E14DaFC](https://etherscan.io/address/0x99EDa8472E93Aa28E5470eEDEc6e32081E14DaFC)|
---
## KOVAN
Network : __kovan (chain id: 42)__
......@@ -72,4 +73,3 @@ Network : __kovan (chain id: 42)__
|Proxy__OVM_L1CrossDomainMessenger|[0x48062eD9b6488EC41c4CfbF2f568D7773819d8C9](https://kovan.etherscan.io/address/0x48062eD9b6488EC41c4CfbF2f568D7773819d8C9)|
|Proxy__OVM_L1ETHGateway|[0xf3902e50dA095bD2e954AB320E8eafDA6152dFDa](https://kovan.etherscan.io/address/0xf3902e50dA095bD2e954AB320E8eafDA6152dFDa)|
|mockOVM_BondManager|[0x77e244ec49014cFb9c4572453568eCC3AbB70A2d](https://kovan.etherscan.io/address/0x77e244ec49014cFb9c4572453568eCC3AbB70A2d)|
---
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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