Platform: Code4rena
Start Date: 29/06/2022
Pot Size: $50,000 USDC
Total HM: 20
Participants: 133
Period: 5 days
Judge: hickuphh3
Total Solo HM: 1
Id: 142
League: ETH
Rank: 47/133
Findings: 2
Award: $103.35
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: xiaoming90
Also found by: 0x1f8b, 0x29A, 0x52, 0xDjango, 0xNazgul, 0xNineDec, 0xSolus, 0xf15ers, 0xsanson, AmitN, Bnke0x0, BowTiedWardens, Chom, David_, ElKu, Funen, GalloDaSballo, GimelSec, Hawkeye, IllIllI, JC, JohnSmith, Kaiziron, Kenshin, Lambda, Limbooo, MadWookie, Metatron, MiloTruck, Nethermind, Picodes, ReyAdmirado, Sneakyninja0129, StErMi, TomJ, Treasure-Seeker, TrungOre, Waze, Yiko, _Adam, __141345__, antonttc, async, aysha, catchup, cccz, cryptphi, csanuragjain, danb, datapunk, defsec, delfin454000, dirk_y, doddle0x, durianSausage, exd0tpy, fatherOfBlocks, gogo, hake, hansfriese, horsefacts, hubble, itsmeSTYJ, joestakey, oyc_109, pedroais, peritoflores, rajatbeladiya, reassor, robee, rokinot, samruna, saneryee, sashik_eth, shenwilly, shung, simon135, sseefried, unforgiven, zer0dot, zzzitron
82.1485 USDC - $82.15
The positionExpirations
, exercisedPositons
, and positionFloorAssetTokenIds
mappings here use uint256 keys when in every use case, the keys are bytes32 cast to uint256. This shouldn't affect gas or anything, but it might make sense just to opt for a bytes32 key, though at times a cast is unavoidable (such as when calling a _mint()
or _transferFrom()
function.)
In cases where the whitelist could be large, consider sorting whitelisted addresses numerically off-chain and implementing a binary search algorithm for determining whether the filler is whitelisted. (Though this implies an off-chain consensus of whitelist formatting from all apps using Putty.)
In the fillOrder()
function in the PuttyV2 contract, there are multiple redundant checks, I'd refactor to branch logic once if the maker order is long, then once again in each branch for whether it's a call. The CEI pattern can still be respected.
Similar to the situation in the case above, the exercise()
function in the PuttyV2 contract has a redundant check. I would combine both order.isCall
checks to have a more streamlined branching structure.
Similar to fillOrder()
and exercise()
, there are redundant checks in the withdraw()
function in the PuttyV2 contract, I'd combine them into a streamlined branching structure.
In the batchFillOrder()
function in the PuttyV2 contract, the two first initial require()
statements use the same revert string, combining them with an ||
operator would likely save code size and possibly gas.
Orders can be cancelled after being filled, this isn't really an issue but should be kept in mind for event consuming applications, as a Cancelled()
event will be emitted.
Orders can be cancelled multiple times, this again doesn't really break anything, but it will emit the same event multiple times.
#0 - outdoteth
2022-07-07T19:07:13Z
high quality report
🌟 Selected for report: GalloDaSballo
Also found by: 0v3rf10w, 0x1f8b, 0xA5DF, 0xDjango, 0xHarry, 0xKitsune, 0xNazgul, 0xNineDec, 0xc0ffEE, 0xf15ers, 0xkatana, 0xsanson, ACai, Aymen0909, Bnke0x0, BowTiedWardens, Chom, ElKu, Fitraldys, Funen, Haruxe, Hawkeye, IllIllI, JC, JohnSmith, Kaiziron, Kenshin, Lambda, Limbooo, MadWookie, Metatron, MiloTruck, Picodes, PwnedNoMore, Randyyy, RedOneN, ReyAdmirado, Ruhum, Sm4rty, StErMi, StyxRave, TerrierLover, TomJ, Tomio, UnusualTurtle, Waze, Yiko, _Adam, __141345__, ajtra, ak1, apostle0x01, asutorufos, c3phas, cRat1st0s, catchup, codetilda, cryptphi, datapunk, defsec, delfin454000, durianSausage, exd0tpy, fatherOfBlocks, gogo, grrwahrr, hake, hansfriese, horsefacts, ignacio, jayfromthe13th, joestakey, ladboy233, m_Rassska, mektigboy, minhquanym, mrpathfindr, natzuu, oyc_109, rajatbeladiya, reassor, rfa, robee, rokinot, sach1r0, saian, sashik_eth, simon135, slywaters, swit, z3s, zeesaw, zer0dot
21.2007 USDC - $21.20
Removing the abi.encodePacked()
from constant typehash encoding when the hashing is done on a single string saves minor gas and removes redundant code. See here and here.
Removing the redundant owner check in the exercise()
function in the PuttyV2 contract (this is already validated in the call to transferFrom()
) saves gas.
Like the point above, removing the redundant owner check in the withdraw()
function in the PuttyV2 contract saves gas.
In the withdraw()
function in the PuttyV2 contract, refactoring the fee stack variable to only be created if needed saves gas. This comes at the cost of a minor readability degradation though.
Here's the change:
// Before: if ((order.isCall && isExercised) || (!order.isCall && !isExercised)) { // send the fee to the admin/DAO if fee is greater than 0% uint256 feeAmount = 0; if (fee > 0) { feeAmount = (order.strike * fee) / 1000; ERC20(order.baseAsset).safeTransfer(owner(), feeAmount); } ERC20(order.baseAsset).safeTransfer(msg.sender, order.strike - feeAmount); return; } // After: if ((order.isCall && isExercised) || (!order.isCall && !isExercised)) { // send the fee to the admin/DAO if fee is greater than 0% if (fee > 0) { uint256 feeAmount = (order.strike * fee) / 1000; ERC20(order.baseAsset).safeTransfer(owner(), feeAmount); ERC20(order.baseAsset).safeTransfer(msg.sender, order.strike - feeAmount); } else { ERC20(order.baseAsset).safeTransfer(msg.sender, order.strike); } return; }