Slingshot Finance contest - daejunpark'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: 7/16

Findings: 2

Award: $1,486.33

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: WatchPug

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

Labels

bug
duplicate
2 (Med Risk)

Awards

588.8441 USDC - $588.84

External Links

Handle

daejunpark

Vulnerability details

Impact

The Slingshot.executeTrades() incorrectly calculates initialBalance when toToken == nativeToken. It should have been the balance of wrapped native tokens (e.g., WETH), rather than that of native currencies (e.g., Ether). This incorrect behavior introduces various attack vectors. Below are example exploit scenarios.

Scenario 1

  1. Suppose the Executioner contract happens to hold a good amount of WETH balance, say 10 WETH. (This could happen, for example, when poor users might have accidentally sent their WETH tokens to it, or some dust leftovers during the previous trades might have been accumulated for a long period of time.)
  2. Suppose the Ether balance of the Executioner contract is zero at this point.
  3. Now, adversaries can call Slingshot.executeTrades(), with fromToken being a dummy token, fromAmount being a tiny amount, and toToken being set to nativeToken.
  4. Then, the returned amount will be the dummy trading output plus the existing 10 WETH tokens. This way, adversaries can drain any idle WETH tokens sitting in the Executioner contract.

Scenario 2

  1. Suppose Alice accidentally or maliciously sends 7 Ether to the Executioner contract.
  2. Suppose Bob calls Slingshot.executeTrades(), attempting to buy 10 WETH, where he accidentally sets finalAmountMin to zero.
  3. Then, Bob will get only 3 WETH in return, and the remaining 7 WETH will stay in the Executioner contract. Note that the remaining 7 WETH tokens can be stolen by adversaries using scenario 1.
  4. Later Alice may ask the Executioner contract owner to rescue her funds.

Remarks for the scenario 2:

  • It is hard to tell if Alice is malicious or not. Thus, in most cases, the contract owner would end up rescuing her funds at step 4 to make her happy.
  • In step 2, if Bob didn't make a mistake and set finalAmountMin large enough, then his trading request will fail, and he can avoid losing his funds, which is good. However, note that any such trades for WETH with a reasonable finalAmountMin value will always fail until Alice's funds are rescued. Thus this behavior can be exploited for griefing attacks.

Recommendation

For the short term, fix the following initialBalance calculation logic to be consistent with that of finalBalance below:

  • initialBalance: Slingshot.sol#L81
    uint256 initialBalance = _getTokenBalance(toToken);
  • finalBalance: Slingshot.sol#L86-L91
    uint finalBalance; if (toToken == nativeToken) { finalBalance = _getTokenBalance(address(wrappedNativeToken)); } else { finalBalance = _getTokenBalance(toToken); }

For the long term, refactor the contract to decouple the native token handling logic from the main business logic. This can minimize the case splits and reduce the likelihood of making a similar mistake again in the future when upgrading the code base.

#0 - tommyz7

2021-11-03T15:03:22Z

Duplicate of #18

Findings Information

🌟 Selected for report: daejunpark

Also found by: pauliax

Labels

bug
1 (Low Risk)

Awards

897.4913 USDC - $897.49

External Links

Handle

daejunpark

Vulnerability details

Impact

In every module, the swap() function is declared payable, while they shouldn’t. In general, it is a better practice to add the payable annotation only for payable functions. Moreover, in this case, the swap() functions should not access msg.value, because they are called inside the delegatecall loop. (See this article for the security implication of accessing msg.value inside delegate call loops.) Thus, removing the payable annotation is a better defensive programming practice to avoid potential mistakes in the future when upgrading the code.

Recommendation

Remove the payable annotation from the swap() function of all the modules:

#0 - tommyz7

2021-11-04T18:09:57Z

Duplicate of #93

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