Platform: Code4rena
Start Date: 29/06/2022
End Date: 04/07/2022
Period: 5 days
Status: Completed
Pot Size: $50,000 USDC
Participants: 133
Reporter: itsmetechjay
Judge: hickuphh3
Id: 142
League: ETH
hansfriese | 1/133 | $4,724.00 | 7 | 2 | 0 | 3 | 0 | - | - | 0 |
hyh | 2/133 | $3,929.01 | 4 | 1 | 0 | 3 | 0 | 0 | 0 | 0 |
minhquanym | 3/133 | $2,830.87 | 5 | 2 | 0 | 2 | 0 | 0 | - | 0 |
csanuragjain | 4/133 | $2,137.90 | 6 | 2 | 0 | 3 | 0 | - | 0 | 0 |
berndartmueller | 5/133 | $2,078.11 | 7 | 1 | 0 | 6 | 0 | 0 | 0 | 0 |
xiaoming90 | 6/133 | $1,995.39 | 7 | 0 | 0 | 6 | 0 | - | 0 | 0 |
zzzitron | 7/133 | $1,816.25 | 5 | 2 | 0 | 2 | 0 | - | 0 | 0 |
IllIllI | 8/133 | $1,773.63 | 8 | 0 | 0 | 6 | 0 | - | - | 0 |
unforgiven | 9/133 | $1,744.06 | 7 | 0 | 0 | 6 | 0 | - | 0 | 0 |
horsefacts | 10/133 | $1,728.45 | 8 | 0 | 0 | 6 | 0 | - | - | 0 |
Auditor per page
An order-book based american options market for NFTs and ERC20s. This project uses the foundry framework for testing/deployment.
foundry
, refer to foundrynodejs
, refer to nodejsyarn
, npm install --global yarn
Clone the repo and then run:
cd contracts yarn forge install forge test --gas-report
Note: earlier versions of foundry may cause the tests to fail.
feel free to have a cuppa and slice of pie while you continue to read through :) ;,' , _o_ ;:;' , , ;' ,-.'---`.__ ; ,'; ' ; ((j`=====',-' :----: _.-._ _`-\ / ________C|====|________.:::::::::.______ `-=-' `----' ~\_______/~
Feel free to contact if you have questions.
out.eth (engineer) - online from 08:00 BST-23:30 BST
Will usually answer within 45 mins.
Name | LOC | Purpose |
---|---|---|
PuttyV2.sol | 327 | Entry point to the protocol. Holds the business logic. |
PuttyV2Nft.sol | 30 | NFT contract to represent Putty short and long positions. |
There are four types of off-chain orders that a user can create.
When an order is filled, 2 option contracts are minted in the form of NFTs. One NFT represents the short position, and the other NFT represents the long position. All options are fully collateralised and physically settled.
Here is an example flow for filling a long put option.
fillOrder()
2 * 54 = 108 ETH. 124 - 108 = 16 ETH profit for Alice.
)exercisedPositions
)withdraw()
) - BAYC #541 and BAYC #8765 are sent from Putty to his walletAt a high level, there are 4 main entry points:
fillOrder(Order memory order, bytes calldata signature, uint256[] memory floorAssetTokenIds)
exercise(Order memory order, uint256[] calldata floorAssetTokenIds)
withdraw(Order memory order)
cancel(Order memory order)
All orders are stored off-chain until they are settled on chain through fillOrder
.
There exists much more rigorous specification files in ./contracts/spec
with diagrams included.
There are various optimizations that may make the contracts harder to reason about. These are done to reduce gas costs but at the expense of code readability. Here are some helpful explanations of those optimizations.
When an order is filled, Putty creates NFTs to represent both the long and short position.
All balanceOf modifications have been removed from the Putty NFTs.
Given our use-case, it is a reasonable tradeoff.
The balanceOf
for each user is set to be defaulted to type(uint256).max
instead of 0
.
// set balanceOf to max for all users function balanceOf(address owner) public pure override returns (uint256) { require(owner != address(0), "ZERO_ADDRESS"); return type(uint256).max; }
This was done to save gas since not tracking the balanceOf
avoids a single storage modification or initialisation on each transfer/mint/burn.
There is one area of the code that is perhaps not so intuitive. That is, floorTokenIds
and positionFloorAssetTokenIds
. This is a way for us to support floor NFT options.
The idea is that Alice can create a put option with 3 floorTokens (address[])
and then when Bob wants to exercise he can send any 3 tokens from the collections listed in floorTokens
. Because he can use any token from the collection, it essentially replicates a floor option; when exercising, he will always choose the lowest value tokens - floors.
Similarly Alice can create an off-chain order for a long call option with 5 floorTokens (address[])
. Bob can fill this order (fillOrder
) and send any 5 tokens (floorTokenIds
) from the collections listed in floorTokens
as collateral.
When an exercise or fillOrder happens, we save the floorTokenIds that Bob used in positionFloorAssetTokenIds
. This is so that we can reference them later for the following situations;
a) Bob exercised his put option. Alice then can withdraw the floor tokens that Bob used.
b) Alice exercised her long call option. The floor tokens are sent to Alice.
c) Alice let her long call option expire. The floor tokens are sent back to Bob.
There are various tokens with custom implementations of how user balances are updated over time. The most common of these are fee-on-transfer and rebase tokens. Due to the complexity and cost of persistent accounting we don't intend to support these tokens.
There are a few places in the code where we have a lower confidence that things are correct. These may potentially serve as low-hanging fruit:
Incorrect EIP-712 implementation
External contract calls via token transfers leading to re-entrancy
Incorrect handling of native ETH to WETH in fillOrder
and exercise
Timestamp manipulation