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

claimAndSwap() gives access to not account specific funds in the Transmuter Contract

Description:

StrategyOp::claimAndSwap, StrategyArb::claimAndSwap and StrategyMainnet::claimAndSwap are lacking checks for _amountClaim which can result in keepers being able to acquire funds not meant for their strategy, but other keepers strategies.

Vulnerable Code:

StrategyOp::claimAndSwap StrategyArb::claimAndSwap & StrategyMainnet::claimAndSwap:

function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IVeloRouter.route[] calldata _path ) external onlyKeepers {
@> transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
@> transmuter.deposit(asset.balanceOf(address(this)), address(this));
}

Transmuterv2::claim:

function claim(uint256 amount, address recipient) external override nonReentrant {
_onlyWhitelisted();
@> _updateAccount(
UpdateAccountParams({
owner: msg.sender,
unexchangedDelta: 0,
exchangedDelta: -SafeCast.toInt256(_normalizeUnderlyingTokensToDebt(amount))
})
);
TokenUtils.safeBurn(syntheticToken, _normalizeUnderlyingTokensToDebt(amount));
ITransmuterBuffer(buffer).withdraw(underlyingToken, amount, recipient);
emit Claim(msg.sender, recipient, amount);
}

Transmuterv2::deposit

function deposit(uint256 amount, address owner) external override nonReentrant {
_onlyWhitelisted();
@> _updateAccount(
UpdateAccountParams({
owner: owner,
unexchangedDelta: SafeCast.toInt256(amount),
exchangedDelta: 0
})
);
TokenUtils.safeTransferFrom(syntheticToken, msg.sender, address(this), amount);
emit Deposit(msg.sender, owner, amount);
}

As you can see in the code, claimAndSwap uses transmuterv2::claim and transmuterv2::deposit consecutively without any input sanitation in claimAndSwap. This results in transmuterv2::mapping(address => Account) private accounts; carrying wrong values which keepers possibly could withdraw.

Impact:

Being able to inflate the keepers balance due to the lack of input sanitation would allow the keeper to withdraw arbitrary amounts out of the protocol.

Likelihood: High
Impact: High

Severity: High

Tools Used:

Manual review.

Recommended Fix:

function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IVeloRouter.route[] calldata _path ) external onlyKeepers {
+ uint256 maxClaimable = transmuter.getClaimableBalance(address(this));
+ if(_amountClaim > maxClaimable) {
+ revert TooHighClaim(_amountClaim, maxClaimable);
+ };
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}

Implementing this fix will make the claimAndSwap function in the contracts fail directly in the keeper should not have sufficient claimable balance with the transmuter.

Updates

Appeal created

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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