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 { ...@@ -6,12 +6,14 @@ contract ICrossDomainMessenger {
contract SimpleStorage { contract SimpleStorage {
address public msgSender; address public msgSender;
address public txOrigin;
address public xDomainSender; address public xDomainSender;
bytes32 public value; bytes32 public value;
uint256 public totalCount; uint256 public totalCount;
function setValue(bytes32 newValue) public { function setValue(bytes32 newValue) public {
msgSender = msg.sender; msgSender = msg.sender;
txOrigin = tx.origin;
xDomainSender = ICrossDomainMessenger(msg.sender) xDomainSender = ICrossDomainMessenger(msg.sender)
.xDomainMessageSender(); .xDomainMessageSender();
value = newValue; value = newValue;
...@@ -20,6 +22,7 @@ contract SimpleStorage { ...@@ -20,6 +22,7 @@ contract SimpleStorage {
function setValueNotXDomain(bytes32 newValue) public { function setValueNotXDomain(bytes32 newValue) public {
msgSender = msg.sender; msgSender = msg.sender;
txOrigin = tx.origin;
value = newValue; value = newValue;
totalCount++; totalCount++;
} }
......
...@@ -2,7 +2,7 @@ import { expect } from 'chai' ...@@ -2,7 +2,7 @@ import { expect } from 'chai'
/* Imports: External */ /* Imports: External */
import { Contract, ContractFactory } from 'ethers' import { Contract, ContractFactory } from 'ethers'
import { predeploys, getContractInterface } from '@eth-optimism/contracts' import { applyL1ToL2Alias, sleep } from '@eth-optimism/core-utils'
/* Imports: Internal */ /* Imports: Internal */
import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json' import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'
...@@ -90,6 +90,9 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -90,6 +90,9 @@ describe('Basic L1<>L2 Communication', async () => {
expect(await L2SimpleStorage.msgSender()).to.equal( expect(await L2SimpleStorage.msgSender()).to.equal(
env.l2Messenger.address env.l2Messenger.address
) )
expect(await L2SimpleStorage.txOrigin()).to.equal(
applyL1ToL2Alias(env.l1Messenger.address)
)
expect(await L2SimpleStorage.xDomainSender()).to.equal( expect(await L2SimpleStorage.xDomainSender()).to.equal(
env.l1Wallet.address env.l1Wallet.address
) )
...@@ -97,6 +100,30 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -97,6 +100,30 @@ describe('Basic L1<>L2 Communication', async () => {
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1) 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 () => { it('should have a receipt with a status of 1 for a successful message', async () => {
const value = `0x${'42'.repeat(32)}` const value = `0x${'42'.repeat(32)}`
......
...@@ -2,7 +2,7 @@ import { expect } from 'chai' ...@@ -2,7 +2,7 @@ import { expect } from 'chai'
/* Imports: Internal */ /* Imports: Internal */
import { providers } from 'ethers' import { providers } from 'ethers'
import { injectL2Context } from '@eth-optimism/core-utils' import { injectL2Context, applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Imports: External */ /* Imports: External */
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
...@@ -57,7 +57,7 @@ describe('Queue Ingestion', () => { ...@@ -57,7 +57,7 @@ describe('Queue Ingestion', () => {
expect(params._target).to.equal('0x' + `${i}`.repeat(40)) expect(params._target).to.equal('0x' + `${i}`.repeat(40))
expect(l2Tx.queueOrigin).to.equal('l1') expect(l2Tx.queueOrigin).to.equal('l1')
expect(l2Tx.l1TxOrigin.toLowerCase()).to.equal( expect(l2Tx.l1TxOrigin.toLowerCase()).to.equal(
env.l1Messenger.address.toLowerCase() applyL1ToL2Alias(env.l1Messenger.address).toLowerCase()
) )
expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber) expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber)
} }
......
...@@ -603,10 +603,9 @@ func (m callmsg) Data() []byte { return m.CallMsg.Data } ...@@ -603,10 +603,9 @@ func (m callmsg) Data() []byte { return m.CallMsg.Data }
// UsingOVM // UsingOVM
// These getters return OVM specific fields // These getters return OVM specific fields
func (m callmsg) L1Timestamp() uint64 { return m.CallMsg.L1Timestamp } func (m callmsg) L1Timestamp() uint64 { return m.CallMsg.L1Timestamp }
func (m callmsg) L1BlockNumber() *big.Int { return m.CallMsg.L1BlockNumber } 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) QueueOrigin() types.QueueOrigin { return m.CallMsg.QueueOrigin }
// filterBackend implements filters.Backend to support filtering for logs without // filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account. // taking bloom-bits acceleration structures into account.
......
...@@ -27,10 +27,6 @@ import ( ...@@ -27,10 +27,6 @@ import (
"github.com/ethereum/go-ethereum/rollup/rcfg" "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 // ChainContext supports retrieving headers and consensus parameters from the
// current blockchain to be used during transaction processing. // current blockchain to be used during transaction processing.
type ChainContext interface { type ChainContext interface {
...@@ -52,28 +48,20 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author ...@@ -52,28 +48,20 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
} }
if rcfg.UsingOVM { if rcfg.UsingOVM {
// When using the OVM, we must: // When using the OVM, we must:
// (1) Attach the L1MessageSender context value and // - Set the BlockNumber to be the msg.L1BlockNumber
// (2) Set the BlockNumber to be the msg.L1BlockNumber // - Set the Time to be the msg.L1Timestamp
// (3) Set the Time to be the msg.L1Timestamp
var l1MessageSender common.Address
if msg.L1MessageSender() == nil {
l1MessageSender = DefaultL1MessageSender
} else {
l1MessageSender = *msg.L1MessageSender()
}
return vm.Context{ return vm.Context{
CanTransfer: CanTransfer, CanTransfer: CanTransfer,
Transfer: Transfer, Transfer: Transfer,
GetHash: GetHashFn(header, chain), GetHash: GetHashFn(header, chain),
Origin: msg.From(), Origin: msg.From(),
Coinbase: dump.OvmFeeWallet, // Coinbase is the fee vault. Coinbase: dump.OvmFeeWallet, // Coinbase is the fee vault.
BlockNumber: new(big.Int).Set(header.Number), BlockNumber: new(big.Int).Set(header.Number),
Time: new(big.Int).SetUint64(msg.L1Timestamp()), Time: new(big.Int).SetUint64(msg.L1Timestamp()),
Difficulty: new(big.Int), // Difficulty always returns zero. Difficulty: new(big.Int), // Difficulty always returns zero.
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
GasPrice: new(big.Int).Set(msg.GasPrice()), GasPrice: new(big.Int).Set(msg.GasPrice()),
L1MessageSender: l1MessageSender, L1BlockNumber: msg.L1BlockNumber(),
L1BlockNumber: msg.L1BlockNumber(),
} }
} else { } else {
return vm.Context{ return vm.Context{
......
...@@ -26,7 +26,6 @@ import ( ...@@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees" "github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rollup/rcfg"
) )
// StateProcessor is a basic Processor, which takes care of transitioning // 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 ...@@ -89,19 +88,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// indicating the block was invalid. // 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) { 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)) msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if rcfg.UsingOVM { if err != nil {
if err != nil { return nil, err
// 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
}
} }
// Create a new context to be used in the EVM environment // Create a new context to be used in the EVM environment
context := NewEVMContext(msg, header, bc, author) context := NewEVMContext(msg, header, bc, author)
......
...@@ -82,7 +82,6 @@ type Message interface { ...@@ -82,7 +82,6 @@ type Message interface {
L1Timestamp() uint64 L1Timestamp() uint64
L1BlockNumber() *big.Int L1BlockNumber() *big.Int
L1MessageSender() *common.Address
QueueOrigin() types.QueueOrigin QueueOrigin() types.QueueOrigin
} }
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "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 //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) { ...@@ -295,20 +296,20 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) {
data: tx.data.Payload, data: tx.data.Payload,
checkNonce: true, checkNonce: true,
l1Timestamp: tx.meta.L1Timestamp, l1Timestamp: tx.meta.L1Timestamp,
l1BlockNumber: tx.meta.L1BlockNumber, l1BlockNumber: tx.meta.L1BlockNumber,
l1MessageSender: tx.meta.L1MessageSender, queueOrigin: tx.meta.QueueOrigin,
queueOrigin: tx.meta.QueueOrigin,
} }
var err error var err error
msg.from, err = Sender(s, tx) if rcfg.UsingOVM {
if tx.meta.QueueOrigin == QueueOriginL1ToL2 && tx.meta.L1MessageSender != nil {
if tx.meta.L1MessageSender != nil { msg.from = *tx.meta.L1MessageSender
msg.l1MessageSender = tx.meta.L1MessageSender } else {
msg.from, err = Sender(s, tx)
}
} else { } else {
addr := common.Address{} msg.from, err = Sender(s, tx)
msg.l1MessageSender = &addr
} }
return msg, err return msg, err
...@@ -479,13 +480,12 @@ type Message struct { ...@@ -479,13 +480,12 @@ type Message struct {
data []byte data []byte
checkNonce bool checkNonce bool
l1Timestamp uint64 l1Timestamp uint64
l1BlockNumber *big.Int l1BlockNumber *big.Int
l1MessageSender *common.Address queueOrigin QueueOrigin
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{ return Message{
from: from, from: from,
to: to, to: to,
...@@ -496,10 +496,9 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b ...@@ -496,10 +496,9 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
data: data, data: data,
checkNonce: checkNonce, checkNonce: checkNonce,
l1Timestamp: l1Timestamp, l1Timestamp: l1Timestamp,
l1BlockNumber: l1BlockNumber, l1BlockNumber: l1BlockNumber,
l1MessageSender: l1MessageSender, queueOrigin: queueOrigin,
queueOrigin: queueOrigin,
} }
} }
...@@ -512,7 +511,6 @@ func (m Message) Nonce() uint64 { return m.nonce } ...@@ -512,7 +511,6 @@ func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data } func (m Message) Data() []byte { return m.data }
func (m Message) CheckNonce() bool { return m.checkNonce } func (m Message) CheckNonce() bool { return m.checkNonce }
func (m Message) L1Timestamp() uint64 { return m.l1Timestamp } func (m Message) L1Timestamp() uint64 { return m.l1Timestamp }
func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber } 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) QueueOrigin() QueueOrigin { return m.queueOrigin }
...@@ -98,8 +98,7 @@ type Context struct { ...@@ -98,8 +98,7 @@ type Context struct {
Difficulty *big.Int // Provides information for DIFFICULTY Difficulty *big.Int // Provides information for DIFFICULTY
// OVM information // 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 // EVM is the Ethereum Virtual Machine base object and provides
......
...@@ -969,11 +969,6 @@ func makeSwap(size int64) executionFunc { ...@@ -969,11 +969,6 @@ func makeSwap(size int64) executionFunc {
} }
// OVM opcodes // 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) { 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))) stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.L1BlockNumber)))
return nil, nil return nil, nil
......
...@@ -1155,13 +1155,6 @@ func newFrontierInstructionSet() JumpTable { ...@@ -1155,13 +1155,6 @@ func newFrontierInstructionSet() JumpTable {
valid: true, valid: true,
writes: true, writes: true,
}, },
L1MESSAGESENDER: {
execute: opL1MessageSender,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
valid: true,
},
L1BLOCKNUMBER: { L1BLOCKNUMBER: {
execute: opL1BlockNumber, execute: opL1BlockNumber,
constantGas: GasQuickStep, constantGas: GasQuickStep,
......
...@@ -104,8 +104,7 @@ const ( ...@@ -104,8 +104,7 @@ const (
CHAINID = 0x46 CHAINID = 0x46
SELFBALANCE = 0x47 SELFBALANCE = 0x47
L1MESSAGESENDER = 0x4A L1BLOCKNUMBER = 0x4B
L1BLOCKNUMBER = 0x4B
) )
// 0x50 range - 'storage' and execution. // 0x50 range - 'storage' and execution.
...@@ -286,9 +285,8 @@ var opCodeToString = map[OpCode]string{ ...@@ -286,9 +285,8 @@ var opCodeToString = map[OpCode]string{
SELFBALANCE: "SELFBALANCE", SELFBALANCE: "SELFBALANCE",
// OVM opcodes // OVM opcodes
// 0x4A // 0x4B
L1MESSAGESENDER: "L1MESSAGESENDER", L1BLOCKNUMBER: "L1BLOCKNUMBER",
L1BLOCKNUMBER: "L1BLOCKNUMBER",
// 0x50 range - 'storage' and execution. // 0x50 range - 'storage' and execution.
POP: "POP", POP: "POP",
...@@ -548,8 +546,7 @@ var stringToOp = map[string]OpCode{ ...@@ -548,8 +546,7 @@ var stringToOp = map[string]OpCode{
"SELFDESTRUCT": SELFDESTRUCT, "SELFDESTRUCT": SELFDESTRUCT,
// OVM opcodes // OVM opcodes
"L1MESSAGESENDER": L1MESSAGESENDER, "L1BLOCKNUMBER": L1BLOCKNUMBER,
"L1BLOCKNUMBER": L1BLOCKNUMBER,
} }
// StringToOp finds the opcode whose name is stored in `str`. // StringToOp finds the opcode whose name is stored in `str`.
......
...@@ -120,10 +120,9 @@ type CallMsg struct { ...@@ -120,10 +120,9 @@ type CallMsg struct {
Value *big.Int // amount of wei sent along with the call Value *big.Int // amount of wei sent along with the call
Data []byte // input data, usually an ABI-encoded contract method invocation Data []byte // input data, usually an ABI-encoded contract method invocation
L1Timestamp uint64 L1Timestamp uint64
L1BlockNumber *big.Int L1BlockNumber *big.Int
L1MessageSender *common.Address QueueOrigin types.QueueOrigin
QueueOrigin types.QueueOrigin
} }
// A ContractCaller provides contract calls, essentially transactions that are executed by // 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 ...@@ -883,7 +883,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
} }
// Create new call message // 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 // Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout. // or, in case of unmetered gas, setup a context with a timeout.
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
pragma solidity ^0.8.8; pragma solidity ^0.8.8;
/* Library Imports */ /* Library Imports */
import { AddressAliasHelper } from "../../standards/AddressAliasHelper.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressManager } from "../../libraries/resolver/Lib_AddressManager.sol"; import { Lib_AddressManager } from "../../libraries/resolver/Lib_AddressManager.sol";
...@@ -62,6 +63,7 @@ contract L1CrossDomainMessenger is ...@@ -62,6 +63,7 @@ contract L1CrossDomainMessenger is
address internal xDomainMsgSender = Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER; address internal xDomainMsgSender = Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER;
/*************** /***************
* Constructor * * Constructor *
***************/ ***************/
...@@ -75,6 +77,7 @@ contract L1CrossDomainMessenger is ...@@ -75,6 +77,7 @@ contract L1CrossDomainMessenger is
Lib_AddressResolver(address(0)) Lib_AddressResolver(address(0))
{} {}
/********************** /**********************
* Function Modifiers * * Function Modifiers *
**********************/ **********************/
...@@ -299,7 +302,7 @@ contract L1CrossDomainMessenger is ...@@ -299,7 +302,7 @@ contract L1CrossDomainMessenger is
// Compute the transactionHash // Compute the transactionHash
bytes32 transactionHash = keccak256( bytes32 transactionHash = keccak256(
abi.encode( abi.encode(
address(this), AddressAliasHelper.applyL1ToL2Alias(address(this)),
Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER, Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER,
_gasLimit, _gasLimit,
_message _message
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
pragma solidity ^0.8.8; pragma solidity ^0.8.8;
/* Library Imports */ /* Library Imports */
import { AddressAliasHelper } from "../../standards/AddressAliasHelper.sol";
import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol"; import { Lib_OVMCodec } from "../../libraries/codec/Lib_OVMCodec.sol";
import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol"; import { Lib_AddressResolver } from "../../libraries/resolver/Lib_AddressResolver.sol";
...@@ -279,9 +280,21 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes ...@@ -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( bytes32 transactionHash = keccak256(
abi.encode( abi.encode(
msg.sender, sender,
_target, _target,
_gasLimit, _gasLimit,
_data _data
...@@ -304,7 +317,7 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes ...@@ -304,7 +317,7 @@ contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressRes
// to divide by 2 and subtract 1. // to divide by 2 and subtract 1.
uint256 queueIndex = queueRef.length() / 2 - 1; uint256 queueIndex = queueRef.length() / 2 - 1;
emit TransactionEnqueued( emit TransactionEnqueued(
msg.sender, sender,
_target, _target,
_gasLimit, _gasLimit,
_data, _data,
......
...@@ -2,18 +2,15 @@ ...@@ -2,18 +2,15 @@
pragma solidity ^0.8.8; pragma solidity ^0.8.8;
/* Library Imports */ /* Library Imports */
import { AddressAliasHelper } from "../../standards/AddressAliasHelper.sol";
import { Lib_CrossDomainUtils } from "../../libraries/bridge/Lib_CrossDomainUtils.sol"; import { Lib_CrossDomainUtils } from "../../libraries/bridge/Lib_CrossDomainUtils.sol";
import { Lib_DefaultValues } from "../../libraries/constants/Lib_DefaultValues.sol"; import { Lib_DefaultValues } from "../../libraries/constants/Lib_DefaultValues.sol";
import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol"; import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";
/* Interface Imports */ /* Interface Imports */
import { IL2CrossDomainMessenger } from "./IL2CrossDomainMessenger.sol"; import { IL2CrossDomainMessenger } from "./IL2CrossDomainMessenger.sol";
import { iOVM_L1MessageSender } from "../predeploys/iOVM_L1MessageSender.sol";
import { iOVM_L2ToL1MessagePasser } from "../predeploys/iOVM_L2ToL1MessagePasser.sol"; import { iOVM_L2ToL1MessagePasser } from "../predeploys/iOVM_L2ToL1MessagePasser.sol";
/* External Imports */
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/** /**
* @title L2CrossDomainMessenger * @title L2CrossDomainMessenger
* @dev The L2 Cross Domain Messenger contract sends messages from L2 to L1, and is the entry point * @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 ...@@ -21,8 +18,7 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuar
* *
*/ */
contract L2CrossDomainMessenger is contract L2CrossDomainMessenger is
IL2CrossDomainMessenger, IL2CrossDomainMessenger
ReentrancyGuard
{ {
/************* /*************
...@@ -36,18 +32,18 @@ contract L2CrossDomainMessenger is ...@@ -36,18 +32,18 @@ contract L2CrossDomainMessenger is
address internal xDomainMsgSender = Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER; address internal xDomainMsgSender = Lib_DefaultValues.DEFAULT_XDOMAIN_SENDER;
address public l1CrossDomainMessenger; address public l1CrossDomainMessenger;
/*************** /***************
* Constructor * * Constructor *
***************/ ***************/
constructor( constructor(
address _l1CrossDomainMessenger address _l1CrossDomainMessenger
) ) {
ReentrancyGuard()
{
l1CrossDomainMessenger = _l1CrossDomainMessenger; l1CrossDomainMessenger = _l1CrossDomainMessenger;
} }
/******************** /********************
* Public Functions * * Public Functions *
********************/ ********************/
...@@ -85,9 +81,13 @@ contract L2CrossDomainMessenger is ...@@ -85,9 +81,13 @@ contract L2CrossDomainMessenger is
sentMessages[keccak256(xDomainCalldata)] = true; sentMessages[keccak256(xDomainCalldata)] = true;
_sendXDomainMessage(xDomainCalldata, _gasLimit); // Actually send the message.
emit SentMessage(_target, msg.sender, _message, messageNonce, _gasLimit); 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; messageNonce += 1;
} }
...@@ -101,11 +101,10 @@ contract L2CrossDomainMessenger is ...@@ -101,11 +101,10 @@ contract L2CrossDomainMessenger is
bytes memory _message, bytes memory _message,
uint256 _messageNonce uint256 _messageNonce
) )
nonReentrant
public public
{ {
require( require(
_verifyXDomainMessage() == true, AddressAliasHelper.undoL1ToL2Alias(msg.sender) == l1CrossDomainMessenger,
"Provided message could not be verified." "Provided message could not be verified."
); );
...@@ -157,43 +156,4 @@ contract L2CrossDomainMessenger is ...@@ -157,43 +156,4 @@ contract L2CrossDomainMessenger is
relayedMessages[relayId] = true; 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 { ...@@ -17,7 +17,6 @@ interface L2Contracts {
eth: Contract eth: Contract
xDomainMessenger: Contract xDomainMessenger: Contract
messagePasser: Contract messagePasser: Contract
messageSender: Contract
deployerWhiteList: Contract deployerWhiteList: Contract
} }
...@@ -90,10 +89,6 @@ export const connectL2Contracts = async ( ...@@ -90,10 +89,6 @@ export const connectL2Contracts = async (
eth: getEthersContract('OVM_ETH'), eth: getEthersContract('OVM_ETH'),
xDomainMessenger: getEthersContract('L2CrossDomainMessenger'), xDomainMessenger: getEthersContract('L2CrossDomainMessenger'),
messagePasser: getEthersContract('OVM_L2ToL1MessagePasser'), messagePasser: getEthersContract('OVM_L2ToL1MessagePasser'),
messageSender: getEthersContract(
'OVM_L1MessageSender',
'iOVM_L1MessageSender'
),
deployerWhiteList: getEthersContract('OVM_DeployerWhitelist'), deployerWhiteList: getEthersContract('OVM_DeployerWhitelist'),
} }
} }
...@@ -67,7 +67,6 @@ export const getL1ContractData = (network: Network) => { ...@@ -67,7 +67,6 @@ export const getL1ContractData = (network: Network) => {
const OVM_ETH = require('../artifacts/contracts/L2/predeploys/OVM_ETH.sol/OVM_ETH.json') 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 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_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') const OVM_DeployerWhitelist = require('../artifacts/contracts/L2/predeploys/OVM_DeployerWhitelist.sol/OVM_DeployerWhitelist.json')
export const getL2ContractData = () => { export const getL2ContractData = () => {
...@@ -84,10 +83,6 @@ export const getL2ContractData = () => { ...@@ -84,10 +83,6 @@ export const getL2ContractData = () => {
abi: OVM_L2ToL1MessagePasser.abi, abi: OVM_L2ToL1MessagePasser.abi,
address: l2Addresses.OVM_L2ToL1MessagePasser, address: l2Addresses.OVM_L2ToL1MessagePasser,
}, },
OVM_L1MessageSender: {
abi: OVM_L1MessageSender.abi,
address: l2Addresses.OVM_L1MessageSender,
},
OVM_DeployerWhitelist: { OVM_DeployerWhitelist: {
abi: OVM_DeployerWhitelist.abi, abi: OVM_DeployerWhitelist.abi,
address: l2Addresses.OVM_DeployerWhitelist, address: l2Addresses.OVM_DeployerWhitelist,
......
...@@ -79,7 +79,6 @@ export const makeL2GenesisFile = async ( ...@@ -79,7 +79,6 @@ export const makeL2GenesisFile = async (
_symbol: 'ETH', _symbol: 'ETH',
}, },
L2CrossDomainMessenger: { L2CrossDomainMessenger: {
_status: 1,
l1CrossDomainMessenger: cfg.l1CrossDomainMessengerAddress, l1CrossDomainMessenger: cfg.l1CrossDomainMessengerAddress,
}, },
} }
...@@ -92,14 +91,11 @@ export const makeL2GenesisFile = async ( ...@@ -92,14 +91,11 @@ export const makeL2GenesisFile = async (
storage: {}, storage: {},
} }
if (predeployName === 'OVM_L1MessageSender') { if (predeployName === 'OVM_L1BlockNumber') {
// OVM_L1MessageSender is a special case where we just inject a specific bytecode string. // OVM_L1BlockNumber 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 // 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 0x4A opcode // directly used in Solidity (yet). This bytecode string simply executes the 0x4B opcode
// and returns the address given by that 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' dump[predeployAddress].code = '0x4B60005260206000F3'
} else { } else {
const artifact = getContractArtifact(predeployName) const artifact = getContractArtifact(predeployName)
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
*/ */
export const predeploys = { export const predeploys = {
OVM_L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000', OVM_L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000',
OVM_L1MessageSender: '0x4200000000000000000000000000000000000001',
OVM_DeployerWhitelist: '0x4200000000000000000000000000000000000002', OVM_DeployerWhitelist: '0x4200000000000000000000000000000000000002',
L2CrossDomainMessenger: '0x4200000000000000000000000000000000000007', L2CrossDomainMessenger: '0x4200000000000000000000000000000000000007',
OVM_GasPriceOracle: '0x420000000000000000000000000000000000000F', OVM_GasPriceOracle: '0x420000000000000000000000000000000000000F',
......
...@@ -4,7 +4,11 @@ import { expect } from '../../../setup' ...@@ -4,7 +4,11 @@ import { expect } from '../../../setup'
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, BigNumber } from 'ethers' import { Signer, ContractFactory, Contract, BigNumber } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock' 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 */ /* Internal Imports */
import { import {
...@@ -20,7 +24,6 @@ import { ...@@ -20,7 +24,6 @@ import {
getNextBlockNumber, getNextBlockNumber,
encodeXDomainCalldata, encodeXDomainCalldata,
} from '../../../helpers' } from '../../../helpers'
import { keccak256 } from 'ethers/lib/utils'
import { predeploys } from '../../../../src' import { predeploys } from '../../../../src'
const MAX_GAS_LIMIT = 8_000_000 const MAX_GAS_LIMIT = 8_000_000
...@@ -157,26 +160,6 @@ describe('L1CrossDomainMessenger', () => { ...@@ -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', () => { describe('sendMessage', () => {
const target = NON_ZERO_ADDRESS const target = NON_ZERO_ADDRESS
const message = NON_NULL_BYTES32 const message = NON_NULL_BYTES32
...@@ -193,13 +176,17 @@ describe('L1CrossDomainMessenger', () => { ...@@ -193,13 +176,17 @@ describe('L1CrossDomainMessenger', () => {
message, message,
0 0
) )
const transactionHash = getTransactionHash( const transactionHash = ethers.utils.keccak256(
L1CrossDomainMessenger.address, ethers.utils.defaultAbiCoder.encode(
Mock__L2CrossDomainMessenger.address, ['address', 'address', 'uint256', 'bytes'],
gasLimit, [
calldata applyL1ToL2Alias(L1CrossDomainMessenger.address),
Mock__L2CrossDomainMessenger.address,
gasLimit,
calldata,
]
)
) )
const queueLength = await CanonicalTransactionChain.getQueueLength() const queueLength = await CanonicalTransactionChain.getQueueLength()
const queueElement = await CanonicalTransactionChain.getQueueElement( const queueElement = await CanonicalTransactionChain.getQueueElement(
queueLength - 1 queueLength - 1
...@@ -274,9 +261,10 @@ describe('L1CrossDomainMessenger', () => { ...@@ -274,9 +261,10 @@ describe('L1CrossDomainMessenger', () => {
messageNonce messageNonce
) )
const storageKey = keccak256( const storageKey = ethers.utils.keccak256(
keccak256(calldata + remove0x(Mock__L2CrossDomainMessenger.address)) + ethers.utils.keccak256(
'00'.repeat(32) calldata + remove0x(Mock__L2CrossDomainMessenger.address)
) + '00'.repeat(32)
) )
const storageGenerator = await TrieTestGenerator.fromNodes({ const storageGenerator = await TrieTestGenerator.fromNodes({
nodes: [ nodes: [
...@@ -294,7 +282,7 @@ describe('L1CrossDomainMessenger', () => { ...@@ -294,7 +282,7 @@ describe('L1CrossDomainMessenger', () => {
address: predeploys.OVM_L2ToL1MessagePasser, address: predeploys.OVM_L2ToL1MessagePasser,
nonce: 0, nonce: 0,
balance: 0, balance: 0,
codeHash: keccak256('0x1234'), codeHash: ethers.utils.keccak256('0x1234'),
storageRoot: toHexString(storageGenerator._trie.root), storageRoot: toHexString(storageGenerator._trie.root),
}, },
], ],
...@@ -436,12 +424,14 @@ describe('L1CrossDomainMessenger', () => { ...@@ -436,12 +424,14 @@ describe('L1CrossDomainMessenger', () => {
) )
expect( expect(
await L1CrossDomainMessenger.successfulMessages(keccak256(calldata)) await L1CrossDomainMessenger.successfulMessages(
ethers.utils.keccak256(calldata)
)
).to.equal(true) ).to.equal(true)
expect( expect(
await L1CrossDomainMessenger.relayedMessages( await L1CrossDomainMessenger.relayedMessages(
keccak256( ethers.utils.keccak256(
calldata + calldata +
remove0x(await signer.getAddress()) + remove0x(await signer.getAddress()) +
remove0x(BigNumber.from(blockNumber).toHexString()).padStart( remove0x(BigNumber.from(blockNumber).toHexString()).padStart(
...@@ -495,16 +485,18 @@ describe('L1CrossDomainMessenger', () => { ...@@ -495,16 +485,18 @@ describe('L1CrossDomainMessenger', () => {
it('should revert if called by an account other than the owner', async () => { it('should revert if called by an account other than the owner', async () => {
const L1CrossDomainMessenger2 = L1CrossDomainMessenger.connect(signer2) const L1CrossDomainMessenger2 = L1CrossDomainMessenger.connect(signer2)
await expect( await expect(
L1CrossDomainMessenger2.blockMessage(keccak256(calldata)) L1CrossDomainMessenger2.blockMessage(ethers.utils.keccak256(calldata))
).to.be.revertedWith('Ownable: caller is not the owner') ).to.be.revertedWith('Ownable: caller is not the owner')
await expect( await expect(
L1CrossDomainMessenger2.allowMessage(keccak256(calldata)) L1CrossDomainMessenger2.allowMessage(ethers.utils.keccak256(calldata))
).to.be.revertedWith('Ownable: caller is not the owner') ).to.be.revertedWith('Ownable: caller is not the owner')
}) })
it('should revert if the message is blocked', async () => { it('should revert if the message is blocked', async () => {
await L1CrossDomainMessenger.blockMessage(keccak256(calldata)) await L1CrossDomainMessenger.blockMessage(
ethers.utils.keccak256(calldata)
)
await expect( await expect(
L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof) L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof)
...@@ -512,13 +504,17 @@ describe('L1CrossDomainMessenger', () => { ...@@ -512,13 +504,17 @@ describe('L1CrossDomainMessenger', () => {
}) })
it('should succeed if the message is blocked, then unblocked', async () => { 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( await expect(
L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof) L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof)
).to.be.revertedWith('Provided message has been blocked.') ).to.be.revertedWith('Provided message has been blocked.')
await L1CrossDomainMessenger.allowMessage(keccak256(calldata)) await L1CrossDomainMessenger.allowMessage(
ethers.utils.keccak256(calldata)
)
await expect( await expect(
L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof) L1CrossDomainMessenger.relayMessage(target, sender, message, 0, proof)
......
import { expect } from '../../../setup' import { expect } from '../../../setup'
/* External Imports */ /* External Imports */
import { ethers } from 'hardhat' import hre, { ethers } from 'hardhat'
import { Signer, ContractFactory, Contract, constants } from 'ethers' import { Signer, ContractFactory, Contract } from 'ethers'
import { smockit, MockContract } from '@eth-optimism/smock' import { smockit, MockContract } from '@eth-optimism/smock'
import { solidityKeccak256 } from 'ethers/lib/utils' import { applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Internal Imports */ /* Internal Imports */
import { import {
NON_NULL_BYTES32, NON_NULL_BYTES32,
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
encodeXDomainCalldata, encodeXDomainCalldata,
getNextBlockNumber,
} from '../../../helpers' } from '../../../helpers'
import { getContractInterface, predeploys } from '../../../../src' import { predeploys } from '../../../../src'
describe('L2CrossDomainMessenger', () => { describe('L2CrossDomainMessenger', () => {
let signer: Signer let signer: Signer
...@@ -23,7 +22,6 @@ describe('L2CrossDomainMessenger', () => { ...@@ -23,7 +22,6 @@ describe('L2CrossDomainMessenger', () => {
let Mock__TargetContract: MockContract let Mock__TargetContract: MockContract
let Mock__L1CrossDomainMessenger: MockContract let Mock__L1CrossDomainMessenger: MockContract
let Mock__OVM_L1MessageSender: MockContract
let Mock__OVM_L2ToL1MessagePasser: MockContract let Mock__OVM_L2ToL1MessagePasser: MockContract
before(async () => { before(async () => {
Mock__TargetContract = await smockit( Mock__TargetContract = await smockit(
...@@ -32,16 +30,30 @@ describe('L2CrossDomainMessenger', () => { ...@@ -32,16 +30,30 @@ describe('L2CrossDomainMessenger', () => {
Mock__L1CrossDomainMessenger = await smockit( Mock__L1CrossDomainMessenger = await smockit(
await ethers.getContractFactory('L1CrossDomainMessenger') await ethers.getContractFactory('L1CrossDomainMessenger')
) )
Mock__OVM_L1MessageSender = await smockit(
getContractInterface('iOVM_L1MessageSender'),
{ address: predeploys.OVM_L1MessageSender }
)
Mock__OVM_L2ToL1MessagePasser = await smockit( Mock__OVM_L2ToL1MessagePasser = await smockit(
await ethers.getContractFactory('OVM_L2ToL1MessagePasser'), await ethers.getContractFactory('OVM_L2ToL1MessagePasser'),
{ address: predeploys.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 let Factory__L2CrossDomainMessenger: ContractFactory
before(async () => { before(async () => {
Factory__L2CrossDomainMessenger = await ethers.getContractFactory( Factory__L2CrossDomainMessenger = await ethers.getContractFactory(
...@@ -94,24 +106,21 @@ describe('L2CrossDomainMessenger', () => { ...@@ -94,24 +106,21 @@ describe('L2CrossDomainMessenger', () => {
sender = await signer.getAddress() 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 () => { 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( 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.') ).to.be.revertedWith('Provided message could not be verified.')
}) })
it('should send a call to the target contract', async () => { 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([ expect(Mock__TargetContract.smocked.setTarget.calls[0]).to.deep.equal([
NON_ZERO_ADDRESS, NON_ZERO_ADDRESS,
...@@ -122,40 +131,38 @@ describe('L2CrossDomainMessenger', () => { ...@@ -122,40 +131,38 @@ describe('L2CrossDomainMessenger', () => {
await expect( await expect(
L2CrossDomainMessenger.xDomainMessageSender() L2CrossDomainMessenger.xDomainMessageSender()
).to.be.revertedWith('xDomainMessageSender is not set') ).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( await expect(
L2CrossDomainMessenger.xDomainMessageSender() L2CrossDomainMessenger.xDomainMessageSender()
).to.be.revertedWith('xDomainMessageSender is not set') ).to.be.revertedWith('xDomainMessageSender is not set')
}) })
it('should revert if trying to send the same message twice', async () => { it('should revert if trying to send the same message twice', async () => {
Mock__OVM_L1MessageSender.smocked.getL1MessageSender.will.return.with( await L2CrossDomainMessenger.connect(
Mock__L1CrossDomainMessenger.address impersonatedL1CrossDomainMessengerSender
) ).relayMessage(target, sender, message, 0)
await L2CrossDomainMessenger.relayMessage(target, sender, message, 0)
await expect( 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.') ).to.be.revertedWith('Provided message has already been received.')
}) })
it('should not make a call if the target is the L2 MessagePasser', async () => { 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 target = predeploys.OVM_L2ToL1MessagePasser
message = Mock__OVM_L2ToL1MessagePasser.interface.encodeFunctionData( message = Mock__OVM_L2ToL1MessagePasser.interface.encodeFunctionData(
'passMessageToL1(bytes)', 'passMessageToL1(bytes)',
[NON_NULL_BYTES32] [NON_NULL_BYTES32]
) )
const resProm = L2CrossDomainMessenger.relayMessage( const resProm = L2CrossDomainMessenger.connect(
target, impersonatedL1CrossDomainMessengerSender
sender, ).relayMessage(target, sender, message, 0)
message,
0
)
// The call to relayMessage() should succeed. // The call to relayMessage() should succeed.
await expect(resProm).to.not.be.reverted await expect(resProm).to.not.be.reverted
...@@ -173,71 +180,12 @@ describe('L2CrossDomainMessenger', () => { ...@@ -173,71 +180,12 @@ describe('L2CrossDomainMessenger', () => {
// The message should be registered as successful. // The message should be registered as successful.
expect( expect(
await L2CrossDomainMessenger.successfulMessages( await L2CrossDomainMessenger.successfulMessages(
solidityKeccak256( ethers.utils.solidityKeccak256(
['bytes'], ['bytes'],
[encodeXDomainCalldata(target, sender, message, 0)] [encodeXDomainCalldata(target, sender, message, 0)]
) )
) )
).to.be.true ).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' ...@@ -7,3 +7,4 @@ export * from './batches'
export * from './bcfg' export * from './bcfg'
export * from './fees' export * from './fees'
export * from './provider' 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