Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
245 changes: 206 additions & 39 deletions test/integration/IntegrationBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ pragma solidity 0.8.15;
import {Test} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";
import {console2} from "forge-std/console2.sol";
import {AddressAliasHelper} from "@eth-optimism-bedrock/src/vendor/AddressAliasHelper.sol";
import {IL1CrossDomainMessenger} from "@eth-optimism-bedrock/interfaces/L1/IL1CrossDomainMessenger.sol";
import {ICrossDomainMessenger} from "@eth-optimism-bedrock/interfaces/universal/ICrossDomainMessenger.sol";
import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
import {FeeSplitterSetup} from "src/libraries/FeeSplitterSetup.sol";
import {RevShareCommon} from "src/libraries/RevShareCommon.sol";
import {Utils} from "src/libraries/Utils.sol";
import {RevShareContractsUpgrader} from "src/RevShareContractsUpgrader.sol";
import {Predeploys} from "@eth-optimism-bedrock/src/libraries/Predeploys.sol";
import {AddressAliasHelper} from "@eth-optimism-bedrock/src/vendor/AddressAliasHelper.sol";
import {IFeeSplitter} from "src/interfaces/IFeeSplitter.sol";
import {IFeeVault} from "src/interfaces/IFeeVault.sol";
import {IL1Withdrawer} from "src/interfaces/IL1Withdrawer.sol";
Expand All @@ -18,56 +19,70 @@ import {ISuperchainRevSharesCalculator} from "src/interfaces/ISuperchainRevShare
/// @title IntegrationBase
/// @notice Base contract for integration tests with L1->L2 deposit transaction relay functionality
abstract contract IntegrationBase is Test {
// Event for testing
using stdStorage for StdStorage;
// Events for testing

event WithdrawalInitiated(address indexed recipient, uint256 amount);
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);

// Fork IDs
uint256 internal _mainnetForkId;
uint256 internal _opMainnetForkId;
uint256 internal _inkMainnetForkId;
uint256 internal _soneiumMainnetForkId;

// Shared upgrader contract
RevShareContractsUpgrader public revShareUpgrader;

// L1 addresses
address internal constant PROXY_ADMIN_OWNER = 0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A;
address internal constant OP_MAINNET_PORTAL = 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed;
address internal constant INK_MAINNET_PORTAL = 0x5d66C1782664115999C47c9fA5cd031f495D3e4F;
address internal constant SONEIUM_MAINNET_PORTAL = 0x88e529A6ccd302c948689Cd5156C83D4614FAE92;
address internal constant REV_SHARE_UPGRADER_ADDRESS = 0x0000000000000000000000000000000000001337;
address internal constant OP_MAINNET_L1_MESSENGER = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1;
address internal constant INK_MAINNET_L1_MESSENGER = 0x69d3Cf86B2Bf1a9e99875B7e2D9B6a84426c171f;
address internal constant SONEIUM_MAINNET_L1_MESSENGER = 0x9CF951E3F74B644e621b36Ca9cea147a78D4c39f;

// FeesDepositor configuration (triggers deposit to OP Mainnet when balance >= threshold)
uint256 internal constant FEES_DEPOSITOR_THRESHOLD = 2 ether;

// OP Mainnet fees recipient (OPM multisig) - target for FeesDepositor deposits
address internal constant OP_MAINNET_FEES_RECIPIENT = 0x16A27462B4D61BDD72CbBabd3E43e11791F7A28c;

// Aliased address for L1→L2 message relay
address internal immutable OP_ALIASED_L1_MESSENGER = AddressAliasHelper.applyL1ToL2Alias(OP_MAINNET_L1_MESSENGER);

// Simulation flag for task execution
bool internal constant IS_SIMULATE = true;

// L2 predeploys (same across all OP Stack chains)
address internal constant SEQUENCER_FEE_VAULT = 0x4200000000000000000000000000000000000011;
address internal constant OPERATOR_FEE_VAULT = 0x420000000000000000000000000000000000001b;
address internal constant BASE_FEE_VAULT = 0x4200000000000000000000000000000000000019;
address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A;
address internal constant FEE_SPLITTER = 0x420000000000000000000000000000000000002B;
address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007;

// Default L2 sender address
address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD;

// Extra gas buffer added to the minimum gas limit for the relayMessage function
uint64 internal constant RELAY_GAS_OVERHEAD = 700_000;
Comment thread
0xChin marked this conversation as resolved.
Comment thread
maurelian marked this conversation as resolved.

// Counter for unique L1→L2 message nonces (to avoid collisions on forks)
uint240 internal _l1ToL2NonceCounter;

// L2 chain configuration struct
struct L2ChainConfig {
uint256 forkId;
address portal;
address l1Messenger;
uint256 minWithdrawalAmount;
address l1WithdrawalRecipient;
uint32 withdrawalGasLimit;
address chainFeesRecipient;
string name;
}

// Test configuration - Globals
uint256 internal constant DEFAULT_MIN_WITHDRAWAL_AMOUNT = 2 ether;
uint32 internal constant DEFAULT_WITHDRAWAL_GAS_LIMIT = 800000;
address internal constant FEES_DEPOSITOR = 0xed9B99a703BaD32AC96FDdc313c0652e379251Fd;

// Test configuration - OP Mainnet
uint256 internal constant OP_MIN_WITHDRAWAL_AMOUNT = DEFAULT_MIN_WITHDRAWAL_AMOUNT;
address internal constant OP_L1_WITHDRAWAL_RECIPIENT = FEES_DEPOSITOR;
uint32 internal constant OP_WITHDRAWAL_GAS_LIMIT = DEFAULT_WITHDRAWAL_GAS_LIMIT;
address internal constant OP_CHAIN_FEES_RECIPIENT = 0x16A27462B4D61BDD72CbBabd3E43e11791F7A28c;

// Test configuration - Ink Mainnet
uint256 internal constant INK_MIN_WITHDRAWAL_AMOUNT = DEFAULT_MIN_WITHDRAWAL_AMOUNT;
address internal constant INK_L1_WITHDRAWAL_RECIPIENT = FEES_DEPOSITOR;
uint32 internal constant INK_WITHDRAWAL_GAS_LIMIT = DEFAULT_WITHDRAWAL_GAS_LIMIT;
address internal constant INK_CHAIN_FEES_RECIPIENT = 0x5f077b4c3509C2c192e50B6654d924Fcb8126A60;

// Test configuration - Soneium Mainnet
uint256 internal constant SONEIUM_MIN_WITHDRAWAL_AMOUNT = DEFAULT_MIN_WITHDRAWAL_AMOUNT;
address internal constant SONEIUM_L1_WITHDRAWAL_RECIPIENT = FEES_DEPOSITOR;
uint32 internal constant SONEIUM_WITHDRAWAL_GAS_LIMIT = DEFAULT_WITHDRAWAL_GAS_LIMIT;
address internal constant SONEIUM_CHAIN_FEES_RECIPIENT = 0xF07b3169ffF67A8AECdBb18d9761AEeE34591112;
// Array to store all L2 chain configurations
L2ChainConfig[] internal l2Chains;

bool internal constant IS_SIMULATE = true;
/// @notice Relay all deposit transactions from L1 to multiple L2s
/// @param _forkIds Array of fork IDs for each L2 chain
/// @param _isSimulate If true, only process the second half of logs to avoid duplicates.
Expand All @@ -76,7 +91,6 @@ abstract contract IntegrationBase is Test {
/// we only process the final simulation results.
/// @param _portals Array of Portal addresses corresponding to each fork.
/// Only events emitted by each portal will be relayed on its corresponding L2.

function _relayAllMessages(uint256[] memory _forkIds, bool _isSimulate, address[] memory _portals) internal {
require(_forkIds.length == _portals.length, "Fork IDs and portals length mismatch");

Expand Down Expand Up @@ -209,10 +223,10 @@ abstract contract IntegrationBase is Test {
bytes32 _salt = RevShareCommon.getSalt("SCRevShareCalculator");
return Utils.getCreate2Address(_salt, _initCode, RevShareCommon.CREATE2_DEPLOYER);
}
/// @notice Fund all fee vaults with specified amount
/// @param _amount Amount to fund each vault with
/// @param _forkId Fork ID to execute on

/// @notice Fund all fee vaults with a specified amount
/// @param _amount The amount to fund each vault with
/// @param _forkId The fork ID of the chain to fund
function _fundVaults(uint256 _amount, uint256 _forkId) internal {
vm.selectFork(_forkId);
vm.deal(SEQUENCER_FEE_VAULT, _amount);
Expand Down Expand Up @@ -312,19 +326,172 @@ abstract contract IntegrationBase is Test {
}

/// @notice Execute disburseFees and assert that it triggers a withdrawal with the expected amount
/// @param _forkId The fork ID of the chain to test
/// @param _l1ForkId The L1 fork ID
/// @param _forkId The fork ID of the L2 chain to test
/// @param _opL2ForkId The OP Mainnet L2 fork ID (for relaying L1→L2 deposits)
/// @param _l1Withdrawer The L1Withdrawer address that emits the WithdrawalInitiated event
/// @param _l1WithdrawalRecipient The expected recipient of the withdrawal
/// @param _expectedWithdrawalAmount The expected withdrawal amount
/// @param _portal The OptimismPortal address for this L2 chain
/// @param _l1Messenger The L1CrossDomainMessenger address for this L2 chain
/// @param _withdrawalGasLimit The gas limit used for L1 withdrawals
function _executeDisburseAndAssertWithdrawal(
uint256 _l1ForkId,
uint256 _forkId,
uint256 _opL2ForkId,
address _l1Withdrawer,
address _l1WithdrawalRecipient,
uint256 _expectedWithdrawalAmount
uint256 _expectedWithdrawalAmount,
address _portal,
address _l1Messenger,
uint32 _withdrawalGasLimit
) internal {
vm.selectFork(_forkId);
vm.warp(block.timestamp + IFeeSplitter(FEE_SPLITTER).feeDisbursementInterval() + 1);

vm.expectEmit(true, true, true, true);
vm.expectEmit(true, true, true, true, _l1Withdrawer);
emit WithdrawalInitiated(_l1WithdrawalRecipient, _expectedWithdrawalAmount);
IFeeSplitter(FEE_SPLITTER).disburseFees();

// Relay the withdrawal message to L1
vm.selectFork(_l1ForkId);

if (_expectedWithdrawalAmount >= FEES_DEPOSITOR_THRESHOLD) {
// Expect TransactionDeposited event from OP Mainnet Portal
// Note: FeesDepositor calls L1CrossDomainMessenger.sendMessage(), which calls OptimismPortal.depositTransaction()
// The 'from' address in TransactionDeposited is the aliased L1CrossDomainMessenger (not the FeesDepositor)
vm.expectEmit(true, true, true, false, OP_MAINNET_PORTAL);
emit TransactionDeposited(
OP_ALIASED_L1_MESSENGER, // aliased L1CrossDomainMessenger (caller of depositTransaction)
L2_CROSS_DOMAIN_MESSENGER, // L2 CrossDomainMessenger
0,
""
);

_relayL2ToL1Message(
_portal,
_l1Messenger,
_l1Withdrawer, // sender on L2
_l1WithdrawalRecipient, // target on L1
_expectedWithdrawalAmount, // value
_withdrawalGasLimit, // minGasLimit
"" // data (empty for ETH transfer)
);

// Now relay the deposit from L1 to OP Mainnet L2
vm.selectFork(_opL2ForkId);

uint256 recipientBalanceBefore = OP_MAINNET_FEES_RECIPIENT.balance;

// Relay the L1→L2 message (simple ETH transfer to OPM multisig)
_relayL1ToL2Message(
OP_ALIASED_L1_MESSENGER,
_l1WithdrawalRecipient, // sender (FeesDepositor)
OP_MAINNET_FEES_RECIPIENT, // target (OPM multisig)
_expectedWithdrawalAmount,
200_000, // gas limit for simple ETH transfer
"" // empty data for ETH transfer
);

uint256 recipientBalanceAfter = OP_MAINNET_FEES_RECIPIENT.balance;
assertEq(
recipientBalanceAfter - recipientBalanceBefore,
_expectedWithdrawalAmount,
"OP Mainnet fees recipient should receive the withdrawal amount"
);
} else {
// FeesDepositor holds the ETH (below threshold)
uint256 recipientBalanceBefore = _l1WithdrawalRecipient.balance;

_relayL2ToL1Message(
_portal,
_l1Messenger,
_l1Withdrawer, // sender on L2
_l1WithdrawalRecipient, // target on L1
_expectedWithdrawalAmount, // value
_withdrawalGasLimit, // minGasLimit
"" // data (empty for ETH transfer)
);

uint256 recipientBalanceAfter = _l1WithdrawalRecipient.balance;
assertEq(
recipientBalanceAfter - recipientBalanceBefore,
_expectedWithdrawalAmount,
"L1 recipient should receive the withdrawal amount"
);
}
}

/// @notice Relay a message from L2 to L1 via the CrossDomainMessenger
/// @dev This simulates the L2->L1 message relay by:
/// 1. Setting the portal's l2Sender to the L2CrossDomainMessenger
/// 2. Calling relayMessage on the L1CrossDomainMessenger from the portal
/// 3. Resetting the l2Sender back to the default value
/// @param _portal The OptimismPortal address
/// @param _l1Messenger The L1CrossDomainMessenger address
/// @param _sender The sender address on L2
/// @param _target The target address on L1
/// @param _value The ETH value to send
/// @param _minGasLimit The minimum gas limit for the message
/// @param _data The message data
function _relayL2ToL1Message(
address _portal,
address _l1Messenger,
address _sender,
address _target,
uint256 _value,
uint256 _minGasLimit,
bytes memory _data
) internal {
// Get the message nonce from the L1 messenger
uint256 _messageNonce = IL1CrossDomainMessenger(_l1Messenger).messageNonce();

// Set the l2Sender on the portal to the L2CrossDomainMessenger
// This is required for the L1CrossDomainMessenger to accept the message
stdstore.target(_portal).sig("l2Sender()").checked_write(L2_CROSS_DOMAIN_MESSENGER);

// Deal ETH to the portal so it can send value with the message
vm.deal(_portal, _value);

// Call relayMessage from the portal with the ETH value
vm.prank(_portal);
IL1CrossDomainMessenger(_l1Messenger).relayMessage{gas: _minGasLimit + RELAY_GAS_OVERHEAD, value: _value}(
_messageNonce, _sender, _target, _value, _minGasLimit, _data
);

// Reset the l2Sender back to the default value
stdstore.target(_portal).sig("l2Sender()").checked_write(DEFAULT_L2_SENDER);
}

/// @notice Relay a message from L1 to L2 via the CrossDomainMessenger
/// @dev This simulates the L1->L2 message relay by calling relayMessage on the L2CrossDomainMessenger.
/// Uses a unique nonce based on block.timestamp to avoid collisions with already-relayed messages on forks.
/// @param _aliasedL1Messenger The aliased L1 messenger address (sender on L2)
/// @param _sender The original sender on L1
/// @param _target The target address on L2
/// @param _value The ETH value to send
/// @param _minGasLimit The minimum gas limit for the message
/// @param _data The message data
function _relayL1ToL2Message(
address _aliasedL1Messenger,
address _sender,
address _target,
uint256 _value,
uint256 _minGasLimit,
bytes memory _data
) internal {
// Use a unique nonce to avoid "message already relayed" errors on forked networks.
// The nonce format is: version (16 bits) | nonce (240 bits)
// Version 1 is used for L1->L2 messages. We combine block.timestamp with a counter for uniqueness.
_l1ToL2NonceCounter++;
uint256 _messageNonce =
(uint256(1) << 240) | uint240(uint256(keccak256(abi.encode(block.timestamp, _l1ToL2NonceCounter))));
vm.deal(_aliasedL1Messenger, _value);
vm.prank(_aliasedL1Messenger);
// OP adds some extra gas for the relayMessage logic
ICrossDomainMessenger(L2_CROSS_DOMAIN_MESSENGER).relayMessage{
gas: _minGasLimit + RELAY_GAS_OVERHEAD,
value: _value
}(_messageNonce, _sender, _target, _value, _minGasLimit, _data);
}
}
Loading