feat: add split method to Splitter#31
Conversation
d0ca8b9 to
a88e08c
Compare
f0f33bc to
b3ca582
Compare
a98b4a6 to
d86c01a
Compare
d86c01a to
278d63b
Compare
|
|
||
| // Burn the burn amount + any dust from rounding | ||
| uint256 _totalBurned = _amount - _totalDistributed; | ||
| if (_totalBurned > 0) $._splitToken.burn(_totalBurned); |
There was a problem hiding this comment.
Why wouldn't we call with _burnAmount here
There was a problem hiding this comment.
This is documented in the @dev note: "Any dust from rounding is burned along with the burn portion." The _totalBurned captures both the intended burn amount and any dust from integer division in the distribution loop. Let me know if you'd like the inline comment or NatSpec to be clearer.
There was a problem hiding this comment.
What numbers are we talking about when it comes to dust? Also, I see n divisions for distributing and 1 for burnings so I would guess there is more dust from distribution meaning we would be burning an excess amount? We can also try to improve the precision by adding a scale factor like we do in staker. It all really depends what the dust numbers look like
There was a problem hiding this comment.
Addressed in 93b4daf: added explicit docs that burn() is called with burnAmount + dust (since dust is intentionally burned), and clarified the dust bound as <= distributors.length - 1.
| address internal admin; | ||
| address internal emergencyAdmin; | ||
| uint256 internal constant DEFAULT_BURN_BPS = 10_000; | ||
| uint256 internal constant BPS_DENOMINATOR = 10_000; |
There was a problem hiding this comment.
Why not reuse the constant above this?
There was a problem hiding this comment.
They represent different concepts: DEFAULT_BURN_BPS is the default burn percentage (happens to be 100%), while BPS_DENOMINATOR is the denominator for basis point math (always 10,000). They have the same value but different semantics - using DEFAULT_BURN_BPS in calculations would be confusing since we're not dividing by the burn percentage.
| splitter.setDistributors(_distributors); | ||
| vm.prank(admin); | ||
| splitter.setBurnPercentage(_burnBps); | ||
| } |
There was a problem hiding this comment.
I would split this into two methods and move to the base test contract. _addDistributors and _burn
There was a problem hiding this comment.
Addressed in 42f2d93 - extracted _addDistributors and _setBurnBps helpers to the base SplitterTest contract.
|
|
||
| function _mintToSplitter(uint256 _amount) internal { | ||
| splitToken.mint(address(splitter), _amount); | ||
| } |
There was a problem hiding this comment.
Is there an existign mint? We should probably reuse that.
There was a problem hiding this comment.
Addressed in b4ec820 - moved _mintToSplitter to the base SplitterTest contract.
| _setupDistributorAndBurn(_recipient, _weight, _burnBps); | ||
| _mintToSplitter(_amount); | ||
|
|
||
| uint256 _expectedBurn = (_amount * _burnBps) / BPS_DENOMINATOR; |
There was a problem hiding this comment.
We are testing the logic with the logic. If there is a bug in the logic we won't catch it.
There was a problem hiding this comment.
Maybe have a couple tests with hardcoded weights
There was a problem hiding this comment.
Addressed in 0c6c57f - added 5 concrete test cases with hardcoded expected values (e.g., 1000 tokens with 50% burn = 500 burned, 500 distributed).
There was a problem hiding this comment.
We can probably remove these, with the hardcode comment I made
| assertEq(splitToken.balanceOf(address(splitter)), 0); | ||
| } | ||
|
|
||
| function testFuzz_Split_100PercentBurn(uint256 _amount) public { |
There was a problem hiding this comment.
Make the names more sentence like so they read better in scopelint spec
There was a problem hiding this comment.
Addressed in 255ec3f - renamed tests to be more sentence-like (e.g., testFuzz_Split_BurnsAllTokensWhenBurnIsOneHundredPercent).
707d49b to
633a6a9
Compare
255ec3f to
a60c35c
Compare
a60c35c to
aba48be
Compare
|
|
||
| // Burn the burn amount + any dust from rounding | ||
| uint256 _totalBurned = _amount - _totalDistributed; | ||
| if (_totalBurned > 0) $._splitToken.burn(_totalBurned); |
There was a problem hiding this comment.
What numbers are we talking about when it comes to dust? Also, I see n divisions for distributing and 1 for burnings so I would guess there is more dust from distribution meaning we would be burning an excess amount? We can also try to improve the precision by adding a scale factor like we do in staker. It all really depends what the dust numbers look like
| _weight2 = _boundWeight(_weight2); | ||
| // Ensure weights don't overflow when added | ||
| vm.assume(_weight1 <= type(uint96).max / 2); | ||
| vm.assume(_weight2 <= type(uint96).max / 2); |
There was a problem hiding this comment.
It is probably best to remove these assumes and rely on more flexible bounding for 786 and 787
| assertEq(splitToken.balanceOf(address(splitter)), 0); | ||
| } | ||
|
|
||
| function testFuzz_Split_BurnsDustFromRounding( |
There was a problem hiding this comment.
This test doesn't test dust burning with a set burn percentage. We should test that case as well
There was a problem hiding this comment.
Also, Isn't this case tested in the earlier tests?
There was a problem hiding this comment.
Good call. Addressed in 2b90467: added an assume(_amount % 3 != 0) so the dust-rounding fuzz test always exercises the non-zero dust path (instead of occasionally duplicating the “no dust” cases).
| splitter.split(); | ||
|
|
||
| assertEq(splitToken.balanceOf(_recipient), _expectedDistribute); | ||
| assertEq(splitToken.balanceOf(address(splitter)), 0); |
There was a problem hiding this comment.
We don't assert that the burn happened. We should see total supply decrease by the burn
|
|
||
| splitter.split(); | ||
|
|
||
| assertEq(splitToken.balanceOf(_recipient), _expectedDistribute); |
There was a problem hiding this comment.
The recipient also could have a balance before the split
There was a problem hiding this comment.
Also we don't need to assert this instead we could assert burned + distributed == amount where burned = totalSupplyBefore - totalSupplyAfter and distrbuted = recipientBalanceAfter - recipientBalanceBefore. This would avoid us having to recreate the logic of the tests
There was a problem hiding this comment.
We should be checking these invariants in other tests as well
| _setupDistributorAndBurn(_recipient, _weight, _burnBps); | ||
| _mintToSplitter(_amount); | ||
|
|
||
| uint256 _expectedBurn = (_amount * _burnBps) / BPS_DENOMINATOR; |
There was a problem hiding this comment.
We can probably remove these, with the hardcode comment I made
fbe2edc to
2a3635e
Compare
| } | ||
|
|
||
| contract Split is SplitterTest { | ||
| function testFuzz_Split_DistributesToSingleDistributor( |
There was a problem hiding this comment.
We don't need the split in these tests
There was a problem hiding this comment.
Addressed in 1755681: removed the redundant Split_ prefix from test names inside contract Split (e.g. testFuzz_Split_... -> testFuzz_...).
| vm.prank(admin); | ||
| splitter.setDistributors(_distributors); | ||
| vm.prank(admin); | ||
| splitter.setBurnPercentage(0); |
There was a problem hiding this comment.
Can we use the setBurn bips helper here?
There was a problem hiding this comment.
Addressed in 84015c3: switched the direct setBurnPercentage(...) call to the existing _setBurnBps(...) helper for consistency.
| uint256 _burned = _totalSupplyBefore - splitToken.totalSupply(); | ||
|
|
||
| assertEq(_distributed + _burned, _splitterBalanceBefore); | ||
| assertLt(_burned, 2); |
There was a problem hiding this comment.
Maye be clear to do lte 1 for this and other tests.
There was a problem hiding this comment.
Addressed in dc4a1f3: tightened dust assertions from < 2 to <= 1 wei (and made the intent explicit in comments).
| assertEq(splitToken.balanceOf(address(splitter)), 0); | ||
| } | ||
|
|
||
| function testFuzz_Split_DistributesAllTokensWhenBurnIsZeroPercent( |
There was a problem hiding this comment.
Don't we test this in the first 2 tests
There was a problem hiding this comment.
Addressed in 963ceb4: removed the redundant 100% burn test since testFuzz_Split_NoDistributors already asserts the 100% burn + event path.
| assertEq(splitToken.balanceOf(address(splitter)), 0); | ||
| } | ||
|
|
||
| function testFuzz_Split_EmitsEvent( |
There was a problem hiding this comment.
We should speciify the event being tested in the name
There was a problem hiding this comment.
Addressed in 6f0749c: renamed the test to testFuzz_Split_EmitsSplitEvent to make it explicit which event is being asserted.
| splitter.split(); | ||
| } | ||
|
|
||
| function testFuzz_Split_ZeroBalance(address _recipient, uint256 _weight) public { |
There was a problem hiding this comment.
Good question, should we allow splitting with a 0 balance
There was a problem hiding this comment.
Addressed in 2d6e4a7: split() is explicitly documented and tested as a no-op when the splitter balance is 0 (no state changes and no Split event emitted).
| assertEq(splitToken.balanceOf(address(splitter)), 0); | ||
| } | ||
|
|
||
| function testFuzz_Split_NoDistributors(uint256 _amount) public { |
There was a problem hiding this comment.
Isn't this tested in the 100% burn test
There was a problem hiding this comment.
Addressed in 433b17f: kept the no-distributors case as an explicit event-emission assertion for the “no distributors / 100% burn” branch, and renamed the test to make that purpose clear (...NoDistributors_EmitsSplitEventAndBurnsAll).
| assertEq(splitToken.balanceOf(address(splitter)), 0); | ||
| } | ||
|
|
||
| function testFuzz_Split_AnyoneCanCall( |
There was a problem hiding this comment.
This should be in one of the default cases, I don't think we need this explicit test
There was a problem hiding this comment.
Addressed in c3a44d9: removed the dedicated testFuzz_Split_AnyoneCanCall test and folded the permissionless-call assertion into testFuzz_Split_DistributesToSingleDistributor by fuzzing _caller and calling split() under vm.prank(_caller).
| } | ||
|
|
||
| // Concrete test cases with hardcoded expected values | ||
| function test_Split_BurnsAndDistributesFiftyPercentEach() public { |
There was a problem hiding this comment.
I don't think we need these tests. We can fuzz the burn rate and assert the invariant we want to enforce. which is burn equals the burn percentage of the previous balance give or take 1 wei
There was a problem hiding this comment.
Addressed in 90c057e: removed the concrete hardcoded split cases and replaced them with a single fuzz invariant testFuzz_Split_BurnedIsExpectedBurnPlusAtMostOneWei (2 distributors so dust <= 1 wei, assert burned is expectedBurn or expectedBurn + 1).
| vm.prank(admin); | ||
| splitter.setDistributors(_distributors); | ||
| vm.prank(admin); | ||
| splitter.setBurnPercentage(_burnBps); |
There was a problem hiding this comment.
Can we reuse some helpers here?
There was a problem hiding this comment.
Addressed in cf9628b: introduced _setDistributors(...) + _addTwoDistributors(...) helpers and refactored the 2-distributor dust/burn tests to use them (also switched those spots to _setBurnBps(...) for consistency).
|
Coverage after merging feat/split-method into main will be
Coverage Report
|
|||||||||||||||||||||||||
Summary
split()method to Splitter contractChanges
split(uint256 _amount)function usingsafeTransferFromto pull tokensSplit(amount, burned, distributed)eventSplitter_AmountMismatcherror for fee-on-transfer token protectionTest Plan
forge test)scopelint check)Resolves #16