_distributeToPayoutSplitsOf
Contract: JBPayoutRedemptionPaymentTerminal
- Step by step
- Code
- Errors
- Events
- Bug bounty
Pays out splits for a project's funding cycle configuration.
Definition
function _distributeToPayoutSplitsOf(
uint256 _projectId,
uint256 _domain,
uint256 _group,
uint256 _amount,
uint256 _feeDiscount
) private returns (uint256 leftoverAmount, uint256 feeEligibleDistributionAmount) { ... }
- Arguments:
_projectId
is the ID of the project for which payout splits are being distributed._domain
is the domain of the splits to distribute the payout between._group
is the group of the splits to distribute the payout between._amount
is the total amount being distributed._feeDiscount
is the amount of discount to apply to the fee, out of the MAX_FEE.
- The function is private to this contract.
- The function returns: the leftover amount if the splits don't add up to 100%
- The function returns:
leftoverAmount
is leftover amount if the splits don't add up to 100%.feeEligibleDistributionAmount
is the amount distributed to splits from which fees can be taken.
Body
-
Save the passed in amount as the leftover amount that will be returned. The subsequent routine will decrement the leftover amount as splits are settled.
// Set the leftover amount to the initial amount.
leftoverAmount = _amount; -
Keep a reference to the leftover percents.
// The total percentage available to split
uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;Library references:
JBConstants
.SPLITS_TOTAL_PERCENT
-
Get a reference to payout splits for the current funding cycle configuration of the project.
// Get a reference to the project's payout splits.
JBSplit[] memory _splits = splitsStore.splitsOf(_projectId, _domain, _group);Internal references:
External references:
-
Loop through each split.
// Transfer between all splits.
for (uint256 _i; _i < _splits.length; ) { ... }-
Get a reference to the current split being iterated on.
// Get a reference to the mod being iterated on.
JBSplit memory _split = _splits[_i]; -
Get a reference to the payout amount that should be sent to the current split. This amount is the total amount multiplied by the percentage of the split, which is a number out of 10000000.
// The amount to send towards the split.
uint256 _payoutAmount = _split.percent == leftoverPercentage
? leftoverAmount
: PRBMath.mulDiv(_amount, _split.percent, JBConstants.SPLITS_TOTAL_PERCENT);Library references:
PRBMath
.mulDiv(...)
JBConstants
.SPLITS_TOTAL_PERCENT
-
Decrement the leftover percentage.
// Decrement the leftover percentage.
leftoverPercentage -= _split.percent; -
If there's at least some funds to send to the payout, determine where they should go, making sure to only debit a fee if the funds are leaving this contract and not going to a feeless terminal. If the split has an
allocator
set, send the funds to itsallocate
function, passing along any relevant params. Otherwise if aprojectId
is specified in the split, send the payout to that project. Add to the project's balance if the split has a preference to do so, otherwise send a payment and use the split'sbeneficiary
as the address that should receive the project's tokens in return, or use the message sender if a beneficiary wasn't provided. If no project was specified, send the funds directly to thebeneficiary
address from the split if one was provided. If the split didn't give any routing information, send the amount to the messag sender. Decrement theleftoverAmount
once the split is settled.// The payout amount substracting any applicable incurred fees.
uint256 _netPayoutAmount;
if (_payoutAmount > 0) {
// Transfer tokens to the split.
// If there's an allocator set, transfer to its `allocate` function.
if (_split.allocator != IJBSplitAllocator(address(0))) {
// If the split allocator is set as feeless, this distribution is not eligible for a fee.
if (
_feeDiscount == JBConstants.MAX_FEE_DISCOUNT ||
isFeelessAddress[address(_split.allocator)]
)
_netPayoutAmount = _payoutAmount;
// This distribution is eligible for a fee since the funds are leaving this contract and the allocator isn't listed as feeless.
else {
unchecked {
_netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
}
// This distribution is eligible for a fee since the funds are leaving the ecosystem.
feeEligibleDistributionAmount += _payoutAmount;
}
// Trigger any inherited pre-transfer logic.
_beforeTransferTo(address(_split.allocator), _netPayoutAmount);
// Create the data to send to the allocator.
JBSplitAllocationData memory _data = JBSplitAllocationData(
token,
_netPayoutAmount,
decimals,
_projectId,
_group,
_split
);
// Trigger the allocator's `allocate` function.
// If this terminal's token is ETH, send it in msg.value.
_split.allocator.allocate{value: token == JBTokens.ETH ? _netPayoutAmount : 0}(_data);
// Otherwise, if a project is specified, make a payment to it.
} else if (_split.projectId != 0) {
// Get a reference to the Juicebox terminal being used.
IJBPaymentTerminal _terminal = directory.primaryTerminalOf(_split.projectId, token);
// The project must have a terminal to send funds to.
if (_terminal == IJBPaymentTerminal(address(0))) revert TERMINAL_IN_SPLIT_ZERO_ADDRESS();
// Save gas if this contract is being used as the terminal.
if (_terminal == this) {
// This distribution does not incur a fee.
_netPayoutAmount = _payoutAmount;
// Send the projectId in the metadata.
bytes memory _projectMetadata = new bytes(32);
_projectMetadata = bytes(abi.encodePacked(_projectId));
// Add to balance if prefered.
if (_split.preferAddToBalance)
_addToBalanceOf(_split.projectId, _netPayoutAmount, false, '', _projectMetadata);
else
_pay(
_netPayoutAmount,
address(this),
_split.projectId,
(_split.beneficiary != address(0)) ? _split.beneficiary : msg.sender,
0,
_split.preferClaimed,
'',
_projectMetadata
);
} else {
// If the terminal is set as feeless, this distribution is not eligible for a fee.
if (
_feeDiscount == JBConstants.MAX_FEE_DISCOUNT || isFeelessAddress[address(_terminal)]
)
_netPayoutAmount = _payoutAmount;
// This distribution is eligible for a fee since the funds are leaving this contract and the terminal isn't listed as feeless.
else {
unchecked {
_netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
}
feeEligibleDistributionAmount += _payoutAmount;
}
// Trigger any inherited pre-transfer logic.
_beforeTransferTo(address(_terminal), _netPayoutAmount);
// If this terminal's token is ETH, send it in msg.value.
uint256 _payableValue = token == JBTokens.ETH ? _netPayoutAmount : 0;
// Send the projectId in the metadata.
bytes memory _projectMetadata = new bytes(32);
_projectMetadata = bytes(abi.encodePacked(_projectId));
// Add to balance if prefered.
if (_split.preferAddToBalance)
_terminal.addToBalanceOf{value: _payableValue}(
_split.projectId,
_netPayoutAmount,
token,
'',
_projectMetadata
);
else
_terminal.pay{value: _payableValue}(
_split.projectId,
_netPayoutAmount,
token,
_split.beneficiary != address(0) ? _split.beneficiary : msg.sender,
0,
_split.preferClaimed,
'',
_projectMetadata
);
}
} else {
// Keep a reference to the beneficiary.
address payable _beneficiary = _split.beneficiary != address(0)
? _split.beneficiary
: payable(msg.sender);
// If there's a full discount, this distribution is not eligible for a fee.
// Don't enforce feeless address for the beneficiary since the funds are leaving the ecosystem.
if (_feeDiscount == JBConstants.MAX_FEE_DISCOUNT)
_netPayoutAmount = _payoutAmount;
// This distribution is eligible for a fee since the funds are leaving this contract and the beneficiary isn't listed as feeless.
else {
unchecked {
_netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
}
feeEligibleDistributionAmount += _payoutAmount;
}
// If there's a beneficiary, send the funds directly to the beneficiary. Otherwise send to the msg.sender.
_transferFrom(address(this), _beneficiary, _netPayoutAmount);
}
unchecked {
// Subtract from the amount to be sent to the beneficiary.
leftoverAmount = leftoverAmount - _payoutAmount;
}
}Library references:
JBConstants
.MAX_FEE_DISCOUNT
JBTokens
.ETH
Internal references:
External references:
-
Emit a
DistributeToPayoutSplit
event for the split being iterated on with the relevant parameters.emit DistributeToPayoutSplit(
_projectId,
_domain,
_group,
_split,
_netPayoutAmount,
msg.sender
);Event references:
-
Increment the loop counter in the most gas efficient way.
unchecked {
++_i;
}
-
/**
@notice
Pays out splits for a project's funding cycle configuration.
@param _projectId The ID of the project for which payout splits are being distributed.
@param _domain The domain of the splits to distribute the payout between.
@param _group The group of the splits to distribute the payout between.
@param _amount The total amount being distributed, as a fixed point number with the same number of decimals as this terminal.
@param _feeDiscount The amount of discount to apply to the fee, out of the MAX_FEE.
@return leftoverAmount If the leftover amount if the splits don't add up to 100%.
@return feeEligibleDistributionAmount The total amount of distributions that are eligible to have fees taken from.
*/
function _distributeToPayoutSplitsOf(
uint256 _projectId,
uint256 _domain,
uint256 _group,
uint256 _amount,
uint256 _feeDiscount
) internal returns (uint256 leftoverAmount, uint256 feeEligibleDistributionAmount) {
// Set the leftover amount to the initial amount.
leftoverAmount = _amount;
// The total percentage available to split
uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;
// Get a reference to the project's payout splits.
JBSplit[] memory _splits = splitsStore.splitsOf(_projectId, _domain, _group);
// Transfer between all splits.
for (uint256 _i; _i < _splits.length; ) {
// Get a reference to the split being iterated on.
JBSplit memory _split = _splits[_i];
// The amount to send towards the split.
uint256 _payoutAmount = _split.percent == leftoverPercentage
? leftoverAmount
: PRBMath.mulDiv(_amount, _split.percent, JBConstants.SPLITS_TOTAL_PERCENT);
// Decrement the leftover percentage.
leftoverPercentage -= _split.percent;
// The payout amount substracting any applicable incurred fees.
uint256 _netPayoutAmount;
if (_payoutAmount > 0) {
// Transfer tokens to the split.
// If there's an allocator set, transfer to its `allocate` function.
if (_split.allocator != IJBSplitAllocator(address(0))) {
// If the split allocator is set as feeless, this distribution is not eligible for a fee.
if (
_feeDiscount == JBConstants.MAX_FEE_DISCOUNT ||
isFeelessAddress[address(_split.allocator)]
)
_netPayoutAmount = _payoutAmount;
// This distribution is eligible for a fee since the funds are leaving this contract and the allocator isn't listed as feeless.
else {
unchecked {
_netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
}
// This distribution is eligible for a fee since the funds are leaving the ecosystem.
feeEligibleDistributionAmount += _payoutAmount;
}
// Trigger any inherited pre-transfer logic.
_beforeTransferTo(address(_split.allocator), _netPayoutAmount);
// Create the data to send to the allocator.
JBSplitAllocationData memory _data = JBSplitAllocationData(
token,
_netPayoutAmount,
decimals,
_projectId,
_group,
_split
);
// Trigger the allocator's `allocate` function.
// If this terminal's token is ETH, send it in msg.value.
_split.allocator.allocate{value: token == JBTokens.ETH ? _netPayoutAmount : 0}(_data);
// Otherwise, if a project is specified, make a payment to it.
} else if (_split.projectId != 0) {
// Get a reference to the Juicebox terminal being used.
IJBPaymentTerminal _terminal = directory.primaryTerminalOf(_split.projectId, token);
// The project must have a terminal to send funds to.
if (_terminal == IJBPaymentTerminal(address(0))) revert TERMINAL_IN_SPLIT_ZERO_ADDRESS();
// Save gas if this contract is being used as the terminal.
if (_terminal == this) {
// This distribution does not incur a fee.
_netPayoutAmount = _payoutAmount;
// Send the projectId in the metadata.
bytes memory _projectMetadata = new bytes(32);
_projectMetadata = bytes(abi.encodePacked(_projectId));
// Add to balance if prefered.
if (_split.preferAddToBalance)
_addToBalanceOf(_split.projectId, _netPayoutAmount, false, '', _projectMetadata);
else
_pay(
_netPayoutAmount,
address(this),
_split.projectId,
(_split.beneficiary != address(0)) ? _split.beneficiary : msg.sender,
0,
_split.preferClaimed,
'',
_projectMetadata
);
} else {
// If the terminal is set as feeless, this distribution is not eligible for a fee.
if (
_feeDiscount == JBConstants.MAX_FEE_DISCOUNT || isFeelessAddress[address(_terminal)]
)
_netPayoutAmount = _payoutAmount;
// This distribution is eligible for a fee since the funds are leaving this contract and the terminal isn't listed as feeless.
else {
unchecked {
_netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
}
feeEligibleDistributionAmount += _payoutAmount;
}
// Trigger any inherited pre-transfer logic.
_beforeTransferTo(address(_terminal), _netPayoutAmount);
// If this terminal's token is ETH, send it in msg.value.
uint256 _payableValue = token == JBTokens.ETH ? _netPayoutAmount : 0;
// Send the projectId in the metadata.
bytes memory _projectMetadata = new bytes(32);
_projectMetadata = bytes(abi.encodePacked(_projectId));
// Add to balance if prefered.
if (_split.preferAddToBalance)
_terminal.addToBalanceOf{value: _payableValue}(
_split.projectId,
_netPayoutAmount,
token,
'',
_projectMetadata
);
else
_terminal.pay{value: _payableValue}(
_split.projectId,
_netPayoutAmount,
token,
_split.beneficiary != address(0) ? _split.beneficiary : msg.sender,
0,
_split.preferClaimed,
'',
_projectMetadata
);
}
} else {
// Keep a reference to the beneficiary.
address payable _beneficiary = _split.beneficiary != address(0)
? _split.beneficiary
: payable(msg.sender);
// If there's a full discount, this distribution is not eligible for a fee.
// Don't enforce feeless address for the beneficiary since the funds are leaving the ecosystem.
if (_feeDiscount == JBConstants.MAX_FEE_DISCOUNT)
_netPayoutAmount = _payoutAmount;
// This distribution is eligible for a fee since the funds are leaving this contract and the beneficiary isn't listed as feeless.
else {
unchecked {
_netPayoutAmount = _payoutAmount - _feeAmount(_payoutAmount, fee, _feeDiscount);
}
feeEligibleDistributionAmount += _payoutAmount;
}
// If there's a beneficiary, send the funds directly to the beneficiary. Otherwise send to the msg.sender.
_transferFrom(address(this), _beneficiary, _netPayoutAmount);
}
// Subtract from the amount to be sent to the beneficiary.
unchecked {
leftoverAmount = leftoverAmount - _payoutAmount;
}
}
emit DistributeToPayoutSplit(
_projectId,
_domain,
_group,
_split,
_netPayoutAmount,
msg.sender
);
unchecked {
++_i;
}
}
}
String | Description |
---|---|
0x4d: BAD_SPLIT | Thrown if the split specifies a project that doesn't have an ETH terminal. |
Name | Data |
---|---|
DistributeToPayoutSplit |
|
Category | Description | Reward |
---|---|---|
Optimization | Help make this operation more efficient. | 0.5ETH |
Low severity | Identify a vulnerability in this operation that could lead to an inconvenience for a user of the protocol or for a protocol developer. | 1ETH |
High severity | Identify a vulnerability in this operation that could lead to data corruption or loss of funds. | 5+ETH |