57093 sc critical potential locked funds due to partial redeem shortfall and miss calculation lead to user loss their myt token forever
Description
Summary:
Description:
function claimRedemption(uint256 id) external {
StakingPosition storage position = _positions[id];
if (position.maturationBlock == 0) {
revert PositionNotFound();
}
if (position.startBlock == block.number) {
revert PrematureClaim();
}
uint256 transmutationTime = position.maturationBlock - position.startBlock;
uint256 blocksLeft = position.maturationBlock > block.number ? position.maturationBlock - block.number : 0;
uint256 rounded = position.amount * blocksLeft / transmutationTime + (position.amount * blocksLeft % transmutationTime == 0 ? 0 : 1);
uint256 amountNottransmuted = blocksLeft > 0 ? rounded : 0;
uint256 amountTransmuted = position.amount - amountNottransmuted;
if (_requireOwned(id) != msg.sender) {
revert CallerNotOwner();
}
// Burn position NFT
_burn(id);
// Ratio of total synthetics issued by the alchemist / underlingying value of collateral stored in the alchemist
// If the system experiences bad debt we use this ratio to scale back the value of yield tokens that are transmuted
uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.myt(), address(this));
// Avoid divide by 0
uint256 denominator = alchemist.getTotalUnderlyingValue() + alchemist.convertYieldTokensToUnderlying(yieldTokenBalance) > 0 ? alchemist.getTotalUnderlyingValue() + alchemist.convertYieldTokensToUnderlying(yieldTokenBalance) : 1;
uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.underlyingToken()) / denominator;
uint256 scaledTransmuted = amountTransmuted;
if (badDebtRatio > 1e18) {
scaledTransmuted = amountTransmuted * FIXED_POINT_SCALAR / badDebtRatio;
}
// @audit after redeem when the totalYield that is transmuted amount of user less then after redeeming then only available amount myt
// will transmute to user the remaining portion is not transfer to user it remain in contract but for user the hole scaledTransmuted amount is burn.
@> uint256 debtValue = alchemist.convertYieldTokensToDebt(yieldTokenBalance);
@> uint256 amountToRedeem = scaledTransmuted > debtValue ? scaledTransmuted - debtValue : 0;
@> if (amountToRedeem > 0) alchemist.redeem(amountToRedeem);
@> uint256 totalYield = alchemist.convertDebtTokensToYield(scaledTransmuted);
// Cap to what we actually hold now (handles redeem() rounding shortfalls).
@> uint256 balAfterRedeem = TokenUtils.safeBalanceOf(alchemist.myt(), address(this));
@> uint256 distributable = totalYield <= balAfterRedeem ? totalYield : balAfterRedeem;
// Split distributable amount. Round fee down; claimant gets the remainder.
uint256 feeYield = distributable * transmutationFee / BPS;
uint256 claimYield = distributable - feeYield;
uint256 syntheticFee = amountNottransmuted * exitFee / BPS;
uint256 syntheticReturned = amountNottransmuted - syntheticFee;
// Remove untransmuted amount from the staking graph
if (blocksLeft > 0) _updateStakingGraph(-position.amount.toInt256() * BLOCK_SCALING_FACTOR / transmutationTime.toInt256(), blocksLeft);
@> TokenUtils.safeTransfer(alchemist.myt(), msg.sender, claimYield);
TokenUtils.safeTransfer(alchemist.myt(), protocolFeeReceiver, feeYield);
TokenUtils.safeTransfer(syntheticToken, msg.sender, syntheticReturned);
TokenUtils.safeTransfer(syntheticToken, protocolFeeReceiver, syntheticFee);
// Burn remaining synths that were not returned
@> TokenUtils.safeBurn(syntheticToken, amountTransmuted);
alchemist.reduceSyntheticsIssued(amountTransmuted);
alchemist.setTransmuterTokenBalance(TokenUtils.safeBalanceOf(alchemist.myt(), address(this)));
totalLocked -= position.amount;
emit PositionClaimed(msg.sender, claimYield, syntheticReturned);
delete _positions[id];
}
Impact:
Mitigation step
Proof of Concept
Proof of Concept
Previous57169 sc low zeroxswapverifier policy bypass via rfq filldata prefix token amount spoof Next58703 sc insight cached interest rate calculation in peapodseth strategy leads to inaccurate apr apy estimates
Was this helpful?