Skip to content

Latest commit

 

History

History
115 lines (84 loc) · 4.99 KB

014.md

File metadata and controls

115 lines (84 loc) · 4.99 KB

Nutty Licorice Crocodile

Medium

Wrong validation in defiToStablecoinSwap could lead to break invariant

Summary

In the AmirX::defiToStablecoinSwap function, parameter validation is performed at the start. Initially, _verifyDefiSwap is called to validate the DeFi swap parameters, followed by _verifyStablecoinSwap for the stablecoin swap validation.

After parameter checks, the function retrieves the current balance of the wallet for the ss.origin token. The DeFi swap is then executed, and the function calculates the difference in the wallet balance before and after the swap to determine the new origin amount, oAmount.

However, this approach poses a potential issue: if the computed oAmount exceeds the expected value set in _verifyStablecoinSwap, it could violate an intended invariant.

A similar issue may also exist in the swap function.

Root Cause

In AmirX::defiToStablecoinSwap, the code calculates the wallet balance before and after executing the defiSwap as follows:

uint256 iBalance = ERC20(ss.origin).balanceOf(wallet);
_defiSwap(wallet, defi);
uint256 fBalance = ERC20(ss.origin).balanceOf(wallet);

The output amount, oAmount, is then adjusted based on the difference between fBalance and iBalance.

However, this approach may lead to a violation of the minSupply invariant, which is enforced in _verifyStablecoinSwap for the origin token based on the original balance. Adjusting oAmount later can bypass the intended minSupply constraint, as the minSupply check will not reflect the updated balance.

https://github.com/sherlock-audit/2024-11-telcoin/blob/main/telcoin-audit/contracts/swap/AmirX.sol#L111-L128

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

  1. The AmirX::defiToStablecoinSwap operation is called with Token A as the origin currency.
  2. Initially, Token A has a supply of 100, with a minSupply threshold of 90 enforced by _verifyStablecoinSwap. The initial oAmount passed is 9, so _verifyStablecoinSwap passes, as the remaining supply would be 91 (100 - 9 > 90).
  3. The function then proceeds to execute _defiSwap. If the balance difference before and after the swap for Token A is 12, the value of oAmount is updated to 12.
  4. This adjustment results in an inconsistent state, as the effective supply of Token A drops below the minSupply threshold of 90, violating the intended supply constraint.

Impact

The protocol enters an inconsistent state where the origin stablecoin used in the swap ends up with a supply lower than the minSupply threshold set in StablecoinHandler::UpdateXYZ. This discrepancy violates the minSupply constraint. Such a state could affect system integrity and potentially allow for unintended behavior, as the minSupply limit is designed to ensure adequate liquidity and stability for that stablecoin.

PoC

No response

Mitigation

Consider move the check for _verifyStablecoinSwap after changing the oAmount:

function defiToStablecoinSwap(
        address wallet,
        StablecoinSwap memory ss,
        DefiSwap memory defi
    ) external payable onlyRole(SWAPPER_ROLE) whenNotPaused {
        // checks if defi will fail
        _verifyDefiSwap(wallet, defi);
        // checks if stablecoin swap will fail
       _verifyStablecoinSwap(wallet, ss);

        //check balance to adjust second swap
        uint256 iBalance = ERC20(ss.origin).balanceOf(wallet);
        _defiSwap(wallet, defi);
        uint256 fBalance = ERC20(ss.origin).balanceOf(wallet);
        ss.oAmount = fBalance - iBalance; 
+     _verifyStablecoinSwap(wallet, ss);
        //change balance to reflect change
        _stablecoinSwap(wallet, ss);
    }


....

function swap(
        address wallet,
        bool directional,
        StablecoinSwap memory ss,
        DefiSwap memory defi
    ) external payable onlyRole(SWAPPER_ROLE) whenNotPaused {
        // checks if it will fail
        if (ss.destination != address(0)) _verifyStablecoinSwap(wallet, ss);
        if (defi.walletData.length != 0) _verifyDefiSwap(wallet, defi);

        if (directional) {
            // if only defi swap
            if (ss.destination == address(0)) _defiSwap(wallet, defi);
            else {
                // if defi then stablecoin swap
                //check balance to adjust second swap
                uint256 iBalance = ERC20(ss.origin).balanceOf(wallet);
                if (defi.walletData.length != 0) _defiSwap(wallet, defi);
                uint256 fBalance = ERC20(ss.origin).balanceOf(wallet);
                //change balance to reflect change
                if (fBalance - iBalance != 0) ss.oAmount = fBalance - iBalance;
+              _verifyStablecoinSwap(wallet, ss);
                _stablecoinSwap(wallet, ss);
            }
        } else {
            // if stablecoin swap
            _stablecoinSwap(wallet, ss);
            // if only stablecoin swap
            if (defi.walletData.length != 0) _defiSwap(wallet, defi);
        }
    }