Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Invalid

Swap refund mechanism allows users to extract more value than they provided to the protocol during swap initiation

Summary

The refundSwap function within the StabilityBranch contract is designed to facilitate the refunding of USD tokens to users if their initiated swaps expire before fulfilment. However, users may be able to receive tokens that are more valuable than those they originally provided, thereby potentially causing financial losses to the protocol.

Vulnerability Details

When a user initiates a swap, they are required to provide specific amounts of tokens they wish to swap for other tokens (amountsIn). Then per every token in, the user also provides vaultIds for which a usdToken will be retrieved based on that vault's engine.
Then finally the user supplies these specified amount for each token to the contract:

function initiateSwap(
>> uint128[] calldata vaultIds,
>> uint128[] calldata amountsIn,
uint128[] calldata minAmountsOut
)
external
{
---SNIP---
for (uint256 i; i < amountsIn.length; i++) {
---SNIP---
// @audit-info Here, the usdTokenOfEngine is the one connected to vault's engine
>> ctx.usdTokenOfEngine = IERC20(configuration.usdTokenOfEngine[currentVault.engine]);
// @audit-info Users supplies that token
>> ctx.usdTokenOfEngine.safeTransferFrom(msg.sender, address(this), amountsIn[i]);
---SNIP---
// @audit-info input amount and vaultId used are saved here
UsdTokenSwapConfig.SwapRequest storage swapRequest = tokenSwapData.swapRequests[msg.sender][ctx.requestId];
>> swapRequest.amountIn = amountsIn[i];
>> swapRequest.vaultId = vaultIds[i];
---SNIP---
}
}

Now, during refundSwap(), the current implementation permits users to specify any engine address for the refund.

>> function refundSwap(uint128 requestId, address engine) external {
---SNIP---
// @audit-info usdToken is retrieved based on whatever engine address the user has provided
>> address usdToken = marketMakingEngineConfiguration.usdTokenOfEngine[engine];
// cache the usd token swap base fee
uint256 baseFeeUsd = tokenSwapData.baseFeeUsd;
---SNIP---
// cache the amount of usd tokens to be refunded
uint256 refundAmountUsd = depositedUsdToken - baseFeeUsd;
// @audit-info refund amount transfered back to user but with the token of his choice
>> IERC20(usdToken).safeTransfer(msg.sender, refundAmountUsd);
---SNIP---
}

This vulnerability potentially allows users to receive more valuable tokens than they originally provided.

Scenario:

  1. A user initiates a swap providing vaultId = 1. This vault has its engine = engineA with usdToken = tokenA

  2. During the refund process, the user then request a refund in tokens from a different engine (e.g., Engine B).

  3. If the usdToken from Engine A and Engine B have different values (e.g., Engine A's token is worth $1, while Engine B's token is worth $1.10), the user could exploit this discrepancy to receive more value than they initially provided.

Impact

The contract may incur financial losses as users could exploit the system to receive more valuable tokens than they originally deposited.

Tools Used

Manual Review

Recommendations

Modify the refundSwap() function to enforce that the engine used for the refund must match the engine used during the swap initiation.

- function refundSwap(uint128 requestId, address engine) external {
+ function refundSwap(uint128 requestId) external {
---SNIP---
// load swap request
UsdTokenSwapConfig.SwapRequest storage request = tokenSwapData.swapRequests[msg.sender][requestId];
- address usdToken = marketMakingEngineConfiguration.usdTokenOfEngine[engine];
+ // @audit Use the usdToken from initiation
+ Vault.Data storage usedVault = Vault.load(request.vaultId);
+ address usdToken = IERC20(configuration.usdTokenOfEngine[usedVault.engine]);
---SNIP---
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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.