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: 52/99
Findings: 5
Award: $108.67
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L605 https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L194-L215
There are several ERC20 tokens that do not revert on failure. Therefore, it is possible for a function call containing transfer()
or transferFrom()
to succeed even if the ERC20 token transfer did not. This can lead to loss of funds for Rubicon users, specifically in the BathToken._withdraw()
function. In the case of an ERC20 that does not revert on failure, if the contract does not have enough tokens to cover the transfer, the user will simply not receive any value from their withdrawal and their bath tokens will still be burnt.
Manual review
Always use safeTransfer()
and safeTransferFrom()
.
#0 - bghughes
2022-06-03T23:35:16Z
Duplicate of #316
🌟 Selected for report: IllIllI
Also found by: 0xDjango, Dravee, SmartSek, StErMi, oyc_109, pauliax, rotcivegaf, sashik_eth, shenwilly, xiaoming90
23.3384 USDC - $23.34
The migration function BathHouse.adminWriteBathToken()
provides a rug vector for the admin of the protocol. They are able to receive deposits of underlying token and then switch the bath token contract associated with the underlying token to any contract they desire.
Manual review
The presence of this function poses a security risk to the users of the protocol. Perhaps migration steps can be completed through a proposal process instead of at will by the owner of the protocol.
#0 - bghughes
2022-06-03T20:48:42Z
Centralization risk is acknowledged #344
#1 - pauliax
2022-06-07T08:56:13Z
I think it was explicitly mentioned that v1 will be a centralized system, and later steps will be taken to improve decentralization: "BathHouse has an admin that is the EOA administrator of the entire protocol in v1."
Thus, I think it is still an issue but definitely not of high severity.
#2 - HickupHH3
2022-06-18T03:56:38Z
Duplicate of #249
🌟 Selected for report: Ruhum
Also found by: 0x1f8b, 0x4non, 0xDjango, Dravee, GimelSec, StErMi, berndartmueller, blackscale, catchup, cccz, csanuragjain, defsec, eccentricexit, ellahi, horsefacts, hubble, joestakey, kenzo, pedroais, peritoflores, reassor, rotcivegaf, sashik_eth, shenwilly, sseefried, throttle, xiaoming90
1.8534 USDC - $1.85
The owner is able to set the feeBPS
as high as they like since no validation is performed in the setter. This can have two effects:
feeBPS
variable to 10000, the owner will effectively steal 100% percent of the withdraw value.feeBPS
variable to 10001, the owner will DOS the withdrawal because the _withdraw()
function will attempt to transfer more token to the feeTo
than available for the withdrawal.Manual review
Apply some validation so that the fee can only be set within a specific range.
#0 - bghughes
2022-06-03T22:30:43Z
Duplicate of #125 #411
#1 - HickupHH3
2022-06-18T04:33:05Z
Duplicate of #21
🌟 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.074 USDC - $52.07
The initialize() function lacks access control, allowing any malicious user to initialize the contract. This will require a redeploy of the contract if successful.
The BathToken.setBathHouse()
function is used to change the bath house contract that is allowed to call the bath token contract's bath house-specific functions. The function lacks a proper ownership transfer pattern. It is recommended to make this a two-step process to ensure that the new bath house is truly the desired address. The first function called will set the pending contract and a second function must be called by the pending contract to accept the transfer.
Given the use of SafeMath, the first parentheses are unnecessary in this arithmetic.
The same contract is imported twice.
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xf15ers, 0xkatana, Chom, DavidGialdi, Dravee, ElKu, FSchmoede, Fitraldys, Funen, GimelSec, JC, Kaiziron, MaratCerby, Metatron, MiloTruck, Picodes, Randyyy, RoiEvenHaim, SmartSek, Tomio, UnusualTurtle, WatchPug, Waze, _Adam, antonttc, asutorufos, berndartmueller, blackscale, blockdev, c3phas, catchup, csanuragjain, defsec, delfin454000, ellahi, fatherOfBlocks, gzeon, hansfriese, ilan, joestakey, minhquanym, oyc_109, pauliax, pedroais, reassor, rfa, rotcivegaf, sach1r0, samruna, sashik_eth, simon135, z3s
31.3128 USDC - $31.31
A lot of gas can be saved on failures by applying the following check prior to the token transfer and event emissions.
require( assetsReceived >= assets, "You cannot withdraw the amount of assets you expected" );
Using multiple require statements can be more performant than a string of &&'s.
Gas can be saved when performing for loops by following this pattern. The pattern moves the increment of i
to an unchecked block removing the overflow checks, while also changing the increment to ++i.
for (uint i = 0; i < length;) { doStuff(); unchecked { ++i; } }
The initialization of variable newOne
is unnecessary. Since the function returns newBathToken
, it is not required to temporarily store this in newOne
. Simply store it in newBathToken
.