Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/optimism support #16

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion l1/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
DEPLOY_RPC_URL=
STARKNET_CORE_ADDRESS=
L2_RECIPIENT_ADDRESS=
OPTIMISM_L2_OUTPUT_ORACLE_ADDRESS=
L2_RECIPIENT_ADDRESS_FROM_ETHEREUM=
L2_RECIPIENT_ADDRESS_FROM_OPTIMISM=
AGGREGATORS_FACTORY_ADDRESS=
ETHERSCAN_API_KEY=
PRIVATE_KEY=
7 changes: 6 additions & 1 deletion l1/script/L1MessagesSender.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "forge-std/console.sol";

import {L1MessagesSender} from "../src/L1MessagesSender.sol";
import {IStarknetCore} from "../src/interfaces/IStarknetCore.sol";
import {IOptimismL2OutputOracle} from "../src/interfaces/IOptimismL2OutputOracle.sol";

contract L1MessagesSenderDeployer is Script {
function run() external {
Expand All @@ -14,7 +15,11 @@ contract L1MessagesSenderDeployer is Script {

L1MessagesSender l1MessagesSender = new L1MessagesSender(
IStarknetCore(vm.envAddress("STARKNET_CORE_ADDRESS")),
vm.envUint("L2_RECIPIENT_ADDRESS"),
IOptimismL2OutputOracle(
vm.envAddress("OPTIMISM_L2_OUTPUT_ORACLE_ADDRESS")
),
vm.envUint("L2_RECIPIENT_ADDRESS_FROM_ETHEREUM"),
vm.envUint("L2_RECIPIENT_ADDRESS_FROM_OPTIMISM"),
vm.envAddress("AGGREGATORS_FACTORY_ADDRESS")
);

Expand Down
62 changes: 48 additions & 14 deletions l1/src/L1MessagesSender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Ownable} from "openzeppelin/access/Ownable.sol";

import {FormatWords64} from "./lib/FormatWords64.sol";
import {IStarknetCore} from "./interfaces/IStarknetCore.sol";
import {IOptimismL2OutputOracle} from "./interfaces/IOptimismL2OutputOracle.sol";

import {IAggregatorsFactory} from "./interfaces/IAggregatorsFactory.sol";
import {IAggregator} from "./interfaces/IAggregator.sol";
Expand All @@ -15,8 +16,10 @@ contract L1MessagesSender is Ownable {
using Uint256Splitter for uint256;

IStarknetCore public immutable starknetCore;
IOptimismL2OutputOracle public immutable optimismOutputOracle;

uint256 public l2RecipientAddr;
uint256 public ethereumCommitmentsInboxAddr;
uint256 public optimismCommitmentsInboxAddr;

IAggregatorsFactory public aggregatorsFactory;

Expand All @@ -29,15 +32,21 @@ contract L1MessagesSender is Ownable {
0x36c76e67f1d589956059cbd9e734d42182d1f8a57d5876390bb0fcfe1090bb4;

/// @param starknetCore_ a StarknetCore address to send and consume messages on/from L2
/// @param l2RecipientAddr_ a L2 recipient address that is the recipient contract on L2.
/// @param optimismOutputOracle_ address of the optimism rollup output contract
/// @param ethereumCommitmentsInboxAddr_ a L2 recipient address that is the recipient contract on L2.
/// @param optimismCommitmentsInboxAddr_ a L2 recipient address that is the recipient contract on L2.
/// @param aggregatorsFactoryAddr_ Herodotus aggregators factory address (where MMR trees are referenced)
constructor(
IStarknetCore starknetCore_,
uint256 l2RecipientAddr_,
IOptimismL2OutputOracle optimismOutputOracle_,
uint256 ethereumCommitmentsInboxAddr_,
uint256 optimismCommitmentsInboxAddr_,
address aggregatorsFactoryAddr_
) {
starknetCore = starknetCore_;
l2RecipientAddr = l2RecipientAddr_;
optimismOutputOracle = optimismOutputOracle_;
ethereumCommitmentsInboxAddr = ethereumCommitmentsInboxAddr_;
optimismCommitmentsInboxAddr = optimismCommitmentsInboxAddr_;
aggregatorsFactory = IAggregatorsFactory(aggregatorsFactoryAddr_);
}

Expand All @@ -47,13 +56,29 @@ contract L1MessagesSender is Ownable {
bytes32 parentHash = blockhash(blockNumber_ - 1);
require(parentHash != bytes32(0), "ERR_INVALID_BLOCK_NUMBER");

_sendBlockHashToL2(parentHash, blockNumber_);
_sendBlockHashToL2(parentHash, blockNumber_, ethereumCommitmentsInboxAddr);
}

// See https://github.com/ethereum-optimism/optimism/blob/0086b6dd4eaa579227607216a83ca0d6a652b264/packages/contracts-bedrock/src/libraries/Hashing.sol#L114
function sendOptimismBlockhashToL2(uint256 outputIndex_, IOptimismL2OutputOracle.OutputRootProof calldata outputRootPreimage_) external payable {
IOptimismL2OutputOracle.OutputProposal memory outputProposal = optimismOutputOracle.getL2Output(outputIndex_);
bytes32 actualOutputRoot = keccak256(
abi.encode(
outputRootPreimage_.version,
outputRootPreimage_.stateRoot,
outputRootPreimage_.messagePasserStorageRoot,
outputRootPreimage_.latestBlockhash
)
);

require(actualOutputRoot == outputProposal.outputRoot, "ERR_OUTPUT_ROOT_PROOF_INVALID");
_sendBlockHashToL2(outputRootPreimage_.latestBlockhash, outputProposal.l2BlockNumber, optimismCommitmentsInboxAddr);
}

/// @notice Send the L1 latest parent hash to L2
function sendLatestParentHashToL2() external payable {
bytes32 parentHash = blockhash(block.number - 1);
_sendBlockHashToL2(parentHash, block.number);
_sendBlockHashToL2(parentHash, block.number, ethereumCommitmentsInboxAddr);
}

/// @param aggregatorId The id of a tree previously created by the aggregators factory
Expand All @@ -76,7 +101,8 @@ contract L1MessagesSender is Ownable {

function _sendBlockHashToL2(
bytes32 parentHash_,
uint256 blockNumber_
uint256 blockNumber_,
uint256 commitmentsInboxAddr_
) internal {
uint256[] memory message = new uint256[](4);
(uint256 parentHashLow, uint256 parentHashHigh) = uint256(parentHash_)
Expand All @@ -89,7 +115,7 @@ contract L1MessagesSender is Ownable {
message[3] = blockNumberHigh;

starknetCore.sendMessageToL2{value: msg.value}(
l2RecipientAddr,
commitmentsInboxAddr_,
RECEIVE_COMMITMENT_L1_HANDLER_SELECTOR,
message
);
Expand All @@ -108,18 +134,26 @@ contract L1MessagesSender is Ownable {

// Pass along msg.value
starknetCore.sendMessageToL2{value: msg.value}(
l2RecipientAddr,
ethereumCommitmentsInboxAddr,
RECEIVE_MMR_L1_HANDLER_SELECTOR,
message
);
}

/// @notice Set the L2 recipient address from ethereum
/// @param newethereumCommitmentsInboxAddr_ The new L2 recipient address from ethereum
function setethereumCommitmentsInboxAddr(
uint256 newethereumCommitmentsInboxAddr_
) external onlyOwner {
ethereumCommitmentsInboxAddr = newethereumCommitmentsInboxAddr_;
}

/// @notice Set the L2 recipient address
/// @param newL2RecipientAddr_ The new L2 recipient address
function setL2RecipientAddr(
uint256 newL2RecipientAddr_
/// @notice Set the L2 recipient address from optimism
/// @param newoptimismCommitmentsInboxAddr_ The new L2 recipient address from optimism
function setoptimismCommitmentsInboxAddr(
uint256 newoptimismCommitmentsInboxAddr_
) external onlyOwner {
l2RecipientAddr = newL2RecipientAddr_;
optimismCommitmentsInboxAddr = newoptimismCommitmentsInboxAddr_;
}

/// @notice Set the aggregators factory address
Expand Down
48 changes: 48 additions & 0 deletions l1/src/interfaces/IOptimismL2OutputOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;

interface IOptimismL2OutputOracle {
/**
* @notice OutputProposal represents a commitment to the L2 state. The timestamp is the L1
* timestamp that the output root is posted. This timestamp is used to verify that the
* finalization period has passed since the output root was submitted.
*
* @custom:field outputRoot Hash of the L2 output.
* @custom:field timestamp Timestamp of the L1 block that the output root was submitted in.
* @custom:field l2BlockNumber L2 block number that the output corresponds to.
*/
struct OutputProposal {
bytes32 outputRoot;
uint128 timestamp;
uint128 l2BlockNumber;
}

/**
* @notice Struct representing the elements that are hashed together to generate an output root
* which itself represents a snapshot of the L2 state.
*
* @custom:field version Version of the output root.
* @custom:field stateRoot Root of the state trie at the block of this output.
* @custom:field messagePasserStorageRoot Root of the message passer storage trie.
* @custom:field latestBlockhash Hash of the block this output was generated from.
*/
struct OutputRootProof {
bytes32 version;
bytes32 stateRoot;
bytes32 messagePasserStorageRoot;
bytes32 latestBlockhash;
}

/**
* @notice Returns an output by index. Exists because Solidity's array access will return a
* tuple instead of a struct.
*
* @param _l2OutputIndex Index of the output to return.
*
* @return The output at the given index.
*/
function getL2Output(uint256 _l2OutputIndex)
external
view
returns (OutputProposal memory);
}
3 changes: 3 additions & 0 deletions l1/test/L1MessagesSender.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "forge-std/Test.sol";

import {L1MessagesSender} from "../src/L1MessagesSender.sol";
import {IStarknetCore} from "../src/interfaces/IStarknetCore.sol";
import {IOptimismL2OutputOracle} from "../src/interfaces/IOptimismL2OutputOracle.sol";

contract L1MessagesSenderTest is Test {
L1MessagesSender public sender;
Expand All @@ -14,7 +15,9 @@ contract L1MessagesSenderTest is Test {

sender = new L1MessagesSender(
IStarknetCore(0xde29d060D45901Fb19ED6C6e959EB22d8626708e),
IOptimismL2OutputOracle(0xdfe97868233d1aa22e815a266982f2cf17685a27),
0x07bf6b32382276bFF5341f810A6811233A9591228642F60160129629448a21b6,
0x047cCc40b58Bb8f4B0c33A4E232478A312a0C9d4e51f8D3A9D9D275f3C167C6A,
0xB8Cb7707b5160eaE8931e0cf02B563a5CeA75F09
);
}
Expand Down
32 changes: 29 additions & 3 deletions multicall/deploy.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[[call]]
call_type = "deploy"
class_hash = "0x710fb6c249e9c76996f01e170218e31aec7498a8ae618842a0f67b66987c436"
class_hash = "0x06835a4591dc71821bfc769372d2da76e428d5eef7dccc048e036e8ca80aa740"
inputs = [
"0x1",
"0x2",
Expand All @@ -12,14 +12,40 @@ unique = false

[[call]]
call_type = "deploy"
class_hash = "0x4965a6ea3851cde0fd10952802af3300fac851122051c3743644aabe901ee13"
class_hash = "0x06835a4591dc71821bfc769372d2da76e428d5eef7dccc048e036e8ca80aa740"
inputs = [
"0x1",
"0x2",
"0x0",
"0x007327d012f432a9f940228ae6b01032ea4742d8bba8599d7a34e1d5c120e983",
]
id = "op_commitments_inbox"
unique = false

[[call]]
call_type = "deploy"
class_hash = "0x2d505685c0bf351d41c0a8d8645c48fa7c1b1ee956da23414e725ff2de35807"
inputs = ["0x1"]
id = "headers_store"
unique = false

[[call]]
call_type = "deploy"
class_hash = "0xfa77018c244261ab65551a07c0f6e3fab9592027e4cd456b9d074f2b94a9ae"
class_hash = "0x2d505685c0bf351d41c0a8d8645c48fa7c1b1ee956da23414e725ff2de35807"
inputs = ["0x1"]
id = "op_headers_store"
unique = false

[[call]]
call_type = "deploy"
class_hash = "0x2d22cd79ecdd6eed68fddbe0640454e3b8613be645b961d059c7ea54e5b0fcc"
inputs = ["0x1"]
id = "evm_facts_registry"
unique = false

[[call]]
call_type = "deploy"
class_hash = "0x2d22cd79ecdd6eed68fddbe0640454e3b8613be645b961d059c7ea54e5b0fcc"
inputs = ["0x1"]
id = "op_facts_registry"
unique = false