Skip to content

Instantly share code, notes, and snippets.

@karlfloersch
Created October 7, 2020 23:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlfloersch/4278f18636cf0bd4111176e46cfcf2c0 to your computer and use it in GitHub Desktop.
Save karlfloersch/4278f18636cf0bd4111176e46cfcf2c0 to your computer and use it in GitHub Desktop.
pragma solidity ^0.5.0;
/* Interface Imports */
import { ICrossDomainMessenger } from "./interfaces/CrossDomainMessenger.interface.sol";
/**
* @title BaseCrossDomainMessenger
*/
contract BaseCrossDomainMessenger is ICrossDomainMessenger {
event SentMessage(bytes32 msgHash);
/*
* Contract Variables
*/
mapping (bytes32 => bool) public receivedMessages;
mapping (bytes32 => bool) public sentMessages;
address public targetMessengerAddress;
uint256 public messageNonce;
address public xDomainMessageSender;
/*
* Public Functions
*/
/**
* Sets the target messenger address.
* @param _targetMessengerAddress New messenger address.
*/
function setTargetMessengerAddress(
address _targetMessengerAddress
)
public
{
targetMessengerAddress = _targetMessengerAddress;
}
/**
* Sends a cross domain message to the target messenger.
* .inheritdoc IL2CrossDomainMessenger
*/
function sendMessage(
address _target,
bytes memory _message,
uint32 _gasLimit
)
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
msg.sender,
_message,
messageNonce
);
_sendXDomainMessage(xDomainCalldata, _gasLimit);
messageNonce += 1;
bytes32 msgHash = keccak256(xDomainCalldata);
sentMessages[msgHash] = true;
emit SentMessage(msgHash);
}
/*
* Internal Functions
*/
/**
* Generates the correct cross domain calldata for a message.
* @param _target Target contract address.
* @param _sender Message sender address.
* @param _message Message to send to the target.
* @param _messageNonce Nonce for the provided message.
* @return ABI encoded cross domain calldata.
*/
function _getXDomainCalldata(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
)
internal
pure
returns (
bytes memory
)
{
return abi.encodeWithSelector(
bytes4(keccak256(bytes("relayMessage(address,address,bytes,uint256)"))),
_target,
_sender,
_message,
_messageNonce
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit OVM gas limit for the message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint32 _gasLimit
) internal;
}
pragma solidity ^0.5.0;
/* Interface Imports */
import { ICrossDomainMessenger } from "./interfaces/CrossDomainMessenger.interface.sol";
/**
* @title BaseCrossDomainMessenger
*/
contract BaseCrossDomainMessenger is ICrossDomainMessenger {
event SentMessage(bytes32 msgHash);
/*
* Contract Variables
*/
mapping (bytes32 => bool) public receivedMessages;
mapping (bytes32 => bool) public sentMessages;
address public targetMessengerAddress;
uint256 public messageNonce;
address public xDomainMessageSender;
/*
* Public Functions
*/
/**
* Sets the target messenger address.
* @param _targetMessengerAddress New messenger address.
*/
function setTargetMessengerAddress(
address _targetMessengerAddress
)
public
{
targetMessengerAddress = _targetMessengerAddress;
}
/**
* Sends a cross domain message to the target messenger.
* .inheritdoc IL2CrossDomainMessenger
*/
function sendMessage(
address _target,
bytes memory _message,
uint32 _gasLimit
)
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
msg.sender,
_message,
messageNonce
);
_sendXDomainMessage(xDomainCalldata, _gasLimit);
messageNonce += 1;
bytes32 msgHash = keccak256(xDomainCalldata);
sentMessages[msgHash] = true;
emit SentMessage(msgHash);
}
/*
* Internal Functions
*/
/**
* Generates the correct cross domain calldata for a message.
* @param _target Target contract address.
* @param _sender Message sender address.
* @param _message Message to send to the target.
* @param _messageNonce Nonce for the provided message.
* @return ABI encoded cross domain calldata.
*/
function _getXDomainCalldata(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
)
internal
pure
returns (
bytes memory
)
{
return abi.encodeWithSelector(
bytes4(keccak256(bytes("relayMessage(address,address,bytes,uint256)"))),
_target,
_sender,
_message,
_messageNonce
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit OVM gas limit for the message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint32 _gasLimit
) internal;
}
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { ContractResolver } from "../utils/resolvers/ContractResolver.sol";
import { EthMerkleTrie } from "../utils/libraries/EthMerkleTrie.sol";
import { BytesLib } from "../utils/libraries/BytesLib.sol";
import { DataTypes } from "../utils/libraries/DataTypes.sol";
/* Contract Imports */
import { BaseCrossDomainMessenger } from "./BaseCrossDomainMessenger.sol";
import { L1ToL2TransactionQueue } from "../queue/L1ToL2TransactionQueue.sol";
import { StateCommitmentChain } from "../chain/StateCommitmentChain.sol";
/**
* @title L1CrossDomainMessenger
*/
contract L1CrossDomainMessenger is BaseCrossDomainMessenger, ContractResolver {
event RelayedL2ToL1Message(bytes32 msgHash);
/*
* Data Structures
*/
struct L2MessageInclusionProof {
bytes32 stateRoot;
uint256 stateRootIndex;
DataTypes.StateElementInclusionProof stateRootProof;
bytes stateTrieWitness;
bytes storageTrieWitness;
}
/*
* Constructor
*/
/**
* @param _addressResolver Address of the AddressResolver contract.
*/
constructor(
address _addressResolver
)
public
ContractResolver(_addressResolver)
{}
/*
* Public Functions
*/
/**
* Relays a cross domain message to a contract.
* .inheritdoc IL1CrossDomainMessenger
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
L2MessageInclusionProof memory _proof
)
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
bytes32 msgHash = keccak256(xDomainCalldata);
require(
_verifyXDomainMessage(
xDomainCalldata,
_proof
) == true,
"Provided message could not be verified."
);
require(
receivedMessages[msgHash] == false,
"Provided message has already been received."
);
xDomainMessageSender = _sender;
_target.call(_message);
// Messages are considered successfully executed if they complete
// without running out of gas (revert or not). As a result, we can
// ignore the result of the call and always mark the message as
// successfully executed because we won't get here unless we have
// enough gas left over.
receivedMessages[msgHash] = true;
emit RelayedL2ToL1Message(msgHash);
}
/**
* Replays a cross domain message to the target messenger.
* .inheritdoc IL1CrossDomainMessenger
*/
function replayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
uint32 _gasLimit
)
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
require(
sentMessages[keccak256(xDomainCalldata)] == true,
"Provided message has not already been sent."
);
_sendXDomainMessage(xDomainCalldata, _gasLimit);
}
/*
* Internal Functions
*/
/**
* Verifies that the given message is valid.
* @param _xDomainCalldata Calldata to verify.
* @param _proof Inclusion proof for the message.
* @return Whether or not the provided message is valid.
*/
function _verifyXDomainMessage(
bytes memory _xDomainCalldata,
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
return (
_verifyStateRootProof(_proof) && _verifyStorageProof(_xDomainCalldata, _proof)
);
}
/**
* Verifies that the state root within an inclusion proof is valid.
* @param _proof Message inclusion proof.
* @return Whether or not the provided proof is valid.
*/
function _verifyStateRootProof(
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
// TODO: We *must* verify that the batch timestamp is sufficiently old.
// However, this requires that we first add timestamps to state batches
// and account for that change in various tests. Change of that size is
// out of scope for this ticket, so "TODO" for now.
StateCommitmentChain stateCommitmentChain = resolveStateCommitmentChain();
return stateCommitmentChain.verifyElement(
abi.encodePacked(_proof.stateRoot),
_proof.stateRootIndex,
_proof.stateRootProof
);
}
/**
* Verifies that the storage proof within an inclusion proof is valid.
* @param _xDomainCalldata Encoded message calldata.
* @param _proof Message inclusion proof.
* @return Whether or not the provided proof is valid.
*/
function _verifyStorageProof(
bytes memory _xDomainCalldata,
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
bytes32 storageKey = keccak256(
BytesLib.concat(
abi.encodePacked(keccak256(_xDomainCalldata)),
abi.encodePacked(uint256(0))
)
);
return EthMerkleTrie.proveAccountStorageSlotValue(
0x4200000000000000000000000000000000000000,
storageKey,
bytes32(uint256(1)),
_proof.stateTrieWitness,
_proof.storageTrieWitness,
_proof.stateRoot
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit OVM gas limit for the message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint32 _gasLimit
)
internal
{
L1ToL2TransactionQueue l1ToL2TransactionQueue = resolveL1ToL2TransactionQueue();
l1ToL2TransactionQueue.enqueueL1ToL2Message(
targetMessengerAddress,
_gasLimit,
_message
);
}
/*
* Contract Resolution
*/
function resolveL1ToL2TransactionQueue()
internal
view
returns (L1ToL2TransactionQueue)
{
return L1ToL2TransactionQueue(resolveContract("L1ToL2TransactionQueue"));
}
function resolveStateCommitmentChain()
internal
view
returns (StateCommitmentChain)
{
return StateCommitmentChain(resolveContract("StateCommitmentChain"));
}
}
pragma solidity ^0.5.0;
/* Interface Imports */
import { IL1MessageSender } from "../ovm/precompiles/L1MessageSender.interface.sol";
import { IL2ToL1MessagePasser } from "../ovm/precompiles/L2ToL1MessagePasser.interface.sol";
/* Contract Imports */
import { BaseCrossDomainMessenger } from "./BaseCrossDomainMessenger.sol";
/**
* @title L2CrossDomainMessenger
*/
contract L2CrossDomainMessenger is BaseCrossDomainMessenger {
event RelayedL1ToL2Message(bytes32 msgHash);
/*
* Contract Variables
*/
address private l1MessageSenderPrecompileAddress;
address private l2ToL1MessagePasserPrecompileAddress;
/*
* Constructor
*/
/**
* @param _l1MessageSenderPrecompileAddress L1MessageSender address.
* @param _l2ToL1MessagePasserPrecompileAddress L2ToL1MessagePasser address.
*/
constructor(
address _l1MessageSenderPrecompileAddress,
address _l2ToL1MessagePasserPrecompileAddress
)
public
{
l1MessageSenderPrecompileAddress = _l1MessageSenderPrecompileAddress;
l2ToL1MessagePasserPrecompileAddress = _l2ToL1MessagePasserPrecompileAddress;
}
/*
* Public Functions
*/
/**
* Relays a cross domain message to a contract.
* .inheritdoc IL2CrossDomainMessenger
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
)
public
{
require(
_verifyXDomainMessage() == true,
"Provided message could not be verified."
);
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
bytes32 msgHash = keccak256(xDomainCalldata);
require(
receivedMessages[msgHash] == false,
"Provided message has already been received."
);
xDomainMessageSender = _sender;
_target.call(_message);
// Messages are considered successfully executed if they complete
// without running out of gas (revert or not). As a result, we can
// ignore the result of the call and always mark the message as
// successfully executed because we won't get here unless we have
// enough gas left over.
receivedMessages[msgHash] = true;
emit RelayedL1ToL2Message(msgHash);
}
/*
* Internal Functions
*/
/**
* Verifies that a received cross domain message is valid.
* @return Whether or not the message is valid.
*/
function _verifyXDomainMessage()
internal
returns (
bool
)
{
IL1MessageSender l1MessageSenderPrecompile = IL1MessageSender(l1MessageSenderPrecompileAddress);
address l1MessageSenderAddress = l1MessageSenderPrecompile.getL1MessageSender();
return l1MessageSenderAddress == targetMessengerAddress;
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit Gas limit for the provided message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint32 _gasLimit
)
internal
{
IL2ToL1MessagePasser l2ToL1MessagePasserPrecompile = IL2ToL1MessagePasser(l2ToL1MessagePasserPrecompileAddress);
l2ToL1MessagePasserPrecompile.passMessageToL1(_message);
}
}
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
/* Library Imports */
import { ContractResolver } from "../utils/resolvers/ContractResolver.sol";
import { EthMerkleTrie } from "../utils/libraries/EthMerkleTrie.sol";
import { BytesLib } from "../utils/libraries/BytesLib.sol";
import { DataTypes } from "../utils/libraries/DataTypes.sol";
/* Contract Imports */
import { BaseCrossDomainMessenger } from "./BaseCrossDomainMessenger.sol";
import { L1ToL2TransactionQueue } from "../queue/L1ToL2TransactionQueue.sol";
import { StateCommitmentChain } from "../chain/StateCommitmentChain.sol";
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
/**
* @title ICrossDomainMessenger
*/
interface ICrossDomainMessenger {
/********************
* View Functions *
********************/
function receivedMessages(bytes32 messageHash) external view returns (bool);
function sentMessages(bytes32 messageHash) external view returns (bool);
function targetMessengerAddress() external view returns (address);
function messageNonce() external view returns (uint256);
function xDomainMessageSender() external view returns (address);
/********************
* Public Functions *
********************/
/**
* Sets the target messenger address.
* @param _targetMessengerAddress New messenger address.
*/
function setTargetMessengerAddress(
address _targetMessengerAddress
) external;
/**
* Sends a cross domain message to the target messenger.
* @param _target Target contract address.
* @param _message Message to send to the target.
* @param _gasLimit Gas limit for the provided message.
*/
function sendMessage(
address _target,
bytes calldata _message,
uint32 _gasLimit
) external;
}
/**
* @title L1CrossDomainMessenger
*/
contract L1CrossDomainMessenger is BaseCrossDomainMessenger, ContractResolver {
event RelayedL2ToL1Message(bytes32 msgHash);
/*
* Data Structures
*/
struct L2MessageInclusionProof {
bytes32 stateRoot;
uint256 stateRootIndex;
DataTypes.StateElementInclusionProof stateRootProof;
bytes stateTrieWitness;
bytes storageTrieWitness;
}
/*
* Constructor
*/
/**
* @param _addressResolver Address of the AddressResolver contract.
*/
constructor(
address _addressResolver
)
public
ContractResolver(_addressResolver)
{}
/*
* Public Functions
*/
/**
* Relays a cross domain message to a contract.
* .inheritdoc IL1CrossDomainMessenger
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
L2MessageInclusionProof memory _proof
)
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
bytes32 msgHash = keccak256(xDomainCalldata);
require(
_verifyXDomainMessage(
xDomainCalldata,
_proof
) == true,
"Provided message could not be verified."
);
require(
receivedMessages[msgHash] == false,
"Provided message has already been received."
);
xDomainMessageSender = _sender;
_target.call(_message);
// Messages are considered successfully executed if they complete
// without running out of gas (revert or not). As a result, we can
// ignore the result of the call and always mark the message as
// successfully executed because we won't get here unless we have
// enough gas left over.
receivedMessages[msgHash] = true;
emit RelayedL2ToL1Message(msgHash);
}
/**
* Replays a cross domain message to the target messenger.
* .inheritdoc IL1CrossDomainMessenger
*/
function replayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce,
uint32 _gasLimit
)
public
{
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
require(
sentMessages[keccak256(xDomainCalldata)] == true,
"Provided message has not already been sent."
);
_sendXDomainMessage(xDomainCalldata, _gasLimit);
}
/*
* Internal Functions
*/
/**
* Verifies that the given message is valid.
* @param _xDomainCalldata Calldata to verify.
* @param _proof Inclusion proof for the message.
* @return Whether or not the provided message is valid.
*/
function _verifyXDomainMessage(
bytes memory _xDomainCalldata,
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
return (
_verifyStateRootProof(_proof) && _verifyStorageProof(_xDomainCalldata, _proof)
);
}
/**
* Verifies that the state root within an inclusion proof is valid.
* @param _proof Message inclusion proof.
* @return Whether or not the provided proof is valid.
*/
function _verifyStateRootProof(
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
// TODO: We *must* verify that the batch timestamp is sufficiently old.
// However, this requires that we first add timestamps to state batches
// and account for that change in various tests. Change of that size is
// out of scope for this ticket, so "TODO" for now.
StateCommitmentChain stateCommitmentChain = resolveStateCommitmentChain();
return stateCommitmentChain.verifyElement(
abi.encodePacked(_proof.stateRoot),
_proof.stateRootIndex,
_proof.stateRootProof
);
}
/**
* Verifies that the storage proof within an inclusion proof is valid.
* @param _xDomainCalldata Encoded message calldata.
* @param _proof Message inclusion proof.
* @return Whether or not the provided proof is valid.
*/
function _verifyStorageProof(
bytes memory _xDomainCalldata,
L2MessageInclusionProof memory _proof
)
internal
returns (
bool
)
{
bytes32 storageKey = keccak256(
BytesLib.concat(
abi.encodePacked(keccak256(_xDomainCalldata)),
abi.encodePacked(uint256(0))
)
);
return EthMerkleTrie.proveAccountStorageSlotValue(
0x4200000000000000000000000000000000000000,
storageKey,
bytes32(uint256(1)),
_proof.stateTrieWitness,
_proof.storageTrieWitness,
_proof.stateRoot
);
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit OVM gas limit for the message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint32 _gasLimit
)
internal
{
L1ToL2TransactionQueue l1ToL2TransactionQueue = resolveL1ToL2TransactionQueue();
l1ToL2TransactionQueue.enqueueL1ToL2Message(
targetMessengerAddress,
_gasLimit,
_message
);
}
/*
* Contract Resolution
*/
function resolveL1ToL2TransactionQueue()
internal
view
returns (L1ToL2TransactionQueue)
{
return L1ToL2TransactionQueue(resolveContract("L1ToL2TransactionQueue"));
}
function resolveStateCommitmentChain()
internal
view
returns (StateCommitmentChain)
{
return StateCommitmentChain(resolveContract("StateCommitmentChain"));
}
}
pragma solidity ^0.5.0;
contract IL1MessageSender {
/*
* Public Functions
*/
function getL1MessageSender() public returns (address);
}
pragma solidity ^0.5.0;
/* Interface Imports */
import { IL1MessageSender } from "../ovm/precompiles/L1MessageSender.interface.sol";
import { IL2ToL1MessagePasser } from "../ovm/precompiles/L2ToL1MessagePasser.interface.sol";
/* Contract Imports */
import { BaseCrossDomainMessenger } from "./BaseCrossDomainMessenger.sol";
/**
* @title L2CrossDomainMessenger
*/
contract L2CrossDomainMessenger is BaseCrossDomainMessenger {
event RelayedL1ToL2Message(bytes32 msgHash);pragma solidity ^0.5.0;
contract IL2ToL1MessagePasser {
/*
* Events
*/
event L2ToL1Message(
uint _nonce,
address _ovmSender,
bytes _callData
);
/*
* Public Functions
*/
function passMessageToL1(bytes memory _messageData) public;
}
/*
* Contract Variables
*/
address private l1MessageSenderPrecompileAddress;
address private l2ToL1MessagePasserPrecompileAddress;
/*
* Constructor
*/
/**
* @param _l1MessageSenderPrecompileAddress L1MessageSender address.
* @param _l2ToL1MessagePasserPrecompileAddress L2ToL1MessagePasser address.
*/
constructor(
address _l1MessageSenderPrecompileAddress,
address _l2ToL1MessagePasserPrecompileAddress
)
public
{
l1MessageSenderPrecompileAddress = _l1MessageSenderPrecompileAddress;
l2ToL1MessagePasserPrecompileAddress = _l2ToL1MessagePasserPrecompileAddress;
}
/*
* Public Functions
*/
/**
* Relays a cross domain message to a contract.
* .inheritdoc IL2CrossDomainMessenger
*/
function relayMessage(
address _target,
address _sender,
bytes memory _message,
uint256 _messageNonce
)
public
{
require(
_verifyXDomainMessage() == true,
"Provided message could not be verified."
);
bytes memory xDomainCalldata = _getXDomainCalldata(
_target,
_sender,
_message,
_messageNonce
);
bytes32 msgHash = keccak256(xDomainCalldata);
require(
receivedMessages[msgHash] == false,
"Provided message has already been received."
);
xDomainMessageSender = _sender;
_target.call(_message);
// Messages are considered successfully executed if they complete
// without running out of gas (revert or not). As a result, we can
// ignore the result of the call and always mark the message as
// successfully executed because we won't get here unless we have
// enough gas left over.
receivedMessages[msgHash] = true;
emit RelayedL1ToL2Message(msgHash);
}
/*
* Internal Functions
*/
/**
* Verifies that a received cross domain message is valid.
* @return Whether or not the message is valid.
*/
function _verifyXDomainMessage()
internal
returns (
bool
)
{
IL1MessageSender l1MessageSenderPrecompile = IL1MessageSender(l1MessageSenderPrecompileAddress);
address l1MessageSenderAddress = l1MessageSenderPrecompile.getL1MessageSender();
return l1MessageSenderAddress == targetMessengerAddress;
}
/**
* Sends a cross domain message.
* @param _message Message to send.
* @param _gasLimit Gas limit for the provided message.
*/
function _sendXDomainMessage(
bytes memory _message,
uint32 _gasLimit
)
internal
{
IL2ToL1MessagePasser l2ToL1MessagePasserPrecompile = IL2ToL1MessagePasser(l2ToL1MessagePasserPrecompileAddress);
l2ToL1MessagePasserPrecompile.passMessageToL1(_message);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment