Skip to content

feat: add revenue sharing specs#47

Merged
0xDiscotech merged 35 commits intosc-feat/revenue-sharingfrom
feat/fee-splitter
Sep 19, 2025
Merged

feat: add revenue sharing specs#47
0xDiscotech merged 35 commits intosc-feat/revenue-sharingfrom
feat/fee-splitter

Conversation

@0xiamflux
Copy link
Copy Markdown

@0xiamflux 0xiamflux commented Aug 4, 2025

Adds predeploy spec for Jovian HF.

Closes OPT-973

@0xiamflux 0xiamflux self-assigned this Aug 4, 2025
@linear
Copy link
Copy Markdown

linear Bot commented Aug 4, 2025

OPT-973 Create a Spec PR

Comment thread specs/protocol/jovian/predeploys.md Outdated

A new predeploy contract, `FeeSplitter`, is introduced to manage the distribution of all fees collected on L2.
Instead of having immutable recipients, the `RECIPIENT` address in each of the four `FeeVault` contracts will
be set to the `FeeSplitter` contract on new chains, while existing chains will be able to opt-in to use this new predeploy.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All chains should be able to opt-in

@0xiamflux 0xiamflux changed the base branch from main to sc-feat/revenue-sharing August 4, 2025 21:28
Comment thread specs/protocol/jovian/predeploys.md Outdated
Comment on lines +428 to +441
#### `disburseFees`

Initiates the routing flow by withdrawing the fees that each of the fee vaults has collected and sends the shares
to the appropriate addresses according to the configured percentage.

```solidity
function disburseFees() external
```

- MUST emit `FeesDisbursed` event upon successful execution.
- MUST emit `NoFeesCollected` event if there are no funds available at the time of the call.
- MUST revert if not enough time has passed since the last successful execution.
- MUST send the appropriate amounts to the recipients.
- The balance of the contract MUST be 0 after a successful execution.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here should be an explanation on the behavior of this function when there are vaults that have a balance below their set setMinWithdrawalAmount, and what NoFeesCollected event means in this context (no fees collected because of minWithdrawalAmount reached or another condition)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a pretty good point.

Copy link
Copy Markdown
Author

@0xiamflux 0xiamflux Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed here.

Comment thread specs/protocol/jovian/predeploys.md Outdated

#### `NoFeesCollected`

Emitted when `disburseFees` is called but there are no funds available in the fee vaults at the time of execution.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest adding "no funds available to withdraw" to avoid confusion on the fact that the vaults have some funds most of the time (unless the specific fee mechanism is arbitrarily set, as is the case with the operatorFee in most of the chains)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed here.

@agusduha agusduha changed the title feat: add predeploys.md feat: add revenue sharing specs Aug 12, 2025
Comment thread specs/protocol/jovian/predeploys.md Outdated
Comment on lines +140 to +141
- Recipient A
- Recipient B
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to rename these names everywhere. We should define something more specific to the usecase, for example: Revenue share recipient and revenue rest recipient

Or something similar

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread specs/protocol/jovian/predeploys.md Outdated
The function MUST emit the `NoFeesCollected` event if the contract doesn't have any funds after the vaults have been withdrawn,
which can happen in scenarios where the vaults have a minimum withdrawal amount of 0.

On chains where any fee vault is not to be used and is not expected to maintain a balance in the contract,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this sentence is not clear enough

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be removed, since if there is no fees it won't revert but it just won't withdraw anything.

Comment thread specs/protocol/jovian/predeploys.md Outdated
Comment on lines +163 to +164
Currently this is the case for `OperatorFeeVault` on some chains, so setting its minimum withdrawal amount to
0 is needed on those chains.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? If the min amount is not met the vault is ignored, right?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread specs/protocol/jovian/predeploys.md Outdated

- MUST emit `FeesDisbursed` event upon successful execution.
- MUST emit `NoFeesCollected` event if there are no funds available in the contract after the vaults have been withdrawn.
- MUST revert if any vault has a balance below its minimum withdrawal amount.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong, we should not revert if there is some other vault with enough balance

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

@agusduha agusduha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try to adapt the specs to the PoC namings and invariants

@agusduha agusduha requested a review from 0xDiscotech August 12, 2025 13:25
Copy link
Copy Markdown

@Joxess Joxess left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These specs are quite good. You might want to review what we have in the PoC to maintain consistency between function types, variable declarations, naming, etc to ensure alignment

Comment thread specs/protocol/jovian/predeploys.md Outdated

| Name | Address | Introduced | Deprecated | Proxied |
| ----------- | ------------------------------------------ | ---------- | ---------- | ------- |
| FeeSplitter | 0x420000000000000000000000000000000000001C | Jovian | No | Yes |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check with the PoC on what the correct address is

Comment thread specs/protocol/jovian/predeploys.md Outdated
- MUST revert if any vault has a recipient different from this contract.
- MUST revert if any vault has a withdrawal network different from `WithdrawalNetwork.L2`.
- MUST withdraw the vault's fees balance if the vault's balance is equal to or greater than the minimum withdrawal amount set.
- MUST set the `lastDisbursementTime` to the current block timestamp.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is helpful to mention that this is updated only when disburseFees collects ETH

Comment thread specs/protocol/jovian/predeploys.md Outdated
## Security Considerations

- Given that vault recipients can now be updated, it's important to ensure that this can only be done by the appropriate address, namely `ProxyAdmin.owner()`.
- Upgrading the vaults and making them compatible with the `FeeSplitter` incurs a process where you have to deploy the new implementations and properly configure the vaults, which introduces complexity and potential for errors. It is important to develop a solution, such as a contract to manage the entire upgrade process, simplifying the UX and reducing the risk of errors.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Upgrading the vaults and making them compatible with the `FeeSplitter` incurs a process where you have to deploy the new implementations and properly configure the vaults, which introduces complexity and potential for errors. It is important to develop a solution, such as a contract to manage the entire upgrade process, simplifying the UX and reducing the risk of errors.
- Upgrading the vaults and making them compatible with the `FeeSplitter` incurs a process that requires deploying the new implementations and properly configuring the vaults, which introduces complexity and potential for errors. It is important to develop a solution, such as a contract to manage the entire upgrade process, simplifying the UX and reducing the risk of errors.

@tynes
Copy link
Copy Markdown

tynes commented Aug 19, 2025

Do you mind updating this with a mermaid diagram of the control flow so that its easier to visualize when explaining to people?

On deployment, the constructor will:

- Read and grab the current values for `recipient()`, `withdrawalNetwork()`, and `minWithdrawalAmount()` from each live vault (`SequencerFeeVault`, `L1FeeVault`, `BaseFeeVault`, `OperatorFeeVault`). Use a `try/catch` block to handle the case where the call fails because the vault is a legacy one, and query for the `RECIPIENT()` and `MIN_WITHDRAWAL_AMOUNT()` immutable values instead.
- Deploy a new implementation for each vault, passing the grabbed values per vault as constructor immutables, and also passing `L2` as the `WITHDRAWAL_NETWORK` constructor immutable.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we keep whatever this value is on the current fee vault? So if it says L1 deploy the new implementation with L1 as withdrawal network.


- Read and grab the current values for `recipient()`, `withdrawalNetwork()`, and `minWithdrawalAmount()` from each live vault (`SequencerFeeVault`, `L1FeeVault`, `BaseFeeVault`, `OperatorFeeVault`). Use a `try/catch` block to handle the case where the call fails because the vault is a legacy one, and query for the `RECIPIENT()` and `MIN_WITHDRAWAL_AMOUNT()` immutable values instead.
- Deploy a new implementation for each vault, passing the grabbed values per vault as constructor immutables, and also passing `L2` as the `WITHDRAWAL_NETWORK` constructor immutable.
- Emit a single event containing the four deployed implementation addresses
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we emit the old values or old implementation address for any reason?

Comment thread specs/protocol/jovian/fee-splitter.md Outdated

- All fee vaults MUST be correctly configured (`WithdrawalNetwork = L2` and `RECIPIENT = FeeSplitter`) or `disburseFees` MUST revert.
- `SharesCalculator` output MUST be valid: `sum(amounts) == totalCollected`, each `recipient != address(0)`, and each `withdrawalNetwork` is supported; otherwise `disburseFees` MUST revert.
- Disbursement MUST be atomic: if any individual payout fails (L2 transfer or L1 withdrawal), `disburseFees` MUST revert the entire transaction.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The splitter only does L2 transfers, L1 withdrawals are handled by the L1Withdrawer

Comment thread specs/protocol/jovian/fee-splitter.md Outdated
- All fee vaults MUST be correctly configured (`WithdrawalNetwork = L2` and `RECIPIENT = FeeSplitter`) or `disburseFees` MUST revert.
- `SharesCalculator` output MUST be valid: `sum(amounts) == totalCollected`, each `recipient != address(0)`, and each `withdrawalNetwork` is supported; otherwise `disburseFees` MUST revert.
- Disbursement MUST be atomic: if any individual payout fails (L2 transfer or L1 withdrawal), `disburseFees` MUST revert the entire transaction.
- If no funds are collected for the call, `disburseFees` MUST return early and MUST NOT consume the disbursement interval.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- If no funds are collected for the call, `disburseFees` MUST return early and MUST NOT consume the disbursement interval.
- If no funds are collected for the call, `disburseFees` MUST revert and MUST NOT consume the disbursement interval.

Also I would remove the "must not consume disbursement interval" given that revering means that any state change is reverted but up to u

Comment thread specs/protocol/jovian/fee-splitter.md Outdated
### Invariants

- All fee vaults MUST be correctly configured (`WithdrawalNetwork = L2` and `RECIPIENT = FeeSplitter`) or `disburseFees` MUST revert.
- `SharesCalculator` output MUST be valid: `sum(amounts) == totalCollected`, each `recipient != address(0)`, and each `withdrawalNetwork` is supported; otherwise `disburseFees` MUST revert.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Share info only consists of recipient and amount, withdrawal network does no longer exist in the ShareInfo struct

Comment thread specs/protocol/jovian/fee-splitter.md Outdated
- `SharesCalculator` output MUST be valid: `sum(amounts) == totalCollected`, each `recipient != address(0)`, and each `withdrawalNetwork` is supported; otherwise `disburseFees` MUST revert.
- Disbursement MUST be atomic: if any individual payout fails (L2 transfer or L1 withdrawal), `disburseFees` MUST revert the entire transaction.
- If no funds are collected for the call, `disburseFees` MUST return early and MUST NOT consume the disbursement interval.
- Cross‑function reentrancy MUST be prevented: `disburseFees` is non‑reentrant and `receive` MUST revert while a disbursement is in progress.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disburseFees is reentrant, and receive MUST NOT revert while a disbursement is in progress. What we actually do regarding receive + reentrancy:

receive reverts if:

  1. It's not in a fees disbursement context, meaning that receive() is only opened when disbursing fees, so when calling FeeVault.withdraw() it doesn't revert
  2. Caller is not one of the four fee vaults

Comment thread specs/protocol/jovian/fees-depositor.md
Comment thread specs/protocol/jovian/predeploys.md Outdated
Comment thread specs/protocol/jovian/predeploys.md Outdated
Comment thread specs/protocol/jovian/predeploys.md
0xiamflux and others added 4 commits September 18, 2025 18:36
Co-authored-by: Disco <131301107+0xDiscotech@users.noreply.github.com>
Signed-off-by: IamFlux <175354924+0xiamflux@users.noreply.github.com>
@0xDiscotech 0xDiscotech merged commit 0ca1934 into sc-feat/revenue-sharing Sep 19, 2025
1 check passed
@0xDiscotech 0xDiscotech deleted the feat/fee-splitter branch September 19, 2025 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants