Slingshot Finance contest - WatchPug's results

Web3 trading platform.

General Information

Platform: Code4rena

Start Date: 30/10/2021

Pot Size: $35,000 ETH

Total HM: 2

Participants: 16

Period: 3 days

Judge: alcueca

Total Solo HM: 1

Id: 48

League: ETH

Slingshot Finance

Findings Distribution

Researcher Performance

Rank: 8/16

Findings: 3

Award: $971.12

🌟 Selected for report: 8

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: WatchPug

Also found by: daejunpark, gpersoon, hickuphh3, kenzo, pmerkleplant

Labels

bug
2 (Med Risk)
disagree with severity

Awards

588.8441 USDC - $588.84

External Links

Handle

WatchPug

Vulnerability details

https://github.com/code-423n4/2021-10-slingshot/blob/9c0432cca2e43731d5a0ae9c151dacf7835b8719/contracts/Slingshot.sol#L65-L92

function executeTrades(
    address fromToken,
    address toToken,
    uint256 fromAmount,
    TradeFormat[] calldata trades,
    uint256 finalAmountMin,
    address depricated
) external nonReentrant payable {
    depricated;
    require(finalAmountMin > 0, "Slingshot: finalAmountMin cannot be zero");
    require(trades.length > 0, "Slingshot: trades cannot be empty");
    for(uint256 i = 0; i < trades.length; i++) {
        // Checks to make sure that module exists and is correct
        require(moduleRegistry.isModule(trades[i].moduleAddress), "Slingshot: not a module");
    }

    uint256 initialBalance = _getTokenBalance(toToken);
    _transferFromOrWrap(fromToken, _msgSender(), fromAmount);

    executioner.executeTrades(trades);

    uint finalBalance;
    if (toToken == nativeToken) {
        finalBalance = _getTokenBalance(address(wrappedNativeToken));
    } else {
        finalBalance = _getTokenBalance(toToken);
    }
    uint finalOutputAmount = finalBalance - initialBalance;
    ...

https://github.com/code-423n4/2021-10-slingshot/blob/9c0432cca2e43731d5a0ae9c151dacf7835b8719/contracts/Slingshot.sol#L157-L163

function _getTokenBalance(address token) internal view returns (uint256) {
    if (token == nativeToken) {
        return address(executioner).balance;
    } else {
        return IERC20(token).balanceOf(address(executioner));
    }
}

When users swap to native token (ETH), the initialBalance should use the balance of wrappedNativeToken instead of native token balance, because finalBalance is the balance of wrappedNativeToken.

In the current implementation, when the toToken is the native token, initialBalance will be the ether balance of executioner contract. Therefore, when the ether balance of executioner is not 0, finalOutputAmount will be wrong.

The attacker can transfer a certain amount of ETH to the executioner contract and malfunction the protocol. Causing fund loss to users because finalOutputAmount is lower than the actual swapped amount, or DoS due to finalAmountMin cant be met.

PoC

Given:

  • The attacker send 0.25 ETH to the executioner contract;
  • The price of ETH in USDC is: 4000
  1. Alice swaps 5000 USDC to 1.25 ETH with finalAmountMin set to 1 ETH;
  2. Alice will get 1 ETH out and lose 0.25 ETH;
  3. Bob swaps 1000 USDC to 0.25 ETH with finalAmountMin set to 1 wei;
  4. Bob's transaction fails due to finalAmountMin cant being met.

Recommendation

Consider updating _getTokenBalance() and return IERC20(wrappedNativeToken).balanceOf(address(executioner)); when token == nativeToken.

#0 - tommyz7

2021-11-03T14:39:09Z

"Alice swaps 5000 USDC to 1.25 ETH with finalAmountMin set to 1 ETH;" this assumption is wrong because it's based on huge slippage assumption. There is no way a Slingshot transaction accepts 20% slippage so funds loss scenario is incorrect.

duplicate of #18, medium risk since no user funds are at risk.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter