Platform: Code4rena
Start Date: 23/05/2022
Pot Size: $50,000 USDC
Total HM: 44
Participants: 99
Period: 5 days
Judge: hickuphh3
Total Solo HM: 11
Id: 129
League: ETH
Rank: 35/99
Findings: 4
Award: $230.99
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: dipp
Also found by: 0x1f8b, csanuragjain, pedroais
162.6494 USDC - $162.65
In the withdrawForETH
function in RubiconRouter.sol
, the targetPool
may be any contract that implements the IBathToken
interface and returns wethAddress
as its underlying token. The withdrawnWETH
amount could be set to the RubiconRouter.sol
contract's WETH balance so that the contract's entire WETH balance is withdrawn, as long as the tagetPool
does not transfer any WETH to RubiconRouter.sol
. The caller of the withdrawForETH
function would then receive the withdraw amount.
function withdrawForETH(uint256 shares, address targetPool) external payable returns (uint256 withdrawnWETH) { IERC20 target = IBathToken(targetPool).underlyingToken(); require(target == ERC20(wethAddress), "target pool not weth pool"); require( IBathToken(targetPool).balanceOf(msg.sender) >= shares, "don't own enough shares" ); IBathToken(targetPool).transferFrom(msg.sender, address(this), shares); withdrawnWETH = IBathToken(targetPool).withdraw(shares); WETH9(wethAddress).withdraw(withdrawnWETH); //Send back withdrawn native eth to sender msg.sender.transfer(withdrawnWETH); }
Let shares
be equal to the contracts WETH balance.
The malicious targetPool
contract returns the wethAddress
as the underlying token on line 480.
targetPool
returns the max uint256 value for its balanceOf function to pass the require condition on line 483 for any value of shares.
The transferFrom on line 486 does not have to do anything and its withdraw function should return the WETH balance of RubiconRouter.sol
.
The RubiconRouter.sol
contract will then withdraw ETH equal to the withdrawWETH
amount, which should be equal to the contract's WETH balance.
The caller of the withdrawForETH
function receives the withdraw ETH without providing any WETH.
Check the contract's WETH balance before the caller is supposed to send the WETH and after the WETH is sent to confirm the contract has received enough WETH from the caller.
#0 - HickupHH3
2022-06-15T07:43:44Z
Will keep this as medium severity because of the pre-requisite of users accidentally sending ETH to the router contract
🌟 Selected for report: berndartmueller
Also found by: 0x1f8b, 0xDjango, 0xsomeone, ACai, Bahurum, BouSalman, CertoraInc, Deivitto, Dravee, GimelSec, IllIllI, JMukesh, Kaiziron, PP1004, Ruhum, SmartSek, VAD37, WatchPug, _Adam, aez121, antonttc, blockdev, broccolirob, camden, cccz, cryptphi, defsec, dipp, ellahi, fatherOfBlocks, gzeon, horsefacts, ilan, jayjonah8, joestakey, kenta, kenzo, minhquanym, oyc_109, pauliax, pedroais, peritoflores, sashik_eth, shenwilly, simon135, throttle, xiaoming90, z3s
0.1022 USDC - $0.10
Judge has assessed an item in Issue #392 as Medium risk. The relevant finding follows:
#0 - HickupHH3
2022-06-25T03:51:33Z
Impact Some tokens return boolean values instead of reverting on failure and should be checked everytime transfer or transferFrom are called. Put the transfer in a require so that a failed transaction that returns false reverts.
dup of #316
🌟 Selected for report: cccz
Also found by: AlleyCat, GimelSec, IllIllI, Ruhum, berndartmueller, csanuragjain, dipp, fatherOfBlocks, gzeon, horsefacts, pedroais, shenwilly
RubiconRouter.sol#L325-L358 RubiconRouter.sol#L383-L409 RubiconRouter.sol#L455-L472
When calling a number of functions in RubiconRouter.sol
that require ETH, msg.value
is allowed to be more than the actual amount needed. Only the amount needed is converted to WETH and the remaining ETH is not sent back to the user.
buyAllAmountWithETH
function example:
A user calls buyAllAmountWithETH
and sends 1000 ETH to the contract.
If the max_fill_amount_with_fee
is 500, only 500 ETH is converted to WETH.
RubiconRouter.sol
now has 500 WETH and 500 ETH.
Lets say only 400 WETH is used in the buyAllAmount
trade in RubiconMarket.sol
, then the remaining WETH is 100.
The user receives the remaining WETH refund on line 356.
The remaining 500 ETH stays in the contract and is not sent back to the user.
offerWithETH
function example:
A user calls offerWithETH
with a pay_amt
of 500 and msg.value
1000 ETH.
Only the pay_amt
amount is converted to WETH and the rest of the ETH remains in the contract.
depositWithETH
function example:
A user calls depositWithETH
with a pay_amt
of 500 and msg.value
1000 ETH.
Only the pay_amt
amount is converted to WETH on line 468.
The shares gained by the targetPool
is sent to the caller on line 471 but the remaining 500 ETH is not.
Consider changing the require conditions from msg.value >=
to msg.value ==
so that users are not allowed to send too much ETH.
#0 - KenzoAgada
2022-06-04T15:32:58Z
Duplicate of #15.
#1 - bghughes
2022-06-04T21:48:00Z
Duplicate of #15
🌟 Selected for report: IllIllI
Also found by: 0x1337, 0x1f8b, 0x4non, 0xDjango, 0xKitsune, 0xNazgul, 0xf15ers, ACai, AlleyCat, Bahurum, BouSalman, CertoraInc, Chom, Dravee, ElKu, FSchmoede, Funen, GimelSec, Hawkeye, JC, JMukesh, Kaiziron, MaratCerby, Metatron, PP1004, Picodes, Ruhum, SmartSek, StErMi, TerrierLover, UVvirus, UnusualTurtle, WatchPug, Waze, _Adam, asutorufos, berndartmueller, blackscale, blockdev, broccolirob, c3phas, catchup, cryptphi, csanuragjain, defsec, delfin454000, dipp, eccentricexit, ellahi, fatherOfBlocks, gzeon, hansfriese, horsefacts, hubble, ilan, joestakey, kebabsec, minhquanym, oyc_109, parashar, pauliax, rotcivegaf, sach1r0, sashik_eth, shenwilly, simon135, sorrynotsorry, sseefried, throttle, unforgiven, xiaoming90
52.0351 USDC - $52.04
The deductions on lines 608, 609, 622 and 623 are not protected against underflows. Use the SafeMath sub function instead, so that if an underflow occurs the transaction reverts.
Some tokens return boolean values instead of reverting on failure and should be checked everytime transfer or transferFrom are called. Put the transfer in a require so that a failed transaction that returns false reverts.