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
Rank: 7/16
Findings: 2
Award: $1,486.33
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: WatchPug
Also found by: daejunpark, gpersoon, hickuphh3, kenzo, pmerkleplant
daejunpark
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.
Slingshot.executeTrades()
, with fromToken
being a dummy token, fromAmount
being a tiny amount, and toToken
being set to nativeToken
.Slingshot.executeTrades()
, attempting to buy 10 WETH, where he accidentally sets finalAmountMin
to zero.Remarks for the scenario 2:
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.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
🌟 Selected for report: daejunpark
Also found by: pauliax
daejunpark
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.
Remove the payable
annotation from the swap() function of all the modules:
#0 - tommyz7
2021-11-04T18:09:57Z
Duplicate of #93