From 629242c60729e480fe4c7aa56e14f9f081042cb0 Mon Sep 17 00:00:00 2001 From: Will Minshew Date: Mon, 29 Jan 2024 13:32:19 -0800 Subject: [PATCH] initial comments/proposed changes (#1) * initial comments/proposed changes * remove caching comments * revert bug with else-if * use address _token in _withdraw * ERC6909X questions * add natspec for new Split struct field * updates from in-person discussion * fix: update tests * fix: remove unnecessary comments --------- Co-authored-by: Rooh Afza --- packages/splits-v2/foundry.toml | 2 +- packages/splits-v2/src/SplitsWarehouse.sol | 247 ++++++--------- .../src/interfaces/ISplitsWarehouse.sol | 2 +- packages/splits-v2/src/libraries/Cast.sol | 8 +- packages/splits-v2/src/libraries/SplitV2.sol | 19 +- .../src/splitters/SplitFactoryV2.sol | 96 +++--- .../splits-v2/src/splitters/SplitWalletV2.sol | 124 +++----- packages/splits-v2/test/Base.t.sol | 48 ++- .../test/splitters/SplitFactoryV2.t.sol | 55 ++-- .../test/splitters/SplitWalletV2.t.sol | 183 +++++------ .../test/warehouse/SplitsWarehouse.t.sol | 294 +++++++----------- .../test/warehouse/SplitsWarehouseHandler.sol | 29 +- 12 files changed, 450 insertions(+), 657 deletions(-) diff --git a/packages/splits-v2/foundry.toml b/packages/splits-v2/foundry.toml index 2e5453f..26e4ef3 100644 --- a/packages/splits-v2/foundry.toml +++ b/packages/splits-v2/foundry.toml @@ -14,7 +14,7 @@ fs_permissions = [{ access = "read-write", path = "./" }] [profile.default.fuzz] max_test_rejects = 1_000_000 -runs = 50 +runs = 1000 [profile.default.invariant] call_override = false diff --git a/packages/splits-v2/src/SplitsWarehouse.sol b/packages/splits-v2/src/SplitsWarehouse.sol index 868858b..6b832d1 100644 --- a/packages/splits-v2/src/SplitsWarehouse.sol +++ b/packages/splits-v2/src/SplitsWarehouse.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: UNLICENSED +// license? pragma solidity ^0.8.18; import { Cast } from "./libraries/Cast.sol"; @@ -13,7 +14,7 @@ import { ShortString, ShortStrings } from "@openzeppelin/contracts/utils/ShortSt /** * @title Splits Token Warehouse * @author Splits - * @notice ERC6909 compliant token warehouse for splits ecosystem of splitters + * @notice ERC6909 compliant token warehouse for Splits ecosystem * @dev Token id here is address(uint160(uint256 id)). */ contract SplitsWarehouse is ERC6909X { @@ -38,7 +39,6 @@ contract SplitsWarehouse is ERC6909X { /* EVENTS */ /* -------------------------------------------------------------------------- */ - event WithdrawalsPaused(address indexed owner, bool paused); event WithdrawConfigUpdated(address indexed owner, WithdrawConfig config); event Withdraw( address indexed owner, address indexed token, address indexed withdrawer, uint256 amount, uint256 reward @@ -58,27 +58,25 @@ contract SplitsWarehouse is ERC6909X { /* -------------------------------------------------------------------------- */ /// @notice prefix for metadata name. - string private constant METADATA_PREFIX_SYMBOL = "Splits"; + string private constant METADATA_PREFIX_NAME = "Splits Wrapped "; /// @notice prefix for metadata symbol. - string private constant METADATA_PREFIX_NAME = "Splits Wrapped "; + string private constant METADATA_PREFIX_SYMBOL = "splits"; - /// @notice address of the native token. + /// @notice address of the native token, inline with ERC 7528. address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice NATIVE_TOKEN.toUint256() uint256 public constant NATIVE_TOKEN_ID = 1_364_068_194_842_176_056_990_105_843_868_530_818_345_537_040_110; /// @notice metadata name of the native token. - ShortString private immutable nativeTokenName; + ShortString private immutable NATIVE_TOKEN_NAME; /// @notice metadata symbol of the native token. - ShortString private immutable nativeTokenSymbol; - - /// @notice Maximum incentive for withdrawing a token. - uint256 public constant MAX_INCENTIVE = 1e5; + ShortString private immutable NATIVE_TOKEN_SYMBOL; - /// @notice Scale for the incentive for withdrawing a token. + /// @notice Scale for any numbers representing percentages. + /// @dev Used for the token withdrawing incentive. uint256 public constant PERCENTAGE_SCALE = 1e6; /* -------------------------------------------------------------------------- */ @@ -98,8 +96,8 @@ contract SplitsWarehouse is ERC6909X { ) ERC6909X("SplitsWarehouse", "v1") { - nativeTokenName = _native_token_name.toShortString(); - nativeTokenSymbol = _native_token_symbol.toShortString(); + NATIVE_TOKEN_NAME = _native_token_name.toShortString(); + NATIVE_TOKEN_SYMBOL = _native_token_symbol.toShortString(); } /* -------------------------------------------------------------------------- */ @@ -109,11 +107,11 @@ contract SplitsWarehouse is ERC6909X { /** * @notice Name of a given token. * @param id The id of the token. - * @return name The name of the token. + * @return The name of the token. */ function name(uint256 id) external view returns (string memory) { if (id == NATIVE_TOKEN_ID) { - return nativeTokenName.toString(); + return NATIVE_TOKEN_NAME.toString(); } return string.concat(METADATA_PREFIX_NAME, IERC20(id.toAddress()).name()); } @@ -121,11 +119,11 @@ contract SplitsWarehouse is ERC6909X { /** * @notice Symbol of a given token. * @param id The id of the token. - * @return symbol The symbol of the token. + * @return The symbol of the token. */ function symbol(uint256 id) external view returns (string memory) { if (id == NATIVE_TOKEN_ID) { - return nativeTokenSymbol.toString(); + return NATIVE_TOKEN_SYMBOL.toString(); } return string.concat(METADATA_PREFIX_SYMBOL, IERC20(id.toAddress()).symbol()); } @@ -133,7 +131,7 @@ contract SplitsWarehouse is ERC6909X { /** * @notice Decimals of a given token. * @param id The id of the token. - * @return decimals The decimals of the token. + * @return The decimals of the token. */ function decimals(uint256 id) external view returns (uint8) { if (id == NATIVE_TOKEN_ID) { @@ -146,107 +144,77 @@ contract SplitsWarehouse is ERC6909X { /* PUBLIC/EXTERNAL FUNCTIONS */ /* -------------------------------------------------------------------------- */ - /* -------------------------------------------------------------------------- */ - /* DEPOSIT */ - /* -------------------------------------------------------------------------- */ - /** * @notice Deposits token to the warehouse for a specified address. * @dev If the token is native, the amount should be sent as value. - * @param _owner The address that will receive the wrapped tokens. + * @param _receiver The address that will receive the wrapped tokens. * @param _token The address of the token to be deposited. * @param _amount The amount of the token to be deposited. */ - function deposit(address _owner, address _token, uint256 _amount) external payable { - if (_owner == address(0)) revert ZeroOwner(); + function deposit(address _receiver, address _token, uint256 _amount) external payable { if (_token == NATIVE_TOKEN) { if (_amount != msg.value) revert InvalidAmount(); } else { IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); } - _mint(_owner, _token.toUint256(), _amount); + _mint(_receiver, _token.toUint256(), _amount); } - /* -------------------------------------------------------------------------- */ - /* WITHDRAW */ - /* -------------------------------------------------------------------------- */ - /** - * @notice Withdraws token from the warehouse for msg.sender. - * @dev It is recommended to withdraw balance - 1 to save gas. - * @param _owner The address whose tokens are withdrawn. - * @param _token The address of the token to be withdrawn. + * @notice Batch deposits token to the warehouse for the specified addresses from msg.sender. + * @dev If the token is native, the amount should be sent as value. + * @param _token The address of the token to be deposited. + * @param _receivers The addresses that will receive the wrapped tokens. + * @param _amounts The amounts of the token to be deposited. */ - function withdraw(address _owner, address _token) external { - uint256 amount = balanceOf[_owner][_token.toUint256()] - 1; - if (msg.sender == _owner) { - _withdraw(_owner, _token.toUint256(), _token, amount, _owner); - } else { - if (withdrawConfig[_owner].paused) revert WithdrawalPaused(_owner); - if (_owner == address(0)) revert ZeroOwner(); - _withdraw(_owner, _token.toUint256(), _token, amount, msg.sender); - } - } + function batchDeposit( + address[] calldata _receivers, + address _token, + uint256[] calldata _amounts + ) + external + payable + { + if (_receivers.length != _amounts.length) revert LengthMismatch(); - /** - * @notice Withdraws tokens from the warehouse for msg.sender. - * @dev It is recommended to withdraw balance - 1 to save gas. - * @param _owner The address whose tokens are withdrawn. - * @param _tokens The addresses of the tokens to be withdrawn. - */ - function withdraw(address _owner, address[] memory _tokens) external { + uint256 sum; uint256 amount; - uint256 tokenId; - address token; - - if (msg.sender == _owner) { - for (uint256 i; i < _tokens.length;) { - token = _tokens[i]; - tokenId = token.toUint256(); - amount = balanceOf[_owner][tokenId] - 1; - - _withdraw(_owner, tokenId, token, amount, _owner); + uint256 tokenId = _token.toUint256(); + uint256 length = _receivers.length; + for (uint256 i; i < length;) { + amount = _amounts[i]; + sum += amount; + _mint(_receivers[i], tokenId, amount); - unchecked { - ++i; - } + unchecked { + ++i; } - } else { - if (withdrawConfig[_owner].paused) revert WithdrawalPaused(_owner); - if (_owner == address(0)) revert ZeroOwner(); - - for (uint256 i; i < _tokens.length;) { - token = _tokens[i]; - tokenId = token.toUint256(); - amount = balanceOf[_owner][tokenId] - 1; - - _withdraw(_owner, tokenId, token, amount, msg.sender); + } - unchecked { - ++i; - } - } + if (_token == NATIVE_TOKEN) { + if (sum != msg.value) revert InvalidAmount(); + } else { + IERC20(_token).safeTransferFrom(msg.sender, address(this), sum); } } /** - * @notice Withdraws token from the warehouse for a specified address. - * @dev It is recommended to withdraw balance - 1 to save gas. + * @notice Withdraws token from the warehouse for _owner. + * @dev Bypasses withdrawal incentives. * @param _owner The address whose tokens are withdrawn. * @param _token The address of the token to be withdrawn. - * @param _amount The amount of the token to be withdrawn. - * @param _withdrawer The address that will receive the withdrawer incentive. */ - function withdraw(address _owner, address _token, uint256 _amount, address _withdrawer) external { - WithdrawConfig memory config = withdrawConfig[_owner]; - if (config.paused) revert WithdrawalPaused(_owner); - if (_owner == address(0)) revert ZeroOwner(); - - uint256 reward = _amount * config.incentive / PERCENTAGE_SCALE; + function withdraw(address _owner, address _token) external { + if (msg.sender != _owner && tx.origin != _owner) { + // nest to reduce gas in the happy-case (solidity/evm won't short circuit) + if (withdrawConfig[_owner].paused) { + revert WithdrawalPaused(_owner); + } + } - if (reward > 0) _withdraw(_owner, _token.toUint256(), _token, _amount, reward, _withdrawer); - else _withdraw(_owner, _token.toUint256(), _token, _amount, _withdrawer); + uint256 amount = balanceOf[_owner][_token.toUint256()] - 1; + _withdraw(_owner, _token, amount, msg.sender, 0); } /** @@ -265,57 +233,51 @@ contract SplitsWarehouse is ERC6909X { ) external { - WithdrawConfig memory config = withdrawConfig[_owner]; if (_tokens.length != _amounts.length) revert LengthMismatch(); + WithdrawConfig memory config = withdrawConfig[_owner]; if (config.paused) revert WithdrawalPaused(_owner); - if (_owner == address(0)) revert ZeroOwner(); - if (config.incentive > 0) { - uint256 reward; - for (uint256 i; i < _tokens.length;) { - reward = _amounts[i] * config.incentive / PERCENTAGE_SCALE; - _withdraw(_owner, _tokens[i].toUint256(), _tokens[i], _amounts[i], reward, _withdrawer); + uint256 reward; + uint256 length = _tokens.length; + for (uint256 i; i < length;) { + reward = _amounts[i] * config.incentive / PERCENTAGE_SCALE; + _withdraw(_owner, _tokens[i], _amounts[i], _withdrawer, reward); - unchecked { - ++i; - } - } - } else { - for (uint256 i; i < _tokens.length;) { - _withdraw(_owner, _tokens[i].toUint256(), _tokens[i], _amounts[i], _withdrawer); - - unchecked { - ++i; - } + unchecked { + ++i; } } } - /* -------------------------------------------------------------------------- */ - /* BATCH_TRANSFER */ - /* -------------------------------------------------------------------------- */ - /** - * @notice batch transfers tokens to the specified addresses from msg.sender. + * @notice Batch transfers tokens to the specified addresses from msg.sender. * @param _token The address of the token to be transferred. * @param _receivers The addresses of the receivers. * @param _amounts The amounts of the tokens to be transferred. */ - function batchTransfer(address _token, address[] calldata _receivers, uint256[] calldata _amounts) external { + function batchTransfer(address[] calldata _receivers, address _token, uint256[] calldata _amounts) external { + if (_receivers.length != _amounts.length) revert LengthMismatch(); + + uint256 sum; uint256 tokenId = _token.toUint256(); - for (uint256 i; i < _receivers.length;) { - transfer(_receivers[i], tokenId, _amounts[i]); + uint256 amount; + address receiver; + uint256 length = _receivers.length; + for (uint256 i; i < length;) { + receiver = _receivers[i]; + amount = _amounts[i]; + + sum += amount; + balanceOf[receiver][tokenId] += amount; + emit Transfer(msg.sender, msg.sender, receiver, tokenId, amount); unchecked { ++i; } } + balanceOf[msg.sender][tokenId] -= sum; } - /* -------------------------------------------------------------------------- */ - /* OWNER ACTIONS */ - /* -------------------------------------------------------------------------- */ - /** * @notice Sets the withdraw config for the msg.sender. * @param _config Includes the incentives for withdrawal and their paused state. @@ -325,57 +287,30 @@ contract SplitsWarehouse is ERC6909X { emit WithdrawConfigUpdated(msg.sender, _config); } - /* -------------------------------------------------------------------------- */ - /* VIEW */ - /* -------------------------------------------------------------------------- */ - - /** - * @notice Returns the withdraw config for a specified address. - * @param _owner The address whose withdraw config is returned. - * @return config The withdraw config for the specified address. - */ - function getWithdrawConfig(address _owner) external view returns (WithdrawConfig memory) { - return withdrawConfig[_owner]; - } - /* -------------------------------------------------------------------------- */ /* INTERNAL/PRIVATE */ /* -------------------------------------------------------------------------- */ - function _withdraw(address _owner, uint256 _id, address _token, uint256 _amount, address _withdrawer) internal { - _burn(_owner, _id, _amount); - - if (_token == NATIVE_TOKEN) { - payable(_owner).sendValue(_amount); - } else { - IERC20(_token).safeTransfer(_owner, _amount); - } - - emit Withdraw(_owner, _token, _withdrawer, _amount, 0); - } - function _withdraw( address _owner, - uint256 _id, address _token, uint256 _amount, - uint256 _reward, - address _withdrawer + address _withdrawer, + uint256 _reward ) internal { - _burn(_owner, _id, _amount); - - uint256 amount = _amount - _reward; + _burn(_owner, _token.toUint256(), _amount); + uint256 amountToOwner = _amount - _reward; if (_token == NATIVE_TOKEN) { - payable(_owner).sendValue(amount); - payable(_withdrawer).sendValue(_reward); + payable(_owner).sendValue(amountToOwner); + if (_reward != 0) payable(_withdrawer).sendValue(_reward); } else { - IERC20(_token).safeTransfer(_owner, amount); - IERC20(_token).safeTransfer(_withdrawer, _reward); + IERC20(_token).safeTransfer(_owner, amountToOwner); + if (_reward != 0) IERC20(_token).safeTransfer(_withdrawer, _reward); } - emit Withdraw(_owner, _token, _withdrawer, amount, _reward); + emit Withdraw(_owner, _token, _withdrawer, amountToOwner, _reward); } } diff --git a/packages/splits-v2/src/interfaces/ISplitsWarehouse.sol b/packages/splits-v2/src/interfaces/ISplitsWarehouse.sol index 6446ef1..b6f0053 100644 --- a/packages/splits-v2/src/interfaces/ISplitsWarehouse.sol +++ b/packages/splits-v2/src/interfaces/ISplitsWarehouse.sol @@ -8,7 +8,7 @@ interface ISplitsWarehouse is IERC6909 { function deposit(address _owner, address _token, uint256 _amount) external payable; - function batchTransfer(address _token, address[] memory _recipients, uint256[] memory _amounts) external; + function batchTransfer(address[] memory _recipients, address _token, uint256[] memory _amounts) external; function withdraw(address _owner, address _token) external; } diff --git a/packages/splits-v2/src/libraries/Cast.sol b/packages/splits-v2/src/libraries/Cast.sol index 2145e04..e59eee7 100644 --- a/packages/splits-v2/src/libraries/Cast.sol +++ b/packages/splits-v2/src/libraries/Cast.sol @@ -12,8 +12,10 @@ library Cast { return uint256(uint160(value)); } - function toUint160(uint256 x) internal pure returns (uint160) { - if (x >= 1 << 160) revert Overflow(); - return uint160(x); + function toUint160(uint256 x) internal pure returns (uint160 y) { + if (x >> 160 != 0) revert Overflow(); + assembly { + y := x + } } } diff --git a/packages/splits-v2/src/libraries/SplitV2.sol b/packages/splits-v2/src/libraries/SplitV2.sol index cc90d7f..e2fed4e 100644 --- a/packages/splits-v2/src/libraries/SplitV2.sol +++ b/packages/splits-v2/src/libraries/SplitV2.sol @@ -7,7 +7,6 @@ library SplitV2Lib { /* -------------------------------------------------------------------------- */ error InvalidSplit_TotalAllocationMismatch(); - error InvalidSplit_InvalidIncentive(); error InvalidSplit_LengthMismatch(); /* -------------------------------------------------------------------------- */ @@ -21,12 +20,15 @@ library SplitV2Lib { * @param allocations The allocations of the split * @param totalAllocation The total allocation of the split * @param distributionIncentive The incentive for distribution + * @param distributeByPush Whether the split balance should be pushed to recipients */ struct Split { address[] recipients; uint256[] allocations; uint256 totalAllocation; + // TODO: should incentive & byPush be saved in storage? uint16 distributionIncentive; + bool distributeByPush; } /* -------------------------------------------------------------------------- */ @@ -48,19 +50,18 @@ library SplitV2Lib { } function validate(Split calldata _split) internal pure { - uint256 numOfRecipients = _split.allocations.length; - if (_split.recipients.length != numOfRecipients) { + uint256 numOfRecipients = _split.recipients.length; + if (_split.allocations.length != numOfRecipients) { revert InvalidSplit_LengthMismatch(); } - uint256 totalAllocation; - for (uint256 i = 0; i < numOfRecipients;) { + uint256 totalAllocation; + for (uint256 i; i < numOfRecipients;) { totalAllocation += _split.allocations[i]; unchecked { ++i; } } - if (totalAllocation != _split.totalAllocation) revert InvalidSplit_TotalAllocationMismatch(); } @@ -76,10 +77,9 @@ library SplitV2Lib { amounts = new uint256[](numOfRecipients); distributorReward = _amount * _split.distributionIncentive / PERCENTAGE_SCALE; - _amount -= distributorReward; - for (uint256 i = 0; i < numOfRecipients;) { + for (uint256 i; i < numOfRecipients;) { amounts[i] = _amount * _split.allocations[i] / _split.totalAllocation; amountDistributed += amounts[i]; unchecked { @@ -100,10 +100,9 @@ library SplitV2Lib { amounts = new uint256[](numOfRecipients); distributorReward = _amount * _split.distributionIncentive / PERCENTAGE_SCALE; - _amount -= distributorReward; - for (uint256 i = 0; i < numOfRecipients;) { + for (uint256 i; i < numOfRecipients;) { amounts[i] = _amount * _split.allocations[i] / _split.totalAllocation; amountDistributed += amounts[i]; unchecked { diff --git a/packages/splits-v2/src/splitters/SplitFactoryV2.sol b/packages/splits-v2/src/splitters/SplitFactoryV2.sol index 2216832..a29ec64 100644 --- a/packages/splits-v2/src/splitters/SplitFactoryV2.sol +++ b/packages/splits-v2/src/splitters/SplitFactoryV2.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.18; +// TODO: do we want to use our clone or the minimal standard? import { Clone } from "../libraries/Clone.sol"; import { SplitV2Lib } from "../libraries/SplitV2.sol"; import { SplitWalletV2 } from "./SplitWalletV2.sol"; @@ -13,33 +14,11 @@ import { SplitWalletV2 } from "./SplitWalletV2.sol"; contract SplitFactoryV2 { using Clone for address; - /* -------------------------------------------------------------------------- */ - /* ERRORS */ - /* -------------------------------------------------------------------------- */ - - error ZeroAddress(); - /* -------------------------------------------------------------------------- */ /* EVENTS */ /* -------------------------------------------------------------------------- */ - event SplitCreated(address indexed split, CreateSplitParams _split); - - /* -------------------------------------------------------------------------- */ - /* STRUCT */ - /* -------------------------------------------------------------------------- */ - - /** - * @notice CreateSplitParams - * @param split Split struct - * @param owner Owner of the split - * @param creator Creator of the split - */ - struct CreateSplitParams { - SplitV2Lib.Split split; - address owner; - address creator; - } + event SplitCreated(address indexed split, SplitV2Lib.Split splitParams, address owner, address creator); /* -------------------------------------------------------------------------- */ /* STORAGE */ @@ -66,85 +45,112 @@ contract SplitFactoryV2 { /** * @notice Create a new split using create2 - * @param _createSplitParams CreateSplitParams struct + * @param _splitParams Params to create split with + * @param _owner Owner of created split + * @param _creator Creator of created split * @param _salt Salt for create2 */ function createSplitDeterministic( - CreateSplitParams calldata _createSplitParams, + SplitV2Lib.Split calldata _splitParams, + address _owner, + address _creator, bytes32 _salt ) external returns (address split) { - split = SPLIT_WALLET_IMPLEMENTATION.cloneDeterministic(_getSalt(_createSplitParams, _salt)); + split = SPLIT_WALLET_IMPLEMENTATION.cloneDeterministic(_getSalt(_splitParams, _owner, _salt)); - SplitWalletV2(split).initialize(_createSplitParams.split, _createSplitParams.owner); + SplitWalletV2(split).initialize(_splitParams, _owner); - emit SplitCreated(split, _createSplitParams); + emit SplitCreated(split, _splitParams, _owner, _creator); } /** * @notice Create a new split using create - * @param _createSplitParams CreateSplitParams struct + * @param _splitParams Params to create split with + * @param _owner Owner of created split + * @param _creator Creator of created split */ - function createSplit(CreateSplitParams calldata _createSplitParams) external returns (address split) { + function createSplit( + SplitV2Lib.Split calldata _splitParams, + address _owner, + address _creator + ) + external + returns (address split) + { split = SPLIT_WALLET_IMPLEMENTATION.clone(); - SplitWalletV2(split).initialize(_createSplitParams.split, _createSplitParams.owner); + SplitWalletV2(split).initialize(_splitParams, _owner); - emit SplitCreated(split, _createSplitParams); + emit SplitCreated(split, _splitParams, _owner, _creator); } /** * @notice Predict the address of a new split - * @param _createSplitParams CreateSplitParams struct + * @param _splitParams Params to create split with + * @param _owner Owner of created split * @param _salt Salt for create2 */ function predictDeterministicAddress( - CreateSplitParams calldata _createSplitParams, + SplitV2Lib.Split calldata _splitParams, + address _owner, bytes32 _salt ) external view returns (address) { - return _predictDeterministicAddress(_createSplitParams, _salt); + return _predictDeterministicAddress(_splitParams, _owner, _salt); } /** * @notice Predict the address of a new split and check if it is deployed - * @param _createSplitParams CreateSplitParams struct + * @param _splitParams Params to create split with + * @param _owner Owner of created split * @param _salt Salt for create2 */ function isDeployed( - CreateSplitParams calldata _createSplitParams, + SplitV2Lib.Split calldata _splitParams, + address _owner, bytes32 _salt ) external view - returns (address split, bool) + returns (address split, bool exists) { - split = _predictDeterministicAddress(_createSplitParams, _salt); - return (split, split.code.length > 0); + split = _predictDeterministicAddress(_splitParams, _owner, _salt); + exists = split.code.length > 0; } /* -------------------------------------------------------------------------- */ /* PRIVATE/INTERNAL FUNCTIONS */ /* -------------------------------------------------------------------------- */ - function _getSalt(CreateSplitParams calldata _createSplitParams, bytes32 _salt) internal pure returns (bytes32) { - return keccak256(bytes.concat(abi.encode(_createSplitParams), _salt)); + function _getSalt( + SplitV2Lib.Split calldata _splitParams, + address _owner, + bytes32 _salt + ) + internal + pure + returns (bytes32) + { + return keccak256(bytes.concat(abi.encode(_splitParams, _owner), _salt)); } function _predictDeterministicAddress( - CreateSplitParams calldata _createSplitParams, + SplitV2Lib.Split calldata _splitParams, + address _owner, bytes32 _salt ) internal view returns (address) { - return - SPLIT_WALLET_IMPLEMENTATION.predictDeterministicAddress(_getSalt(_createSplitParams, _salt), address(this)); + return SPLIT_WALLET_IMPLEMENTATION.predictDeterministicAddress( + _getSalt(_splitParams, _owner, _salt), address(this) + ); } } diff --git a/packages/splits-v2/src/splitters/SplitWalletV2.sol b/packages/splits-v2/src/splitters/SplitWalletV2.sol index 9526ec2..698b010 100644 --- a/packages/splits-v2/src/splitters/SplitWalletV2.sol +++ b/packages/splits-v2/src/splitters/SplitWalletV2.sol @@ -34,8 +34,7 @@ contract SplitWalletV2 is Wallet { /* EVENTS */ /* -------------------------------------------------------------------------- */ - event SplitUpdated(address indexed _owner, SplitV2Lib.Split _split); - event DistributeDirectionUpdated(bool _distributeByPush); + event SplitUpdated(SplitV2Lib.Split _split); event SplitDistributed( address indexed _token, address indexed _distributor, @@ -55,15 +54,12 @@ contract SplitWalletV2 is Wallet { address public immutable FACTORY; /// @notice address of native token - address public immutable NATIVE; + address public immutable NATIVE_TOKEN; /* -------------------------------------------------------------------------- */ /* STORAGE */ /* -------------------------------------------------------------------------- */ - /// @notice Controls the distribution direction of the split - bool public distributeByPush; - /// @notice the split hash - Keccak256 hash of the split struct bytes32 public splitHash; @@ -73,7 +69,7 @@ contract SplitWalletV2 is Wallet { constructor(address _splitWarehouse) { SPLITS_WAREHOUSE = ISplitsWarehouse(_splitWarehouse); - NATIVE = SPLITS_WAREHOUSE.NATIVE_TOKEN(); + NATIVE_TOKEN = SPLITS_WAREHOUSE.NATIVE_TOKEN(); FACTORY = msg.sender; } @@ -81,7 +77,7 @@ contract SplitWalletV2 is Wallet { * @notice Initializes the split wallet with a split and its corresponding data. * @dev Only the factory can call this function. By default, the distribution direction is push and distributions * are unpaused. - * @param split the split struct containing the split data that gets initialized + * @param split The split struct containing the split data that gets initialized */ function initialize(SplitV2Lib.Split calldata split, address _owner) external { if (msg.sender != FACTORY) revert UnauthorizedInitializer(); @@ -101,17 +97,17 @@ contract SplitWalletV2 is Wallet { * @notice Distributes the split to the recipients. It distributes the amount of tokens present in Warehouse and the * split wallet. * @dev The split must be initialized and the split hash must match the split hash of the split wallet. - * @param _split the split struct containing the split data that gets distributed - * @param _token the token to distribute - * @param _distributor the distributor of the split + * @param _split The split struct containing the split data that gets distributed + * @param _token The token to distribute + * @param _distributor The distributor of the split */ function distribute(SplitV2Lib.Split calldata _split, address _token, address _distributor) external pausable { if (splitHash != _split.getHash()) revert InvalidSplit(); - (uint256 splitBalance, uint256 warehouseBalance) = _getSplitBalance(_token); - if (distributeByPush) { - if (warehouseBalance >= 2) { - _withdrawFromWarehouse(_token); + (uint256 splitBalance, uint256 warehouseBalance) = getSplitBalance(_token); + if (_split.distributeByPush) { + if (warehouseBalance > 1) { + withdrawFromWarehouse(_token); unchecked { warehouseBalance -= 1; } @@ -122,11 +118,11 @@ contract SplitWalletV2 is Wallet { } pushDistribute(_split, _token, warehouseBalance + splitBalance, _distributor); } else { - if (splitBalance >= 2) { + if (splitBalance > 1) { unchecked { splitBalance -= 1; } - _depositToWarehouse(_token, splitBalance); + depositToWarehouse(_token, splitBalance); } else if (splitBalance > 0) { unchecked { splitBalance -= 1; @@ -139,17 +135,20 @@ contract SplitWalletV2 is Wallet { /** * @notice Distributes the split to the recipients. It distributes the amount of tokens present in Warehouse and the * split wallet. + * @dev The split must be initialized and the split hash must match the split hash of the split wallet. * @dev The amount of tokens to distribute must be present in the split wallet or the warehouse depending on the * distribution direction. * @param _split the split struct containing the split data that gets distributed - * @param _token the token to distribute - * @param _amount the amount of tokens to distribute - * @param _distributor the distributor of the split + * @param _token The token to distribute + * @param _distributeAmount The amount of tokens to distribute + * @param _warehouseTransferAmount The amount of tokens to transfer from/to the warehouse + * @param _distributor The distributor of the split */ function distribute( SplitV2Lib.Split calldata _split, address _token, - uint256 _amount, + uint256 _distributeAmount, + uint256 _warehouseTransferAmount, address _distributor ) external @@ -157,10 +156,12 @@ contract SplitWalletV2 is Wallet { { if (splitHash != _split.getHash()) revert InvalidSplit(); - if (distributeByPush) { - pushDistribute(_split, _token, _amount, _distributor); + if (_split.distributeByPush) { + if (_warehouseTransferAmount != 0) withdrawFromWarehouse(_token); + pushDistribute(_split, _token, _distributeAmount, _distributor); } else { - pullDistribute(_split, _token, _amount, _distributor); + if (_warehouseTransferAmount != 0) depositToWarehouse(_token, _warehouseTransferAmount); + pullDistribute(_split, _token, _distributeAmount, _distributor); } } @@ -169,16 +170,23 @@ contract SplitWalletV2 is Wallet { * @param _token the token to deposit * @param _amount the amount of tokens to deposit */ - function depositToWarehouse(address _token, uint256 _amount) external { - _depositToWarehouse(_token, _amount); + function depositToWarehouse(address _token, uint256 _amount) public { + if (_token == NATIVE_TOKEN) { + SPLITS_WAREHOUSE.deposit{ value: _amount }(address(this), _token, _amount); + } else { + if (IERC20(_token).allowance(address(this), address(SPLITS_WAREHOUSE)) < _amount) { + IERC20(_token).approve(address(SPLITS_WAREHOUSE), type(uint256).max); + } + SPLITS_WAREHOUSE.deposit(address(this), _token, _amount); + } } /** * @notice Withdraws tokens from the warehouse to the split wallet * @param _token the token to withdraw */ - function withdrawFromWarehouse(address _token) external { - _withdrawFromWarehouse(_token); + function withdrawFromWarehouse(address _token) public { + SPLITS_WAREHOUSE.withdraw(address(this), _token); } /** @@ -187,14 +195,11 @@ contract SplitWalletV2 is 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, uint256) { - return _getSplitBalance(_token); + function getSplitBalance(address _token) public view returns (uint256 splitBalance, uint256 warehouseBalance) { + splitBalance = (_token == NATIVE_TOKEN) ? address(this).balance : IERC20(_token).balanceOf(address(this)); + warehouseBalance = SPLITS_WAREHOUSE.balanceOf(address(this), _token.toUint256()); } - /* -------------------------------------------------------------------------- */ - /* OWNER FUNCTIONS */ - /* -------------------------------------------------------------------------- */ - /** * @notice Updates the split * @dev Only the owner can call this function. @@ -204,49 +209,14 @@ contract SplitWalletV2 is Wallet { // throws error if invalid _split.validate(); splitHash = _split.getHash(); - emit SplitUpdated(owner, _split); - } - - /** - * @notice Sets the split distribution direction to be push or pull - * @dev Only the owner can call this function. - * @param _distributeByPush whether to distribute by push or pull - */ - function updateDistributeDirection(bool _distributeByPush) external onlyOwner { - distributeByPush = _distributeByPush; - emit DistributeDirectionUpdated(_distributeByPush); + emit SplitUpdated(_split); } /* -------------------------------------------------------------------------- */ /* INTERNAL/PRIVATE */ /* -------------------------------------------------------------------------- */ - function _withdrawFromWarehouse(address _token) internal { - SPLITS_WAREHOUSE.withdraw(address(this), _token); - } - - function _depositToWarehouse(address _token, uint256 _amount) internal { - if (_token == NATIVE) { - SPLITS_WAREHOUSE.deposit{ value: _amount }(address(this), _token, _amount); - } else { - if (IERC20(_token).allowance(address(this), address(SPLITS_WAREHOUSE)) < _amount) { - IERC20(_token).approve(address(SPLITS_WAREHOUSE), type(uint256).max); - } - SPLITS_WAREHOUSE.deposit(address(this), _token, _amount); - } - } - - 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()); - } else { - splitBalance = IERC20(_token).balanceOf(address(this)); - warehouseBalance = SPLITS_WAREHOUSE.balanceOf(address(this), _token.toUint256()); - } - } - - // assumes the amount is already present in the split wallet + /// @dev Assumes the amount is already present in the split wallet function pushDistribute( SplitV2Lib.Split calldata _split, address _token, @@ -261,14 +231,15 @@ contract SplitWalletV2 is Wallet { uint256 amountDistributed; uint256 allocatedAmount; - if (_token == NATIVE) { - for (uint256 i = 0; i < numOfRecipients;) { + if (_token == NATIVE_TOKEN) { + for (uint256 i; i < numOfRecipients;) { allocatedAmount = _amount * _split.allocations[i] / _split.totalAllocation; amountDistributed += allocatedAmount; if (!_split.recipients[i].trySafeTransferETH(allocatedAmount, SafeTransferLib.GAS_STIPEND_NO_GRIEF)) { SPLITS_WAREHOUSE.deposit{ value: allocatedAmount }(_split.recipients[i], _token, allocatedAmount); } + unchecked { ++i; } @@ -276,11 +247,12 @@ contract SplitWalletV2 is Wallet { if (distributorReward > 0) _distributor.safeTransferETH(distributorReward); } else { - for (uint256 i = 0; i < numOfRecipients;) { + for (uint256 i; i < numOfRecipients;) { allocatedAmount = _amount * _split.allocations[i] / _split.totalAllocation; amountDistributed += allocatedAmount; IERC20(_token).safeTransfer(_split.recipients[i], allocatedAmount); + unchecked { ++i; } @@ -292,7 +264,7 @@ contract SplitWalletV2 is Wallet { emit SplitDistributed(_token, _distributor, amountDistributed, distributorReward, true); } - // assumes the amount is already deposited to the warehouse + /// @dev Assumes the amount is already deposited to the warehouse function pullDistribute( SplitV2Lib.Split calldata _split, address _token, @@ -303,8 +275,8 @@ contract SplitWalletV2 is Wallet { { (uint256[] memory amounts, uint256 amountDistributed, uint256 distibutorReward) = _split.getDistributions(_amount); + SPLITS_WAREHOUSE.batchTransfer(_split.recipients, _token, amounts); 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/Base.t.sol b/packages/splits-v2/test/Base.t.sol index 18ae713..8cf5515 100644 --- a/packages/splits-v2/test/Base.t.sol +++ b/packages/splits-v2/test/Base.t.sol @@ -117,58 +117,46 @@ contract BaseTest is PRBTest, StdCheats, StdInvariant, StdUtils { } } - function createSplit( - SplitReceiver[] memory _recievers, - uint16 _distributionIncentive + function createSplitParams( + SplitReceiver[] memory _receivers, + uint16 _distributionIncentive, + bool _distributeByPush ) internal pure returns (SplitV2Lib.Split memory) { + vm.assume(_receivers.length <= 100); uint256 totalAllocation; - uint256[] memory allocations = new uint256[](_recievers.length); - address[] memory recipients = new address[](_recievers.length); - - for (uint256 i = 0; i < _recievers.length; i++) { - totalAllocation += uint256(_recievers[i].allocation); - allocations[i] = _recievers[i].allocation; - recipients[i] = _recievers[i].receiver; + uint256[] memory allocations = new uint256[](_receivers.length); + address[] memory recipients = new address[](_receivers.length); + + for (uint256 i = 0; i < _receivers.length; i++) { + vm.assume(_receivers[i].receiver != address(0)); + totalAllocation += uint256(_receivers[i].allocation); + allocations[i] = _receivers[i].allocation; + recipients[i] = _receivers[i].receiver; } - return SplitV2Lib.Split(recipients, allocations, totalAllocation, _distributionIncentive); + return SplitV2Lib.Split(recipients, allocations, totalAllocation, _distributionIncentive, _distributeByPush); } function predictCreateAddress(address addr, uint256 nonce) internal pure returns (address) { return computeCreateAddress(addr, nonce); } - function getCreatSplitParams( - SplitReceiver[] memory _receivers, - uint16 _distributionIncentive, - address _owner, - address _creator - ) - internal - pure - returns (SplitFactoryV2.CreateSplitParams memory) - { - SplitV2Lib.Split memory splitParams = createSplit(_receivers, _distributionIncentive); - - return SplitFactoryV2.CreateSplitParams({ split: splitParams, owner: _owner, creator: _creator }); - } - function createSplit( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner, address _creator ) internal returns (address split) { - SplitFactoryV2.CreateSplitParams memory params = - getCreatSplitParams(_receivers, _distributionIncentive, _owner, _creator); - - split = splitFactory.createSplit(params); + split = splitFactory.createSplit( + createSplitParams(_receivers, _distributionIncentive, _distributeByPush), _owner, _creator + ); } } diff --git a/packages/splits-v2/test/splitters/SplitFactoryV2.t.sol b/packages/splits-v2/test/splitters/SplitFactoryV2.t.sol index 9d24a2a..a258f3f 100644 --- a/packages/splits-v2/test/splitters/SplitFactoryV2.t.sol +++ b/packages/splits-v2/test/splitters/SplitFactoryV2.t.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; +import { SplitV2Lib } from "../../src/libraries/SplitV2.sol"; import { SplitFactoryV2 } from "../../src/splitters/SplitFactoryV2.sol"; import { SplitWalletV2 } from "../../src/splitters/SplitWalletV2.sol"; import { BaseTest } from "../Base.t.sol"; contract SplitFactoryV2Test is BaseTest { - event SplitCreated(address indexed split, SplitFactoryV2.CreateSplitParams _split); + event SplitCreated(address indexed split, SplitV2Lib.Split splitParams, address owner, address creator); function setUp() public override { super.setUp(); @@ -16,19 +17,19 @@ contract SplitFactoryV2Test is BaseTest { function testFuzz_create2Split( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner, + address _creator, bytes32 _salt ) public { - SplitFactoryV2.CreateSplitParams memory params = - getCreatSplitParams(_receivers, _distributionIncentive, _owner, address(this)); - - address predictedAddress = splitFactory.predictDeterministicAddress(params, _salt); + SplitV2Lib.Split memory params = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); + address predictedAddress = splitFactory.predictDeterministicAddress(params, _owner, _salt); vm.expectEmit(); - emit SplitCreated(predictedAddress, params); - SplitWalletV2 split = SplitWalletV2(splitFactory.createSplitDeterministic(params, _salt)); + emit SplitCreated(predictedAddress, params, _owner, _creator); + SplitWalletV2 split = SplitWalletV2(splitFactory.createSplitDeterministic(params, _owner, _creator, _salt)); assertEq(predictedAddress, address(split)); assertEq(split.owner(), _owner); @@ -37,31 +38,33 @@ contract SplitFactoryV2Test is BaseTest { function testFuzz_create2Split_Revert_SplitAlreadyExists( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner, + address _creator, bytes32 _salt ) public { - testFuzz_create2Split(_receivers, _distributionIncentive, _owner, _salt); + testFuzz_create2Split(_receivers, _distributionIncentive, _distributeByPush, _owner, _creator, _salt); - SplitFactoryV2.CreateSplitParams memory params = - getCreatSplitParams(_receivers, _distributionIncentive, _owner, address(this)); + SplitV2Lib.Split memory params = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); vm.expectRevert(); - splitFactory.createSplitDeterministic(params, _salt); + splitFactory.createSplitDeterministic(params, _owner, _creator, _salt); } function testFuzz_createSplit( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, - address _owner + bool _distributeByPush, + address _owner, + address _creator ) public { - SplitFactoryV2.CreateSplitParams memory params = - getCreatSplitParams(_receivers, _distributionIncentive, _owner, address(this)); + SplitV2Lib.Split memory params = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); - SplitWalletV2 split = SplitWalletV2(splitFactory.createSplit(params)); + SplitWalletV2 split = SplitWalletV2(splitFactory.createSplit(params, _owner, _creator)); assertEq(split.owner(), _owner); } @@ -69,16 +72,17 @@ contract SplitFactoryV2Test is BaseTest { function testFuzz_createSplit_sameParams( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, - address _owner + bool _distributeByPush, + address _owner, + address _creator ) public { - testFuzz_createSplit(_receivers, _distributionIncentive, _owner); + testFuzz_createSplit(_receivers, _distributionIncentive, _distributeByPush, _owner, _creator); - SplitFactoryV2.CreateSplitParams memory params = - getCreatSplitParams(_receivers, _distributionIncentive, _owner, address(this)); + SplitV2Lib.Split memory params = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); - SplitWalletV2 split = SplitWalletV2(splitFactory.createSplit(params)); + SplitWalletV2 split = SplitWalletV2(splitFactory.createSplit(params, _owner, _owner)); assertEq(split.owner(), _owner); } @@ -86,21 +90,22 @@ contract SplitFactoryV2Test is BaseTest { function testFuzz_isDeployed( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner, + address _creator, bytes32 _salt ) public { - SplitFactoryV2.CreateSplitParams memory params = - getCreatSplitParams(_receivers, _distributionIncentive, _owner, address(this)); + SplitV2Lib.Split memory params = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); - (address predictedAddress, bool isDeployed) = splitFactory.isDeployed(params, _salt); + (address predictedAddress, bool isDeployed) = splitFactory.isDeployed(params, _owner, _salt); assertEq(isDeployed, false); - testFuzz_create2Split(_receivers, _distributionIncentive, _owner, _salt); + testFuzz_create2Split(_receivers, _distributionIncentive, _distributeByPush, _owner, _creator, _salt); - (predictedAddress, isDeployed) = splitFactory.isDeployed(params, _salt); + (predictedAddress, isDeployed) = splitFactory.isDeployed(params, _owner, _salt); assertEq(isDeployed, true); } diff --git a/packages/splits-v2/test/splitters/SplitWalletV2.t.sol b/packages/splits-v2/test/splitters/SplitWalletV2.t.sol index 4a460ac..8589812 100644 --- a/packages/splits-v2/test/splitters/SplitWalletV2.t.sol +++ b/packages/splits-v2/test/splitters/SplitWalletV2.t.sol @@ -12,11 +12,13 @@ import { Pausable } from "../../src/utils/Pausable.sol"; import { BaseTest } from "../Base.t.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { console2 } from "forge-std/console2.sol"; + contract SplitWalletV2Test is BaseTest { using SplitV2Lib for SplitV2Lib.Split; using Address for address; - event SplitUpdated(address indexed _owner, SplitV2Lib.Split _split); + event SplitUpdated(SplitV2Lib.Split _split); event SplitDistributionsPaused(bool _paused); event DistributeDirectionUpdated(bool _distributeByPush); event SplitDistributed(address indexed _token, uint256 _amount, address _distributor); @@ -31,15 +33,6 @@ contract SplitWalletV2Test is BaseTest { function setUp() public override { super.setUp(); - SplitV2Lib.Split memory splitWithIncentive = getDefaultSplitWithIncentive(); - SplitFactoryV2.CreateSplitParams memory _createSplitParams = - SplitFactoryV2.CreateSplitParams(splitWithIncentive, ALICE.addr, address(0)); - walletWithIncentive = SplitWalletV2(splitFactory.createSplit(_createSplitParams)); - - SplitV2Lib.Split memory splitWithNoIncentive = getDefaultSplitWithNoIncentive(); - _createSplitParams = SplitFactoryV2.CreateSplitParams(splitWithNoIncentive, ALICE.addr, address(0)); - walletWithNoIncentive = SplitWalletV2(splitFactory.createSplit(_createSplitParams)); - wallet = SplitWalletV2(Clone.clone(address(new SplitWalletV2(address(warehouse))))); } @@ -50,17 +43,17 @@ contract SplitWalletV2Test is BaseTest { function testFuzz_initialize( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner ) public { - SplitV2Lib.Split memory split = createSplit(_receivers, _distributionIncentive); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); wallet.initialize(split, _owner); assertEq(wallet.owner(), _owner); assertEq(address(wallet.SPLITS_WAREHOUSE()), address(warehouse)); - assertEq(wallet.distributeByPush(), false); assertEq(wallet.splitHash(), split.getHashMem()); } @@ -73,12 +66,13 @@ contract SplitWalletV2Test is BaseTest { function testFuzz_initialize_Revert_InvalidSplit_LengthMismatch( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner ) public { vm.assume(_receivers.length > 1); - SplitV2Lib.Split memory split = createSplit(_receivers, _distributionIncentive); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); address[] memory receivers = new address[](split.recipients.length - 1); split.recipients = receivers; @@ -90,11 +84,12 @@ contract SplitWalletV2Test is BaseTest { function testFuzz_initialize_Revert_InvalidSplit_TotalAllocationMismatch( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner ) public { - SplitV2Lib.Split memory split = createSplit(_receivers, _distributionIncentive); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); split.totalAllocation = split.totalAllocation + 1; @@ -109,27 +104,29 @@ contract SplitWalletV2Test is BaseTest { function testFuzz_updateSplit( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner ) public { - testFuzz_initialize(_receivers, _distributionIncentive, _owner); + testFuzz_initialize(_receivers, _distributionIncentive, _distributeByPush, _owner); - SplitV2Lib.Split memory split = createSplit(_receivers, _distributionIncentive); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); vm.expectEmit(); - emit SplitUpdated(_owner, split); + emit SplitUpdated(split); vm.prank(_owner); wallet.updateSplit(split); } function testFuzz_updateSplit_Revert_Unauthorized( SplitReceiver[] memory _receivers, - uint16 _distributionIncentive + uint16 _distributionIncentive, + bool _distributeByPush ) public { - SplitV2Lib.Split memory split = createSplit(_receivers, _distributionIncentive); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); vm.expectRevert(Ownable.Unauthorized.selector); wallet.updateSplit(split); @@ -138,13 +135,14 @@ contract SplitWalletV2Test is BaseTest { function testFuzz_updateSplit_Revert_InvalidSplit_LengthMismatch( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner ) public { vm.assume(_receivers.length > 1); - testFuzz_initialize(_receivers, _distributionIncentive, _owner); - SplitV2Lib.Split memory split = createSplit(_receivers, _distributionIncentive); + testFuzz_initialize(_receivers, _distributionIncentive, _distributeByPush, _owner); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); address[] memory receivers = new address[](split.recipients.length - 1); split.recipients = receivers; @@ -157,12 +155,13 @@ contract SplitWalletV2Test is BaseTest { function testFuzz_updateSplit_Revert_InvalidSplit_TotalAllocationMismatch( SplitReceiver[] memory _receivers, uint16 _distributionIncentive, + bool _distributeByPush, address _owner ) public { - testFuzz_initialize(_receivers, _distributionIncentive, _owner); - SplitV2Lib.Split memory split = createSplit(_receivers, _distributionIncentive); + testFuzz_initialize(_receivers, _distributionIncentive, _distributeByPush, _owner); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); split.totalAllocation = split.totalAllocation + 1; @@ -171,25 +170,18 @@ contract SplitWalletV2Test is BaseTest { wallet.updateSplit(split); } - function testFuzz_updateDistributeByPush(bool _distributeByPush) public { - vm.expectEmit(); - emit DistributeDirectionUpdated(_distributeByPush); - vm.prank(wallet.owner()); - wallet.updateDistributeDirection(_distributeByPush); - } - - function testFuzz_updateDistributeByPush_Revert_Unauthorized(bool _distributeByPush) public { - vm.expectRevert(Ownable.Unauthorized.selector); - wallet.updateDistributeDirection(_distributeByPush); - } - /* -------------------------------------------------------------------------- */ /* DISTRIBUTE FUNCTIONS */ /* -------------------------------------------------------------------------- */ - function test_distribute_Revert_whenPaused() public { - SplitV2Lib.Split memory split = getDefaultSplitWithNoIncentive(); - + function test_distribute_Revert_whenPaused( + SplitReceiver[] memory _receivers, + uint16 _distributionIncentive, + bool _distributeByPush + ) + public + { + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); wallet.initialize(split, ALICE.addr); vm.prank(ALICE.addr); @@ -199,88 +191,89 @@ contract SplitWalletV2Test is BaseTest { wallet.distribute(split, address(usdc), ALICE.addr); } - function test_distribute_Revert_whenInvalidSplit() public { - SplitV2Lib.Split memory split = getDefaultSplitWithNoIncentive(); - + function test_distribute_Revert_whenInvalidSplit( + SplitReceiver[] memory _receivers, + uint16 _distributionIncentive, + bool _distributeByPush + ) + public + { + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); wallet.initialize(split, ALICE.addr); - split.distributionIncentive += 1; + if (_distributionIncentive == type(uint16).max) { + split.distributionIncentive -= 1; + } else { + split.distributionIncentive += 1; + } vm.expectRevert(SplitWalletV2.InvalidSplit.selector); wallet.distribute(split, address(usdc), ALICE.addr); } function testFuzz_distribute_whenPaused_byOwner( - uint96 _splitAmount, - uint96 _warehouseAmount, + SplitReceiver[] memory _receivers, + uint16 _distributionIncentive, bool _distributeByPush, bool _native, - bool _incentive + uint96 _splitAmount, + uint96 _warehouseAmount ) public { + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); address token; if (_native) token = native; else token = address(usdc); - SplitV2Lib.Split memory split; - if (_incentive) split = getDefaultSplitWithIncentive(); - else split = getDefaultSplitWithNoIncentive(); - wallet.initialize(split, ALICE.addr); dealSplit(address(wallet), token, _splitAmount, _warehouseAmount); vm.startPrank(ALICE.addr); - wallet.updateDistributeDirection(_distributeByPush); wallet.setPaused(true); wallet.distribute(split, token, ALICE.addr); vm.stopPrank(); - assertDistribute(split, token, _warehouseAmount, _splitAmount, ALICE.addr, _distributeByPush, type(uint256).max); + assertDistribute(split, token, _warehouseAmount, _splitAmount, ALICE.addr); } function testFuzz_distribute( - uint96 _splitAmount, - uint96 _warehouseAmount, + SplitReceiver[] memory _receivers, + uint16 _distributionIncentive, bool _distributeByPush, bool _native, - bool _incentive + uint96 _splitAmount, + uint96 _warehouseAmount ) public { + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, _distributeByPush); address token; if (_native) token = native; else token = address(usdc); - SplitV2Lib.Split memory split; - if (_incentive) split = getDefaultSplitWithIncentive(); - else split = getDefaultSplitWithNoIncentive(); - wallet.initialize(split, ALICE.addr); dealSplit(address(wallet), token, _splitAmount, _warehouseAmount); - vm.prank(ALICE.addr); - wallet.updateDistributeDirection(_distributeByPush); - wallet.distribute(split, token, ALICE.addr); - assertDistribute(split, token, _warehouseAmount, _splitAmount, ALICE.addr, _distributeByPush, type(uint256).max); + assertDistribute(split, token, _warehouseAmount, _splitAmount, ALICE.addr); } function testFuzz_distribute_NativeByPush_whenRecipientReverts( + SplitReceiver[] memory _receivers, + uint16 _distributionIncentive, uint96 _splitAmount, - uint96 _warehouseAmount, - bool _incentive + uint96 _warehouseAmount ) public { - SplitV2Lib.Split memory split; - if (_incentive) split = getDefaultSplitWithIncentive(); - else split = getDefaultSplitWithNoIncentive(); - + vm.assume(_receivers.length > 1); + SplitV2Lib.Split memory split = createSplitParams(_receivers, _distributionIncentive, true); split.recipients[0] = BAD_ACTOR; + split.distributeByPush = true; wallet.initialize(split, ALICE.addr); @@ -288,36 +281,15 @@ contract SplitWalletV2Test is BaseTest { dealSplit(address(wallet), native, splitAmount, _warehouseAmount); - vm.prank(ALICE.addr); - wallet.updateDistributeDirection(true); - wallet.distribute(split, native, ALICE.addr); - assertDistribute(split, native, _warehouseAmount, splitAmount, ALICE.addr, true, 0); + assertDistribute(split, native, _warehouseAmount, splitAmount, ALICE.addr); } function testFuzz_wallet_receiveEthEvent(uint256 _amount) public { deal(address(this), _amount); vm.expectEmit(); emit ReceiveETH(_amount); - Address.sendValue(payable(address(walletWithIncentive)), _amount); - } - - function getDefaultSplitWithNoIncentive() internal pure returns (SplitV2Lib.Split memory) { - SplitReceiver[] memory receivers = new SplitReceiver[](10); - for (uint256 i = 100; i < 100 + receivers.length; i++) { - receivers[i - 100] = SplitReceiver(address(uint160(i + 1)), uint32(10)); - } - - return createSplit(receivers, 0); - } - - function getDefaultSplitWithIncentive() internal pure returns (SplitV2Lib.Split memory) { - SplitReceiver[] memory receivers = new SplitReceiver[](10); - for (uint256 i = 100; i < 100 + receivers.length; i++) { - receivers[i - 100] = SplitReceiver(address(uint160(i + 1)), uint32(10)); - } - - return createSplit(receivers, uint16(1.1e4)); + Address.sendValue(payable(address(wallet)), _amount); } function dealSplit(address _split, address _token, uint256 _splitAmount, uint256 _warehouseAmount) internal { @@ -344,43 +316,38 @@ contract SplitWalletV2Test is BaseTest { address _token, uint256 _warehouseAmount, uint256 _splitAmount, - address _distributor, - bool _distributeByPush, - uint256 _badRecipient + address _distributor ) internal { uint256 totalAmount = _warehouseAmount + _splitAmount; - if (_warehouseAmount > 0 && _distributeByPush == true) totalAmount -= 1; - if (_splitAmount > 0 && _distributeByPush == false) totalAmount -= 1; + if (_warehouseAmount > 0 && _split.distributeByPush == true) totalAmount -= 1; + if (_splitAmount > 0 && _split.distributeByPush == false) totalAmount -= 1; (uint256[] memory amounts,, uint256 reward) = SplitV2Lib.getDistributionsMem(_split, totalAmount); - if (_distributeByPush) { + if (_split.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]); - } + uint256 balance = address(_split.recipients[i]).balance + + warehouse.balanceOf(_split.recipients[i], tokenToId(_token)); + assertGte(balance, amounts[i]); } if (reward > 0) { - assertEq(_distributor.balance, reward); + assertGte(_distributor.balance, reward); } } else { for (uint256 i = 0; i < _split.recipients.length; i++) { - assertEq(IERC20(_token).balanceOf(_split.recipients[i]), amounts[i]); + assertGte(IERC20(_token).balanceOf(_split.recipients[i]), amounts[i]); } if (reward > 0) { - assertEq(IERC20(_token).balanceOf(_distributor), reward); + assertGte(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]); + assertGte(warehouse.balanceOf(_split.recipients[i], tokenToId(_token)), amounts[i]); } - assertEq(warehouse.balanceOf(_distributor, tokenToId(_token)), reward); + assertGte(warehouse.balanceOf(_distributor, tokenToId(_token)), reward); } } } diff --git a/packages/splits-v2/test/warehouse/SplitsWarehouse.t.sol b/packages/splits-v2/test/warehouse/SplitsWarehouse.t.sol index af23590..011c3e4 100644 --- a/packages/splits-v2/test/warehouse/SplitsWarehouse.t.sol +++ b/packages/splits-v2/test/warehouse/SplitsWarehouse.t.sol @@ -25,6 +25,11 @@ contract SplitsWarehouseTest is BaseTest, Fuzzer { address public token; address[] public defaultTokens; + struct Receiver { + address receiver; + uint32 amount; + } + function setUp() public override { super.setUp(); token = address(usdc); @@ -60,7 +65,7 @@ contract SplitsWarehouseTest is BaseTest, Fuzzer { /* -------------------------------------------------------------------------- */ function test_symbol_whenERC20_returnsWrappedERC20Symbol() public { - assertEq(warehouse.symbol(tokenToId(address(usdc))), string.concat("Splits", usdc.symbol())); + assertEq(warehouse.symbol(tokenToId(address(usdc))), string.concat("splits", usdc.symbol())); } function test_symbol_whenNativeToken_returnsWrappedSymbol() public { @@ -129,241 +134,160 @@ contract SplitsWarehouseTest is BaseTest, Fuzzer { warehouse.deposit{ value: 100 ether }(msg.sender, native, 99 ether); } - function test_depositSingleOwner_whenNativeToken_Revert_whenOwnerIsZero() public { - vm.assume(msg.sender != address(0)); - - vm.expectRevert(ZeroOwner.selector); - warehouse.deposit{ value: 100 ether }(address(0), native, 100 ether); - } - function test_depositSingleOwner_Revert_whenNonERC20() public { vm.expectRevert(); warehouse.deposit(msg.sender, address(this), 100 ether); } /* -------------------------------------------------------------------------- */ - /* TEST_WITHDRAW_OWNER */ + /* TEST_BATCH_DEPOSIT */ /* -------------------------------------------------------------------------- */ - function testFuzz_withdrawOwner_whenERC20(address _owner, uint256 _amount) public { - assumeAddress(_owner); + function testFuzz_batchDeposit(address _depositor, Receiver[] memory _receivers, bool _native) public { + assumeAddress(_depositor); - vm.assume(_amount > 0); + address _token = _native ? native : address(usdc); - testFuzz_depositSingleOwner_whenERC20(_owner, _owner, _amount); + uint256 totalAmount = getTotalAmount(_receivers); - vm.prank(_owner); - vm.expectEmit(); - emit Withdraw(_owner, token, _owner, _amount - 1, 0); - warehouse.withdraw(_owner, token); + if (_native) { + deal(_depositor, totalAmount); + } else { + deal(_token, _depositor, totalAmount); + vm.prank(_depositor); + ERC20(token).approve(address(warehouse), totalAmount); + } - assertEq(warehouse.balanceOf(_owner, tokenToId(token)), 1); - assertEq(ERC20(token).balanceOf(address(warehouse)), 1); - } + (address[] memory receivers, uint256[] memory amounts) = getReceiversAndAmounts(_receivers); - function testFuzz_withdrawOwner_whenNative(address _owner, uint256 _amount) public { - assumeAddress(_owner); + vm.prank(_depositor); + if (_native) { + warehouse.batchDeposit{ value: totalAmount }(receivers, _token, amounts); + } else { + warehouse.batchDeposit(receivers, _token, amounts); + } - vm.assume(_amount > 0); + for (uint256 i = 0; i < receivers.length; i++) { + assertGte(warehouse.balanceOf(receivers[i], tokenToId(_token)), amounts[i]); + } + } - testFuzz_depositSingleOwner_whenNativeToken(_owner, _owner, _amount); + function testFuzz_batchDeposit_Revert_InvalidAmount(address _depositor, Receiver[] memory _receivers) public { + assumeAddress(_depositor); - vm.prank(_owner); - vm.expectEmit(); - emit Withdraw(_owner, native, _owner, _amount - 1, 0); - warehouse.withdraw(_owner, native); + address _token = native; - assertEq(warehouse.balanceOf(_owner, tokenToId(native)), 1); - assertEq(address(warehouse).balance, 1); - } + uint256 totalAmount = getTotalAmount(_receivers); + vm.assume(totalAmount > 0); - function test_withdrawOwner_Revert_whenOwnerReenters() public { - address owner = BAD_ACTOR; + deal(_depositor, totalAmount); - deposit(owner, native, 100 ether); + (address[] memory receivers, uint256[] memory amounts) = getReceiversAndAmounts(_receivers); - vm.prank(owner); - vm.expectRevert(); - warehouse.withdraw(owner, native); + vm.prank(_depositor); + vm.expectRevert(InvalidAmount.selector); + warehouse.batchDeposit{ value: totalAmount - 1 }(receivers, _token, amounts); } - function test_withdrawOwner_Revert_whenNonERC20() public { - address owner = ALICE.addr; + function testFuzz_batchDeposit_Revert_LengthMismatch( + address[] memory _receivers, + uint256[] memory _amounts + ) + public + { + vm.assume(_receivers.length != _amounts.length); - vm.prank(owner); - vm.expectRevert(); - warehouse.withdraw(owner, address(this)); + vm.expectRevert(LengthMismatch.selector); + warehouse.batchDeposit(_receivers, token, _amounts); } /* -------------------------------------------------------------------------- */ - /* TEST_WITHDRAW_OWNER_MULTIPLE_TOKENS */ + /* BATCH_TRANSFER */ /* -------------------------------------------------------------------------- */ - function testFuzz_withdrawOwner_multipleTokens(uint256 _amount) public { - address owner = ALICE.addr; + function testFuzz_batchTransfer(address _depositor, Receiver[] memory _receivers) public { + assumeAddress(_depositor); - vm.assume(_amount > 0); + uint256 totalAmount = getTotalAmount(_receivers); - depositDefaultTokens(owner, _amount); + deal(_depositor, totalAmount); + vm.prank(_depositor); + warehouse.deposit{ value: totalAmount }(_depositor, native, totalAmount); - vm.prank(owner); - vm.expectEmit(); - for (uint256 i = 0; i < defaultTokens.length; i++) { - emit Withdraw(owner, defaultTokens[i], owner, _amount - 1, 0); - } - warehouse.withdraw(owner, defaultTokens); + (address[] memory receivers, uint256[] memory amounts) = getReceiversAndAmounts(_receivers); - for (uint256 i = 0; i < defaultTokens.length; i++) { - assertEq(warehouse.balanceOf(owner, tokenToId(defaultTokens[i])), 1); - if (defaultTokens[i] == native) { - assertEq(address(warehouse).balance, 1); - } else { - assertEq(ERC20(defaultTokens[i]).balanceOf(address(warehouse)), 1); - } + vm.prank(_depositor); + warehouse.batchTransfer(receivers, native, amounts); + + for (uint256 i = 0; i < receivers.length; i++) { + assertGte(warehouse.balanceOf(receivers[i], tokenToId(native)), amounts[i]); } } - function test_withdrawOwner_multipleTokens_Revert_whenOwnerReenters() public { - address owner = BAD_ACTOR; - - depositDefaultTokens(owner, 100 ether); + function testFuzz_batchTransfer_Revert_LengthMismatch( + address[] memory _receivers, + uint256[] memory _amounts + ) + public + { + vm.assume(_receivers.length != _amounts.length); - vm.prank(owner); - vm.expectRevert(); - warehouse.withdraw(owner, defaultTokens); + vm.expectRevert(LengthMismatch.selector); + warehouse.batchTransfer(_receivers, token, _amounts); } /* -------------------------------------------------------------------------- */ - /* WITHDRAW_FOR_OWNER_SINGLE_TOKEN */ + /* TEST_WITHDRAW_OWNER */ /* -------------------------------------------------------------------------- */ - function testFuzz_withdrawForOwner_singleToken_whenERC20(address _owner, uint256 _amount) public { + function testFuzz_withdrawOwner_whenERC20(address _owner, uint256 _amount) public { assumeAddress(_owner); + vm.assume(_amount > 0); + testFuzz_depositSingleOwner_whenERC20(_owner, _owner, _amount); + vm.prank(_owner); vm.expectEmit(); - emit Withdraw(_owner, token, address(this), _amount, 0); - warehouse.withdraw(_owner, token, _amount, address(this)); + emit Withdraw(_owner, token, _owner, _amount - 1, 0); + warehouse.withdraw(_owner, token); - assertEq(warehouse.balanceOf(_owner, tokenToId(token)), 0); - assertEq(ERC20(token).balanceOf(_owner), _amount); - assertEq(ERC20(token).balanceOf(address(warehouse)), 0); + assertEq(warehouse.balanceOf(_owner, tokenToId(token)), 1); + assertEq(ERC20(token).balanceOf(address(warehouse)), 1); } - function testFuzz_withdrawForOwner_singleToken_whenNative(address _owner, uint256 _amount) public { + function testFuzz_withdrawOwner_whenNative(address _owner, uint256 _amount) public { assumeAddress(_owner); + vm.assume(_amount > 0); + testFuzz_depositSingleOwner_whenNativeToken(_owner, _owner, _amount); + vm.prank(_owner); vm.expectEmit(); - emit Withdraw(_owner, native, address(this), _amount, 0); - warehouse.withdraw(_owner, native, _amount, address(this)); - - assertEq(warehouse.balanceOf(_owner, tokenToId(native)), 0); - assertEq(address(_owner).balance, _amount); - assertEq(address(warehouse).balance, 0); - } - - function test_withdrawForOwner_singleToken_Revert_whenWithdrawGreaterThanBalance() public { - address owner = ALICE.addr; - - testFuzz_depositSingleOwner_whenERC20(owner, owner, 100 ether); + emit Withdraw(_owner, native, _owner, _amount - 1, 0); + warehouse.withdraw(_owner, native); - vm.expectRevert(); - warehouse.withdraw(owner, token, 101 ether, address(this)); + assertEq(warehouse.balanceOf(_owner, tokenToId(native)), 1); + assertEq(address(warehouse).balance, 1); } - function test_withdrawForOwner_singleToken_Revert_whenOwnerReenters() public { + function test_withdrawOwner_Revert_whenOwnerReenters() public { address owner = BAD_ACTOR; - deposit(BAD_ACTOR, native, 1 ether); + deposit(owner, native, 100 ether); + vm.prank(owner); vm.expectRevert(); - warehouse.withdraw(owner, native, 1 ether, address(this)); + warehouse.withdraw(owner, native); } - function test_withdrawForOwner_singleToken_Revert_whenNonERC20() public { + function test_withdrawOwner_Revert_whenNonERC20() public { address owner = ALICE.addr; + vm.prank(owner); vm.expectRevert(); - warehouse.withdraw(owner, address(this), 100 ether, address(this)); - } - - function test_withdrawForOwner_singleToken_Revert_whenWithdrawalPaused() public { - address owner = ALICE.addr; - - testFuzz_depositSingleOwner_whenERC20(owner, owner, 100 ether); - - SplitsWarehouse.WithdrawConfig memory config = SplitsWarehouse.WithdrawConfig({ incentive: 0, paused: true }); - - vm.startPrank(owner); - warehouse.setWithdrawConfig(config); - - vm.expectRevert(abi.encodeWithSelector(WithdrawalPaused.selector, owner)); - warehouse.withdraw(owner, token, 100 ether, address(this)); - vm.stopPrank(); - } - - function test_withdrawForOwner_singleToken_Revert_whenZeroOwner() public { - vm.expectRevert(ZeroOwner.selector); - warehouse.withdraw(address(0), token, 100 ether, address(this)); - } - - function testFuzz_withdrawForOwner_singleToken_whenERC20WithIncentive( - address _owner, - uint192 _amount, - uint16 _incentive, - address _withdrawer - ) - public - { - assumeAddress(_owner); - assumeAddress(_withdrawer); - vm.assume(_owner != _withdrawer); - vm.assume(_withdrawer.balance == 0); - - testFuzz_depositSingleOwner_whenERC20(_owner, _owner, _amount); - testFuzz_setWithdrawalConfig(_owner, SplitsWarehouse.WithdrawConfig({ incentive: _incentive, paused: false })); - - uint256 reward = uint256(_amount) * uint256(_incentive) / warehouse.PERCENTAGE_SCALE(); - - vm.expectEmit(); - emit Withdraw(_owner, token, _withdrawer, _amount - reward, reward); - warehouse.withdraw(_owner, token, _amount, _withdrawer); - - assertEq(warehouse.balanceOf(_owner, tokenToId(token)), 0); - assertEq(ERC20(token).balanceOf(_owner), _amount - reward); - assertEq(ERC20(token).balanceOf(_withdrawer), reward); - assertEq(ERC20(token).balanceOf(address(warehouse)), 0); - } - - function testFuzz_withdrawForOwner_singleToken_whenNativeWithIncentive( - address _owner, - uint192 _amount, - uint16 _incentive, - address _withdrawer - ) - public - { - assumeAddress(_owner); - assumeAddress(_withdrawer); - vm.assume(_owner != _withdrawer); - vm.assume(_withdrawer.balance == 0); - - testFuzz_depositSingleOwner_whenNativeToken(_owner, _owner, _amount); - testFuzz_setWithdrawalConfig(_owner, SplitsWarehouse.WithdrawConfig({ incentive: _incentive, paused: false })); - - uint256 reward = uint256(_amount) * uint256(_incentive) / warehouse.PERCENTAGE_SCALE(); - - vm.expectEmit(); - emit Withdraw(_owner, native, _withdrawer, _amount - reward, reward); - warehouse.withdraw(_owner, native, _amount, _withdrawer); - - assertEq(warehouse.balanceOf(_owner, tokenToId(native)), 0); - assertEq(address(_owner).balance, _amount - reward); - assertEq(_withdrawer.balance, reward); - assertEq(address(warehouse).balance, 0); + warehouse.withdraw(owner, address(this)); } /* -------------------------------------------------------------------------- */ @@ -442,7 +366,7 @@ contract SplitsWarehouseTest is BaseTest, Fuzzer { } function test_withdrawForOwner_multipleTokens_Revert_whenZeroOwner() public { - vm.expectRevert(ZeroOwner.selector); + vm.expectRevert(); warehouse.withdraw(address(0), defaultTokens, getAmounts(100 ether), address(this)); } @@ -489,10 +413,10 @@ contract SplitsWarehouseTest is BaseTest, Fuzzer { vm.prank(_owner); warehouse.setWithdrawConfig(_config); - SplitsWarehouse.WithdrawConfig memory config = warehouse.getWithdrawConfig(_owner); + (uint16 incentive, bool paused) = warehouse.withdrawConfig(_owner); - assertEq(config.paused, _config.paused); - assertEq(config.incentive, _config.incentive); + assertEq(paused, _config.paused); + assertEq(incentive, _config.incentive); } /* -------------------------------------------------------------------------- */ @@ -528,4 +452,26 @@ contract SplitsWarehouseTest is BaseTest, Fuzzer { } vm.stopPrank(); } + + function getTotalAmount(Receiver[] memory receivers) internal pure returns (uint256 totalAmount) { + for (uint256 i = 0; i < receivers.length; i++) { + totalAmount += receivers[i].amount; + } + } + + function getReceiversAndAmounts(Receiver[] memory receivers) + internal + pure + returns (address[] memory, uint256[] memory) + { + address[] memory _receivers = new address[](receivers.length); + uint256[] memory amounts = new uint256[](receivers.length); + + for (uint256 i = 0; i < receivers.length; i++) { + _receivers[i] = receivers[i].receiver; + amounts[i] = receivers[i].amount; + } + + return (_receivers, amounts); + } } diff --git a/packages/splits-v2/test/warehouse/SplitsWarehouseHandler.sol b/packages/splits-v2/test/warehouse/SplitsWarehouseHandler.sol index f291d2f..29c24ce 100644 --- a/packages/splits-v2/test/warehouse/SplitsWarehouseHandler.sol +++ b/packages/splits-v2/test/warehouse/SplitsWarehouseHandler.sol @@ -100,33 +100,6 @@ contract SplitsWarehouseHandler is CommonBase, StdCheats, StdUtils { warehouseBalance[token] -= balance - 1; } - function withdrawForUser(uint256 _user, uint256 _token, uint192 _amount, uint256 _withdrawer) public { - _user = bound(_user, 0, users.length - 1); - address user = users[_user]; - - _token = bound(_token, 0, tokens.length - 1); - address token = tokens[_token]; - - _withdrawer = bound(_withdrawer, 0, users.length - 1); - address withdrawer = users[_withdrawer]; - - uint256 amount = bound(_amount, 0, warehouse.balanceOf(user, token.toUint256())); - - uint256 reward = amount * uint256(warehouse.getWithdrawConfig(user).incentive) / warehouse.PERCENTAGE_SCALE(); - - if (user == badActor && token == native) { - return; - } else if (withdrawer == badActor && token == native && reward > 0) { - return; - } else if (warehouse.getWithdrawConfig(user).paused) { - return; - } - vm.prank(withdrawer); - warehouse.withdraw(user, token, amount, withdrawer); - - warehouseBalance[token] -= amount; - } - function transfer(uint256 _sender, uint256 _receiver, uint256 _token, uint256 _amount) public mockUser(_sender) { _sender = bound(_sender, 0, users.length - 1); _receiver = bound(_receiver, 0, users.length - 1); @@ -216,7 +189,7 @@ contract SplitsWarehouseHandler is CommonBase, StdCheats, StdUtils { _token = bound(_token, 0, tokens.length - 1); address token = tokens[_token]; - warehouse.batchTransfer(token, receiverAddresses, amounts); + warehouse.batchTransfer(receiverAddresses, token, amounts); } function filter(