Platform: Code4rena
Start Date: 03/05/2022
Pot Size: $30,000 USDC
Total HM: 6
Participants: 93
Period: 3 days
Judge: gzeon
Id: 118
League: ETH
Rank: 83/93
Findings: 1
Award: $15.49
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: BowTiedWardens
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xProf, 0xc0ffEE, 0xf15ers, 0xkatana, 0xliumin, ACai, AlleyCat, CertoraInc, Cityscape, Cr4ckM3, DavidGialdi, Dinddle, FSchmoede, Funen, GimelSec, Hawkeye, IllIllI, Kulk0, M0ndoHEHE, MaratCerby, MiloTruck, Picodes, RoiEvenHaim, Tadashi, TerrierLover, TrungOre, VAD37, WatchPug, antonttc, catchup, defsec, delfin454000, dirk_y, eccentricexit, ellahi, fatherOfBlocks, gzeon, hake, hansfriese, hickuphh3, horsefacts, ilan, joestakey, kebabsec, kenta, kenzo, marximimus, minhquanym, noobie, oyc_109, p4st13r4, pauliax, rajatbeladiya, reassor, rfa, robee, rotcivegaf, saian, samruna, shenwilly, shung, simon135, slywaters, sorrynotsorry, throttle, unforgiven, z3s
15.491 USDC - $15.49
Here will first introduce 3 tricks to optimize the gas cost for issueRefund
, and then several other tricks for overall gas use.
daAmountPaid
, daAmountRefunded
and daNumMinted
. These 3 variables are read and updated in issueRefud
, so if we pack them into single storage slot, you save tons of gas by not using 3 SLOAD instructions.
Detail implementation can be found here in forked PR
You can see that I pack these 3 vars all into a DARecord
struct, (all in uint80
forms so they can fit into 256 bits). The constraint is that non of these values can be higher than 2^80, which is translated to 1208925 eth.
This saves you the most gas. From the existing tests, gas reduced from average of 115K to 86K.The following 2 tricks have minor impacts on issueRefund
, but given that you said you expected the function to run for around 3000 loops, it will worth something:
Remove unnecessary internal functions in issueRefund
: that is _safeTransferETHWithFallback
and _safeTransferETH
. The reason is that these 2 functions are only used once by private function refundAddress
, and specifying them as separate function will require an additional JUMP
instruction to go to the destination, also the EVM will need to go through all the function selectors to find the correct destination. I think from the code readability stand point, merging them together doesn't make it bad. I also had it implemented in the PR, with a @trick2 tag.
This saves average of 100 gas based on the gas report .
Optimize the function name.
Last trick to ultimately optimize issueRefund
is to find better function name for internal functions refundAddress
and _refundOwed
. The reason is that EVM will go through a list of function selectors and choose what is the correct hash, and the list is sorted by how many leading 0s the hash has. So the more 0s we can put in front of the function selector, the faster it will get selected in the matching phase and save some gas:
This tool would be helpful to do such thing: https://emn178.github.io/solidity-optimize-name/
The names I found are:
refundAddress
=> refundAddress_v4W
refundOwed
=> change to internal and use _refundOwed_vBw
This saves dozens of gas.This is all the idea I had to optimize issueRefund
, the rest of the report will be gas saving around other functions.