DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: high
Invalid

Lacking of claim function from transmuter may cause users unable to withdraw their assets

Summary

The strategy does not have a claim function to claim claimable underlyings from Alchemix transmuter may cause users unable to withdraw their assets in time.

Vulnerability Details

When users trying to withdraw or redeem their assets from strategy vaults. The Strategy will internally call __freeFunds() within withdraw() _and _redeem() functions. __freeFunds function is supposed to release available alAssets from yield source which in here is the Alchemix transmuter. The function will try to cap user's withdraw amount to all available unexchangedBalance in transmuter. As in

function _freeFunds(uint256 _amount) internal override {
uint256 totalAvailabe = transmuter.getUnexchangedBalance(address(this));
if (_amount > totalAvailabe) {
transmuter.withdraw(totalAvailabe, address(this));
} else {
transmuter.withdraw(_amount, address(this));
}
}

However, the avaible unexchangedBalance does not stand for all avaible balance in strategy. When strategy deposit alAsset into transmuter (either from users or from keep to call claimAndSwap), alAsset will slowly changed into underlying tokens in transmuter, which could get from transmuter.getClaimableBalance(). This part of balance should be claimed and reconverse to alAsset before user claim their funds.

Now the only possible way to converse underlying tokens back to alAsset is through claimAndSwap() called by keeper. But this function largly depends on whether there is a depeg happens. If not, the underlying tokens may struck in transmuter for a long time and no one can claim it.

As in

function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
require(_minOut > _amountClaim, "minOut too low");
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}

To make the claimAndSwap() run success, the market must be profitable, as the function logic requires minOut>amoutClaim. This means the returned tokens amount should always larger than the claim amout. However, if Alchemix protocol runs as intended and the ratio between alToken and underlying token stays 1:1 matched, or in some unusually cases, alToken value more than underlying token. The claimAndSwap action will fail. Hence the underlying token has no way to claim from transmuter.

Another rare but theoratically possiable situation is :

If the router is down. There is no way to claim the underlying tokens from transmuter too.

Note although in StrategyOp and StrategyArb, router is setable. In StrategyMainnet, router is hardcoded in the contract which means once deployed, there is nothing can do if the router is down.

Impact

when a user is to withdraw their asset, strategy calls transmuter to withdraw unexchanged balance. But if most alAsset are already exchanged to claimable underlyings, user may not be able to claim their funds.

Tools Used

manual

Recommendations

consider add a claim logic to claim underlying tokens from Alchemix transmuter and redeposit in _freeFunds function.

Updates

Lead Judging Commences

inallhonesty Lead Judge
8 months ago

Appeal created

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.