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: 38/63
Findings: 1
Award: $39.86
š 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
39.8637 USDC - $39.86
Marking constants as private
save gas upon deployment, as the compiler does not have to create getter functions for these variables. It is worth noting that a private
variable can still be read using either the verified contract source code or the bytecode.
Instances:
ICauldron private immutable cauldron
Manual Analysis
Make ICauldron
private
instead of public
:
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3076398 ā 15658 ā ā ā ā ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3066367 ā 15601 ā ā ā ā ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
This saves 10,031
gas upon deployment.
Constructor parameters are expensive. The contract deployment will be cheaper in gas if they are hard coded instead of using constructor parameters.
Instances:
cauldron = cauldron_
ladle = ladle_
Manual Analysis
Hardcode cauldron
and ladle
with their initial value instead of writing them during contract deployment with constructor parameters.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained here
Custom errors are defined using the error statement
17 instances:
require(param == "ladle", "Unrecognized")
require(initialOffer <= 1e18, "InitialOffer above 100%")
require(proportion <= 1e18, "Proportion above 100%")
require(initialOffer == 0 || initialOffer >= 0.01e18,"InitialOffer below 1%")
require(proportion >= 0.01e18, "Proportion below 1%")
require(cauldron.level(vaultId) < 0, "Not undercollateralized")
require(limits_.sum <= limits_.max, "Collateral limit reached")
require(auction_.start > 0, "Vault not under auction")
require(cauldron.level(vaultId) >= 0, "Undercollateralized")
require(auction_.start > 0, "Vault not under auction")
require(liquidatorCut >= minInkOut, "Not enough bought")
require(baseJoin != IJoin(address(0)), "Join not found")
require(auction_.start > 0, "Vault not under auction")
require(liquidatorCut >= minInkOut, "Not enough bought")
require(baseJoin != IJoin(address(0)), "Join not found")
require(auction_.start > 0, "Vault not under auction")
require(auction_.art - artIn >= debt.min * (10**debt.dec),"Leaves dust")
Manual Analysis
Replace require and revert statements with custom errors.
For instance:
Replace
require(param == "ladle", "Unrecognized")
with
if (param != "ladle") { revert Unrecognized(); }
and define the custom error in the contract
error Unrecognized();
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3076398 ā 15658 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā point ā 2934 ā 5098 ā 2942 ā 9420 ā 3 ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3070390 ā 15628 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā point ā 2870 ā 5074 ā 2934 ā 9420 ā 3 ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
Changing this one require
statement into a custom error saves 6,008
gas upon deployment, and an average of 24
gas per function call.
block.timestamp
and block.number
are added to event information by default, explicitly adding them is a waste of gas.
emit Auctioned(vaultId, uint32(block.timestamp))
Manual Analysis
Remove the start
event field of Auctioned
.
A function with access control marked as payable will lbe cheaper for legitimate callers: the compiler removes checks for msg.value
, saving approximately 20
gas per function call.
6 instances:
function point(bytes32 param, address value) external auth
function setLine(bytes6 ilkId,bytes6 baseId,uint32 duration,uint64 proportion,uint64 initialOffer) external auth
function setLimit() bytes6 ilkId,bytes6 baseId,uint128 max) external auth
function setAnotherWitch(address value, bool isWitch) external aut
function setIgnoredPair(bytes6 ilkId,bytes6 baseId,bool ignore) external auth
function setAuctioneerReward(uint128 auctioneerReward_) external auth
Manual Analysis
add the payable
modifier to these functions.
X += Y costs more gas than X = X + Y. This can mean a lot of gas wasted in a function call when the computation is repeated n
times (loops)
6 instances include:
limits_.sum += auction_.ink
limits_.sum -= auction_.ink
auction_.ink -= inkOut.u128()
auction_.art -= artIn.u128()
limits_.sum -= inkOut.u128()
liquidatorCut -= auctioneerCut
Manual Analysis
use X = X + Y
instead of X += Y
(same with -
)
For instance, just by changing limits_.sum += auction_.ink
into limits_.sum = limits_.sum + auction_.ink
:
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3076398 ā 15658 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā auction ā 4219 ā 70111 ā 81848 ā 91148 ā 30 ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3074398 ā 15648 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā auction ā 4219 ā 70086 ā 81819 ā 91119 ā 30 ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
It saves 2,000
gas upon deployment, and an average of 25
gas per function call.
When a require
statement is used multiple times, it is cheaper in deployment costs to use a modifier instead.
3 instances:
require(auction_.start > 0, "Vault not under auction")
require(auction_.start > 0, "Vault not under auction")
require(auction_.start > 0, "Vault not under auction")
require(auction_.start > 0, "Vault not under auction")
require(baseJoin != IJoin(address(0)), "Join not found")
require(baseJoin != IJoin(address(0)), "Join not found")
require(liquidatorCut >= minInkOut, "Not enough bought")
require(liquidatorCut >= minInkOut, "Not enough bought")
Manual Analysis
Use modifiers for these repeated statements
require
statements checking function arguments should be at the top of the function, to prevent unnecessary extra computation when they revert
2 instances:
require(cauldron.level(vaultId) < 0, "Not undercollateralized")
require(cauldron.level(vaultId) >= 0, "Undercollateralized")
Manual Analysis
Place these statements at the top of their respective functions.
Each storage slot has 256 bits. Whenever a uint smaller than 256 (or even a bool) is pulled from storage, the EVM casts it to a uint256. Calculations are also unexceptionally performed in uint256 by the EVM.
uint128 public auctioneerReward = 0.01e18
Manual Analysis
Replace uint128
with uint256
. Downcast when required.
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked
block will save gas
auction_.art -= artIn.u128() cannot underflow because of the check line 438
Manual Analysis
Place this operation in an unchecked
block
Where it does not affect readability, using assembly allows to save gas not only on deployment, but also on function calls. This is the case for instance for simple admin setters.
Instances:
auctioneerReward = auctioneerReward_
- auctioneerReward = auctioneerReward_; + assembly { + sstore(auctioneerReward.slot, auctioneerReward_) + }
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3076398 ā 15658 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā setAuctioneerReward ā 2859 ā 4874 ā 2885 ā 8879 ā 3 ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāā¬āāāāāāāā¬āāāāāāāāāā® ā Witch contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāŖāāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā 3071390 ā 15633 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāā¼āāāāāāāā¼āāāāāāāāā⤠ā setAuctioneerReward ā 2859 ā 4866 ā 2885 ā 8855 ā 3 ā ā°āāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāā“āāāāāāāā“āāāāāāāāāāÆ
It saves 5,008
gas upon deployment, and an average of 8
gas per function call.