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: 38/99
Findings: 5
Award: $213.47
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x1f8b
Also found by: CertoraInc, IllIllI, rotcivegaf, unforgiven
Broke the EIP 2612
Some contract or dapp/backend could building the DOMAIN_SEPARATOR
consulting the "rigth" name
to the BathToken
and build the signature with "rigth" digest
message
When these try to use the permit
function (L713), with the "rigth" signature(v, r, s), the permit
function will revert with the message "bathToken: INVALID_SIGNATURE"
because the expect DOMAIN_SEPARATOR
in the BathToken.sol
contract was built with "wrong" name
In the initialize
function(L181) move the name
definition before the DOMAIN_SEPARATOR
definition
function initialize( ERC20 token, address market, address _feeTo ) external { require(!initialized); string memory _symbol = string( abi.encodePacked(("bath"), token.symbol()) ); symbol = _symbol; underlyingToken = token; RubiconMarketAddress = market; bathHouse = msg.sender; //NOTE: assumed admin is creator on BathHouse uint256 chainId; assembly { chainId := chainid() } name = string(abi.encodePacked(_symbol, (" v1"))); DOMAIN_SEPARATOR = keccak256( abi.encode( keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ), keccak256(bytes(name)), keccak256(bytes("1")), chainId, address(this) ) ); decimals = token.decimals(); // v1 Change - 4626 Adherence // Add infinite approval of Rubicon Market for this asset IERC20(address(token)).approve(RubiconMarketAddress, 2**256 - 1); emit LogInit(block.timestamp); feeTo = address(this); //This contract is the fee recipient, rewarding HODLers feeBPS = 3; //Fee set to 3 BPS initially // Complete constract instantiation initialized = true; }
#0 - bghughes
2022-06-03T22:01:16Z
Duplicate of #38
🌟 Selected for report: IllIllI
Also found by: 0xDjango, Dravee, SmartSek, StErMi, oyc_109, pauliax, rotcivegaf, sashik_eth, shenwilly, xiaoming90
23.3384 USDC - $23.34
Judge has assessed an item in Issue #194 as Medium risk. The relevant finding follows:
#0 - HickupHH3
2022-06-27T14:25:13Z
Add ability to remove tokens from bonusTokens array In BathToken.sol: Add a remove bonus token function If add a wrong token by mistake there don't have a posibility to remove it
dup 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
Judge has assessed an item in Issue #194 as Medium risk. The relevant finding follows:
#0 - HickupHH3
2022-06-27T14:26:11Z
[L-01] Add a upper bound In BathToken.sol L256: The feeBPS, feeTo setter dont have a upper bound, by mistake could set high value and broke functions like _withdraw
dup 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
68.338 USDC - $68.34
Contest: Rubicon
Autor: Rotcivegaf
Core:
Peripheral:
:star: = Areas of concern :heavy_check_mark: = Audited Contract :white_check_mark: = Semi-Audited Contract(< 100%)
Use @openzeppelin
or @rari-capital/solmate
to clarify code avoid mistake and don't repeat logic, PLEASE
The library contract SafeMathE.sol
its not used, remove it
Move the files IERC20.sol
, ERC20.sol
, TokenWithFaucet.sol
to a test folder
Also the you can use the IERC20.sol
, ERC20.sol
from @openzeppelin
or @rari-capital/solmate
and clean these
Import and use the interfaces/IWETH.sol
in the RubiconRouter.sol
instead of peripheral_contracts/WETH9.sol
, solo move it to test folder
The peripheral_contracts/VestingWallet.sol
its imported from peripheral_contracts/BathBuddy.sol
but only use the SafeMath
library de @openzeppelin
Also the interface interfaces/IVestingWallet.sol
its unused
Import only the SafeMath
library and remove VestingWallet.sol
and IVestingWallet.sol
contracts
In BathBuddy.sol L6: The import its imported from IBathBuddy.sol
type(uint256).max
to clarify codeIn BathToken.sol Lines 214, 256, 421, 451, 703: replace 2**256 - 1
and uint256(-1)
to type(uint256).max
In BathToken.sol L134, L144: In event LogPoolCancel
and LogPoolOffer
use the same parameter name, e.g. orderId
In BathToken.sol: Remove or comment function parameter name to mitigate this warnings
_feeTo
parameter:function initialize( ERC20 token, address market, address /*_feeTo*/ ) external {
receiver
parameter:function maxDeposit(address /*receiver*/) public pure returns (uint256 maxAssets) {
receiver
parameter:function maxMint(address /*receiver*/) public pure returns (uint256 maxShares) {
index
, and revert
if don't found itIn BathPair.sol, L311:
function getIndexFromElement(uint256 uid, uint256[] storage array) internal view returns (uint256 _index) { for (uint256 index = 0; index < array.length; index++) { if (uid == array[index]) { return index; } } require("Didnt Find that element in live list, cannot scrub"); }
indexed
fieldsEach event
should use three indexed
fields if there are three or more fields
BathHouse.sol:
LogNewBathToken
LogOpenCreationSignal
BathPair.sol:
LogStrategistTrade
LogScrubbedStratTrade
LogStrategistRewardClaim
BathToken.sol:
LogDeposit
LogWithdraw
LogRebalance
LogPoolCancel
LogPoolOffer
LogRemoveFilledTradeAmount
RubiconMarket.sol:
LogItemUpdate
OfferDeleted
LogMinSell
LogUnsortedOffer
LogSortedOffer
LogInsert
LogDelete
LogMatch
RubiconRouter.sol:
LogSwap
BathBuddy.sol:
LogClaimBonusToken
RubiconRouter.sol, L25: Remove the LogNote
event
RubiconMarket.sol, L1262: Remove IWETH
interface
In BathToken.sol L256: The feeBPS
, feeTo
setter dont have a upper bound, by mistake could set high value and broke functions like _withdraw
uint256 public constant MAX_FEE_BPS = 10000; // 100 % function setFeeBPS(uint256 _feeBPS) external onlyBathHouse { require(_feeBPS <= MAX_FEE_BPS, "Too high fee"); feeBPS = _feeBPS; } uint256 public constant MAX_FEE_TO = 10000; // 100 % function setFeeTo(address _feeTo) external onlyBathHouse { require(_feeTo <= MAX_FEE_TO, "Too high fee"); feeTo = _feeTo; }
!= address(0)
require in _mint
functionIn BathToken.sol L657:
function _mint(address to, uint256 value) internal { require(to != address(0), "ERC20: mint to the zero address"); totalSupply = totalSupply.add(value); balanceOf[to] = balanceOf[to].add(value); emit Transfer(address(0), to, value); }
bonusTokens
arrayIn BathToken.sol: Add a remove bonus token function If add a wrong token by mistake there don't have a posibility to remove it
function removeBonusToken(uint256 index, address bonusERC20ToRemove) external onlyBathHouse { require(bonusTokens[index] == bonusERC20ToRemove, "Wrong index or bonusERC20ToRemove"); bonusTokens[index] = bonusTokens[bonusTokens.length - 1]; bonusTokens.pop(); }
PLEASE USE THIS AND IGNORE THE FIRST
🌟 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
32.1089 USDC - $32.11
Contest: Rubicon
Autor: Rotcivegaf
Core:
Peripheral:
:star: = Areas of concern :heavy_check_mark: = Audited Contract :white_check_mark: = Semi-Audited Contract(< 100%)
Gas save: 6 for each require
* For the pragma versions minor than 0.8.13
In BathToken.sol:
LogDeposit
and Deposit
log repeated the assets
, shares
and msg.sender
LogWithdraw
and Withdraw
log repeated the amountWithdrawn
, _shares
and msg.sender
Can remove from this parameters from LogDeposit
and LogWithdraw
events to save gas and clarify code
In BathToken.sol L205: Remove the bytes
cast
In BathToken.sol L214: Use market
instead of RubiconMarketAddress
to save the SLOAD
Use ++index
instead of index++
and unchecked the increment:
for (...; i++) { ... }
to
unchecked { for (...;) { ... unchecked { ++i; } }
Caching the array length is more gas efficient:
for (uint256 i = 0; i < array.length; i++) { ...
to
uint256 arrayLength = array.length; for (uint256 i = 0; i < arrayLength; i++) { ...
rewardsVestingWallet
, feeBPS
and bonusTokens.length
in vars and directly bonusTokens[index]
in release
function callIn BathToken.sol, L629 distributeBonusTokenRewards
function:
function distributeBonusTokenRewards( address receiver, uint256 sharesWithdrawn, uint256 initialTotalSupply ) internal { uint256 bonusTokensLength = bonusTokens.length; IBathBuddy _rewardsVestingWallet = rewardsVestingWallet; uint256 _feeBPS = feeBPS; if (bonusTokensLength > 0) { for (uint256 index = 0; index < bonusTokensLength; index++) { // Note: Shares already burned in Bath Token _withdraw // Pair each bonus token with a lightly adapted OZ Vesting wallet. Each time a user withdraws, they // are released their relative share of this pool, of vested BathBuddy rewards // The BathBuddy pool should accrue ERC-20 rewards just like OZ VestingWallet and simply just release the withdrawer's relative share of releaseable() tokens if (_rewardsVestingWallet != IBathBuddy(0)) { _rewardsVestingWallet.release( IERC20(bonusTokens[index]), receiver, sharesWithdrawn, initialTotalSupply, _feeBPS ); } } } }
allowance[from][msg.sender]
in var to save SLOAD gas costIn BathToken.sol, L698 transferFrom
function:
function transferFrom( address from, address to, uint256 value ) external returns (bool) { uint256 currentAllowance = allowance[from][msg.sender]; if (currentAllowance != uint256(-1)) { allowance[from][msg.sender] = currentAllowance.sub( value ); } _transfer(from, to, value); return true; }
IBathHouse(_bathHouse).getMarket()
in a varIn BathToken.sol, L115: In initialize
function save in a var and use msg.sender
instead of _bathHouse
to save SLOAD gas
function initialize(uint256 _maxOrderSizeBPS, int128 _shapeCoefNum) external { require(!initialized); address _bathHouse = msg.sender; //Assume the initializer is BathHouse _rubiconMarket = IBathHouse(msg.sender).getMarket(); require( _rubiconMarket != address(0x0000000000000000000000000000000000000000) && IBathHouse(msg.sender).initialized(), "BathHouse not initialized" ); bathHouse = msg.sender; RubiconMarketAddress = _rubiconMarket; ...