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: 20/133
Findings: 5
Award: $841.73
π Selected for report: 0
π Solo Findings: 0
420.5209 USDC - $420.52
https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L324 https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L349 https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L454
The PuttyV2._transferERC20sIn()
function loops over every token that's part of an order. The amount of different ERC20 tokens that can conform an order is uncapped. If the array is big enough, the gas consumption of _transferERC20sIn()
may exceed the current max. block size and the whole execution will be reverted. For put orders, this will prevent orders from being exercised causing a denial of service that prevents a user from taking profits of the held position.
Scenario A:
long put
offchain order with a considerable amount of different ERC20 tokens with a strike of 124 WETH, premium of 0.8 WETH for 2 BAYC.long put
the fillOrder
call handles the premium transfer with line 324 and the collateral transfer with line 349. The order fulfillment of Bob does not call internally at any time _transferERC20sIn
, _transferERC721sIn
or _transferFloorsIn
._transferERC20sIn
on line 454 and the max. block size is exceeded reverting the whole call.As a result of this scenario, Alice will never be able to exercise her option. Bob will be forced to pay feeAmount
while calling withdraw
.
Scenario B:
short put
offchain order with a considerable amount of different ERC20 tokens with a strike of 124 WETH.long
side option and sends strike 124 WETH from Alice to PuttyV2
._transferERC20sIn
on line 454 will exceed the max. block size and Bob will never be able to take profits over his position.This scenario is far more noxious than the first one because Alice is preventing Bob from taking profits of his position.
Although max. block sizes can change in the future, in order to prevent this denial of service limiting the maximum amount of ERC20Assets
can patch this. To do so, a max. amount cap can be checked with a require statement while filling an order. This will prevent orders with a high amount of elements on the ERC20Asset
to be fulfilled and thus prevent the mentioned high gas consumption.
#0 - outdoteth
2022-07-07T13:41:47Z
Duplicate: Setting an erc20Asset with a zero amount or with no code at the address will result in a revert when exercising a put option: https://github.com/code-423n4/2022-06-putty-findings/issues/223
#1 - HickupHH3
2022-07-09T02:58:01Z
Closer dup of 29 in that its effect is DoS, but the attack vector described is more conventional: unbounded looping becasue the no. of ERC20 tokens accepted for payment is unbounded.
I don't see any similar issue raised, making this the primary issue.
#2 - outdoteth
2022-07-09T14:36:40Z
@HickupHH3 Ah I misunderstood the issue.
There exists a duplicate for this issue here: https://github.com/code-423n4/2022-06-putty-findings/issues/227
"Unbounded loop can prevent put option from being exercised"
#3 - HickupHH3
2022-07-11T00:36:50Z
Awesome thank u!
π Selected for report: Picodes
Also found by: 0xNineDec, 0xsanson, Metatron, antonttc, berndartmueller, catchup, dirk_y, sseefried, unforgiven
Currently the implementation of PuttyV2.setFee()
can be changed at any time although it emits the respective event and has a maximum value. Because the owner()
of this contract can be any address (multisig, EOA, DAO multisig, etc.) there are no insights on how are going to be changed the fees and when.
Sticking up to the contract code itself, the fee can be changed freely within the given range. This means that for instance a malicious owner can be monitoring the mempool, frontrun other benign users and increase the fee up to 3% whenever he wants which will convey in a sort of fraud. If the attack is wanted to be performed in an even more intricate way, the owner can also backrun the transaction and take the fee back to what it was before this process.
In order to prevent this, timelocking the setFee function will help provide trust and predictability on how the protocol will work.
#0 - outdoteth
2022-07-06T18:35:57Z
Duplicate: Admin can change fee at any time for existing orders: https://github.com/code-423n4/2022-06-putty-findings/issues/422
π 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
47.1306 USDC - $47.13
Q/A - N: KEY STRUCTS LACKING COMMENTS
Under PuttyV2.sol, although the ERC20Asset
, ERC721Asset
, Order
structs inner variables are self-explanatory, it would increase even more the understanding of the protocol having comments explaining what means each variable and why it's important.
π 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.1707 USDC - $21.17
The following functions of PuttyV2.sol
rely on for loops that read array.length
at each iteration:
_transferERC20sIn
_transferERC721sIn
_transferFloorsIn
_transferERC20sOut
_transferERC721sOut
_transferFloorsOut
isWhitelisted
encodeERC20Assets
encodeERC721Assets
Caching the array.length
in the stack instead of checking i < arr.length
saves around 3 gas units per iteration.
The following functions update the counter with i++
which performs two memory steps (one to increment and other one to read the return value of the index) which can be modified with ++i
(performing the update and return in one step) saving gas on each iteration.
_transferERC20sIn
_transferERC721sIn
_transferFloorsIn
_transferERC20sOut
_transferERC721sOut
_transferFloorsOut
isWhitelisted
encodeERC20Assets
encodeERC721Assets
While enabling the optimizer at 10k runs and being inside a require
statement, this comparison strategy will save gas.
The following lines check that a value is greater than zero for unsigned integers.
CUSTOM ERRORS: USING CUSTOM ERRORS INSTEAD OF RETURN STRINGS
Custom errors from Solidity v0.8.13 provide a cheaper and cleaner way of feedback than revert strings.
Quoiting the Solidity docs
Errors in Solidity provide a convenient and gas-efficient way to explain to the user why an operation failed. They can be defined inside and outside of contracts (including interfaces and libraries). They have to be used together with the revert statement which causes all changes in the current call to be reverted and passes the error data back to the caller.
An example on how a custom error
can be generated and implemented is within the official doc link from before, and they can be inherited.