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: 17/133
Findings: 3
Award: $920.96
๐ 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
276.7971 USDC - $276.80
Low
Tokens ERC20/ERC721 are locked in the contract:
short call
and long call
ordersexercise
and withdraw
- short put
and short call
ordersThe issue is that contract PuttyV2
does not have functionality for withdrawing excess of ERC20/ERC721
tokens which means that all potential airdrops/benefits for holding given tokens are getting locked in the contract.
Manual Review / VSCode
It is recommended to track total amounts of currently deposited tokens and allow owner
to withdraw the excess, that could be later used for benefiting users of PuttyV2
.
Low
Contract PuttyV2
charges feeAmount
from order.strike
that is calculated based on defined fee
.
Following code is responsible for charging fee:
uint256 feeAmount = 0; if (fee > 0) { feeAmount = (order.strike * fee) / 1000; ERC20(order.baseAsset).safeTransfer(owner(), feeAmount); }
In case the order.strike
is small - order.strike * fee < 1000
it will get rounded down to 0
and then function safeTransfer
will attempt to send 0
tokens to the owner.
PuttyV2.sol
:
Manual Review / VSCode
It is recommended to add additional check before safeTransfer
invocation to make sure that feeAmount
is not equal to 0
.
Low
It is possible to use ERC721
tokens as order's base asset which will work fine for fillOrder
since it is using safeTransferFrom
that is also implemented for ERC721
tokens. The issue is that these kind of orders will be properly filled but it will not possible to exercise
/withdraw
them since the contract will try to use safeTransfer
function that does not exist in ERC721
.
PuttyV2.sol
:
order.premium
used as tokenId - https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L324order.premium
used as tokenId - https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L338order.strike
used as tokenId - https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L344order.strike
used as tokenId - https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L360Manual Review / VSCode
It is recommended to add check in fillOrder
(via ERC165
interface) to make sure that order's base asset is not ERC721 token.
Low
It is possible to use ERC721
tokens as order's ERC20
assets erc20Assets
which will work fine for transferring assets to the contract since safeTransferFrom
is being used in _transferERC20sIn
and it is also implemented by ERC721
tokens. The issue is that orders crafted like that will fail for any trigger of _transferERC20sOut
that is using safeTransfer
that is not implemented in ERC721
tokens.
PuttyV2.sol
:
Manual Review / VSCode
It is recommended to add check in _transferERC20sIn
(via ERC165
interface) to make sure that order's base asset is not ERC721
token.
Low
It is possible to use PuttyV2 NFT (long/short positions) as order's erc721Assets
or floorTokens
. This might lead to unpredictable scenarios where malicious orders can be filled but cannot be properly processed via exercise
or withdraw
.
PuttyV2.sol
:
Manual Review / VSCode
It is recommended to add checks in fillOrder
to make sure that none of erc721Assets
and floorTokens
are Putty's contract address.
Low
Changing critical addresses such as ownership should be a two-step process where the first transaction (from the old/current address) registers the new address (i.e. grants ownership) and the second transaction (from the new address) replaces the old address with the new one. This gives an opportunity to recover from incorrect addresses mistakenly used in the first step. If not, contract functionality might become inaccessible.
PuttyV2.sol
:
Manual Review / VSCode
It is recommended to implement two-step process for changing ownership.
Low
Contract PuttyV2
does not support tokens with fees.
Contract PuttyV2
does not handle ERC20
tokens that charge fee on their transfers. Implementation of such a tokens does not transfer exact amount provided to transfer()
but part of it is charged as a fee, burned or used in some other way. This leads to incorrect accounting and effectively to loss of funds.
Since the team accepted that the contract will not be supporting Rebase and fee-on-transfer
tokens the severity has been downgraded to low.
PuttyV2.sol
:
Manual Review / VSCode
It is recommended to add support for ERC20
tokens with built-in fees.
Contract PuttyV2
does not support rebase tokens.
Rebasing tokens are tokens that have each holderโs balanceof()
increase over time. Aave aTokens are an example of such tokens.
Since the team accepted that the contract will not be supporting Rebase and fee-on-transfer
tokens the severity has been downgraded to low.
PuttyV2.sol
:
Manual Review / VSCode
It is recommended to add support for ERC20
tokens wth rebase functionality by tracking total amounts currently deposited and allow owner
to withdraw excess.
Non-Critical
Contract PuttyV2
implements effectively options logic in just 3 complex functions:
fillOrder
exercise
withdraw
While the approach is very smart and neat it makes code less readable and thus prone to errors.
fillOrder
functions - https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L268-L380exercise
function - https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L389-L458withdraw
function - https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L466-L520Manual Review / VSCode
It is recommended to distribute logic of listed functions into multiple internal/private functions.
Non-Critical
Contracts PuttyV2NFT
and PuttyV2
are missing some natspec comments which makes code more difficult to read and prone to errors.
PuttyV2NFT.sol
:
StakingPuttyV2.sol
:
constructor
- https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L209-L213Manual Review / VSCode
It is recommended to add missing natspec comments.
#0 - outdoteth
2022-07-07T19:15:32Z
- Missing airdrops/benefits from locked ERC20/ERC721 tokens
Duplicate: Airdrops will be stuck in the contract: https://github.com/code-423n4/2022-06-putty-findings/issues/79
- ERC721 token used as order's base asset
Duplicate: If an ERC721 token is used in places where ERC20 assets are supposed to be used then ERC721 tokens can get stuck in withdraw() and exercise(): https://github.com/code-423n4/2022-06-putty-findings/issues/52
#1 - outdoteth
2022-07-07T19:15:37Z
High quality report
#2 - HickupHH3
2022-07-16T01:17:01Z
- Attempt to send 0 fee for small strike price
fails to make the key step to reverts for zero amount transfers (#283) and thus isn't upgraded.
๐ 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.1718 USDC - $21.17
Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.
PuttyV2.sol:556: for (uint256 i = 0; i < orders.length; i++) { PuttyV2.sol:594: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:611: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:627: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:637: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:647: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:658: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:670: for (uint256 i = 0; i < whitelist.length; i++) { PuttyV2.sol:728: for (uint256 i = 0; i < arr.length; i++) { PuttyV2.sol:742: for (uint256 i = 0; i < arr.length; i++) {
It is recommended to cache the array length outside of for loop.
Usage of custom errors reduces the gas cost.
Contracts that should be using custom errors:
PuttyV2.sol
PuttyV2Nft.sol
It is recommended to add custom errors to listed contracts.
++i
or --i
costs less gas compared to i++
, i += 1
, i--
or i -= 1
for unsigned integer as pre-increment/pre-decrement is cheaper (about 5 gas per iteration).
PuttyV2.sol:556: for (uint256 i = 0; i < orders.length; i++) { PuttyV2.sol:594: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:611: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:627: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:637: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:647: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:658: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:670: for (uint256 i = 0; i < whitelist.length; i++) { PuttyV2.sol:728: for (uint256 i = 0; i < arr.length; i++) { PuttyV2.sol:742: for (uint256 i = 0; i < arr.length; i++) {
It is recommended to use ++i
or --i
instead of i++
, i += 1
, i--
or i -= 1
to increment value of an unsigned integer variable.
Starting from solidity 0.8.0
there is built-in check for overflows/underflows. This mechanism automatically checks if the variable overflows or underflows and throws an error. Multiple contracts use increments that cannot overflow but consume additional gas for checks.
PuttyV2.sol:556: for (uint256 i = 0; i < orders.length; i++) { PuttyV2.sol:594: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:611: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:627: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:637: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:647: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:658: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:670: for (uint256 i = 0; i < whitelist.length; i++) { PuttyV2.sol:728: for (uint256 i = 0; i < arr.length; i++) { PuttyV2.sol:742: for (uint256 i = 0; i < arr.length; i++) {
It is recommended to wrap incrementing with unchecked
block, for example: unchecked { ++i }
or unchecked { --i }
If a variable is not set/initialized, it is assumed to have the default value (0
for uint, false
for bool, address(0)
for addresses). Explicitly initializing it with its default value is an anti-pattern and waste of gas.
PuttyV2.sol:497: uint256 feeAmount = 0; PuttyV2.sol:556: for (uint256 i = 0; i < orders.length; i++) { PuttyV2.sol:594: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:611: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:627: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:637: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:647: for (uint256 i = 0; i < assets.length; i++) { PuttyV2.sol:658: for (uint256 i = 0; i < floorTokens.length; i++) { PuttyV2.sol:670: for (uint256 i = 0; i < whitelist.length; i++) { PuttyV2.sol:728: for (uint256 i = 0; i < arr.length; i++) { PuttyV2.sol:742: for (uint256 i = 0; i < arr.length; i++) {
It is recommended to remove explicit initializations with default values.
When dealing with unsigned integer types, comparisons with != 0
are cheaper than with > 0
.
PuttyV2.sol:293: require(order.baseAsset.code.length > 0, "baseAsset is not contract"); PuttyV2.sol:327: if (weth == order.baseAsset && msg.value > 0) { PuttyV2.sol:351: if (weth == order.baseAsset && msg.value > 0) { PuttyV2.sol:427: if (weth == order.baseAsset && msg.value > 0) { PuttyV2.sol:498: if (fee > 0) { PuttyV2.sol:598: require(token.code.length > 0, "ERC20: Token is not contract"); PuttyV2.sol:599: require(tokenAmount > 0, "ERC20: Amount too small");
It is recommended to use != 0
instead of > 0
.