Skip to content

feat: add Splitter contract with burn/distribute and FeeFlow integration#32

Merged
alexkeating merged 4 commits into
feat/split-methodfrom
feat/feeflow-split
Apr 2, 2026
Merged

feat: add Splitter contract with burn/distribute and FeeFlow integration#32
alexkeating merged 4 commits into
feat/split-methodfrom
feat/feeflow-split

Conversation

@marcomariscal
Copy link
Copy Markdown
Contributor

@marcomariscal marcomariscal commented Dec 16, 2025

Summary

  • Add Splitter contract that splits received ZK tokens between burning and distributing to configured recipients
  • Integrate FeeFlow with Splitter: claim() automatically calls split() on the destination
  • Add configurable burn percentage (0-100% in basis points)
  • Add fork integration tests against ZKsync Era mainnet with real ZK token
  • Add deployment script for both contracts with UUPS proxies

Test plan

  • Unit tests for Splitter
  • Unit tests for FeeFlow integration
  • Mock-based integration tests with fuzz values
  • Fork tests against ZKsync Era mainnet (requires ZKSYNC_MAINNET_RPC_URL secret)
  • All 111 tests passing

@marcomariscal marcomariscal force-pushed the feat/split-method branch 2 times, most recently from d86c01a to 278d63b Compare December 16, 2025 20:22
@marcomariscal marcomariscal linked an issue Dec 16, 2025 that may be closed by this pull request
@marcomariscal marcomariscal force-pushed the feat/feeflow-split branch 2 times, most recently from 914eb97 to e8e3352 Compare December 16, 2025 22:34
@marcomariscal marcomariscal changed the title feat: call split on destination in FeeFlow.claim feat: add Splitter contract with burn/distribute and FeeFlow integration Dec 16, 2025
@marcomariscal marcomariscal changed the base branch from feat/split-method to main January 6, 2026 18:03
@marcomariscal marcomariscal changed the base branch from main to feat/split-method January 6, 2026 18:03
@marcomariscal marcomariscal force-pushed the feat/split-method branch 2 times, most recently from fbe2edc to 2a3635e Compare January 16, 2026 18:16
Comment thread .github/workflows/ci.yml Outdated
- name: Run fork tests
env:
ZKSYNC_MAINNET_RPC_URL: ${{ secrets.ZKSYNC_MAINNET_RPC_URL }}
run: forge test --match-contract FeeFlowForkTest --zksync
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should probably split this by calling the integration test file rather than the contract

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Removed the fork-test split and the match/no-match plumbing by dropping the fork suite and consolidating on the mock integration tests. CI now just runs forge test.

Commit: 085b691

Comment thread .github/workflows/ci.yml Outdated

- name: Run coverage
run: forge coverage --report summary --report lcov
run: forge coverage --no-match-contract FeeFlowForkTest --report summary --report lcov
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why exclude these from coverage? If it is taking too long lower the fuzz runs really low

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Only for this step though

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Fork tests are removed, so we no longer exclude them from coverage/test runs.

Also re your follow-up ("Only for this step though"): since we removed the fork suite/job entirely, there isn't any step-specific special-casing left to scope.

Commit: 085b691

Comment thread script/Deploy.s.sol Outdated
import {ERC1967Proxy} from "lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

/// @title Deploy
/// @notice Deployment script for FeeFlow and Splitter contracts.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We probably want more detailed natspec

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Expanded NatSpec and refactored Deploy script to a MultiGov-style shape: run() loads env config via an internal helper and passes it to a shared deploy function.

Commit: 8d3315b

Comment thread script/Deploy.s.sol Outdated
/// @notice Deploys the Splitter and FeeFlow contracts with proxies.
/// @param _params The deployment parameters.
/// @return _result The deployed contract instances.
function deploy(DeploymentParams memory _params)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not put this in the run? I recommend looking at the deploy scripts for something like multigov where we define the configuration in an internal function and pass it to a shared run

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Refactored to match the MultiGov pattern: run() now calls an internal config builder and then passes the params to a shared deploy routine.

Commit: 8d3315b

Comment thread test/helpers/IntegrationTestBase.sol Outdated
/// @notice Sets up a claimer with ZK tokens and approval.
/// @param _claimer The claimer address.
/// @param _amount The amount of tokens to give and approve.
function _setupClaimer(address _claimer, uint256 _amount) internal {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
function _setupClaimer(address _claimer, uint256 _amount) internal {
function _mintAndApproveZkTokens(address _claimer, uint256 _amount) internal {

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Removed IntegrationTestBase along with the fork tests, so this suggestion is no longer applicable.

Commit: 085b691

Comment thread test/FeeFlow.fork.t.sol Outdated
feeFlow.setClaimableToken(IERC20(address(feeToken)), true);
}

function test_Fork_FullFlow_ClaimTriggersSplitAndBurn() public {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We don't need fork here, and prefer fuzz tests

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Agreed. Dropped the fork tests and consolidated on the mock integration tests (+ fuzz) so we don't depend on forking/mainnet RPC.

Commit: 085b691

Comment thread test/FeeFlow.fork.t.sol Outdated
);
}

function test_Fork_FullFlow_ZeroPercentBurn() public {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

U

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Removed the fork suite as part of consolidating on the mock integration tests.

Commit: 085b691

Comment thread test/FeeFlow.fork.t.sol Outdated

/// @title FeeFlow Fork Tests
/// @notice Fork tests against ZKsync Era mainnet with real ZK token.
contract FeeFlowForkTest is IntegrationTestBase {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It would be a little clearer if these were structured the same way as the unit tests

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Removed the fork-based FeeFlow suite and consolidated on the mock integration tests (plus fuzz coverage), so the integration tests are structured like the unit tests and don't require forking/ZKsync RPC setup.

Commit: 085b691

/// @dev These tests use mock tokens to verify the full integration flow.
/// For fork tests against ZKsync Era mainnet, use foundry-zksync:
/// https://github.com/matter-labs/foundry-zksync
contract FeeFlowIntegrationTest is Test {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

To me the integration and fork tests can be thought of as the same thing

Copy link
Copy Markdown
Contributor Author

@marcomariscal marcomariscal Feb 10, 2026

Choose a reason for hiding this comment

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

Agreed. Consolidated by removing the fork suite and keeping a single mock-based integration suite with fuzz coverage.

Commit: 085b691

@github-actions
Copy link
Copy Markdown

Coverage after merging feat/feeflow-split into feat/split-method will be

100.00%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src
   FeeFlow.sol100%100%100%100%
   Splitter.sol100%100%100%100%

@marcomariscal
Copy link
Copy Markdown
Contributor Author

marcomariscal commented Feb 11, 2026

@alexkeating quick question on burn ABI assumptions.

On ZKsync Sepolia, the ZK token at 0x06eb75609e3c8daebe26e479e7232edeb25bc1c2 exposes burn(address,uint256) (not burn(uint256)).

We currently model the bid token as burn(uint256) and Splitter burns via that interface.

Do you want Splitter to support both burn(uint256) and burn(address,uint256) (e.g. try burn(uint256) then fallback to burn(address(this), amount)), or should we standardize on a single signature and treat the other as out of scope?

@marcomariscal
Copy link
Copy Markdown
Contributor Author

@alexkeating quick question on burn ABI assumptions.

On ZKsync Sepolia, the ZK token at 0x06eb75609e3c8daebe26e479e7232edeb25bc1c2 exposes burn(address,uint256) (not burn(uint256)).

We currently model the bid token as burn(uint256) and Splitter burns via that interface.

Do you want Splitter to support both burn(uint256) and burn(address,uint256) (e.g. try burn(uint256) then fallback to burn(address(this), amount)), or should we standardize on a single signature and treat the other as out of scope?

Got confused looking at the sepolia zk contract and the abi/contract is different from actual zksync mainnet ZK token. No action needed here.

@alexkeating alexkeating merged commit fdf26ec into feat/split-method Apr 2, 2026
5 checks passed
alexkeating pushed a commit that referenced this pull request Apr 14, 2026
* Add Splitter contract with burn/distribute and FeeFlow integration
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.

Splitter Contract-04: Call split in fee flow claim

2 participants