Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
bcdc27a
Merge pull request #18 from defi-wonderland/chore/sync
0xDiscotech Sep 30, 2025
31e3d01
feat: add Revenue Share Upgrade Path (#8)
0xiamflux Sep 30, 2025
9310434
feat: Deploy FeesDepositor (#15)
0xiamflux Sep 30, 2025
129528c
feat: late opt-in template (#21)
0xiamflux Oct 6, 2025
47d480c
refactor: contracts update creationcode (#23)
0xiamflux Oct 6, 2025
e87344f
fix: sync (#24)
0xiamflux Oct 6, 2025
a8f4c70
test: add require tests for fields on Revenue Sharing templates (#26)
0xiamflux Oct 9, 2025
faee280
fix: L2 target ProxyAdmin (#27)
0xiamflux Oct 9, 2025
a65cb39
chore: update cost of upgrades and deployments (#29)
0xiamflux Oct 9, 2025
727e2dc
test: revenue sharing upgrade unit tests (#22)
0xChin Oct 15, 2025
9c5f1a2
test: add basic validation for fees depositor template (#32)
0xiamflux Oct 16, 2025
3a9fb15
chore: updates the bytecode for vaults, using initialize (#31)
0xiamflux Oct 16, 2025
944db58
test: integration supersim revshare (#30)
0xChin Oct 16, 2025
4e6f269
Merge branch 'main' into chore/sync-branch-revshare
Oct 17, 2025
5d968de
fix: broken simulations
Oct 17, 2025
bd67ee1
fix: wrong location of synced regression tests
Oct 17, 2025
4525ada
Merge pull request #37 from defi-wonderland/chore/sync-branch-revshare
0xDiscotech Oct 17, 2025
cf494c8
test: integration revshare late opt in (#33)
0xChin Oct 17, 2025
430b335
fix: ir fixes (#38)
0xDiscotech Oct 17, 2025
79c574a
Merge branch 'main' into chore/sync-branch-revshare
Oct 20, 2025
2448164
Merge pull request #42 from defi-wonderland/chore/sync-branch-revshare
0xDiscotech Oct 20, 2025
fe00ae7
chore: gas costs (#39)
0xChin Oct 20, 2025
a6ca033
fix: minors (#46)
0xChin Oct 24, 2025
143ba2e
fix: remove vm usage for getCreate2Address utils function (#48)
0xChin Oct 27, 2025
5d23831
feat: descope late opt in (#51)
0xChin Oct 28, 2025
300e54e
Merge branch 'main' into chore/sync-branch-revshare-v500
0xChin Nov 4, 2025
bd2ce05
Merge pull request #52 from defi-wonderland/chore/sync-branch-revshar…
0xDiscotech Nov 4, 2025
91a73ea
feat: add rev share upgrader (#53)
0xChin Nov 11, 2025
adf3de0
fix: bash on comment
0xDiscotech Nov 11, 2025
5361ed2
Merge branch 'main' into sc-feat/revshare-setup
0xDiscotech Nov 12, 2025
0666a5d
fix: forge fmt
0xDiscotech Nov 12, 2025
265d38e
refactor: reduce code size by splitting logic between lib and contrac…
0xDiscotech Nov 12, 2025
69b857c
refactor: split library (#60)
0xDiscotech Nov 12, 2025
09ad923
Merge branch 'main' into sc-feat/revshare-setup
0xDiscotech Nov 12, 2025
5142d69
feat: regression tests (#61)
0xChin Nov 12, 2025
6464067
fix: relay typo
0xDiscotech Nov 12, 2025
51d0459
fix: improve template validation checks (#62)
0xChin Nov 12, 2025
a5f7f3d
fix: wrong fee splitter bytecode (#63)
0xChin Nov 12, 2025
26ca8d6
Merge branch 'main' into sc-feat/revshare-setup
0xDiscotech Nov 12, 2025
a6e7094
fix: skip comment
0xDiscotech Nov 12, 2025
7373b09
fix: rename test create two deployer
0xDiscotech Nov 12, 2025
21dc253
chore: add commit
0xDiscotech Nov 12, 2025
79cbf92
feat: add upgrader deployment script (#64)
0xChin Nov 13, 2025
0c155c0
chore: update gas limits
0xChin Nov 13, 2025
a2d749d
refactor: use create two to get a deterministic deployment of the upg…
0xDiscotech Nov 13, 2025
fcc615d
chore: add todos (#66)
0xChin Nov 14, 2025
8b6d074
chore: remove tenderly simulations (#65)
0xChin Nov 14, 2025
c8d6eea
chore: deploy and update new contract (#67)
0xChin Nov 14, 2025
20e4142
fix: forge fmt (#69)
0xDiscotech Nov 14, 2025
0fb035c
chore: fuzz init code on get create2 test (#70)
0xDiscotech Nov 14, 2025
15fd88b
Merge branch 'main' into sc-feat/revshare-setup
0xDiscotech Nov 14, 2025
54b5bdf
feat: deploy revshare mainnet (#71)
0xChin Nov 17, 2025
b7e519d
fix: forge fmt (#72)
0xDiscotech Nov 17, 2025
70c7088
chore: add todo comments (#73)
0xChin Nov 17, 2025
5b67b4f
chore: reduce tenderly gas
0xDiscotech Nov 17, 2025
a7694d6
fix: reduce tenderly gas limit on sepolia (#77)
0xDiscotech Nov 17, 2025
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
6 changes: 5 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ jobs:
command: |
just install
forge --version
forge test -vvv
# Skip integration tests - they require RPC endpoints for L1/L2 fork simulation, and for now the unique
# test we have is for revenue sharing and is not the standard way of testing, for now we are excluding it
# from the CI.
# Run manually with: forge test --match-contract Integration
forge test --skip Integration -vvv
Comment thread
maurelian marked this conversation as resolved.
- notify-failures-on-main:
mentions: "@evm-safety-team"

Expand Down
113 changes: 113 additions & 0 deletions src/RevShareContractsUpgrader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {FeeVaultUpgrader} from "src/libraries/FeeVaultUpgrader.sol";
import {FeeSplitterSetup} from "src/libraries/FeeSplitterSetup.sol";
import {Utils} from "src/libraries/Utils.sol";
import {RevShareCommon} from "src/libraries/RevShareCommon.sol";

// Interfaces
import {IOptimismPortal2} from "@eth-optimism-bedrock/interfaces/L1/IOptimismPortal2.sol";
import {IProxyAdmin} from "@eth-optimism-bedrock/interfaces/universal/IProxyAdmin.sol";
import {ICreate2Deployer} from "src/interfaces/ICreate2Deployer.sol";
import {IFeeSplitter} from "src/interfaces/IFeeSplitter.sol";
import {IFeeVault} from "src/interfaces/IFeeVault.sol";

/// @title RevShareContractsUpgrader
/// @notice Upgrader contract that manages RevShare deployments and configuration via delegatecall.
/// @dev Supports two operations:
/// 1. setupRevShare() - Setup revenue sharing on already-upgraded contracts
/// 2. upgradeAndSetupRevShare() - Combined upgrade + setup (most efficient)
/// All operations use the default calculator (L1Withdrawer + SuperchainRevenueShareCalculator).
contract RevShareContractsUpgrader {
/// @notice Thrown when portal address is zero
error PortalCannotBeZeroAddress();

/// @notice Thrown when L1Withdrawer recipient is zero address
error L1WithdrawerRecipientCannotBeZeroAddress();

/// @notice Thrown when chain fees recipient is zero address
error ChainFeesRecipientCannotBeZeroAddress();

/// @notice Thrown when gas limit is zero
error GasLimitCannotBeZero();

/// @notice Thrown when array is empty
error EmptyArray();

/// @notice Emitted when a chain's RevShare setup deposits are completed
/// @param portal The portal address for the chain
/// @param chainIndex The index of the chain in the configs array
event ChainProcessed(address portal, uint256 chainIndex);

/// @notice Struct for RevShare setup configuration per chain.
/// @param portal OptimismPortal2 address for the target L2
/// @param l1WithdrawerConfig L1Withdrawer configuration
/// @param chainFeesRecipient Chain fees recipient address for the calculator
struct RevShareConfig {
address portal;
FeeSplitterSetup.L1WithdrawerConfig l1WithdrawerConfig;
address chainFeesRecipient;
}

/// @notice Upgrades vault and splitter contracts and sets up revenue sharing in one transaction for multiple chains.
/// This is the most efficient path as vaults are initialized with RevShare config from the start.
/// @param _configs Array of RevShare configuration structs, one per chain.
function upgradeAndSetupRevShare(RevShareConfig[] calldata _configs) external {
if (_configs.length == 0) revert EmptyArray();

for (uint256 i; i < _configs.length; i++) {
Comment thread
maurelian marked this conversation as resolved.
RevShareConfig calldata config = _configs[i];
if (config.portal == address(0)) revert PortalCannotBeZeroAddress();
Comment thread
0xDiscotech marked this conversation as resolved.
if (config.l1WithdrawerConfig.recipient == address(0)) revert L1WithdrawerRecipientCannotBeZeroAddress();
if (config.chainFeesRecipient == address(0)) revert ChainFeesRecipientCannotBeZeroAddress();
if (config.l1WithdrawerConfig.gasLimit == 0) revert GasLimitCannotBeZero();
Comment thread
0xDiscotech marked this conversation as resolved.

// Deploy L1Withdrawer and SuperchainRevenueShareCalculator
address precalculatedCalculator = FeeSplitterSetup.deployRevSharePeriphery(
config.portal, config.l1WithdrawerConfig, config.chainFeesRecipient
);

// Deploy and setup FeeSplitter
FeeSplitterSetup.deployAndSetupFeeSplitter(config.portal, precalculatedCalculator);
Comment thread
maurelian marked this conversation as resolved.

// Upgrade all 4 vaults with RevShare configuration (recipient=FeeSplitter, minWithdrawal=0, network=L2)
FeeVaultUpgrader.upgradeVaultsWithRevShareConfig(config.portal);

emit ChainProcessed(config.portal, i);
}
}
Comment thread
0xDiscotech marked this conversation as resolved.

/// @notice Enables revenue sharing after vaults have been upgraded and `FeeSplitter` initialized.
/// Deploys L1Withdrawer and calculator, then configures vaults and splitter for multiple chains.
/// @param _configs Array of RevShare configuration structs, one per chain.
function setupRevShare(RevShareConfig[] calldata _configs) external {
Comment thread
maurelian marked this conversation as resolved.
if (_configs.length == 0) revert EmptyArray();

for (uint256 i; i < _configs.length; i++) {
RevShareConfig calldata config = _configs[i];
if (config.portal == address(0)) revert PortalCannotBeZeroAddress();
if (config.l1WithdrawerConfig.recipient == address(0)) revert L1WithdrawerRecipientCannotBeZeroAddress();
if (config.chainFeesRecipient == address(0)) revert ChainFeesRecipientCannotBeZeroAddress();
if (config.l1WithdrawerConfig.gasLimit == 0) revert GasLimitCannotBeZero();

// Deploy L1Withdrawer and SuperchainRevenueShareCalculator
address calculator = FeeSplitterSetup.deployRevSharePeriphery(
config.portal, config.l1WithdrawerConfig, config.chainFeesRecipient
);

// Set calculator on fee splitter
RevShareCommon.depositCall(
config.portal,
RevShareCommon.FEE_SPLITTER,
FeeVaultUpgrader.SETTERS_GAS_LIMIT,
abi.encodeCall(IFeeSplitter.setSharesCalculator, (calculator))
);

// Configure all 4 vaults for revenue sharing
FeeVaultUpgrader.configureVaultsForRevShare(config.portal);

emit ChainProcessed(config.portal, i);
}
}
}
211 changes: 211 additions & 0 deletions src/doc/simulate-l2-deposit-transactions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Simulating L2 Deposit Transactions with Integration Tests

The following steps describe how to automatically simulate L2 deposit transactions prior to L1 task execution using integration tests. This approach is based on the [manual Tenderly simulation approach](./simulate-l2-ownership-transfer.md), with the difference that it uses a local supersim instance and automated transaction replay instead of manual Tenderly simulation.

## Overview

When executing L1 transactions that trigger L2 deposit transactions (via OptimismPortal), we can gain additional confidence by automatically replaying these deposit transactions on local L2 forks, simulating what op-node does. The `IntegrationBase` contract provides a `_relayAllMessages` function that:

1. Extracts all `TransactionDeposited` events from the L1 execution
2. Filters events by portal address to ensure only relevant events are relayed to each L2
3. Decodes the deposit transaction parameters
4. Executes each transaction on the corresponding L2 fork(s) with the correct sender
5. Asserts that all transactions succeed

This automated approach is particularly useful for:
- Complex tasks that emit multiple deposit transactions (e.g., revenue share upgrades with 12+ transactions per chain)
- Multi-chain deployments where the same L1 transaction affects multiple L2s

## Prerequisites

### Supersim Setup

You'll need to run supersim with forked chains to test against real network state. Supersim is a lightweight tool that runs local L1 and L2 nodes with forking capabilities.

Install supersim if you haven't already:

https://github.com/ethereum-optimism/supersim

Start supersim with forked chains for multiple L2s:

```bash
supersim fork --chains=op,ink
```

**Note:** You can specify any L2 chains supported by supersim (e.g., `op`, `base`, `mode`, `ink`, etc.). The default ports are:
- L1 (Ethereum): `http://127.0.0.1:8545`
- L2 (OP Mainnet): `http://127.0.0.1:9545`
- L2 (Ink Mainnet): `http://127.0.0.1:9546`
- Additional L2s will increment the port (9547, 9548, etc.)

For different L2 chains, adjust the RPC URLs and network IDs accordingly.

## Creating an Integration Test

### Step 1: Inherit from IntegrationBase

Create a test contract that inherits from `IntegrationBase`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {IntegrationBase} from "test/integration/IntegrationBase.t.sol";
import {YourTemplate} from "src/template/YourTemplate.sol";

contract YourIntegrationTest is IntegrationBase {
YourTemplate public template;

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

// Portal addresses (L1)
address internal constant OP_MAINNET_PORTAL = 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed;
address internal constant INK_MAINNET_PORTAL = 0x5d66C1782664115999C47c9fA5cd031f495D3e4F;

function setUp() public {
// Create forks pointing to supersim instances
_mainnetForkId = vm.createFork("http://127.0.0.1:8545");
_opMainnetForkId = vm.createFork("http://127.0.0.1:9545");
_inkMainnetForkId = vm.createFork("http://127.0.0.1:9546");

// Deploy template on L1 fork
vm.selectFork(_mainnetForkId);
template = new YourTemplate();
}
}
```

### Step 2: Execute L1 Transaction and Relay Messages
Comment thread
maurelian marked this conversation as resolved.

In your test function, execute the L1 transaction while recording logs, then relay all deposit messages to multiple L2s:

```solidity
function test_yourTask_integration() public {
string memory _configPath = "path/to/your/config.toml";

// Step 1: Record logs for L1→L2 message replay
vm.recordLogs();

// Step 2: Execute task simulation
template.simulate(_configPath);

// Step 3: Relay deposit transactions from L1 to all L2s
uint256[] memory forkIds = new uint256[](2);
forkIds[0] = _opMainnetForkId;
forkIds[1] = _inkMainnetForkId;

address[] memory portals = new address[](2);
portals[0] = OP_MAINNET_PORTAL;
portals[1] = INK_MAINNET_PORTAL;

// Pass true for _isSimulate since simulate() emits events twice
// (once during dry-run validation, once during actual simulation)
_relayAllMessages(forkIds, true, portals);

// Step 4: Assert the state of each L2 chain
vm.selectFork(_opMainnetForkId);
// Add OP Mainnet assertions here...

vm.selectFork(_inkMainnetForkId);
// Add Ink Mainnet assertions here...
}
```

### Step 3: Add State Assertions

After relaying messages, assert that the L2 state matches expectations:
Comment thread
0xDiscotech marked this conversation as resolved.

```solidity
// Example: Checking a contract's owner
assertEq(
OwnableUpgradeable(l2Contract).owner(),
vm.parseTomlAddress(_config, ".newOwner")
);

// Example: Checking a configuration value
assertEq(
IYourContract(l2Contract).someValue(),
vm.parseTomlUint(_config, ".expectedValue")
);
```

## Example: Revenue Share Integration Test

See [RevShareContractsUpgraderIntegration.t.sol](../../test/integration/RevShareContractsUpgraderIntegration.t.sol) for a complete example that:

- Tests multi-chain deployments (OP Mainnet and Ink Mainnet simultaneously)
- Validates multiple L2 contracts (L1Withdrawer, RevShareCalculator, FeeSplitter, FeeVaults)
- Asserts complex state relationships between contracts
- Uses portal filtering to ensure correct event routing

Key test structure:

```solidity
function test_upgradeAndSetupRevShare_integration() public {
// Step 1: Record logs for L1→L2 message replay
vm.recordLogs();

// Step 2: Execute task simulation
revShareTask.simulate("test/tasks/example/eth/016-revshare-upgrade-and-setup/config.toml");

// Step 3: Relay deposit transactions from L1 to all L2s
uint256[] memory forkIds = new uint256[](2);
forkIds[0] = _opMainnetForkId;
forkIds[1] = _inkMainnetForkId;

address[] memory portals = new address[](2);
portals[0] = OP_MAINNET_PORTAL;
portals[1] = INK_MAINNET_PORTAL;

_relayAllMessages(forkIds, IS_SIMULATE, portals);

// Step 4: Assert the state of the OP Mainnet contracts
vm.selectFork(_opMainnetForkId);
_assertL2State(OP_L1_WITHDRAWER, OP_REV_SHARE_CALCULATOR, ...);

// Step 5: Assert the state of the Ink Mainnet contracts
vm.selectFork(_inkMainnetForkId);
_assertL2State(INK_L1_WITHDRAWER, INK_REV_SHARE_CALCULATOR, ...);
}
```

## Understanding the Output

When you run an integration test, `_relayAllMessages` will output for each L2:

```
================================================================================
=== Relaying Deposit Transactions on L2 ===
=== Portal: 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed
=== Network is set to 10 ===
================================================================================

=== Summary ===
Total transactions processed: 11
Successful transactions: 11
Failed transactions: 0
```

This output repeats for each L2 chain being tested, with different portal addresses and chain IDs.

## Portal Filtering

The `_relayAllMessages` function filters events by portal address to ensure that only deposit transactions meant for a specific L2 are relayed to that chain. This is critical for multi-chain deployments where:

1. A single L1 transaction may emit deposit transactions to multiple L2 chains
2. Each L2 should only receive transactions deposited through its corresponding portal
3. The portal address identifies which OptimismPortal emitted the `TransactionDeposited` event

For example:
- Events from `0xbEb5Fc579115071764c7423A4f12eDde41f106Ed` (OP Mainnet Portal) → OP Mainnet fork
- Events from `0x5d66C1782664115999C47c9fA5cd031f495D3e4F` (Ink Mainnet Portal) → Ink Mainnet fork

This filtering prevents cross-contamination of deposit transactions between chains.

## Troubleshooting

### Fork Issues
When first running the fork test against supersim, do it with a `--match-test` that does only one fork for caching the network states. If you try to run more than one at the same time by, for example, using `--match-contract`, you might get timeout issues
Comment thread
0xDiscotech marked this conversation as resolved.
7 changes: 7 additions & 0 deletions src/interfaces/ICreate2Deployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

/// @notice Interface of the Create2 Preinstall in L2.
interface ICreate2Deployer {
function deploy(uint256 _value, bytes32 _salt, bytes memory _code) external;
}
9 changes: 9 additions & 0 deletions src/interfaces/IFeeSplitter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

/// @notice Interface for the FeeSplitter in L2.
interface IFeeSplitter {
function initialize(address _sharesCalculator) external;
function sharesCalculator() external view returns (address);
function setSharesCalculator(address _calculator) external;
}
23 changes: 23 additions & 0 deletions src/interfaces/IFeeVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

interface IFeeVault {
enum WithdrawalNetwork {
L1,
L2
}

function initialize(address _recipient, uint256 _minWithdrawalAmount, WithdrawalNetwork _withdrawalNetwork)
external;

function RECIPIENT() external view returns (address);
function MIN_WITHDRAWAL_AMOUNT() external view returns (uint256);
function WITHDRAWAL_NETWORK() external view returns (WithdrawalNetwork);
function minWithdrawalAmount() external view returns (uint256);
function recipient() external view returns (address);
function withdrawalNetwork() external view returns (WithdrawalNetwork);

function setRecipient(address _recipient) external;
function setMinWithdrawalAmount(uint256 _minWithdrawalAmount) external;
function setWithdrawalNetwork(WithdrawalNetwork _withdrawalNetwork) external;
}
8 changes: 8 additions & 0 deletions src/interfaces/IL1Withdrawer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

interface IL1Withdrawer {
function minWithdrawalAmount() external view returns (uint256);
function recipient() external view returns (address);
function withdrawalGasLimit() external view returns (uint32);
}
Loading