57662 sc critical portion of users alasset amount that staked in transmuter can be lost forever when amount cumulativeearmarked
Description
Brief/Intro
Vulnerability Details
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;
}
// If the contract has a balance of yield tokens from alchemist repayments then we only need to redeem partial or none from Alchemist earmarked
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 Details
References
Proof of Concept
Proof of Concept
Previous58751 sc medium setminimumcollateralization allows for increasing the current minimumcollateralization instantly exposing users to risk of liquidationNext57057 sc low wrong order of balance checks in morphoyearnogwethstrategy
Was this helpful?