Assessment reports>Programmable Derivatives>High findings>Number of coupon tokens obtained from the auction may differ from number of coupon tokens to be distributed
Category: Business Logic

Number of coupon tokens obtained from the auction may differ from number of coupon tokens to be distributed

High Severity
High Impact
High Likelihood

Description

The pool creates auctions to acquire coupon tokens for distribution. The number of coupon tokens the pool will acquire is based on the current total supply of bond tokens and the _sharesPerToken value obtained from the globalPool variable of the bondToken.

function startAuction() external {
    // [...]

    // Check if auction for current period has already started
    (uint256 currentPeriod, uint256 _sharesPerToken) = bondToken.globalPool();
    require(auctions[currentPeriod] == address(0), AuctionAlreadyStarted());

    auctions[currentPeriod] = Utils.deploy(
      address(new Auction()),
      abi.encodeWithSelector(
        Auction.initialize.selector,
        address(couponToken),
        address(reserveToken),
!       (bondToken.totalSupply() * _sharesPerToken).toBaseUnit(bondToken.SHARES_DECIMALS()),
        block.timestamp + auctionPeriod,
        1000,
        address(this),
        liquidationThreshold
      )
    );
}

If the auction succeeds, the pool will transfer the obtained coupon tokens to the distributor. The number of coupon tokens transferred is based on the current total supply of bond tokens and the state variable sharesPerToken. But these two values may differ from when the startAuction function is called.

function distribute() external whenNotPaused auctionSucceeded {
  // [...]

  Distributor distributor = Distributor(poolFactory.distributor());

  // [...]

  uint256 normalizedTotalSupply = bondToken.totalSupply().normalizeAmount(bondDecimals, maxDecimals);
  uint256 normalizedShares = sharesPerToken.normalizeAmount(sharesDecimals, maxDecimals);

  // Calculate the coupon amount to distribute
  uint256 couponAmountToDistribute = (normalizedTotalSupply * normalizedShares)
      .toBaseUnit(maxDecimals * 2 - IERC20(couponToken).safeDecimals());
  
  // Increase the bond token period
  bondToken.increaseIndexedAssetPeriod(sharesPerToken);

  // Transfer coupon tokens to the distributor
  IERC20(couponToken).safeTransfer(address(distributor), couponAmountToDistribute);

  // [...]
}

The total supply of bond tokens increases or decreases as users deposit or redeem. Additionally, during distribution, users holding more bond tokens can claim more coupon tokens.

function _create(
  // [...]
  ) private returns(uint256) {
  // [...]

  // Mint tokens
  if (tokenType == TokenType.BOND) {
    bondToken.mint(recipient, amount);
  }

  // [...]
}

function _redeem(
  // [...]
  ) private returns(uint256) {
  // [...]

  // Burn derivative tokens
  if (tokenType == TokenType.BOND) {
    bondToken.burn(msg.sender, depositAmount);
  }

  // [...]
}

When not in auction, addresses with GOV_ROLE can modify the state variable sharesPerToken. The shares-per-token value stored in the state variable globalPool of the bondToken can only be updated to the value set in the pool during each distribution (i.e., after a successful auction).

function setSharesPerToken(uint256 _sharesPerToken) external NotInAuction onlyRole(poolFactory.GOV_ROLE()) {
  sharesPerToken = _sharesPerToken;

  emit SharesPerTokenChanged(sharesPerToken);
}

Impact

Users may deposit before the distribution to acquire more bond tokens in order to be able to claim more coupon tokens.

For the pool, an inconsistent coupon amount may result in the pool not having enough coupon tokens to transfer to the distributor or leaving some coupon tokens remaining in the pool.

Recommendations

Consider prohibiting users from depositing or redeeming during the auction period.

For shares per token, consider using the value obtained from the globalPool variable of bondToken in both the startAuction and distribute functions.

Remediation

This issue has been acknowledged by Plaza Finance, and fixes were implemented in the following commits:

Zellic © 2025Back to top ↑