From 362b55bce85c863850631567722a1e10081bddf0 Mon Sep 17 00:00:00 2001 From: Rooh Afza Date: Tue, 23 Jan 2024 11:09:36 -0800 Subject: [PATCH] fix: smol fixes --- packages/splits-v2/src/SplitsWarehouse.sol | 2 +- packages/splits-v2/src/libraries/SplitV2.sol | 24 ++ .../splits-v2/src/splitters/SplitWalletV2.sol | 48 ++-- .../test/splitters/SplitWalletV2.t.sol | 252 ++++++++---------- 4 files changed, 170 insertions(+), 156 deletions(-) diff --git a/packages/splits-v2/src/SplitsWarehouse.sol b/packages/splits-v2/src/SplitsWarehouse.sol index 9c13329..868858b 100644 --- a/packages/splits-v2/src/SplitsWarehouse.sol +++ b/packages/splits-v2/src/SplitsWarehouse.sol @@ -127,7 +127,7 @@ contract SplitsWarehouse is ERC6909X { if (id == NATIVE_TOKEN_ID) { return nativeTokenSymbol.toString(); } - return string.concat(METADATA_PREFIX_SYMBOL, IERC20(id.toAddress()).name()); + return string.concat(METADATA_PREFIX_SYMBOL, IERC20(id.toAddress()).symbol()); } /** diff --git a/packages/splits-v2/src/libraries/SplitV2.sol b/packages/splits-v2/src/libraries/SplitV2.sol index a25bb2f..cc90d7f 100644 --- a/packages/splits-v2/src/libraries/SplitV2.sol +++ b/packages/splits-v2/src/libraries/SplitV2.sol @@ -88,6 +88,30 @@ library SplitV2Lib { } } + function getDistributionsMem( + Split memory _split, + uint256 _amount + ) + internal + pure + returns (uint256[] memory amounts, uint256 amountDistributed, uint256 distributorReward) + { + uint256 numOfRecipients = _split.recipients.length; + amounts = new uint256[](numOfRecipients); + + distributorReward = _amount * _split.distributionIncentive / PERCENTAGE_SCALE; + + _amount -= distributorReward; + + for (uint256 i = 0; i < numOfRecipients;) { + amounts[i] = _amount * _split.allocations[i] / _split.totalAllocation; + amountDistributed += amounts[i]; + unchecked { + ++i; + } + } + } + function calculateDistributorReward( uint16 _distributionIncentive, uint256 _amount diff --git a/packages/splits-v2/src/splitters/SplitWalletV2.sol b/packages/splits-v2/src/splitters/SplitWalletV2.sol index d127857..9526ec2 100644 --- a/packages/splits-v2/src/splitters/SplitWalletV2.sol +++ b/packages/splits-v2/src/splitters/SplitWalletV2.sol @@ -107,24 +107,32 @@ contract SplitWalletV2 is Wallet { */ function distribute(SplitV2Lib.Split calldata _split, address _token, address _distributor) external pausable { if (splitHash != _split.getHash()) revert InvalidSplit(); - (uint256 _splitBalance, uint256 _warehouseBalance) = _getSplitBalance(_token); + (uint256 splitBalance, uint256 warehouseBalance) = _getSplitBalance(_token); if (distributeByPush) { - if (_warehouseBalance > 1) { + if (warehouseBalance >= 2) { + _withdrawFromWarehouse(_token); unchecked { - _warehouseBalance -= 1; + warehouseBalance -= 1; + } + } else if (warehouseBalance > 0) { + unchecked { + warehouseBalance -= 1; } - _withdrawFromWarehouse(_token); } - pushDistribute(_split, _token, _splitBalance + _warehouseBalance, _distributor); + pushDistribute(_split, _token, warehouseBalance + splitBalance, _distributor); } else { - if (_splitBalance > 1) { + if (splitBalance >= 2) { + unchecked { + splitBalance -= 1; + } + _depositToWarehouse(_token, splitBalance); + } else if (splitBalance > 0) { unchecked { - _splitBalance -= 1; + splitBalance -= 1; } - _depositToWarehouse(_token, _splitBalance); } - pullDistribute(_split, _token, _splitBalance + _warehouseBalance, _distributor); + pullDistribute(_split, _token, warehouseBalance + splitBalance, _distributor); } } @@ -176,10 +184,10 @@ contract SplitWalletV2 is Wallet { /** * @notice Gets the total token balance of the split wallet and the warehouse * @param _token the token to get the balance of - * @return _splitBalance the token balance in the split wallet - * @return _warehouseBalance the token balance in the warehouse of the split wallet + * @return splitBalance the token balance in the split wallet + * @return warehouseBalance the token balance in the warehouse of the split wallet */ - function getSplitBalance(address _token) public view returns (uint256 _splitBalance, uint256 _warehouseBalance) { + function getSplitBalance(address _token) public view returns (uint256, uint256) { return _getSplitBalance(_token); } @@ -228,13 +236,13 @@ contract SplitWalletV2 is Wallet { } } - function _getSplitBalance(address _token) private view returns (uint256 _splitBalance, uint256 _warehouseBalance) { + function _getSplitBalance(address _token) private view returns (uint256 splitBalance, uint256 warehouseBalance) { if (_token == NATIVE) { - _splitBalance = address(this).balance; - _warehouseBalance = SPLITS_WAREHOUSE.balanceOf(address(this), _token.toUint256()); + splitBalance = address(this).balance; + warehouseBalance = SPLITS_WAREHOUSE.balanceOf(address(this), _token.toUint256()); } else { - _splitBalance = IERC20(_token).balanceOf(address(this)); - _warehouseBalance = SPLITS_WAREHOUSE.balanceOf(address(this), _token.toUint256()); + splitBalance = IERC20(_token).balanceOf(address(this)); + warehouseBalance = SPLITS_WAREHOUSE.balanceOf(address(this), _token.toUint256()); } } @@ -266,7 +274,7 @@ contract SplitWalletV2 is Wallet { } } - _distributor.safeTransferETH(distributorReward); + if (distributorReward > 0) _distributor.safeTransferETH(distributorReward); } else { for (uint256 i = 0; i < numOfRecipients;) { allocatedAmount = _amount * _split.allocations[i] / _split.totalAllocation; @@ -278,7 +286,7 @@ contract SplitWalletV2 is Wallet { } } - IERC20(_token).safeTransfer(_distributor, distributorReward); + if (distributorReward > 0) IERC20(_token).safeTransfer(_distributor, distributorReward); } emit SplitDistributed(_token, _distributor, amountDistributed, distributorReward, true); @@ -295,7 +303,7 @@ contract SplitWalletV2 is Wallet { { (uint256[] memory amounts, uint256 amountDistributed, uint256 distibutorReward) = _split.getDistributions(_amount); - SPLITS_WAREHOUSE.transfer(_distributor, _token.toUint256(), distibutorReward); + if (distibutorReward > 0) SPLITS_WAREHOUSE.transfer(_distributor, _token.toUint256(), distibutorReward); SPLITS_WAREHOUSE.batchTransfer(_token, _split.recipients, amounts); emit SplitDistributed(_token, _distributor, amountDistributed, distibutorReward, false); } diff --git a/packages/splits-v2/test/splitters/SplitWalletV2.t.sol b/packages/splits-v2/test/splitters/SplitWalletV2.t.sol index 7c1d5cb..4a460ac 100644 --- a/packages/splits-v2/test/splitters/SplitWalletV2.t.sol +++ b/packages/splits-v2/test/splitters/SplitWalletV2.t.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; +import { Clone } from "../../src/libraries/Clone.sol"; import { SplitV2Lib } from "../../src/libraries/SplitV2.sol"; import { SplitFactoryV2 } from "../../src/splitters/SplitFactoryV2.sol"; import { SplitWalletV2 } from "../../src/splitters/SplitWalletV2.sol"; import { Ownable } from "../../src/utils/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Pausable } from "../../src/utils/Pausable.sol"; import { BaseTest } from "../Base.t.sol"; @@ -38,7 +40,7 @@ contract SplitWalletV2Test is BaseTest { _createSplitParams = SplitFactoryV2.CreateSplitParams(splitWithNoIncentive, ALICE.addr, address(0)); walletWithNoIncentive = SplitWalletV2(splitFactory.createSplit(_createSplitParams)); - wallet = new SplitWalletV2(address(warehouse)); + wallet = SplitWalletV2(Clone.clone(address(new SplitWalletV2(address(warehouse))))); } /* -------------------------------------------------------------------------- */ @@ -208,173 +210,89 @@ contract SplitWalletV2Test is BaseTest { wallet.distribute(split, address(usdc), ALICE.addr); } - function testFuzz_distribute_ERC20_whenPaused_byOwner(uint256 _amount, bool _distributeByPush) public { - SplitV2Lib.Split memory split = getDefaultSplitWithNoIncentive(); + function testFuzz_distribute_whenPaused_byOwner( + uint96 _splitAmount, + uint96 _warehouseAmount, + bool _distributeByPush, + bool _native, + bool _incentive + ) + public + { + address token; + if (_native) token = native; + else token = address(usdc); - wallet.initialize(split, ALICE.addr); + SplitV2Lib.Split memory split; + if (_incentive) split = getDefaultSplitWithIncentive(); + else split = getDefaultSplitWithNoIncentive(); - _amount = bound(_amount, split.totalAllocation, type(uint160).max); + wallet.initialize(split, ALICE.addr); - deal(address(usdc), address(wallet), _amount); + dealSplit(address(wallet), token, _splitAmount, _warehouseAmount); vm.startPrank(ALICE.addr); wallet.updateDistributeDirection(_distributeByPush); wallet.setPaused(true); - wallet.distribute(split, address(usdc), ALICE.addr); + wallet.distribute(split, token, ALICE.addr); vm.stopPrank(); - assertAlmostEq(usdc.balanceOf(address(wallet)), 0, 10); - - if (_distributeByPush) { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(usdc.balanceOf(split.recipients[i]), 0); - } - } else { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(warehouse.balanceOf(split.recipients[i], tokenToId(address(usdc))), 0); - } - } - } - - function testFuzz_distribute_whenERC20WithoutIncentive(uint256 _amount, bool _distributeByPush) public { - SplitV2Lib.Split memory split = getDefaultSplitWithNoIncentive(); - - wallet.initialize(split, ALICE.addr); - - _amount = bound(_amount, split.totalAllocation, type(uint160).max); - - deal(address(usdc), address(wallet), _amount); - - vm.prank(ALICE.addr); - wallet.updateDistributeDirection(_distributeByPush); - - wallet.distribute(split, address(usdc), ALICE.addr); - - assertAlmostEq(usdc.balanceOf(address(wallet)), 0, 10); - - if (_distributeByPush) { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(usdc.balanceOf(split.recipients[i]), 0); - } - } else { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(warehouse.balanceOf(split.recipients[i], tokenToId(address(usdc))), 0); - } - } + assertDistribute(split, token, _warehouseAmount, _splitAmount, ALICE.addr, _distributeByPush, type(uint256).max); } - function testFuzz_distribute_whenERC20WithIncentive(uint256 _amount, bool _distributeByPush) public { - SplitV2Lib.Split memory split = getDefaultSplitWithIncentive(); - - wallet.initialize(split, ALICE.addr); - - _amount = bound(_amount, split.totalAllocation, type(uint160).max); - - deal(address(usdc), address(wallet), _amount); - - vm.prank(ALICE.addr); - wallet.updateDistributeDirection(_distributeByPush); - - wallet.distribute(split, address(usdc), ALICE.addr); - - assertAlmostEq(usdc.balanceOf(address(wallet)), 0, 10); - - if (_distributeByPush) { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(usdc.balanceOf(split.recipients[i]), 0); - } - assertGt(usdc.balanceOf(ALICE.addr), 0); - } else { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(warehouse.balanceOf(split.recipients[i], tokenToId(address(usdc))), 0); - } - assertGt(warehouse.balanceOf(ALICE.addr, tokenToId(address(usdc))), 0); - } - } + function testFuzz_distribute( + uint96 _splitAmount, + uint96 _warehouseAmount, + bool _distributeByPush, + bool _native, + bool _incentive + ) + public + { + address token; + if (_native) token = native; + else token = address(usdc); - function testFuzz_distribute_whenNativeWithoutIncentive(uint256 _amount, bool _distributeByPush) public { - SplitV2Lib.Split memory split = getDefaultSplitWithNoIncentive(); + SplitV2Lib.Split memory split; + if (_incentive) split = getDefaultSplitWithIncentive(); + else split = getDefaultSplitWithNoIncentive(); wallet.initialize(split, ALICE.addr); - _amount = bound(_amount, split.totalAllocation, type(uint160).max); - - deal(address(wallet), _amount); + dealSplit(address(wallet), token, _splitAmount, _warehouseAmount); vm.prank(ALICE.addr); wallet.updateDistributeDirection(_distributeByPush); - wallet.distribute(split, native, ALICE.addr); - - assertAlmostEq(address(wallet).balance, 0, 10); + wallet.distribute(split, token, ALICE.addr); - if (_distributeByPush) { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(split.recipients[i].balance, 0); - } - } else { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(warehouse.balanceOf(split.recipients[i], tokenToId(native)), 0); - } - } + assertDistribute(split, token, _warehouseAmount, _splitAmount, ALICE.addr, _distributeByPush, type(uint256).max); } - function testFuzz_distribute_whenNativeWithIncentive(uint256 _amount, bool _distributeByPush) public { - SplitV2Lib.Split memory split = getDefaultSplitWithIncentive(); - - wallet.initialize(split, ALICE.addr); - - _amount = bound(_amount, split.totalAllocation, type(uint160).max); - - deal(address(wallet), _amount); - - vm.prank(ALICE.addr); - wallet.updateDistributeDirection(_distributeByPush); - - uint256 distributorBalance = ALICE.addr.balance; - - wallet.distribute(split, native, ALICE.addr); - - assertAlmostEq(address(wallet).balance, 0, 10); - - if (_distributeByPush) { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(split.recipients[i].balance, 0); - } - assertGt(ALICE.addr.balance, distributorBalance); - } else { - for (uint256 i = 0; i < split.recipients.length; i++) { - assertGt(warehouse.balanceOf(split.recipients[i], tokenToId(native)), 0); - } - assertGt(warehouse.balanceOf(ALICE.addr, tokenToId(native)), 0); - } - } + function testFuzz_distribute_NativeByPush_whenRecipientReverts( + uint96 _splitAmount, + uint96 _warehouseAmount, + bool _incentive + ) + public + { + SplitV2Lib.Split memory split; + if (_incentive) split = getDefaultSplitWithIncentive(); + else split = getDefaultSplitWithNoIncentive(); - function testFuzz_distribute_pushNative_whenRecipientReverts(uint256 _amount) public { - SplitV2Lib.Split memory split = getDefaultSplitWithIncentive(); split.recipients[0] = BAD_ACTOR; wallet.initialize(split, ALICE.addr); - _amount = bound(_amount, split.totalAllocation, type(uint160).max); + uint256 splitAmount = bound(_splitAmount, split.totalAllocation, type(uint160).max); - deal(address(wallet), _amount); + dealSplit(address(wallet), native, splitAmount, _warehouseAmount); vm.prank(ALICE.addr); wallet.updateDistributeDirection(true); - uint256 distributorBalance = ALICE.addr.balance; - wallet.distribute(split, native, ALICE.addr); - - assertAlmostEq(address(wallet).balance, 0, 10); - assertGt(ALICE.addr.balance, distributorBalance); - - for (uint256 i = 0; i < split.recipients.length; i++) { - if (split.recipients[i] == BAD_ACTOR) assertEq(split.recipients[i].balance, 0); - else assertGt(split.recipients[i].balance, 0); - } - assertGt(warehouse.balanceOf(BAD_ACTOR, tokenToId(native)), 0); + assertDistribute(split, native, _warehouseAmount, splitAmount, ALICE.addr, true, 0); } function testFuzz_wallet_receiveEthEvent(uint256 _amount) public { @@ -401,4 +319,68 @@ contract SplitWalletV2Test is BaseTest { return createSplit(receivers, uint16(1.1e4)); } + + function dealSplit(address _split, address _token, uint256 _splitAmount, uint256 _warehouseAmount) internal { + if (_token == native) deal(_split, _splitAmount); + else deal(_token, _split, _splitAmount); + + address depositor = createUser("depositor").addr; + if (_token == native) deal(depositor, _warehouseAmount); + else deal(_token, depositor, _warehouseAmount); + + vm.startPrank(depositor); + + if (_token == native) { + warehouse.deposit{ value: _warehouseAmount }(_split, _token, _warehouseAmount); + } else { + IERC20(_token).approve(address(warehouse), _warehouseAmount); + warehouse.deposit(_split, _token, _warehouseAmount); + } + vm.stopPrank(); + } + + function assertDistribute( + SplitV2Lib.Split memory _split, + address _token, + uint256 _warehouseAmount, + uint256 _splitAmount, + address _distributor, + bool _distributeByPush, + uint256 _badRecipient + ) + internal + { + uint256 totalAmount = _warehouseAmount + _splitAmount; + if (_warehouseAmount > 0 && _distributeByPush == true) totalAmount -= 1; + if (_splitAmount > 0 && _distributeByPush == false) totalAmount -= 1; + (uint256[] memory amounts,, uint256 reward) = SplitV2Lib.getDistributionsMem(_split, totalAmount); + + if (_distributeByPush) { + if (_token == native) { + for (uint256 i = 0; i < _split.recipients.length; i++) { + if (i == _badRecipient) { + assertEq(_split.recipients[i].balance, 0); + assertEq(warehouse.balanceOf(_split.recipients[i], tokenToId(_token)), amounts[i]); + } else { + assertEq(_split.recipients[i].balance, amounts[i]); + } + } + if (reward > 0) { + assertEq(_distributor.balance, reward); + } + } else { + for (uint256 i = 0; i < _split.recipients.length; i++) { + assertEq(IERC20(_token).balanceOf(_split.recipients[i]), amounts[i]); + } + if (reward > 0) { + assertEq(IERC20(_token).balanceOf(_distributor), reward); + } + } + } else { + for (uint256 i = 0; i < _split.recipients.length; i++) { + assertEq(warehouse.balanceOf(_split.recipients[i], tokenToId(_token)), amounts[i]); + } + assertEq(warehouse.balanceOf(_distributor, tokenToId(_token)), reward); + } + } }