Platform: Code4rena
Start Date: 14/07/2022
Pot Size: $25,000 USDC
Total HM: 2
Participants: 63
Period: 3 days
Judge: PierrickGT
Total Solo HM: 1
Id: 147
League: ETH
Rank: 45/63
Findings: 1
Award: $20.97
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, Aymen0909, Chom, Deivitto, ElKu, JC, JohnSmith, Kaiziron, Limbooo, MadWookie, Meera, ReyAdmirado, Rohan16, Sm4rty, SooYa, TomJ, Trumpero, Waze, __141345__, ajtra, ak1, antonttc, bulej93, c3phas, cRat1st0s, csanuragjain, defsec, durianSausage, fatherOfBlocks, gogo, hake, hickuphh3, ignacio, joestakey, karanctf, kyteg, m_Rassska, pashov, rajatbeladiya, rbserver, robee, rokinot, samruna, sashik_eth, simon135, tofunmi
20.968 USDC - $20.97
> 0
is less gas efficient than!= 0
while using the optimizer at 10k AND used in a require statement. Detailed explanation with the opcodes here.
contracts/Witch.sol: 255: require(auction_.start > 0, "Vault not under auction"); 300: require(auction_.start > 0, "Vault not under auction"); 358: require(auction_.start > 0, "Vault not under auction"); 416: require(auction_.start > 0, "Vault not under auction");
!= 0
when comparisons with 0 in require
- require(auction_.start > 0, "Vault not under auction"); + require(auction_.start != 0, "Vault not under auction");
Caching will replace each Gwarmaccess (100 gas) with a much cheaper stack read.
DataTypes.Line storage line
Cashing it in memory save gas for 2 read instances; line.proportion
contracts/Witch.sol: 223 function _calcAuction( ... 229 ) internal view returns (DataTypes.Auction memory) { ... 231: DataTypes.Line storage line = lines[vault.ilkId][series.baseId]; //@gas 232 uint128 art = uint256(balances.art).wmul(line.proportion).u128(); ... 236 : uint256(balances.ink).wmul(line.proportion).u128(); ... 249 }
uint64 lineProportion
- DataTypes.Line storage line = lines[vault.ilkId][series.baseId]; + uint64 lineProportion = lines[vault.ilkId][series.baseId].proportion; - line.proportion + lineProportion
DataTypes.Auction storage auction_
Cashing it in memory save gas for 5 read instances; auction_.start
, auction_.ilkId
, auction_.baseId
, auction_.ink
, auction_.owner
contracts/Witch.sol: 253 function cancel(bytes12 vaultId) external { 254: DataTypes.Auction storage auction_ = auctions[vaultId]; 255 require(auction_.start > 0, "Vault not under auction"); ... 259 limits[auction_.ilkId][auction_.baseId].sum -= auction_.ink; 260 261 _auctionEnded(vaultId, auction_.owner); ... 264 }
DataTypes.Auction memory auction_
- DataTypes.Auction storage auction_ = auctions[vaultId]; + DataTypes.Auction memory auction_ = auctions[vaultId];
There is no opcode for >=
and <=
, when using them 2 operation are preformed; for (>
or <
) and =
.
>=
(6)contracts/Witch.sol: 105: initialOffer == 0 || initialOffer >= 0.01e18, 108: require(proportion >= 0.01e18, "Proportion below 1%"); 256: require(cauldron.level(vaultId) >= 0, "Undercollateralized"); 313: require(liquidatorCut >= minInkOut, "Not enough bought"); 365: require(liquidatorCut >= minInkOut, "Not enough bought"); 438: auction_.art - artIn >= debt.min * (10**debt.dec),
>
, decrement the comperedReplace >=
with >
, decrement the compered variable as needed.
<=
(3)ontracts/Witch.sol: 102: require(initialOffer <= 1e18, "InitialOffer above 100%"); 103: require(proportion <= 1e18, "Proportion above 100%"); 200: require(limits_.sum <= limits_.max, "Collateral limit reached");
<
, increment the comperedReplace <=
with <
, increment the compered variable as needed.
require
In Solidity ^0.8.4
, custom errors are cheaper than revert string from require
as explained here. Also it can be customizable using intermediate internal functions to build a generic errors.
contracts/Witch.sol: 84: require(param == "ladle", "Unrecognized"); 102: require(initialOffer <= 1e18, "InitialOffer above 100%"); 103: require(proportion <= 1e18, "Proportion above 100%"); 104: require( 105 initialOffer == 0 || initialOffer >= 0.01e18, 106 "InitialOffer below 1%" 107 ); 108: require(proportion >= 0.01e18, "Proportion below 1%"); 189: require(cauldron.level(vaultId) < 0, "Not undercollateralized"); 200: require(limits_.sum <= limits_.max, "Collateral limit reached"); 255: require(auction_.start > 0, "Vault not under auction"); 256: require(cauldron.level(vaultId) >= 0, "Undercollateralized"); 300: require(auction_.start > 0, "Vault not under auction"); 313: require(liquidatorCut >= minInkOut, "Not enough bought"); 328: require(baseJoin != IJoin(address(0)), "Join not found"); 358: require(auction_.start > 0, "Vault not under auction"); 365: require(liquidatorCut >= minInkOut, "Not enough bought"); 395: require(ilkJoin != IJoin(address(0)), "Join not found"); 416: require(auction_.start > 0, "Vault not under auction"); 437: require( 438 auction_.art - artIn >= debt.min * (10**debt.dec), 439 "Leaves dust" 440 );
revert
with custom error onlyReplace require
with revert
predefine custom errors. Also, combined the same error logics with one custom error type, it can be used with intermediate functions.