Commit 9ddbe51a authored by Kelvin Fichter's avatar Kelvin Fichter

feat: rework msg sender for l1 to l2 messages

parent 973589da
......@@ -6,12 +6,14 @@ contract ICrossDomainMessenger {
contract SimpleStorage {
address public msgSender;
address public txOrigin;
address public xDomainSender;
bytes32 public value;
uint256 public totalCount;
function setValue(bytes32 newValue) public {
msgSender = msg.sender;
txOrigin = tx.origin;
xDomainSender = ICrossDomainMessenger(msg.sender)
.xDomainMessageSender();
value = newValue;
......@@ -20,6 +22,7 @@ contract SimpleStorage {
function setValueNotXDomain(bytes32 newValue) public {
msgSender = msg.sender;
txOrigin = tx.origin;
value = newValue;
totalCount++;
}
......
......@@ -2,7 +2,7 @@ import { expect } from 'chai'
/* Imports: External */
import { Contract, ContractFactory } from 'ethers'
import { predeploys, getContractInterface } from '@eth-optimism/contracts'
import { applyL1ToL2Alias, sleep } from '@eth-optimism/core-utils'
/* Imports: Internal */
import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'
......@@ -90,6 +90,9 @@ describe('Basic L1<>L2 Communication', async () => {
expect(await L2SimpleStorage.msgSender()).to.equal(
env.l2Messenger.address
)
expect(await L2SimpleStorage.txOrigin()).to.equal(
applyL1ToL2Alias(env.l1Messenger.address)
)
expect(await L2SimpleStorage.xDomainSender()).to.equal(
env.l1Wallet.address
)
......@@ -97,6 +100,30 @@ describe('Basic L1<>L2 Communication', async () => {
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
it('should deposit from L1 -> L2 directly via enqueue', async () => {
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
await env.ctc
.connect(env.l1Wallet)
.enqueue(
L2SimpleStorage.address,
5000000,
L2SimpleStorage.interface.encodeFunctionData('setValueNotXDomain', [
value,
])
)
// TODO: We need to have a function that can wait for enqueued txs.
await sleep(10000)
// No aliasing when an EOA goes directly to L2.
expect(await L2SimpleStorage.msgSender()).to.equal(env.l1Wallet.address)
expect(await L2SimpleStorage.txOrigin()).to.equal(env.l1Wallet.address)
expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
it('should have a receipt with a status of 1 for a successful message', async () => {
const value = `0x${'42'.repeat(32)}`
......
......@@ -2,7 +2,7 @@ import { expect } from 'chai'
/* Imports: Internal */
import { providers } from 'ethers'
import { injectL2Context } from '@eth-optimism/core-utils'
import { injectL2Context, applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Imports: External */
import { OptimismEnv } from './shared/env'
......@@ -57,7 +57,7 @@ describe('Queue Ingestion', () => {
expect(params._target).to.equal('0x' + `${i}`.repeat(40))
expect(l2Tx.queueOrigin).to.equal('l1')
expect(l2Tx.l1TxOrigin.toLowerCase()).to.equal(
env.l1Messenger.address.toLowerCase()
applyL1ToL2Alias(env.l1Messenger.address).toLowerCase()
)
expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber)
}
......
......@@ -603,10 +603,9 @@ func (m callmsg) Data() []byte { return m.CallMsg.Data }
// UsingOVM
// These getters return OVM specific fields
func (m callmsg) L1Timestamp() uint64 { return m.CallMsg.L1Timestamp }
func (m callmsg) L1BlockNumber() *big.Int { return m.CallMsg.L1BlockNumber }
func (m callmsg) L1MessageSender() *common.Address { return m.CallMsg.L1MessageSender }
func (m callmsg) QueueOrigin() types.QueueOrigin { return m.CallMsg.QueueOrigin }
func (m callmsg) L1Timestamp() uint64 { return m.CallMsg.L1Timestamp }
func (m callmsg) L1BlockNumber() *big.Int { return m.CallMsg.L1BlockNumber }
func (m callmsg) QueueOrigin() types.QueueOrigin { return m.CallMsg.QueueOrigin }
// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
......
......@@ -27,10 +27,6 @@ import (
"github.com/ethereum/go-ethereum/rollup/rcfg"
)
// DefaultL1MessageSender is the default L1MessageSender value attached to a transaction that is
// not an L1 to L2 message.
var DefaultL1MessageSender = common.HexToAddress("0x00000000000000000000000000000000000beef")
// ChainContext supports retrieving headers and consensus parameters from the
// current blockchain to be used during transaction processing.
type ChainContext interface {
......@@ -52,28 +48,20 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
}
if rcfg.UsingOVM {
// When using the OVM, we must:
// (1) Attach the L1MessageSender context value and
// (2) Set the BlockNumber to be the msg.L1BlockNumber
// (3) Set the Time to be the msg.L1Timestamp
var l1MessageSender common.Address
if msg.L1MessageSender() == nil {
l1MessageSender = DefaultL1MessageSender
} else {
l1MessageSender = *msg.L1MessageSender()
}
// - Set the BlockNumber to be the msg.L1BlockNumber
// - Set the Time to be the msg.L1Timestamp
return vm.Context{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Origin: msg.From(),
Coinbase: dump.OvmFeeWallet, // Coinbase is the fee vault.
BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).SetUint64(msg.L1Timestamp()),
Difficulty: new(big.Int), // Difficulty always returns zero.
GasLimit: header.GasLimit,
GasPrice: new(big.Int).Set(msg.GasPrice()),
L1MessageSender: l1MessageSender,
L1BlockNumber: msg.L1BlockNumber(),
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Origin: msg.From(),
Coinbase: dump.OvmFeeWallet, // Coinbase is the fee vault.
BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).SetUint64(msg.L1Timestamp()),
Difficulty: new(big.Int), // Difficulty always returns zero.
GasLimit: header.GasLimit,
GasPrice: new(big.Int).Set(msg.GasPrice()),
L1BlockNumber: msg.L1BlockNumber(),
}
} else {
return vm.Context{
......
......@@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
)
// StateProcessor is a basic Processor, which takes care of transitioning
......@@ -89,19 +88,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if rcfg.UsingOVM {
if err != nil {
// This should only be allowed to pass if the transaction is in the ctc
// already. The presence of `Index` should specify this.
index := tx.GetMeta().Index
if index == nil && msg.QueueOrigin() != types.QueueOriginL1ToL2 {
return nil, err
}
}
} else {
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
// Create a new context to be used in the EVM environment
context := NewEVMContext(msg, header, bc, author)
......
......@@ -82,7 +82,6 @@ type Message interface {
L1Timestamp() uint64
L1BlockNumber() *big.Int
L1MessageSender() *common.Address
QueueOrigin() types.QueueOrigin
}
......
......@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/rcfg"
)
//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go
......@@ -295,20 +296,20 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) {
data: tx.data.Payload,
checkNonce: true,
l1Timestamp: tx.meta.L1Timestamp,
l1BlockNumber: tx.meta.L1BlockNumber,
l1MessageSender: tx.meta.L1MessageSender,
queueOrigin: tx.meta.QueueOrigin,
l1Timestamp: tx.meta.L1Timestamp,
l1BlockNumber: tx.meta.L1BlockNumber,
queueOrigin: tx.meta.QueueOrigin,
}
var err error
msg.from, err = Sender(s, tx)
if tx.meta.L1MessageSender != nil {
msg.l1MessageSender = tx.meta.L1MessageSender
if rcfg.UsingOVM {
if tx.meta.QueueOrigin == QueueOriginL1ToL2 && tx.meta.L1MessageSender != nil {
msg.from = *tx.meta.L1MessageSender
} else {
msg.from, err = Sender(s, tx)
}
} else {
addr := common.Address{}
msg.l1MessageSender = &addr
msg.from, err = Sender(s, tx)
}
return msg, err
......@@ -479,13 +480,12 @@ type Message struct {
data []byte
checkNonce bool
l1Timestamp uint64
l1BlockNumber *big.Int
l1MessageSender *common.Address
queueOrigin QueueOrigin
l1Timestamp uint64
l1BlockNumber *big.Int
queueOrigin QueueOrigin
}
func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool, l1MessageSender *common.Address, l1BlockNumber *big.Int, l1Timestamp uint64, queueOrigin QueueOrigin) Message {
func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool, l1BlockNumber *big.Int, l1Timestamp uint64, queueOrigin QueueOrigin) Message {
return Message{
from: from,
to: to,
......@@ -496,10 +496,9 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
data: data,
checkNonce: checkNonce,
l1Timestamp: l1Timestamp,
l1BlockNumber: l1BlockNumber,
l1MessageSender: l1MessageSender,
queueOrigin: queueOrigin,
l1Timestamp: l1Timestamp,
l1BlockNumber: l1BlockNumber,
queueOrigin: queueOrigin,
}
}
......@@ -512,7 +511,6 @@ func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) CheckNonce() bool { return m.checkNonce }
func (m Message) L1Timestamp() uint64 { return m.l1Timestamp }
func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber }
func (m Message) L1MessageSender() *common.Address { return m.l1MessageSender }
func (m Message) QueueOrigin() QueueOrigin { return m.queueOrigin }
func (m Message) L1Timestamp() uint64 { return m.l1Timestamp }
func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber }
func (m Message) QueueOrigin() QueueOrigin { return m.queueOrigin }
......@@ -98,8 +98,7 @@ type Context struct {
Difficulty *big.Int // Provides information for DIFFICULTY
// OVM information
L1MessageSender common.Address // Provides information for L1MESSAGESENDER
L1BlockNumber *big.Int // Provides information for L1BLOCKNUMBER
L1BlockNumber *big.Int // Provides information for L1BLOCKNUMBER
}
// EVM is the Ethereum Virtual Machine base object and provides
......
......@@ -969,11 +969,6 @@ func makeSwap(size int64) executionFunc {
}
// OVM opcodes
func opL1MessageSender(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.L1MessageSender.Bytes()))
return nil, nil
}
func opL1BlockNumber(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.L1BlockNumber)))
return nil, nil
......
......@@ -1155,13 +1155,6 @@ func newFrontierInstructionSet() JumpTable {
valid: true,
writes: true,
},
L1MESSAGESENDER: {
execute: opL1MessageSender,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
valid: true,
},
L1BLOCKNUMBER: {
execute: opL1BlockNumber,
constantGas: GasQuickStep,
......
......@@ -104,8 +104,7 @@ const (
CHAINID = 0x46
SELFBALANCE = 0x47
L1MESSAGESENDER = 0x4A
L1BLOCKNUMBER = 0x4B
L1BLOCKNUMBER = 0x4B
)
// 0x50 range - 'storage' and execution.
......@@ -286,9 +285,8 @@ var opCodeToString = map[OpCode]string{
SELFBALANCE: "SELFBALANCE",
// OVM opcodes
// 0x4A
L1MESSAGESENDER: "L1MESSAGESENDER",
L1BLOCKNUMBER: "L1BLOCKNUMBER",
// 0x4B
L1BLOCKNUMBER: "L1BLOCKNUMBER",
// 0x50 range - 'storage' and execution.
POP: "POP",
......@@ -548,8 +546,7 @@ var stringToOp = map[string]OpCode{
"SELFDESTRUCT": SELFDESTRUCT,
// OVM opcodes
"L1MESSAGESENDER": L1MESSAGESENDER,
"L1BLOCKNUMBER": L1BLOCKNUMBER,
"L1BLOCKNUMBER": L1BLOCKNUMBER,
}
// StringToOp finds the opcode whose name is stored in `str`.
......
......@@ -120,10 +120,9 @@ type CallMsg struct {
Value *big.Int // amount of wei sent along with the call
Data []byte // input data, usually an ABI-encoded contract method invocation
L1Timestamp uint64
L1BlockNumber *big.Int
L1MessageSender *common.Address
QueueOrigin types.QueueOrigin
L1Timestamp uint64
L1BlockNumber *big.Int
QueueOrigin types.QueueOrigin
}
// A ContractCaller provides contract calls, essentially transactions that are executed by
......
......@@ -883,7 +883,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
}
// Create new call message
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, &addr, blockNumber, timestamp, types.QueueOriginSequencer)
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, blockNumber, timestamp, types.QueueOriginSequencer)
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
......
......@@ -2,6 +2,7 @@
pragma solidity ^0.8.8;
/* Library Imports */
import { AddressAliasHelper } from "../../standards/AddressAliasHelper.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressManager } from "../../libraries/resolver/Lib_AddressManager.sol";
......@@ -62,6 +63,7 @@ contract L1CrossDomainMessenger is
address internal xDomainMsgSender = Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER;
/***************
* Constructor *
***************/
......@@ -75,6 +77,7 @@ contract L1CrossDomainMessenger is
Lib_AddressResolver(address(0))
{}
/**********************
* Function Modifiers *
**********************/
......@@ -299,7 +302,7 @@ contract L1CrossDomainMessenger is
// Compute the transactionHash
bytes32 transactionHash = keccak256(
abi.encode(
address(this),
AddressAliasHelper.applyL1ToL2Alias(address(this)),
Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER,
_gasLimit,
_message
......
......@@ -2,6 +2,7 @@
pragma solidity ^0.8.8;
/* Library Imports */
import { AddressAliasHelper } from "../../standards/AddressAliasHelper.sol";
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
......@@ -279,9 +280,21 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
}
}
// Apply an aliasing unless msg.sender == tx.origin. This prevents an attack in which a
// contract on L1 has the same address as a contract on L2 but doesn't have the same code.
// We can safely ignore this for EOAs because they're guaranteed to have the same "code"
// (i.e. no code at all). This also makes it possible for users to interact with contracts
// on L2 even when the Sequencer is down.
address sender;
if (msg.sender == tx.origin) {
sender = msg.sender;
} else {
sender = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
}
bytes32 transactionHash = keccak256(
abi.encode(
msg.sender,
sender,
_target,
_gasLimit,
_data
......@@ -304,7 +317,7 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
// to divide by 2 and subtract 1.
uint256 queueIndex = queueRef.length() / 2 - 1;
emit TransactionEnqueued(
msg.sender,
sender,
_target,
_gasLimit,
_data,
......
......@@ -2,18 +2,15 @@
pragma solidity ^0.8.8;
/* Library Imports */
import { AddressAliasHelper } from "../../standards/AddressAliasHelper.sol";
import { Lib_CrossDomainUtils } from "../../libraries/bridge/Lib_CrossDomainUtils.sol";
import { Lib_DefaultValues } from "../../libraries/constants/Lib_DefaultValues.sol";
import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";
/* Interface Imports */
import { IL2CrossDomainMessenger } from "./IL2CrossDomainMessenger.sol";
import { iOVM_L1MessageSender } from "../predeploys/iOVM_L1MessageSender.sol";
import { iOVM_L2ToL1MessagePasser } from "../predeploys/iOVM_L2ToL1MessagePasser.sol";
/* External Imports */
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/**
* @title L2CrossDomainMessenger
* @dev The L2 Cross Domain Messenger contract sends messages from L2 to L1, and is the entry point
......@@ -21,8 +18,7 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuar
*
*/
contract L2CrossDomainMessenger is
IL2CrossDomainMessenger,
ReentrancyGuard
IL2CrossDomainMessenger
{
/*************
......@@ -36,18 +32,18 @@ contract L2CrossDomainMessenger is
address internal xDomainMsgSender = Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER;
address public l1CrossDomainMessenger;
/***************
* Constructor *
***************/
constructor(
address _l1CrossDomainMessenger
)
ReentrancyGuard()
{
) {
l1CrossDomainMessenger = _l1CrossDomainMessenger;
}
/********************
* Public Functions *
********************/
......@@ -85,9 +81,13 @@ contract L2CrossDomainMessenger is
sentMessages[keccak256(xDomainCalldata)] = true;
_sendXDomainMessage(xDomainCalldata, _gasLimit);
emit SentMessage(_target, msg.sender, _message, messageNonce, _gasLimit);
// Actually send the message.
iOVM_L2ToL1MessagePasser(
Lib_PredeployAddresses.L2_TO_L1_MESSAGE_PASSER
).passMessageToL1(xDomainCalldata);
// Emit an event before we bump the nonce or the nonce will be off by one.
emit SentMessage(_target, msg.sender, _message, messageNonce, _gasLimit);
messageNonce += 1;
}
......@@ -101,11 +101,10 @@ contract L2CrossDomainMessenger is
bytes memory _message,
uint256 _messageNonce
)
nonReentrant
public
{
require(
_verifyXDomainMessage() == true,
AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1CrossDomainMessenger,
"Provided message could not be verified."
);
......@@ -157,43 +156,4 @@ contract L2CrossDomainMessenger is
relayedMessages[relayId] = true;
}
/**********************
* Internal Functions *
**********************/
/**
* Verifies that a received cross domain message is valid.
* @return _valid Whether or not the message is valid.
*/
function _verifyXDomainMessage()
internal
view
returns (
bool _valid
)
{
return (
iOVM_L1MessageSender(
Lib_PredeployAddresses.L1_MESSAGE_SENDER
).getL1MessageSender() == l1CrossDomainMessenger
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* param _gasLimit Gas limit for the provided message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint256 // _gasLimit
)
internal
{
iOVM_L2ToL1MessagePasser(
Lib_PredeployAddresses.L2_TO_L1_MESSAGE_PASSER
).passMessageToL1(_message);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/**
* @title iOVM_L1MessageSender
*/
interface iOVM_L1MessageSender {
/********************
* Public Functions *
********************/
function getL1MessageSender() external view returns (address);
}
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright 2019-2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pragma solidity ^0.8.7;
library AddressAliasHelper {
uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);
/// @notice Utility function that converts the address in the L1 that submitted a tx to
/// the inbox to the msg.sender viewed in the L2
/// @param l1Address the address in the L1 that triggered the tx to L2
/// @return l2Address L2 address as viewed in msg.sender
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
unchecked {
l2Address = address(uint160(l1Address) + offset);
}
}
/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l2Address L2 address as viewed in msg.sender
/// @return l1Address the address in the L1 that triggered the tx to L2
function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
unchecked {
l1Address = address(uint160(l2Address) - offset);
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
/* Library Imports */
import { AddressAliasHelper } from "../../standards/AddressAliasHelper.sol";
/**
* @title TestLib_AddressAliasHelper
*/
contract TestLib_AddressAliasHelper {
function applyL1ToL2Alias(
address _address
)
public
pure
returns (
address
)
{
return AddressAliasHelper.applyL1ToL2Alias(_address);
}
function undoL1ToL2Alias(
address _address
)
public
pure
returns (
address
)
{
return AddressAliasHelper.undoL1ToL2Alias(_address);
}
}
......@@ -17,7 +17,6 @@ interface L2Contracts {
eth: Contract
xDomainMessenger: Contract
messagePasser: Contract
messageSender: Contract
deployerWhiteList: Contract
}
......@@ -90,10 +89,6 @@ export const connectL2Contracts = async (
eth: getEthersContract('OVM_ETH'),
xDomainMessenger: getEthersContract('L2CrossDomainMessenger'),
messagePasser: getEthersContract('OVM_L2ToL1MessagePasser'),
messageSender: getEthersContract(
'OVM_L1MessageSender',
'iOVM_L1MessageSender'
),
deployerWhiteList: getEthersContract('OVM_DeployerWhitelist'),
}
}
......@@ -67,7 +67,6 @@ export const getL1ContractData = (network: Network) => {
const OVM_ETH = require('../artifacts/contracts/L2/predeploys/OVM_ETH.sol/OVM_ETH.json')
const L2CrossDomainMessenger = require('../artifacts/contracts/L2/messaging/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json')
const OVM_L2ToL1MessagePasser = require('../artifacts/contracts/L2/predeploys/OVM_L2ToL1MessagePasser.sol/OVM_L2ToL1MessagePasser.json')
const OVM_L1MessageSender = require('../artifacts/contracts/L2/predeploys/iOVM_L1MessageSender.sol/iOVM_L1MessageSender.json')
const OVM_DeployerWhitelist = require('../artifacts/contracts/L2/predeploys/OVM_DeployerWhitelist.sol/OVM_DeployerWhitelist.json')
export const getL2ContractData = () => {
......@@ -84,10 +83,6 @@ export const getL2ContractData = () => {
abi: OVM_L2ToL1MessagePasser.abi,
address: l2Addresses.OVM_L2ToL1MessagePasser,
},
OVM_L1MessageSender: {
abi: OVM_L1MessageSender.abi,
address: l2Addresses.OVM_L1MessageSender,
},
OVM_DeployerWhitelist: {
abi: OVM_DeployerWhitelist.abi,
address: l2Addresses.OVM_DeployerWhitelist,
......
......@@ -79,7 +79,6 @@ export const makeL2GenesisFile = async (
_symbol: 'ETH',
},
L2CrossDomainMessenger: {
_status: 1,
l1CrossDomainMessenger: cfg.l1CrossDomainMessengerAddress,
},
}
......@@ -92,14 +91,11 @@ export const makeL2GenesisFile = async (
storage: {},
}
if (predeployName === 'OVM_L1MessageSender') {
// OVM_L1MessageSender is a special case where we just inject a specific bytecode string.
// We do this because it uses the custom L1MESSAGESENDER opcode (0x4A) which cannot be
// directly used in Solidity (yet). This bytecode string simply executes the 0x4A opcode
if (predeployName === 'OVM_L1BlockNumber') {
// OVM_L1BlockNumber is a special case where we just inject a specific bytecode string.
// We do this because it uses the custom L1BLOCKNUMBER opcode (0x4B) which cannot be
// directly used in Solidity (yet). This bytecode string simply executes the 0x4B opcode
// and returns the address given by that opcode.
dump[predeployAddress].code = '0x4A60005260206000F3'
} else if (predeployName === 'OVM_L1BlockNumber') {
// Same as above but for OVM_L1BlockNumber (0x4B).
dump[predeployAddress].code = '0x4B60005260206000F3'
} else {
const artifact = getContractArtifact(predeployName)
......
......@@ -9,7 +9,6 @@
*/
export const predeploys = {
OVM_L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000',
OVM_L1MessageSender: '0x4200000000000000000000000000000000000001',
OVM_DeployerWhitelist: '0x4200000000000000000000000000000000000002',
L2CrossDomainMessenger: '0x4200000000000000000000000000000000000007',
OVM_GasPriceOracle: '0x420000000000000000000000000000000000000F',
......
......@@ -4,7 +4,11 @@ import { expect } from '../../../setup'
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
import { remove0x, toHexString } from '@eth-optimism/core-utils'
import {
remove0x,
toHexString,
applyL1ToL2Alias,
} from '@eth-optimism/core-utils'
/* Internal Imports */
import {
......@@ -20,7 +24,6 @@ import {
getNextBlockNumber,
encodeXDomainCalldata,
} from '../../../helpers'
import { keccak256 } from 'ethers/lib/utils'
import { predeploys } from '../../../../src'
const MAX_GAS_LIMIT = 8_000_000
......@@ -157,26 +160,6 @@ describe('L1CrossDomainMessenger', () => {
})
})
const getTransactionHash = (
sender: string,
target: string,
gasLimit: number,
data: string
): string => {
return keccak256(encodeQueueTransaction(sender, target, gasLimit, data))
}
const encodeQueueTransaction = (
sender: string,
target: string,
gasLimit: number,
data: string
): string => {
return ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'bytes'],
[sender, target, gasLimit, data]
)
}
describe('sendMessage', () => {
const target = NON_ZERO_ADDRESS
const message = NON_NULL_BYTES32
......@@ -193,13 +176,17 @@ describe('L1CrossDomainMessenger', () => {
message,
0
)
const transactionHash = getTransactionHash(
L1CrossDomainMessenger.address,
Mock__L2CrossDomainMessenger.address,
gasLimit,
calldata
const transactionHash = ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'uint256', 'bytes'],
[
applyL1ToL2Alias(L1CrossDomainMessenger.address),
Mock__L2CrossDomainMessenger.address,
gasLimit,
calldata,
]
)
)
const queueLength = await CanonicalTransactionChain.getQueueLength()
const queueElement = await CanonicalTransactionChain.getQueueElement(
queueLength - 1
......@@ -274,9 +261,10 @@ describe('L1CrossDomainMessenger', () => {
messageNonce
)
const storageKey = keccak256(
keccak256(calldata + remove0x(Mock__L2CrossDomainMessenger.address)) +
'00'.repeat(32)
const storageKey = ethers.utils.keccak256(
ethers.utils.keccak256(
calldata + remove0x(Mock__L2CrossDomainMessenger.address)
) + '00'.repeat(32)
)
const storageGenerator = await TrieTestGenerator.fromNodes({
nodes: [
......@@ -294,7 +282,7 @@ describe('L1CrossDomainMessenger', () => {
address: predeploys.OVM_L2ToL1MessagePasser,
nonce: 0,
balance: 0,
codeHash: keccak256('0x1234'),
codeHash: ethers.utils.keccak256('0x1234'),
storageRoot: toHexString(storageGenerator._trie.root),
},
],
......@@ -436,12 +424,14 @@ describe('L1CrossDomainMessenger', () => {
)
expect(
await L1CrossDomainMessenger.successfulMessages(keccak256(calldata))
await L1CrossDomainMessenger.successfulMessages(
ethers.utils.keccak256(calldata)
)
).to.equal(true)
expect(
await L1CrossDomainMessenger.relayedMessages(
keccak256(
ethers.utils.keccak256(
calldata +
remove0x(await signer.getAddress()) +
remove0x(BigNumber.from(blockNumber).toHexString()).padStart(
......@@ -495,16 +485,18 @@ describe('L1CrossDomainMessenger', () => {
it('should revert if called by an account other than the owner', async () => {
const L1CrossDomainMessenger2 = L1CrossDomainMessenger.connect(signer2)
await expect(
L1CrossDomainMessenger2.blockMessage(keccak256(calldata))
L1CrossDomainMessenger2.blockMessage(ethers.utils.keccak256(calldata))
).to.be.revertedWith('Ownable: caller is not the owner')
await expect(
L1CrossDomainMessenger2.allowMessage(keccak256(calldata))
L1CrossDomainMessenger2.allowMessage(ethers.utils.keccak256(calldata))
).to.be.revertedWith('Ownable: caller is not the owner')
})
it('should revert if the message is blocked', async () => {
await L1CrossDomainMessenger.blockMessage(keccak256(calldata))
await L1CrossDomainMessenger.blockMessage(
ethers.utils.keccak256(calldata)
)
await expect(
L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof)
......@@ -512,13 +504,17 @@ describe('L1CrossDomainMessenger', () => {
})
it('should succeed if the message is blocked, then unblocked', async () => {
await L1CrossDomainMessenger.blockMessage(keccak256(calldata))
await L1CrossDomainMessenger.blockMessage(
ethers.utils.keccak256(calldata)
)
await expect(
L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof)
).to.be.revertedWith('Provided message has been blocked.')
await L1CrossDomainMessenger.allowMessage(keccak256(calldata))
await L1CrossDomainMessenger.allowMessage(
ethers.utils.keccak256(calldata)
)
await expect(
L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof)
......
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, constants } from 'ethers'
import hre, { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock'
import { solidityKeccak256 } from 'ethers/lib/utils'
import { applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Internal Imports */
import {
NON_NULL_BYTES32,
NON_ZERO_ADDRESS,
encodeXDomainCalldata,
getNextBlockNumber,
} from '../../../helpers'
import { getContractInterface, predeploys } from '../../../../src'
import { predeploys } from '../../../../src'
describe('L2CrossDomainMessenger', () => {
let signer: Signer
......@@ -23,7 +22,6 @@ describe('L2CrossDomainMessenger', () => {
let Mock__TargetContract: MockContract
let Mock__L1CrossDomainMessenger: MockContract
let Mock__OVM_L1MessageSender: MockContract
let Mock__OVM_L2ToL1MessagePasser: MockContract
before(async () => {
Mock__TargetContract = await smockit(
......@@ -32,16 +30,30 @@ describe('L2CrossDomainMessenger', () => {
Mock__L1CrossDomainMessenger = await smockit(
await ethers.getContractFactory('L1CrossDomainMessenger')
)
Mock__OVM_L1MessageSender = await smockit(
getContractInterface('iOVM_L1MessageSender'),
{ address: predeploys.OVM_L1MessageSender }
)
Mock__OVM_L2ToL1MessagePasser = await smockit(
await ethers.getContractFactory('OVM_L2ToL1MessagePasser'),
{ address: predeploys.OVM_L2ToL1MessagePasser }
)
})
let impersonatedL1CrossDomainMessengerSender: Signer
before(async () => {
const impersonatedAddress = applyL1ToL2Alias(
Mock__L1CrossDomainMessenger.address
)
await hre.network.provider.request({
method: 'hardhat_impersonateAccount',
params: [impersonatedAddress],
})
await hre.network.provider.request({
method: 'hardhat_setBalance',
params: [impersonatedAddress, '0xFFFFFFFFFFFFFFFFF'],
})
impersonatedL1CrossDomainMessengerSender = await ethers.getSigner(
impersonatedAddress
)
})
let Factory__L2CrossDomainMessenger: ContractFactory
before(async () => {
Factory__L2CrossDomainMessenger = await ethers.getContractFactory(
......@@ -94,24 +106,21 @@ describe('L2CrossDomainMessenger', () => {
sender = await signer.getAddress()
})
beforeEach(async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
Mock__L1CrossDomainMessenger.address
)
})
it('should revert if the L1 message sender is not the L1CrossDomainMessenger', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
constants.AddressZero
)
await expect(
L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
L2CrossDomainMessenger.connect(signer).relayMessage(
target,
sender,
message,
0
)
).to.be.revertedWith('Provided message could not be verified.')
})
it('should send a call to the target contract', async () => {
await L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
await L2CrossDomainMessenger.connect(
impersonatedL1CrossDomainMessengerSender
).relayMessage(target, sender, message, 0)
expect(Mock__TargetContract.smocked.setTarget.calls[0]).to.deep.equal([
NON_ZERO_ADDRESS,
......@@ -122,40 +131,38 @@ describe('L2CrossDomainMessenger', () => {
await expect(
L2CrossDomainMessenger.xDomainMessageSender()
).to.be.revertedWith('xDomainMessageSender is not set')
await L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
await L2CrossDomainMessenger.connect(
impersonatedL1CrossDomainMessengerSender
).relayMessage(target, sender, message, 0)
await expect(
L2CrossDomainMessenger.xDomainMessageSender()
).to.be.revertedWith('xDomainMessageSender is not set')
})
it('should revert if trying to send the same message twice', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
Mock__L1CrossDomainMessenger.address
)
await L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
await L2CrossDomainMessenger.connect(
impersonatedL1CrossDomainMessengerSender
).relayMessage(target, sender, message, 0)
await expect(
L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
L2CrossDomainMessenger.connect(
impersonatedL1CrossDomainMessengerSender
).relayMessage(target, sender, message, 0)
).to.be.revertedWith('Provided message has already been received.')
})
it('should not make a call if the target is the L2 MessagePasser', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
Mock__L1CrossDomainMessenger.address
)
target = predeploys.OVM_L2ToL1MessagePasser
message = Mock__OVM_L2ToL1MessagePasser.interface.encodeFunctionData(
'passMessageToL1(bytes)',
[NON_NULL_BYTES32]
)
const resProm = L2CrossDomainMessenger.relayMessage(
target,
sender,
message,
0
)
const resProm = L2CrossDomainMessenger.connect(
impersonatedL1CrossDomainMessengerSender
).relayMessage(target, sender, message, 0)
// The call to relayMessage() should succeed.
await expect(resProm).to.not.be.reverted
......@@ -173,71 +180,12 @@ describe('L2CrossDomainMessenger', () => {
// The message should be registered as successful.
expect(
await L2CrossDomainMessenger.successfulMessages(
solidityKeccak256(
ethers.utils.solidityKeccak256(
['bytes'],
[encodeXDomainCalldata(target, sender, message, 0)]
)
)
).to.be.true
})
it('should revert if trying to reenter `relayMessage`', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with(
Mock__L1CrossDomainMessenger.address
)
const reentrantMessage =
L2CrossDomainMessenger.interface.encodeFunctionData('relayMessage', [
target,
sender,
message,
1,
])
// Calculate xDomainCallData used for indexing
// (within the first call to the L2 Messenger).
const xDomainCallData = encodeXDomainCalldata(
L2CrossDomainMessenger.address,
sender,
reentrantMessage,
0
)
// Make the call.
await L2CrossDomainMessenger.relayMessage(
L2CrossDomainMessenger.address,
sender,
reentrantMessage,
0
)
// We can't test for the nonReentrant revert string because it occurs in the second call frame,
// and target.call() won't "bubble up" the revert. So we need to use other criteria to ensure the
// right things are happening.
// Criteria 1: the reentrant message is NOT listed in successful messages.
expect(
await L2CrossDomainMessenger.successfulMessages(
solidityKeccak256(['bytes'], [xDomainCallData])
)
).to.be.false
// Criteria 2: the relayID of the reentrant message is recorded.
// Get blockNumber at time of the call.
const blockNumber = (await getNextBlockNumber(ethers.provider)) - 1
const relayId = solidityKeccak256(
['bytes'],
[
ethers.utils.solidityPack(
['bytes', 'address', 'uint256'],
[xDomainCallData, sender, blockNumber]
),
]
)
expect(await L2CrossDomainMessenger.relayedMessages(relayId)).to.be.true
// Criteria 3: the target contract did not receive a call.
expect(Mock__TargetContract.smocked.setTarget.calls[0]).to.be.undefined
})
})
})
import { expect } from '../../../setup'
/* External Imports */
import { ethers } from 'hardhat'
import { Contract } from 'ethers'
import { applyL1ToL2Alias, undoL1ToL2Alias } from '@eth-optimism/core-utils'
describe('AddressAliasHelper', () => {
let AddressAliasHelper: Contract
before(async () => {
AddressAliasHelper = await (
await ethers.getContractFactory('TestLib_AddressAliasHelper')
).deploy()
})
describe('applyL1ToL2Alias', () => {
it('should be able to apply the alias to a valid address', async () => {
expect(
await AddressAliasHelper.applyL1ToL2Alias(
'0x0000000000000000000000000000000000000000'
)
).to.equal(applyL1ToL2Alias('0x0000000000000000000000000000000000000000'))
})
it('should be able to apply the alias even if the operation overflows', async () => {
expect(
await AddressAliasHelper.applyL1ToL2Alias(
'0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
)
).to.equal(applyL1ToL2Alias('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'))
})
})
describe('undoL1ToL2Alias', () => {
it('should be able to undo the alias from a valid address', async () => {
expect(
await AddressAliasHelper.undoL1ToL2Alias(
'0x1111000000000000000000000000000000001111'
)
).to.equal(undoL1ToL2Alias('0x1111000000000000000000000000000000001111'))
})
it('should be able to undo the alias even if the operation underflows', async () => {
expect(
await AddressAliasHelper.undoL1ToL2Alias(
'0x1111000000000000000000000000000000001110'
)
).to.equal(undoL1ToL2Alias('0x1111000000000000000000000000000000001110'))
})
})
})
import { ethers } from 'ethers'
export const L1_TO_L2_ALIAS_OFFSET =
'0x1111000000000000000000000000000000001111'
const bnToAddress = (bn: ethers.BigNumber): string => {
if (bn.isNegative()) {
bn = ethers.BigNumber.from('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
.add(bn)
.add(1)
}
const addr = bn.toHexString().slice(2).padStart(40, '0')
return ethers.utils.getAddress(
'0x' + addr.slice(addr.length - 40, addr.length)
)
}
export const applyL1ToL2Alias = (address: string): string => {
if (!ethers.utils.isAddress(address)) {
throw new Error(`not a valid address: ${address}`)
}
return bnToAddress(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET))
}
export const undoL1ToL2Alias = (address: string): string => {
if (!ethers.utils.isAddress(address)) {
throw new Error(`not a valid address: ${address}`)
}
return bnToAddress(ethers.BigNumber.from(address).sub(L1_TO_L2_ALIAS_OFFSET))
}
......@@ -7,3 +7,4 @@ export * from './batches'
export * from './bcfg'
export * from './fees'
export * from './provider'
export * from './alias'
import { expect } from './setup'
import { applyL1ToL2Alias, undoL1ToL2Alias } from '../src/alias'
describe('address aliasing utils', () => {
describe('applyL1ToL2Alias', () => {
it('should be able to apply the alias to a valid address', () => {
expect(
applyL1ToL2Alias('0x0000000000000000000000000000000000000000')
).to.equal('0x1111000000000000000000000000000000001111')
})
it('should be able to apply the alias even if the operation overflows', () => {
expect(
applyL1ToL2Alias('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
).to.equal('0x1111000000000000000000000000000000001110')
})
it('should throw if the input is not a valid address', () => {
expect(() => {
applyL1ToL2Alias('0x1234')
}).to.throw
})
})
describe('undoL1ToL2Alias', () => {
it('should be able to undo the alias from a valid address', () => {
expect(
undoL1ToL2Alias('0x1111000000000000000000000000000000001111')
).to.equal('0x0000000000000000000000000000000000000000')
})
it('should be able to undo the alias even if the operation underflows', () => {
expect(
undoL1ToL2Alias('0x1111000000000000000000000000000000001110')
).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF')
})
it('should throw if the input is not a valid address', () => {
expect(() => {
undoL1ToL2Alias('0x1234')
}).to.throw
})
})
})
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