_redeemTokensOf
Contract: JBPayoutRedemptionPaymentTerminal
- Step by step
 - Code
 - Errors
 - Events
 - Bug bounty
 
Holders can redeem their tokens to claim the project's overflowed tokens, or to trigger rules determined by the project's current funding cycle's data source.
Only a token holder or a designated operator can redeem its tokens.
Definition
function _redeemTokensOf(
  address _holder,
  uint256 _projectId,
  uint256 _tokenCount,
  uint256 _minReturnedTokens,
  address payable _beneficiary,
  string memory _memo,
  bytes memory _metadata
) private returns (uint256 reclaimAmount) { ... }
- Arguments:
_holderis the account to redeem tokens for._projectIdis the ID of the project to which the tokens being redeemed belong._tokenCountis the number of project tokens to redeem, as a fixed point number with 18 decimals._minReturnedTokensis the minimum amount of terminal tokens expected in return, as a fixed point number with the same amount of decimals as this terminal._beneficiaryis the address to send the terminal tokens to._memois a memo to pass along to the emitted event._metadataare bytes to send along to the data source, delegate, and emitted event, if provided.
 - The function is private to this contract.
 - The function returns the amount of terminal tokens that the tokens were redeemed for, as a fixed point number with the same amount of decimals as this terminal.
 
Body
- 
Make sure the provided beneficiary of the claimed funds isn't the zero address.
// Can't send reclaimed funds to the zero address.
if (_beneficiary == address(0)) revert REDEEM_TO_ZERO_ADDRESS(); - 
Define a reference to the project's funding cycle during which the redemption is being made.
// Define variables that will be needed outside the scoped section below.
// Keep a reference to the funding cycle during which the redemption is being made.
JBFundingCycle memory _fundingCycle; - 
The following scoped block is a bit of a hack to prevent a "Stack too deep" error.
// Scoped section prevents stack too deep. `_delegateAllocations` only used within scope.
{ ... }- 
Get a reference to the redemption delegate that.
JBRedemptionDelegateAllocation[] memory _delegateAllocations; - 
Record the redemption and get a reference to the funding cycle during which the redemption was made, the terminal token amount that should be reclaimed, the delegate allocations to fulfill, and an updated memo.
// Record the redemption.
(_fundingCycle, reclaimAmount, _delegateAllocations, _memo) = store.recordRedemptionFor(
_holder,
_projectId,
_tokenCount,
_memo,
_metadata
);Internal references:
External references:
 - 
Make sure the amount of terminal tokens being reclaimed is at least as much as the specified minimum.
// The amount being reclaimed must be at least as much as was expected.
if (reclaimAmount < _minReturnedTokens) revert INADEQUATE_RECLAIM_AMOUNT(); - 
Burn the project's tokens if needed.
// Burn the project tokens.
if (_tokenCount > 0)
IJBController(directory.controllerOf(_projectId)).burnTokensOf(
_holder,
_projectId,
_tokenCount,
'',
false
);Internal references:
External references:
 - 
If delegate allocations were specified, fulfill each of their
didRedeemfunctions, and emit an event with the relevant parameters.// If delegate allocations were specified by the data source, fulfill them.
if (_delegateAllocations.length != 0) {
// Keep a reference to the token amount being forwarded to the delegate.
JBTokenAmount memory _forwardedAmount = JBTokenAmount(token, 0, decimals, currency);
JBDidRedeemData memory _data = JBDidRedeemData(
_holder,
_projectId,
_tokenCount,
_fundingCycle.configuration,
JBTokenAmount(token, reclaimAmount, decimals, currency),
_forwardedAmount,
_beneficiary,
_memo,
_metadata
);
uint256 _numDelegates = _delegateAllocations.length;
for (uint256 _i; _i < _numDelegates; ) {
// Get a reference to the delegate being iterated on.
JBRedemptionDelegateAllocation memory _delegateAllocation = _delegateAllocations[_i];
// Trigger any inherited pre-transfer logic.
_beforeTransferTo(address(_delegateAllocation.delegate), _delegateAllocation.amount);
// Keep track of the msg.value to use in the delegate call
uint256 _payableValue;
// If this terminal's token is ETH, send it in msg.value.
if (token == JBTokens.ETH) _payableValue = _delegateAllocation.amount;
// Pass the correct token forwardedAmount to the delegate
_data.forwardedAmount.value = _delegateAllocation.amount;
_delegateAllocation.delegate.didRedeem{value: _payableValue}(_data);
emit DelegateDidRedeem(
_delegateAllocation.delegate,
_data,
_delegateAllocation.amount,
msg.sender
);
unchecked {
++_i;
}
}
}Internal references:
Virtual references:
External references:
Event references:
 
 - 
 - 
If an amount is being reclaimed, send the funds to the beneficiary.
// Send the reclaimed funds to the beneficiary.
if (reclaimAmount > 0) _transferFrom(address(this), _beneficiary, reclaimAmount);Internal references:
 - 
Emit a
RedeemTokensevent with the relevant parameters.emit RedeemTokens(
_fundingCycle.configuration,
_fundingCycle.number,
_projectId,
_holder,
_beneficiary,
_tokenCount,
reclaimAmount,
_memo,
msg.sender
);Event references:
 
/**
  @notice
  Holders can redeem their tokens to claim the project's overflowed tokens, or to trigger rules determined by the project's current funding cycle's data source.
  @dev
  Only a token holder or a designated operator can redeem its tokens.
  @param _holder The account to redeem tokens for.
  @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 _minReturnedTokens The minimum amount of terminal tokens expected in return, as a fixed point number with the same amount of decimals as the terminal.
  @param _beneficiary The address to send the terminal tokens to.
  @param _memo A memo to pass along to the emitted event.
  @param _metadata Bytes to send along to the data source, delegate, and emitted event, if provided.
  @return reclaimAmount The amount of terminal tokens that the project tokens were redeemed for, as a fixed point number with 18 decimals.
*/
function _redeemTokensOf(
  address _holder,
  uint256 _projectId,
  uint256 _tokenCount,
  uint256 _minReturnedTokens,
  address payable _beneficiary,
  string memory _memo,
  bytes memory _metadata
) internal returns (uint256 reclaimAmount) {
  // Can't send reclaimed funds to the zero address.
  if (_beneficiary == address(0)) revert REDEEM_TO_ZERO_ADDRESS();
  // Define variables that will be needed outside the scoped section below.
  // Keep a reference to the funding cycle during which the redemption is being made.
  JBFundingCycle memory _fundingCycle;
  // Scoped section prevents stack too deep. `_delegateAllocations` only used within scope.
  {
    JBRedemptionDelegateAllocation[] memory _delegateAllocations;
    // Record the redemption.
    (_fundingCycle, reclaimAmount, _delegateAllocations, _memo) = store.recordRedemptionFor(
      _holder,
      _projectId,
      _tokenCount,
      _memo,
      _metadata
    );
    // The amount being reclaimed must be at least as much as was expected.
    if (reclaimAmount < _minReturnedTokens) revert INADEQUATE_RECLAIM_AMOUNT();
    // Burn the project tokens.
    if (_tokenCount > 0)
      IJBController(directory.controllerOf(_projectId)).burnTokensOf(
        _holder,
        _projectId,
        _tokenCount,
        '',
        false
      );
    // If delegate allocations were specified by the data source, fulfill them.
    if (_delegateAllocations.length != 0) {
      // Keep a reference to the token amount being forwarded to the delegate.
      JBTokenAmount memory _forwardedAmount = JBTokenAmount(token, 0, decimals, currency);
      JBDidRedeemData memory _data = JBDidRedeemData(
        _holder,
        _projectId,
        _fundingCycle.configuration,
        _tokenCount,
        JBTokenAmount(token, reclaimAmount, decimals, currency),
        _forwardedAmount,
        _beneficiary,
        _memo,
        _metadata
      );
      uint256 _numDelegates = _delegateAllocations.length;
      for (uint256 _i; _i < _numDelegates; ) {
        // Get a reference to the delegate being iterated on.
        JBRedemptionDelegateAllocation memory _delegateAllocation = _delegateAllocations[_i];
        // Trigger any inherited pre-transfer logic.
        _beforeTransferTo(address(_delegateAllocation.delegate), _delegateAllocation.amount);
        // Keep track of the msg.value to use in the delegate call
        uint256 _payableValue;
        // If this terminal's token is ETH, send it in msg.value.
        if (token == JBTokens.ETH) _payableValue = _delegateAllocation.amount;
        // Pass the correct token forwardedAmount to the delegate
        _data.forwardedAmount.value = _delegateAllocation.amount;
        _delegateAllocation.delegate.didRedeem{value: _payableValue}(_data);
        emit DelegateDidRedeem(
          _delegateAllocation.delegate,
          _data,
          _delegateAllocation.amount,
          msg.sender
        );
        unchecked {
          ++_i;
        }
      }
    }
  }
  // Send the reclaimed funds to the beneficiary.
  if (reclaimAmount > 0) _transferFrom(address(this), _beneficiary, reclaimAmount);
  emit RedeemTokens(
    _fundingCycle.configuration,
    _fundingCycle.number,
    _projectId,
    _holder,
    _beneficiary,
    _tokenCount,
    reclaimAmount,
    _memo,
    _metadata,
    msg.sender
  );
}
| String | Description | 
|---|---|
REDEEM_TO_ZERO_ADDRESS | Thrown if the zero address was sent as the beneficiary. | 
INADEQUATE_RECLAIM_AMOUNT | Thrown if the amount being reclaimed is less than the specified minimum. | 
| Name | Data | 
|---|---|
RedeemTokens | 
  | 
| 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 |