Skip to content

Latest commit

 

History

History
139 lines (109 loc) · 4.7 KB

005.md

File metadata and controls

139 lines (109 loc) · 4.7 KB

Gorgeous Maroon Stallion

High

AmirX.sol's swap function could cause totalSupply to fall below getMinLimit due to outdated checking

Summary

At the start of swap in AmirX.sol, the code calls if (ss.destination != address(0)) _verifyStablecoinSwap(wallet, ss);

Among the things checked, _verifyStablecoinSwap will ensure that Stablecoin(ss.origin).totalSupply() - ss.oAmount >= getMinLimit(ss.origin).

However ss.oAmount is the not real amount that is used for the swap as later on in the function, the variable is changed and passed in to _stablecoinSwap which no longer checks if the new ss.oAmount will break the limit.

Root Cause

For context, _verifyStablecoinSwap checks that ss.oAmount does not cause totalSupply() to break below getMinLimit(ss.origin)

function _verifyStablecoinSwap(
    address wallet,
    StablecoinSwap memory ss
) internal view nonZero(ss) {
    .....
    if (isXYZ(ss.origin)) {
        // Ensure the total supply does not drop below the minimum limit after burning the specified amount.
        if (
-->         Stablecoin(ss.origin).totalSupply() - ss.oAmount <
-->         getMinLimit(ss.origin)
-->     ) revert InvalidMintBurnBoundry(ss.origin, ss.oAmount);
    } else if (ss.liquiditySafe == address(0)) {
        .....
    }
    .....
}

Referencing the swap function in AmirX.sol

https://github.com/sherlock-audit/2024-11-telcoin/blob/b9c751b59e78a7123a636e31ecafc9147046f190/telcoin-audit/contracts/swap/AmirX.sol#L73-L102

function swap(
    address wallet,
    bool directional,
    StablecoinSwap memory ss,
    DefiSwap memory defi
) external payable onlyRole(SWAPPER_ROLE) whenNotPaused {
        // checks if it will fail
1.)->   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
2.)->               if (fBalance - iBalance != 0) ss.oAmount = fBalance - iBalance;
3.)->               _stablecoinSwap(wallet, ss);
              }
        } else {
              // if stablecoin swap
              _stablecoinSwap(wallet, ss);
              // if only stablecoin swap
              if (defi.walletData.length != 0) _defiSwap(wallet, defi);
        }
}
  • As you can see, the limit is only checked in _verifyStablecoinSwap of line 1.) and then ss.oAmount changes value in line 2.).

  • Afterwards line 3.) straightaway calls the internal function _stablecoinSwap.

  • The internal function _stablecoinSwap is different from the external function stablecoinSwap. Namely, the internal function does not check the limit.

Hence the outdated limit check in line 1.) before the value changes allows totalSupply to break below the limit against intended behaviour.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

Inside the swap, totalSupply to breaks below the getMinLimit

PoC

No response

Mitigation

Add an updated check after the value changes

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;
+           if (fBalance - iBalance != 0) {
+               ss.oAmount = fBalance - iBalance;
+               _verifyStablecoinSwap(wallet, ss);
+           }
            _stablecoinSwap(wallet, ss);
        }
    } else {
        .....
        .....
    }
}