@@ -87,6 +87,8 @@ contract FeeSplitter_TestInit is CommonTest {
8787/// @title FeeSplitter_Initialize_Test
8888/// @notice Tests the initialization functions of the `FeeSplitter` contract.
8989contract FeeSplitter_Initialize_Test is FeeSplitter_TestInit {
90+ event Initialized (uint8 version );
91+
9092 /// @notice Test that re-initialization fails on the already-initialized predeploy
9193 function test_reinitialization_reverts () public {
9294 // The FeeSplitter at the predeploy address is already initialized through genesis
@@ -107,6 +109,28 @@ contract FeeSplitter_Initialize_Test is FeeSplitter_TestInit {
107109 assertEq (address (IFeeSplitter (payable (impl)).sharesCalculator ()), address (_defaultSharesCalculator));
108110 assertEq (IFeeSplitter (payable (impl)).feeDisbursementInterval (), 1 days);
109111 }
112+
113+ /// @notice Test that the implementation contract disables initializers in the constructor
114+ function test_feeSplitterImplementation_constructorDisablesInitializers () public {
115+ bytes memory creationCode = vm.getCode ("FeeSplitter.sol:FeeSplitter " );
116+ address implementation;
117+
118+ // Expect the Initialized event to be emitted
119+ vm.expectEmit (true , true , true , true );
120+ emit Initialized (type (uint8 ).max);
121+
122+ // Deploy the implementation contract
123+ assembly {
124+ implementation := create (0 , add (creationCode, 0x20 ), mload (creationCode))
125+ }
126+
127+ // Verify the implementation contract is not zero address
128+ assertTrue (implementation != address (0 ));
129+
130+ // Verify re-initialization fails
131+ vm.expectRevert ("Initializable: contract is already initialized " );
132+ IFeeSplitter (payable (implementation)).initialize (ISharesCalculator (address (_defaultSharesCalculator)));
133+ }
110134}
111135
112136/// @title FeeSplitter_Receive_Test
@@ -123,29 +147,27 @@ contract FeeSplitter_Receive_Test is FeeSplitter_TestInit {
123147
124148 /// @notice Test receive function from non-approved vault reverts even during disbursement
125149 function testFuzz_feeSplitterReceive_WhenNonFeeVault_Reverts (address _caller , uint256 _amount ) public {
126- _amount = bound (_amount, 1 ether, type (uint256 ).max);
127150 vm.assume (_caller != Predeploys.SEQUENCER_FEE_WALLET);
128151 vm.assume (_caller != Predeploys.BASE_FEE_VAULT);
129152 vm.assume (_caller != Predeploys.OPERATOR_FEE_VAULT);
130153 vm.assume (_caller != Predeploys.L1_FEE_VAULT);
131- vm.assume (_caller != address (0 ));
132154
133155 // Mock the _isTransientDisbursing() function to return true
134156 // This allows us to test the sender validation logic
135157 vm.mockCall (address (feeSplitter), abi.encodeWithSignature ("_isTransientDisbursing() " ), abi.encode (true ));
136158
137159 // Setup disbursement conditions but expect revert from non-approved sender
138160 vm.deal (_caller, _amount);
161+ vm.startPrank (_caller);
139162
140- vm.prank (_caller);
141163 // Now we test the actual sender validation
142164 vm.expectRevert (IFeeSplitter.FeeSplitter_SenderNotApprovedVault.selector );
143165 payable (address (feeSplitter)).call { value: _amount }("" );
144166 }
145167
146168 /// @notice Test receive function works during disbursement from SequencerFeeVault
147169 function test_feeSplitterReceive_SequencerFeeVault_Succeeds (uint256 _amount ) public {
148- _amount = bound (_amount, 1 ether , type (uint256 ).max);
170+ _amount = bound (_amount, 1 , type (uint256 ).max);
149171
150172 // Setup mocks - only sequencer vault has balance
151173 _mockFeeVaultForSuccessfulWithdrawal (Predeploys.SEQUENCER_FEE_WALLET, _amount);
@@ -179,7 +201,7 @@ contract FeeSplitter_Receive_Test is FeeSplitter_TestInit {
179201
180202 /// @notice Test receive function works during disbursement from BaseFeeVault
181203 function test_feeSplitterReceive_BaseFeeVault_Succeeds (uint256 _amount ) public {
182- _amount = bound (_amount, 1 ether , type (uint256 ).max);
204+ _amount = bound (_amount, 1 , type (uint256 ).max);
183205
184206 // Setup mocks - only sequencer vault has balance
185207 _mockFeeVaultForSuccessfulWithdrawal (Predeploys.SEQUENCER_FEE_WALLET, 0 );
@@ -213,7 +235,7 @@ contract FeeSplitter_Receive_Test is FeeSplitter_TestInit {
213235
214236 /// @notice Test receive function works during disbursement from L1FeeVault
215237 function test_feeSplitterReceive_L1FeeVault_Succeeds (uint256 _amount ) public {
216- _amount = bound (_amount, 1 ether , type (uint256 ).max);
238+ _amount = bound (_amount, 1 , type (uint256 ).max);
217239
218240 // Setup mocks - only sequencer vault has balance
219241 _mockFeeVaultForSuccessfulWithdrawal (Predeploys.SEQUENCER_FEE_WALLET, 0 );
@@ -247,7 +269,7 @@ contract FeeSplitter_Receive_Test is FeeSplitter_TestInit {
247269
248270 /// @notice Test receive function works during disbursement from OperatorFeeVault
249271 function test_feeSplitterReceive_OperatorFeeVault_Succeeds (uint256 _amount ) public {
250- _amount = bound (_amount, 1 ether , type (uint256 ).max);
272+ _amount = bound (_amount, 1 , type (uint256 ).max);
251273
252274 // Setup mocks - only sequencer vault has balance
253275 _mockFeeVaultForSuccessfulWithdrawal (Predeploys.SEQUENCER_FEE_WALLET, 0 );
@@ -296,7 +318,7 @@ contract FeeSplitter_DisburseFees_Test is FeeSplitter_TestInit {
296318 function test_feeSplitterDisburseFees_WhenNoFeesCollected_Reverts () public {
297319 _setupStandardFeeVaultMocks (0 , 0 , 0 , 0 );
298320
299- vm.warp (block .timestamp + 25 hours );
321+ vm.warp (block .timestamp + feeSplitter. feeDisbursementInterval () + 1 );
300322 vm.expectRevert (IFeeSplitter.FeeSplitter_NoFeesCollected.selector );
301323 feeSplitter.disburseFees ();
302324 }
@@ -310,7 +332,7 @@ contract FeeSplitter_DisburseFees_Test is FeeSplitter_TestInit {
310332 abi.encode (Types.WithdrawalNetwork.L1)
311333 );
312334
313- vm.warp (block .timestamp + 25 hours );
335+ vm.warp (block .timestamp + feeSplitter. feeDisbursementInterval () + 1 );
314336 vm.expectRevert (IFeeSplitter.FeeSplitter_FeeVaultMustWithdrawToL2.selector );
315337 feeSplitter.disburseFees ();
316338 }
@@ -327,7 +349,7 @@ contract FeeSplitter_DisburseFees_Test is FeeSplitter_TestInit {
327349 Predeploys.SEQUENCER_FEE_WALLET, abi.encodeCall (IFeeVault.recipient, ()), abi.encode (address (0x123 ))
328350 );
329351
330- vm.warp (block .timestamp + 25 hours );
352+ vm.warp (block .timestamp + feeSplitter. feeDisbursementInterval () + 1 );
331353 vm.expectRevert (IFeeSplitter.FeeSplitter_FeeVaultMustWithdrawToFeeSplitter.selector );
332354 feeSplitter.disburseFees ();
333355 }
@@ -363,7 +385,7 @@ contract FeeSplitter_DisburseFees_Test is FeeSplitter_TestInit {
363385 );
364386
365387 // Fast forward time to allow disbursement
366- vm.warp (block .timestamp + 25 hours );
388+ vm.warp (block .timestamp + feeSplitter. feeDisbursementInterval () + 1 );
367389
368390 // Store initial balances
369391 uint256 revenueShareRecipientBalanceBefore = address (_defaultRevenueShareRecipient).balance;
@@ -391,6 +413,68 @@ contract FeeSplitter_DisburseFees_Test is FeeSplitter_TestInit {
391413 // Verify the fee splitter has no balance
392414 assertEq (address (feeSplitter).balance, 0 );
393415 }
416+
417+ /// @notice Test disburseFees reverts when shares calculator returns an empty array
418+ function test_feeSplitterDisburseFees_WhenSharesInfoEmpty_Reverts () public {
419+ uint256 _sequencerAmount = 2 ether ;
420+ _setupStandardFeeVaultMocks (_sequencerAmount, 0 , 0 , 0 );
421+
422+ address actualSharesCalculator = address (feeSplitter.sharesCalculator ());
423+ ISharesCalculator.ShareInfo[] memory emptyShareInfo = new ISharesCalculator.ShareInfo [](0 );
424+ vm.mockCall (
425+ actualSharesCalculator,
426+ abi.encodeCall (ISharesCalculator.getRecipientsAndAmounts, (_sequencerAmount, 0 , 0 , 0 )),
427+ abi.encode (emptyShareInfo)
428+ );
429+
430+ vm.warp (block .timestamp + feeSplitter.feeDisbursementInterval () + 1 );
431+
432+ vm.expectRevert (IFeeSplitter.FeeSplitter_FeeShareInfoEmpty.selector );
433+ feeSplitter.disburseFees ();
434+ }
435+
436+ /// @notice Test disburseFees reverts when sending to a recipient fails
437+ function test_feeSplitterDisburseFees_WhenSendingFails_Reverts () public {
438+ uint256 _sequencerAmount = 1 ether ;
439+ _setupStandardFeeVaultMocks (_sequencerAmount, 0 , 0 , 0 );
440+
441+ address revertingRecipient = address (new RevertingRecipient ());
442+ ISharesCalculator.ShareInfo[] memory shareInfo = new ISharesCalculator.ShareInfo [](1 );
443+ shareInfo[0 ] = ISharesCalculator.ShareInfo (payable (revertingRecipient), _sequencerAmount);
444+
445+ address actualSharesCalculator = address (feeSplitter.sharesCalculator ());
446+ vm.mockCall (
447+ actualSharesCalculator,
448+ abi.encodeCall (ISharesCalculator.getRecipientsAndAmounts, (_sequencerAmount, 0 , 0 , 0 )),
449+ abi.encode (shareInfo)
450+ );
451+
452+ vm.warp (block .timestamp + feeSplitter.feeDisbursementInterval () + 1 );
453+
454+ vm.expectRevert (IFeeSplitter.FeeSplitter_FailedToSendToRevenueShareRecipient.selector );
455+ feeSplitter.disburseFees ();
456+ }
457+
458+ /// @notice Test disburseFees reverts when total shares do not match gross revenue
459+ function test_feeSplitterDisburseFees_WhenSharesMalformed_Reverts () public {
460+ uint256 _sequencerAmount = 1 ether ;
461+ _setupStandardFeeVaultMocks (_sequencerAmount, 0 , 0 , 0 );
462+
463+ ISharesCalculator.ShareInfo[] memory shareInfo = new ISharesCalculator.ShareInfo [](1 );
464+ shareInfo[0 ] = ISharesCalculator.ShareInfo (payable (_defaultRevenueShareRecipient), _sequencerAmount - 1 );
465+
466+ address actualSharesCalculator = address (feeSplitter.sharesCalculator ());
467+ vm.mockCall (
468+ actualSharesCalculator,
469+ abi.encodeCall (ISharesCalculator.getRecipientsAndAmounts, (_sequencerAmount, 0 , 0 , 0 )),
470+ abi.encode (shareInfo)
471+ );
472+
473+ vm.warp (block .timestamp + feeSplitter.feeDisbursementInterval () + 1 );
474+
475+ vm.expectRevert (IFeeSplitter.FeeSplitter_SharesCalculatorMalformedOutput.selector );
476+ feeSplitter.disburseFees ();
477+ }
394478}
395479
396480/// @title FeeSplitter_SetSharesCalculator_Test
@@ -500,7 +584,7 @@ contract FeeSplitter_DisburseFees_TestFail is FeeSplitter_TestInit {
500584 // Override the selected vault with insufficient balance
501585 _setFeeVaultData (vaults[_vaultIndex], insufficientBalance, _minWithdrawalAmount);
502586
503- vm.warp (block .timestamp + 25 hours );
587+ vm.warp (block .timestamp + feeSplitter. feeDisbursementInterval () + 1 );
504588
505589 // The entire disbursement should revert because one vault doesn't meet its minimum
506590 vm.expectRevert ("FeeVault: withdrawal amount must be greater than minimum withdrawal amount " );
@@ -625,3 +709,10 @@ contract MockFeeVault {
625709 return value;
626710 }
627711}
712+
713+ /// @notice Helper recipient that always reverts on receiving ETH
714+ contract RevertingRecipient {
715+ receive () external payable {
716+ revert ("RevertingRecipient: cannot accept ETH " );
717+ }
718+ }
0 commit comments