recordRedemptionFor
Contract: JBSingleTokenPaymentTerminalStore
Interface: IJBSingleTokenPaymentTerminalStore
- Step by step
- Code
- Errors
- Bug bounty
Records newly redeemed tokens of a project.
Redeems the project's tokens according to values provided by a configured data source. If no data source is configured, redeems tokens along a redemption bonding curve that is a function of the number of tokens being burned.
The msg.sender must be an IJBSingleTokenPaymentTerminal
.
Definition
function recordRedemptionFor(
address _holder,
uint256 _projectId,
uint256 _tokenCount,
string memory _memo,
bytes memory _metadata
)
external
override
nonReentrant
returns (
JBFundingCycle memory fundingCycle,
uint256 reclaimAmount,
JBRedemptionDelegateAllocation[] memory delegateAllocations,
string memory memo
) { ... }
- Arguments:
_holder
is the account that is having its tokens redeemed._projectId
is the ID of the project to which the tokens being redeemed belong._tokenCount
is the number of project tokens to redeem, as a fixed point number with 18 decimals._memo
is a memo to pass along to the emitted event._metadata
are bytes to send along to the data source, if one is provided.
- The resulting function overrides a function definition from the
JBSingleTokenPaymentTerminalStore
interface. - The function returns:
fundingCycle
is the funding cycle during which the redemption was made.reclaimAmount
is the amount of terminal tokens reclaimed, as a fixed point number with 18 decimals.delegateAllocations
is the amount to send to delegates instead of sending to the beneficiary.memo
is a memo that should be passed along to the emitted event.
Body
-
Get a reference to the project's current funding cycle.
// Get a reference to the project's current funding cycle.
fundingCycle = fundingCycleStore.currentOf(_projectId);External references:
-
Make sure the project's funding cycle isn't configured to pause redemptions.
// The current funding cycle must not be paused.
if (fundingCycle.redeemPaused()) revert FUNDING_CYCLE_REDEEM_PAUSED();Library references:
JBFundingCycleMetadataResolver
.redeemPaused(...)
-
The following scoped block is a bit of a hack to prevent a "Stack too deep" error.
// Scoped section prevents stack too deep. `_reclaimedTokenAmount`, `_currentOverflow`, and `_totalSupply` only used within scope.
{ ... }-
Keep a reference to the reclaimed token amount, current overflow amount, and total supply variables to use outside of the subsequent scoped block.
// Get a reference to the reclaimed token amount struct, the current overflow, and the total token supply.
JBTokenAmount memory _reclaimedTokenAmount;
uint256 _currentOverflow;
uint256 _totalSupply; -
The following other scoped block uses the same hack to prevent a "Stack too deep" error.
// Another scoped section prevents stack too deep. `_token`, `_decimals`, and `_currency` only used within scope.
{ ... }-
Get a reference to the terminal's token, decimals, and currency.
// Get a reference to the terminal's tokens.
address _token = IJBSingleTokenPaymentTerminal(msg.sender).token();
// Get a reference to the terminal's decimals.
uint256 _decimals = IJBSingleTokenPaymentTerminal(msg.sender).decimals();
// Get areference to the terminal's currency.
uint256 _currency = IJBSingleTokenPaymentTerminal(msg.sender).currency();External references:
-
Get a reference to the amount of overflow the project has. Either the project's total overflow or the overflow local to the msg.sender's balance will be used depending on how the project's funding cycle is configured.
// Get the amount of current overflow.
// Use the local overflow if the funding cycle specifies that it should be used. Otherwise, use the project's total overflow across all of its terminals.
_currentOverflow = fundingCycle.useTotalOverflowForRedemptions()
? _currentTotalOverflowOf(_projectId, _decimals, _currency)
: _overflowDuring(
IJBSingleTokenPaymentTerminal(msg.sender),
_projectId,
fundingCycle,
_currency
);Library references:
JBFundingCycleMetadataResolver
.useTotalOverflowForRedemptions(...)
Internal references:
-
Get a reference to the total outstanding supply of project tokens.
// Get the number of outstanding tokens the project has.
_totalSupply = IJBController(directory.controllerOf(_projectId))
.totalOutstandingTokensOf(_projectId, fundingCycle.reservedRate());Library references:
JBFundingCycleMetadataResolver
.reservedRate(...)
Internal references:
External references:
-
Make sure the provided token count is within the bounds of the total supply.
// Can't redeem more tokens that is in the supply.
if (_tokenCount > _totalSupply) revert INSUFFICIENT_TOKENS(); -
Get a reference to the reclaimable overflow if there is overflow.
if (_currentOverflow != 0)
// Calculate reclaim amount using the current overflow amount.
reclaimAmount = _reclaimableOverflowDuring(
_projectId,
fundingCycle,
_tokenCount,
_totalSupply,
_currentOverflow
);Internal references:
-
Construct the reclaim amount struct.
_reclaimedTokenAmount = JBTokenAmount(_token, reclaimAmount, _decimals, _currency);
-
-
If the project's current funding cycle is configured to use a data source when making redemptions, ask the data source for the parameters that should be used throughout the rest of the function given provided contextual values in a
JBRedeemParamsData
structure. Otherwise default parameters are used.if (fundingCycle.useDataSourceForRedeem() && fundingCycle.dataSource() != address(0)) {
// Yet another scoped section prevents stack too deep. `_state` only used within scope.
{
// Get a reference to the ballot state.
JBBallotState _state = fundingCycleStore.currentBallotStateOf(_projectId);
// Create the params that'll be sent to the data source.
JBRedeemParamsData memory _data = JBRedeemParamsData(
IJBSingleTokenPaymentTerminal(msg.sender),
_holder,
_projectId,
fundingCycle.configuration,
_tokenCount,
_totalSupply,
_currentOverflow,
_reclaimedTokenAmount,
fundingCycle.useTotalOverflowForRedemptions(),
_state == JBBallotState.Active
? fundingCycle.ballotRedemptionRate()
: fundingCycle.redemptionRate(),
_memo,
_metadata
);
(reclaimAmount, memo, delegateAllocations) = IJBFundingCycleDataSource(
fundingCycle.dataSource()
).redeemParams(_data);
}
} else {
memo = _memo;
}Library references:
JBFundingCycleMetadataResolver
.useDataSourceForRedeem(...)
.dataSource(...)
.redemptionRate(...)
.ballotRedemptionRate(...)
.useTotalOverflowForRedemptions(...)
-
-
Keep a reference the amount difference to apply to the balance. Initially this is the full reclaim value.
// Keep a reference to the amount that should be subtracted from the project's balance.
uint256 _balanceDiff = reclaimAmount; -
If delegate allocations were returned by the data source, increment the amount that will get subtracted from the project's balance by each delegated allocation.
if (delegateAllocations.length != 0) {
// Validate all delegated amounts.
for (uint256 _i; _i < delegateAllocations.length; ) {
// Get a reference to the amount to be delegated.
uint256 _delegatedAmount = delegateAllocations[_i].amount;
// Validate if non-zero.
if (_delegatedAmount != 0)
// Increment the total amount being subtracted from the balance.
_balanceDiff = _balanceDiff + _delegatedAmount;
unchecked {
++_i;
}
}
} -
Make sure the amount being decremented from the balance is within the bounds of the project's balance.
// The amount being reclaimed must be within the project's balance.
if (_balanceDiff > balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId])
revert INADEQUATE_PAYMENT_TERMINAL_STORE_BALANCE();Internal references:
-
Decrement any claimed funds from the project's balance if needed.
// Remove the reclaimed funds from the project's balance.
if (_balanceDiff != 0) {
unchecked {
balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] =
balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] -
_balanceDiff;
}
}Internal references:
/**
@notice
Records newly redeemed tokens of a project.
@dev
Redeems the project's tokens according to values provided by a configured data source. If no data source is configured, redeems tokens along a redemption bonding curve that is a function of the number of tokens being burned.
@dev
The msg.sender must be an IJBSingleTokenPaymentTerminal. The amount specified in the params is in terms of the msg.senders tokens.
@param _holder The account that is having its tokens redeemed.
@param _projectId The ID of the project to which the tokens being redeemed belong.
@param _tokenCount The number of project tokens to redeem, as a fixed point number with 18 decimals.
@param _memo A memo to pass along to the emitted event.
@param _metadata Bytes to send along to the data source, if one is provided.
@return fundingCycle The funding cycle during which the redemption was made.
@return reclaimAmount The amount of terminal tokens reclaimed, as a fixed point number with 18 decimals.
@return delegateAllocations The amount to send to delegates instead of sending to the beneficiary.
@return memo A memo that should be passed along to the emitted event.
*/
function recordRedemptionFor(
address _holder,
uint256 _projectId,
uint256 _tokenCount,
string memory _memo,
bytes memory _metadata
)
external
override
nonReentrant
returns (
JBFundingCycle memory fundingCycle,
uint256 reclaimAmount,
JBRedemptionDelegateAllocation[] memory delegateAllocations,
string memory memo
)
{
// Get a reference to the project's current funding cycle.
fundingCycle = fundingCycleStore.currentOf(_projectId);
// The current funding cycle must not be paused.
if (fundingCycle.redeemPaused()) revert FUNDING_CYCLE_REDEEM_PAUSED();
// Scoped section prevents stack too deep. `_reclaimedTokenAmount`, `_currentOverflow`, and `_totalSupply` only used within scope.
{
// Get a reference to the reclaimed token amount struct, the current overflow, and the total token supply.
JBTokenAmount memory _reclaimedTokenAmount;
uint256 _currentOverflow;
uint256 _totalSupply;
// Another scoped section prevents stack too deep. `_token`, `_decimals`, and `_currency` only used within scope.
{
// Get a reference to the terminal's tokens.
address _token = IJBSingleTokenPaymentTerminal(msg.sender).token();
// Get a reference to the terminal's decimals.
uint256 _decimals = IJBSingleTokenPaymentTerminal(msg.sender).decimals();
// Get areference to the terminal's currency.
uint256 _currency = IJBSingleTokenPaymentTerminal(msg.sender).currency();
// Get the amount of current overflow.
// Use the local overflow if the funding cycle specifies that it should be used. Otherwise, use the project's total overflow across all of its terminals.
_currentOverflow = fundingCycle.useTotalOverflowForRedemptions()
? _currentTotalOverflowOf(_projectId, _decimals, _currency)
: _overflowDuring(
IJBSingleTokenPaymentTerminal(msg.sender),
_projectId,
fundingCycle,
_currency
);
// Get the number of outstanding tokens the project has.
_totalSupply = IJBController(directory.controllerOf(_projectId)).totalOutstandingTokensOf(
_projectId,
fundingCycle.reservedRate()
);
// Can't redeem more tokens that is in the supply.
if (_tokenCount > _totalSupply) revert INSUFFICIENT_TOKENS();
if (_currentOverflow != 0)
// Calculate reclaim amount using the current overflow amount.
reclaimAmount = _reclaimableOverflowDuring(
_projectId,
fundingCycle,
_tokenCount,
_totalSupply,
_currentOverflow
);
_reclaimedTokenAmount = JBTokenAmount(_token, reclaimAmount, _decimals, _currency);
}
// If the funding cycle has configured a data source, use it to derive a claim amount and memo.
if (fundingCycle.useDataSourceForRedeem() && fundingCycle.dataSource() != address(0)) {
// Yet another scoped section prevents stack too deep. `_state` only used within scope.
{
// Get a reference to the ballot state.
JBBallotState _state = fundingCycleStore.currentBallotStateOf(_projectId);
// Create the params that'll be sent to the data source.
JBRedeemParamsData memory _data = JBRedeemParamsData(
IJBSingleTokenPaymentTerminal(msg.sender),
_holder,
_projectId,
fundingCycle.configuration,
_tokenCount,
_totalSupply,
_currentOverflow,
_reclaimedTokenAmount,
fundingCycle.useTotalOverflowForRedemptions(),
_state == JBBallotState.Active
? fundingCycle.ballotRedemptionRate()
: fundingCycle.redemptionRate(),
_memo,
_metadata
);
(reclaimAmount, memo, delegateAllocations) = IJBFundingCycleDataSource(
fundingCycle.dataSource()
).redeemParams(_data);
}
} else {
memo = _memo;
}
}
// Keep a reference to the amount that should be subtracted from the project's balance.
uint256 _balanceDiff = reclaimAmount;
if (delegateAllocations.length != 0) {
// Validate all delegated amounts.
for (uint256 _i; _i < delegateAllocations.length; ) {
// Get a reference to the amount to be delegated.
uint256 _delegatedAmount = delegateAllocations[_i].amount;
// Validate if non-zero.
if (_delegatedAmount != 0)
// Increment the total amount being subtracted from the balance.
_balanceDiff = _balanceDiff + _delegatedAmount;
unchecked {
++_i;
}
}
}
// The amount being reclaimed must be within the project's balance.
if (_balanceDiff > balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId])
revert INADEQUATE_PAYMENT_TERMINAL_STORE_BALANCE();
// Remove the reclaimed funds from the project's balance.
if (_balanceDiff != 0) {
unchecked {
balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] =
balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] -
_balanceDiff;
}
}
}
String | Description |
---|---|
FUNDING_CYCLE_REDEEM_PAUSED | Thrown if the project has configured its current funding cycle to pause redemptions. |
INADEQUATE_PAYMENT_TERMINAL_STORE_BALANCE | Thrown if the project's balance isn't sufficient to fulfill the desired claim. |
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 |