Skip to content

Commit

Permalink
feat and chore: Optimism L1 to L2 message passing and deployment script
Browse files Browse the repository at this point in the history
  • Loading branch information
kupermind committed Feb 21, 2024
1 parent e0e927c commit 57b8868
Show file tree
Hide file tree
Showing 17 changed files with 712 additions and 19 deletions.
1 change: 1 addition & 0 deletions abis/test/L1CrossDomainMessenger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"msgHash","type":"bytes32"}],"name":"FailedRelayedMessage","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"msgHash","type":"bytes32"}],"name":"RelayedMessage","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"messageNonce","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasLimit","type":"uint256"}],"name":"SentMessage","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"SentMessageExtension1","type":"event"},{"inputs":[],"name":"MESSAGE_VERSION","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_GAS_CALLDATA_OVERHEAD","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OTHER_MESSENGER","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PORTAL","outputs":[{"internalType":"contract OptimismPortal","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAY_CALL_OVERHEAD","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAY_CONSTANT_OVERHEAD","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAY_GAS_CHECK_BUFFER","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RELAY_RESERVED_GAS","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"}],"name":"baseGas","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"failedMessages","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract OptimismPortal","name":"_portal","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"portal","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"address","name":"_sender","type":"address"},{"internalType":"address","name":"_target","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_minGasLimit","type":"uint256"},{"internalType":"bytes","name":"_message","type":"bytes"}],"name":"relayMessage","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"bytes","name":"_message","type":"bytes"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"}],"name":"sendMessage","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"successfulMessages","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"xDomainMessageSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
171 changes: 171 additions & 0 deletions contracts/bridges/OptimismMessenger.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/// @dev Interface to the CrossDomainMessenger (CDM) Contract Proxy.
interface ICrossDomainMessenger {
function xDomainMessageSender() external returns (address);
}

/// @dev Provided zero address.
error ZeroAddress();

/// @dev Only self contract is allowed to call the function.
/// @param sender Sender address.
/// @param instance Required contract instance address.
error SelfCallOnly(address sender, address instance);

/// @dev Only `CDMContractProxyHome` is allowed to call the function.
/// @param sender Sender address.
/// @param CDMContractProxyHome Required CDM Contract Proxy (Home) address.
error CDMContractProxyHomeOnly(address sender, address CDMContractProxyHome);

/// @dev Only on behalf of `foreignGovernor` the function is allowed to process the data.
/// @param sender Sender address.
/// @param foreignGovernor Required Foreign Governor address.
error ForeignGovernorOnly(address sender, address foreignGovernor);

/// @dev Provided incorrect data length.
/// @param expected Expected minimum data length.
/// @param provided Provided data length.
error IncorrectDataLength(uint256 expected, uint256 provided);

/// @dev Provided value is bigger than the actual balance.
/// @param value Provided value.
/// @param balance Actual balance.
error InsufficientBalance(uint256 value, uint256 balance);

/// @dev Target execution failed.
/// @param target Target address.
/// @param value Provided value.
/// @param payload Provided payload.
error TargetExecFailed(address target, uint256 value, bytes payload);

/// @title OptimismMessenger - Smart contract for the governor home (Optimism) bridge implementation
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract OptimismMessenger {
event FundsReceived(address indexed sender, uint256 value);
event ForeignGovernorUpdated(address indexed foreignMessageSender);
event MessageReceived(address indexed foreignMessageSender, bytes data);

// Default payload data length includes the number of bytes of at least one address (20 bytes or 160 bits),
// value (12 bytes or 96 bits) and the payload size (4 bytes or 32 bits)
uint256 public constant DEFAULT_DATA_LENGTH = 36;
// CDM Contract Proxy (Home) address on L2 that receives the message across the bridge from the foreign L1 network
address public immutable CDMContractProxyHome;
// Foreign governor address on L1 that is authorized to propagate the transaction execution across the bridge
address public foreignGovernor;

/// @dev HomeMediator constructor.
/// @param _CDMContractProxyHome CDM Contract Proxy (Home) address (Optimism).
/// @param _foreignGovernor Foreign Governor address (ETH).
constructor(address _CDMContractProxyHome, address _foreignGovernor) {
// Check fo zero addresses
if (_CDMContractProxyHome == address(0) || _foreignGovernor == address(0)) {
revert ZeroAddress();
}

CDMContractProxyHome = _CDMContractProxyHome;
foreignGovernor = _foreignGovernor;
}

/// @dev Receives native network token.
receive() external payable {
emit FundsReceived(msg.sender, msg.value);
}

/// @dev Changes the Foreign Governor address (original Timelock).
/// @notice The only way to change the Foreign Governor address is by the Timelock on L1 to request that change.
/// This triggers a self-contract transaction of HomeMediator that changes the Foreign Governor address.
/// @param newForeignGovernor New Foreign Governor address.
function changeForeignGovernor(address newForeignGovernor) external {
// Check if the change is authorized by the previous governor itself
// This is possible only if all the checks in the message process function pass and the contract calls itself
if (msg.sender != address(this)) {
revert SelfCallOnly(msg.sender, address(this));
}

// Check for the zero address
if (newForeignGovernor == address(0)) {
revert ZeroAddress();
}

foreignGovernor = newForeignGovernor;
emit ForeignGovernorUpdated(newForeignGovernor);
}

/// @dev Processes a message received from the CDM Contract Proxy (Home) contract.
/// @notice The sender must be the Foreign Governor address (Timelock).
/// @param data Bytes message sent from the CDM Contract Proxy (Home) contract. The data must be encoded as a set of
/// continuous transactions packed into a single buffer, where each transaction is composed as follows:
/// - target address of 20 bytes (160 bits);
/// - value of 12 bytes (96 bits), as a limit for all of Autonolas ecosystem contracts;
/// - payload length of 4 bytes (32 bits), as 2^32 - 1 characters is more than enough to fill a whole block;
/// - payload as bytes, with the length equal to the specified payload length.
function processMessageFromForeign(bytes memory data) external payable {
// Check for the CDM Contract Proxy (Home) address
if (msg.sender != CDMContractProxyHome) {
revert CDMContractProxyHomeOnly(msg.sender, CDMContractProxyHome);
}

// Check for the Foreign Governor address
address governor = foreignGovernor;
address bridgeGovernor = ICrossDomainMessenger(CDMContractProxyHome).xDomainMessageSender();
if (bridgeGovernor != governor) {
revert ForeignGovernorOnly(bridgeGovernor, governor);
}

// Check for the correct data length
uint256 dataLength = data.length;
if (dataLength < DEFAULT_DATA_LENGTH) {
revert IncorrectDataLength(DEFAULT_DATA_LENGTH, data.length);
}

// Unpack and process the data
for (uint256 i = 0; i < dataLength;) {
address target;
uint96 value;
uint32 payloadLength;
// solhint-disable-next-line no-inline-assembly
assembly {
// First 20 bytes is the address (160 bits)
i := add(i, 20)
target := mload(add(data, i))
// Offset the data by 12 bytes of value (96 bits)
i := add(i, 12)
value := mload(add(data, i))
// Offset the data by 4 bytes of payload length (32 bits)
i := add(i, 4)
payloadLength := mload(add(data, i))
}

// Check for the zero address
if (target == address(0)) {
revert ZeroAddress();
}

// Check for the value compared to the contract's balance
if (value > address(this).balance) {
revert InsufficientBalance(value, address(this).balance);
}

// Get the payload
bytes memory payload = new bytes(payloadLength);
for (uint256 j = 0; j < payloadLength; ++j) {
payload[j] = data[i + j];
}
// Offset the data by the payload number of bytes
i += payloadLength;

// Call the target with the provided payload
(bool success, ) = target.call{value: value}(payload);
if (!success) {
revert TargetExecFailed(target, value, payload);
}
}

// Emit received message
emit MessageReceived(governor, data);
}
}
9 changes: 2 additions & 7 deletions contracts/bridges/test/MockTimelock.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// IFxStateSender interface
interface IFxStateSender {
function sendMessageToChild(address _receiver, bytes calldata _data) external;
}

error ExecFailed(address fxRoot, bytes payload);

/// @title MockTimelock - Mock of Timelock contract on the L1 side
Expand All @@ -21,8 +16,8 @@ contract MockTimelock {

/// @dev Executes the payload at the Fx Root address.
/// @param payload Bytes of payload.
function execute(bytes memory payload) external {
(bool success, ) = fxRoot.call(payload);
function execute(bytes memory payload) external payable {
(bool success, ) = fxRoot.call{value: msg.value}(payload);
if (!success) {
revert ExecFailed(fxRoot, payload);
}
Expand Down
126 changes: 120 additions & 6 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ require("@nomicfoundation/hardhat-toolbox");
const ALCHEMY_API_KEY_MAINNET = process.env.ALCHEMY_API_KEY_MAINNET;
const ALCHEMY_API_KEY_MATIC = process.env.ALCHEMY_API_KEY_MATIC;
const ALCHEMY_API_KEY_GOERLI = process.env.ALCHEMY_API_KEY_GOERLI;
const ALCHEMY_API_KEY_SEPOLIA = process.env.ALCHEMY_API_KEY_SEPOLIA;
const ALCHEMY_API_KEY_MUMBAI = process.env.ALCHEMY_API_KEY_MUMBAI;
const GNOSIS_CHAIN_API_KEY = process.env.GNOSIS_CHAIN_API_KEY;
const CHIADO_CHAIN_API_KEY = "10200";
let TESTNET_MNEMONIC = process.env.TESTNET_MNEMONIC;

const accounts = {
Expand All @@ -33,6 +32,11 @@ if (!TESTNET_MNEMONIC) {

const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;
const POLYGONSCAN_API_KEY = process.env.POLYGONSCAN_API_KEY;
const GNOSISSCAN_API_KEY = process.env.GNOSISSCAN_API_KEY;
const ARBISCAN_API_KEY = process.env.ARBISCAN_API_KEY;
const OPSCAN_API_KEY = process.env.OPSCAN_API_KEY;
const BASESCAN_API_KEY = process.env.BASESCAN_API_KEY;
const CELOSCAN_API_KEY = process.env.CELOSCAN_API_KEY;

module.exports = {
networks: {
Expand All @@ -54,11 +58,36 @@ module.exports = {
accounts: accounts,
chainId: 100,
},
arbitrumOne: {
url: "https://arb1.arbitrum.io/rpc",
accounts: accounts,
chainId: 42161,
},
optimistic: {
url: "https://optimism.drpc.org",
accounts: accounts,
chainId: 10,
},
base: {
url: "https://mainnet.base.org",
accounts: accounts,
chainId: 8453,
},
celo: {
url: "https://forno.celo.org",
accounts: accounts,
chainId: 42220,
},
goerli: {
url: "https://eth-goerli.g.alchemy.com/v2/" + ALCHEMY_API_KEY_GOERLI,
chainId: 5,
accounts: accounts,
},
sepolia: {
url: "https://eth-sepolia.g.alchemy.com/v2/" + ALCHEMY_API_KEY_SEPOLIA,
accounts: accounts,
chainId: 11155111,
},
polygonMumbai: {
url: "https://polygon-mumbai.g.alchemy.com/v2/" + ALCHEMY_API_KEY_MUMBAI,
accounts: accounts,
Expand All @@ -67,6 +96,26 @@ module.exports = {
url: "https://rpc.chiadochain.net",
accounts: accounts,
},
arbitrumSepolia: {
url: "https://sepolia-rollup.arbitrum.io/rpc",
accounts: accounts,
chainId: 421614,
},
optimisticSepolia: {
url: "https://sepolia.optimism.io",
accounts: accounts,
chainId: 11155420,
},
baseSepolia: {
url: "https://sepolia.base.org",
accounts: accounts,
chainId: 84532,
},
celoAlfajores: {
url: "https://alfajores-forno.celo-testnet.org",
accounts: accounts,
chainId: 44787,
},
hardhat: {
allowUnlimitedContractSize: true
},
Expand All @@ -77,8 +126,8 @@ module.exports = {
network: "chiado",
chainId: 10200,
urls: {
apiURL: "https://blockscout.com/gnosis/chiado/api",
browserURL: "https://blockscout.com/gnosis/chiado",
apiURL: "https://gnosis-chiado.blockscout.com/api",
browserURL: "https://gnosis-chiado.blockscout.com/",
},
},
{
Expand All @@ -89,14 +138,79 @@ module.exports = {
browserURL: "https://gnosisscan.io/"
},
},
{
network: "arbitrumSepolia",
chainId: 421614,
urls: {
apiURL: "https://api-sepolia.arbiscan.io/api",
browserURL: "https://sepolia.arbiscan.io"
},
},
{
network: "optimistic",
chainId: 10,
urls: {
apiURL: "https://api-optimistic.etherscan.io/api",
browserURL: "https://sepolia-optimistic.etherscan.io"
},
},
{
network: "optimisticSepolia",
chainId: 11155420,
urls: {
apiURL: "https://api-sepolia-optimism.etherscan.io/api",
browserURL: "https://sepolia-optimistic.etherscan.io"
},
},
{
network: "base",
chainId: 8453,
urls: {
apiURL: "https://api.basescan.org/api",
browserURL: "https://basescan.org"
},
},
{
network: "baseSepolia",
chainId: 84532,
urls: {
apiURL: "https://base-sepolia.blockscout.com/api",
browserURL: "https://base-sepolia.blockscout.com/"
},
},
{
network: "celo",
chainId: 42220,
urls: {
apiURL: "https://api.celoscan.io/api",
browserURL: "https://explorer.celo.org/"
},
},
{
network: "celoAlfajores",
chainId: 44787,
urls: {
apiURL: "https://api-alfajores.celoscan.io/api",
browserURL: "https://alfajores-blockscout.celo-testnet.org/"
},
},
],
apiKey: {
mainnet: ETHERSCAN_API_KEY,
polygon: POLYGONSCAN_API_KEY,
gnosis: GNOSIS_CHAIN_API_KEY,
gnosis: GNOSISSCAN_API_KEY,
arbitrumOne: ARBISCAN_API_KEY,
optimistic: OPSCAN_API_KEY,
base: BASESCAN_API_KEY,
celo: CELOSCAN_API_KEY,
goerli: ETHERSCAN_API_KEY,
sepolia: ETHERSCAN_API_KEY,
polygonMumbai: POLYGONSCAN_API_KEY,
chiado: CHIADO_CHAIN_API_KEY,
chiado: GNOSISSCAN_API_KEY,
arbitrumSepolia: ARBISCAN_API_KEY,
optimisticSepolia: OPSCAN_API_KEY,
baseSepolia: OPSCAN_API_KEY,
celoAlfajores: CELOSCAN_API_KEY
}
},
solidity: {
Expand Down
4 changes: 2 additions & 2 deletions scripts/deployment/bridges/gnosis/deploy_01_home_mediator.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ async function main() {

let networkURL;
if (providerName === "gnosis") {
if (!process.env.GNOSIS_CHAIN_API_KEY) {
console.log("set GNOSIS_CHAIN_API_KEY env variable");
if (!process.env.GNOSISSCAN_API_KEY) {
console.log("set GNOSISSCAN_API_KEY env variable");
return;
}
networkURL = "https://rpc.gnosischain.com";
Expand Down
Loading

0 comments on commit 57b8868

Please sign in to comment.