Skip to content

Commit

Permalink
Make the split balances slot non-zero (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeSandwich committed Mar 4, 2024
1 parent 7995018 commit d77b048
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 7 deletions.
25 changes: 18 additions & 7 deletions src/Splits.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ abstract contract Splits {
uint32 internal constant _TOTAL_SPLITS_WEIGHT = 1_000_000;
/// @notice The amount the contract can keep track of each ERC-20 token.
// slither-disable-next-line unused-state
uint128 internal constant _MAX_SPLITS_BALANCE = type(uint128).max;
uint128 internal constant _MAX_SPLITS_BALANCE = _SPLITTABLE_MASK;
/// @notice The storage slot holding a single `SplitsStorage` structure.
bytes32 private immutable _splitsStorageSlot;
/// @notice The mask for `SplitsBalance.splittable` where the actual value is stored.
uint128 private constant _SPLITTABLE_MASK = type(uint128).max >> 1;

/// @notice Emitted when an account collects funds
/// @param accountId The account ID.
Expand Down Expand Up @@ -90,6 +92,8 @@ abstract contract Splits {

struct SplitsBalance {
/// @notice The not yet split balance, must be split before collecting by the account.
/// The bits outside of the `_SPLITTABLE_MASK` mask may be set to `1`
/// to keep the storage slot non-zero, so always clear these bits when reading.
uint128 splittable;
/// @notice The already split balance, ready to be collected by the account.
uint128 collectable;
Expand All @@ -101,17 +105,21 @@ abstract contract Splits {
}

function _addSplittable(uint256 accountId, IERC20 erc20, uint128 amt) internal {
// This will not overflow if the requirement of tracking in the contract
// no more than `_MAX_SPLITS_BALANCE` of each token is followed.
_splitsStorage().splitsStates[accountId].balances[erc20].splittable += amt;
unchecked {
// This will not overflow if the requirement of tracking in the contract
// no more than `_MAX_SPLITS_BALANCE` of each token is followed.
_splitsStorage().splitsStates[accountId].balances[erc20].splittable += amt;
}
}

/// @notice Returns account's received but not split yet funds.
/// @param accountId The account ID.
/// @param erc20 The used ERC-20 token.
/// @return amt The amount received but not split yet.
function _splittable(uint256 accountId, IERC20 erc20) internal view returns (uint128 amt) {
return _splitsStorage().splitsStates[accountId].balances[erc20].splittable;
// Clear the bits outside of the mask
return
_splitsStorage().splitsStates[accountId].balances[erc20].splittable & _SPLITTABLE_MASK;
}

/// @notice Calculate the result of splitting an amount using the current splits configuration.
Expand Down Expand Up @@ -160,11 +168,14 @@ abstract contract Splits {
_assertCurrSplits(accountId, currReceivers);
SplitsBalance storage balance = _splitsStorage().splitsStates[accountId].balances[erc20];

collectableAmt = balance.splittable;
// Clear the bits outside of the mask
collectableAmt = balance.splittable & _SPLITTABLE_MASK;
if (collectableAmt == 0) {
return (0, 0);
}
balance.splittable = 0;
// Set the value of `splittable` to `0`,
// and the bits outside of the mask to `1` to keep the storage slot non-zero.
balance.splittable = ~_SPLITTABLE_MASK;

unchecked {
uint256 splitsWeight = 0;
Expand Down
12 changes: 12 additions & 0 deletions test/Splits.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,17 @@ contract SplitsTest is Test, Splits {
split(accountId, 4, 6);
}

function testSplitTwice() public {
// 60% split
setSplits(accountId, splitsReceivers(receiver, (Splits._TOTAL_SPLITS_WEIGHT / 10) * 6));
// Split for the first time
addSplittable(accountId, 5);
splitCollect(accountId, 2, 3);
// Split for the second time
addSplittable(accountId, 10);
splitCollect(accountId, 4, 6);
}

function testLimitsTheTotalSplitsReceiversCount() public {
uint256 countMax = Splits._MAX_SPLITS_RECEIVERS;
SplitsReceiver[] memory receiversGood = new SplitsReceiver[](countMax);
Expand Down Expand Up @@ -362,6 +373,7 @@ contract SplitsTest is Test, Splits {
uint256 receiversLengthRaw,
uint256 totalWeightRaw
) public {
amt %= _MAX_SPLITS_BALANCE + 1;
SplitsReceiver[] memory receivers =
sanitizeReceivers(receiversRaw, receiversLengthRaw, totalWeightRaw);
Splits._addSplittable(usedAccountId, usedErc20, amt);
Expand Down

0 comments on commit d77b048

Please sign in to comment.