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: 10/99
Findings: 9
Award: $1,635.62
🌟 Selected for report: 0
🚀 Solo Findings: 0
##Impact
Anyone can cancel orders from the router and get the tokens
##Proof of concept -A user makes a WETH order from the router
-Any attacker can call the cancel function with the order ID and get all the unfilled funds from the order
Basically orders in the router are up for grabs to the first who cancels then since the orders are made in name of the router, not the user msg.sender in cancelation will be the router.
Only allow users to cancel orders they themselves sent
#0 - bghughes
2022-06-04T22:44:03Z
Duplicate of #17
🌟 Selected for report: xiaoming90
803.2069 USDC - $803.21
#impact Users could get exposed to higher risk than desired and funds to withdraw from the vault could not be available
The reserve ratio is the parameter that ensures a percentage of the tokens is always available to be withdrawn from a pool by the users. It also ensures a certain risk limit to how many funds can be risked at the same time. From the documentatión : enforceReserveRatio - this modifier ensures that the reserveRatio for each of the underlying liquidity pools (asset and quote bathTokens) is observed before and after function execution.
In bathPair.sol the reserve ratio is verified before but not after an offer is made so strategists can make trades that break the reserve ratio. https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathPair.sol#L344
Enforce reserve ratio only after orders are placed. Doing it before doesn’t serve any purpose since if the reserve ratio isn’t met before but is met after a trade then that trade would actually be enforcing the ratio.
So the ratio should ideally be checked after execution and not checked before.
#0 - HickupHH3
2022-06-16T14:33:05Z
Duplicate of #106
🌟 Selected for report: dipp
Also found by: 0x1f8b, csanuragjain, pedroais
Detailed description of the impact of this finding.
The withdrawForETH() function has a target pool address as input. This address is never validated. This means an attacker contract can be introduced as a fake pool.
The attacker contract could pass all the checks since all checks are calls to the contract.
Step by step : An attacker makes a malicious contract that implements the BathToken interface
When called targetPool.UnderlyingToken will return wethAddress to pass the check
The same can happen for targetPool.balanceOF and transferFrom
Finally targetPool.withdraw is called which doesn't withdraw anything since it's a fake pool but returns the router's ETH balance
Finally msg.sender.transfer(withdrawnWETH) happens and the attacker gets all the ETH from the contract
I consider this to be medium severity since it's not clear why the contract would hold ETH but the contract has a receive and fallback function so it's able to receive ETH.
Add input validation for targetPool address
#0 - KenzoAgada
2022-06-05T09:45:30Z
Duplicate of #356.
#1 - bghughes
2022-06-07T22:42:09Z
Duplicate of #356
🌟 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/rubiconPools/BathToken.sol#L565 https://github.com/RubiconDeFi/rubicon-protocol-v1/blob/6a0e1d89ff710f81428228d132e7d0c42de3a3cd/contracts/rubiconPools/BathPair.sol#L601 https://github.com/RubiconDeFi/rubicon-protocol-v1/blob/6a0e1d89ff710f81428228d132e7d0c42de3a3cd/contracts/rubiconPools/BathPair.sol#L615
ERC20 missing return value check
Funds may not be transferred but accounted as so
The erc20 interface ensures a token will return false on transfer failure but not necessarily revert. The return value should always be checked to account for these types of tokens.
This happens in various instances : https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L605
Also all token transfers in rubiconRouter
Use SafeERC20 library from openZeppelin
#0 - bghughes
2022-06-03T23:40:52Z
Duplicate of #316
🌟 Selected for report: cccz
Also found by: AlleyCat, GimelSec, IllIllI, Ruhum, berndartmueller, csanuragjain, dipp, fatherOfBlocks, gzeon, horsefacts, pedroais, shenwilly
Users can lose funds
In the swapWithEth() function there is a pay amount input value is used to make the swap. msg.value is required to be greater than or equal to the pay amount plus the fee. If msg. value is greater then the extra ETH will be lost.
require msg.value == amtWithFee instead of >= or directly set pay amount equal to msg.value - fee
#0 - KenzoAgada
2022-06-04T15:53:41Z
Duplicate of #15.
#1 - bghughes
2022-06-07T22:41:48Z
Duplicate of #15
🌟 Selected for report: kenzo
Also found by: CertoraInc, PP1004, pedroais
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L514 https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L499 https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L604
When a user withdraws from the vault the withdrawal fee is charged twice
The withdraw function computes shares from previewWithdraw() and then passes that share amount to _withdraw()
PreviewWithdraw() takes the amount subtracts the fee and returns the number of shares representing that amount
_withdraw then takes that share amount, converts it to asset again and subtract the fee
So the fee is being subtracted twice from the withdraw
Instead of PreviewWithdraw() ConvertToShares() should be used to get the number of shares and charge the fee only once
#0 - bghughes
2022-06-03T23:16:08Z
Duplicate of #140
🌟 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
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L261 https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L1232
Rugpull vector : Fee can be frontrunned to 100%. It can also be set to more than 100%. In that case substraction underflow and revert. Funds will be locked in the pool.
There is no limit to the fee which can be set to any percentage. This happens both in the market and in the withrawal fee for pools.
Add a maximum fee
#0 - HickupHH3
2022-06-21T06:13:48Z
duplicate of #21
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathPair.sol#L493 https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L352
Any strategist can steal all funds from a given pool
The rebalancing mechanism ensures different tokens go to their corresponding pools after trades. Strategists can rebalance pools with tokens that aren’t their underlying asset to send tokens where they belong. They charge a fee for doing so.
A strategist can call the rebalance method with the same pool as asset and quote address. This means a pool will be rebalanced with itself so it will transfer funds to itself. It's a neutral action that doesn't change anything. The strategist will still get the rebalancing fee.
A strategist can rebalance a pool with itself an infinite amount of times and get the fee. In that way, he can drain all funds from any pool. This can only be done is a pool is rebalanced with itself wich shouldn’t happen.
Check assetAddress != quote address
#0 - bghughes
2022-06-03T21:53:54Z
Duplicate of #246
#1 - HickupHH3
2022-06-17T02:19:06Z
Duplicate of #157
#2 - HickupHH3
2022-06-17T02:56:43Z
Centralisation risk issue: #344
#3 - HickupHH3
2022-06-23T15:18:18Z
Duplicate of #211: BathToken.rebalance allows underlying token as filledAssetToRebalance.
🌟 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
30.835 USDC - $30.84
GAS G-01: Useless if statement. If the if is false the for loop wouldn’t start so the if can be safely deleted to save gas. https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L634
G-02: since askDelta = info.askPayAmt - offer1.pay_amt;
info.askPayAmt.sub(askDelta) will be equal to offer1.pay_amt
Making the substraction is not necesary and wastes gas. offer1.pay_amt can be used instead
Also