From 6934db1df696f2c30c3166d3f8284146fb75b52a Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Wed, 28 Sep 2022 00:59:20 -0400 Subject: [PATCH 1/2] docs: tokenid --- docs/developers/tokenid.md | 67 +++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/docs/developers/tokenid.md b/docs/developers/tokenid.md index 70518a7c..f900c71c 100644 --- a/docs/developers/tokenid.md +++ b/docs/developers/tokenid.md @@ -3,7 +3,72 @@ sidebar_position: 7 --- # TokenId -Methods to store 4-leg option data inside 256 bits. +In Panoptic, every option position is represented by a unique ID. This ID is a uint256 and thus contains 256 bits. The reason is is referred to as an "ID" is because it is the ID of an ERC1155 semi-fungible NFT. +Taking a step back, in Uniswap V3 an LP position is represented as an NFT also with a unique ID. This gives each LP a unique position and the NFT represents this. +In our case, it would be more beneficial and gas efficient to represent option positions by ERC115s and here is why: An ERC1155 can hold an element with a unique ID. But multiple users can hold a copy of this ID. +The classic example is a sword in a video game. There is a single type of sword (the ID), but multiple people can own it (multiple users per ID). +Similarly, there is a single type of option position (and thus we don't tie it to the initial creator of the position, we track that simply via `balanceOf`) but multiple users could hold it simultaneously. This also means that we get access to other gas saving features like batch minting and burning, etc. In comparison, Uniswap V3 uses an ERC721 NFT to represents a position and uses the LP's address as part of its ID (so 1 position has 1 owner at most always; therefore, if multiple users provide liquidity in the exact same range, multiple separate NFTs need to be minted one at a time). +Having explained what the tokenId is and what it represents, we also mentioned its number of bits. Why? Well this is very important because it is here that the details of the option position lies. +Specifically, first we realize that the option position is with respect to a specific Uniswap V3 pool. Thus, we take the first 10 bytes (80 bits) of that address and use for the first 80 bits starting at the least significant bit (LSB, in a big endian format). + +Then, we follow that up with 4 bits per potential leg which consists of 3 bits quantifying the `optionRatio` and 1 bit for the `numeraire` identifier (the bit is 0/1 if token0/1 is the numeraire). +Thus, the optionRatio+numeraire segment takes up a total of 16 bits since we support up to 4 legs in one position. + +Following this, each leg takes up 40 bits and consists of the following details: The first bit signals whether the position is long or short. If long, liquidity is removed from Uniswap. The next bit signals the `tokenType` that is moved when deployed and therefore we are signaling whether it's a call or a put. Following this, we specify which potential risk partner this leg has (this would be another leg in the position). Then follows the strike tick for 24 bits stored as $(tickUpper + tickLower)/2$ of the Uniswap V3 liquidity that reprents the position. The width is stored next for 12 bits defined as $(tickUpper - tickLower) / 2$. + +As mentioned, up to four legs can be part of a position. A leg *index* specifies which of the four legs are meant and takes values in {0,1,2,3}. The leg takes values in {1,2,3,4}. +So leg $n$ has leg index $n-1$ in our nomenclature. + +## Diagram of the tokenId + +So, in summary the 256 bits of the uint256 is a fingerprint of the option position. A unique number representing the ID of the option position and it is broken down into the elements: + +(Insert figure) + +## Technical Details + +The following table gives a thorough technical overview of the tokenId composition - use the diagram above as a visual reference: + +|What is stored|Size in bits|Relative offset into ID from LSB|Description| +|---|---|---|---| +|**Uni V3 Pool ID**|80|0|The first 80 bits of the Uni V3 pool address.| +|**optionRatio Leg 1**|3|80|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| +|**Numeraire Leg 1**|1|83|Specify which token is the numeraire: 0/1 if token0/1 is.| +|**optionRatio Leg 2**|3|84|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| +|**Numeraire Leg 2**|1|87|Specify which token is the numeraire: 0/1 if token0/1 is.| +|**optionRatio Leg 3**|3|88|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| +|**Numeraire Leg 3**|1|91|Specify which token is the numeraire: 0/1 if token0/1 is.| +|**optionRatio Leg 4**|3|92|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| +|**Numeraire Leg 4**|1|95|Specify which token is the numeraire: 0/1 if token0/1 is.| +|**Leg 1 $(leg\_index=0)$**|||| +|** \\- isLong**|1|96|The first leg's first bit telling us whether this leg is long (1) or short (0).| +|** \\- tokenType**|1|97|The first leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| +|** \\- riskPartner**|2|98|The first leg's risk partner (another leg in this position).| +|** \\- strike**|24|100|The first leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| +|** \\- width**|12|124|The first leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| +|**Leg 2 $(leg\_index=1)$**|||| +|** \\- isLong**|1|136|The second leg's first bit telling us whether this leg is long (1) or short (0).| +|** \\- tokenType**|1|137|The second leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| +|** \\- riskPartner**|2|138|The second leg's risk partner (another leg in this position).| +|** \\- strike**|24|140|The second leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| +|** \\- width**|12|164|The second leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| +|**Leg 3 $(leg\_index=2)$**|||| +|** \\- isLong**|1|176|The third leg's first bit telling us whether this leg is long (1) or short (0).| +|** \\- tokenType**|1|177|The third leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| +|** \\- riskPartner**|2|178|The third leg's risk partner (another leg in this position).| +|** \\- strike**|24|180|The third leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| +|** \\- width**|12|204|The third leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| +|**Leg 4 $(leg\_index=3)$**|||| +|** \\- isLong**|1|216|The third leg's first bit telling us whether this leg is long (1) or short (0).| +|** \\- tokenType**|1|217|The third leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| +|** \\- riskPartner**|2|218|The third leg's risk partner (another leg in this position).| +|** \\- strike**|24|220|The third leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| +|** \\- width**|12|244|The third leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| + +We note that the final piece of information starts at bit index 244 and takes up 12 bits thus ending at 256 (non-inclusive; to be sure, the final bit is located at 255 as expected). + +We use the tokenId library to store and extract the information from within this bit pattern. In other words, by leveraging bit shifting and other techniques, we can always extract any piece of information from the tokenId that we see fit. +Similarly, we can store into the pattern as well and thus we can both read and write to this. From 1a8f91f4c295e70e33f046e79233b88400829634 Mon Sep 17 00:00:00 2001 From: Jesper Kristensen Date: Tue, 4 Oct 2022 17:02:04 -0400 Subject: [PATCH 2/2] docs: fix - simplify tokenID docs --- docs/developers/tokenid.md | 124 ++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 70 deletions(-) diff --git a/docs/developers/tokenid.md b/docs/developers/tokenid.md index f900c71c..7a3dfe4a 100644 --- a/docs/developers/tokenid.md +++ b/docs/developers/tokenid.md @@ -2,73 +2,57 @@ sidebar_position: 7 --- -# TokenId - -In Panoptic, every option position is represented by a unique ID. This ID is a uint256 and thus contains 256 bits. The reason is is referred to as an "ID" is because it is the ID of an ERC1155 semi-fungible NFT. - -Taking a step back, in Uniswap V3 an LP position is represented as an NFT also with a unique ID. This gives each LP a unique position and the NFT represents this. -In our case, it would be more beneficial and gas efficient to represent option positions by ERC115s and here is why: An ERC1155 can hold an element with a unique ID. But multiple users can hold a copy of this ID. -The classic example is a sword in a video game. There is a single type of sword (the ID), but multiple people can own it (multiple users per ID). -Similarly, there is a single type of option position (and thus we don't tie it to the initial creator of the position, we track that simply via `balanceOf`) but multiple users could hold it simultaneously. This also means that we get access to other gas saving features like batch minting and burning, etc. In comparison, Uniswap V3 uses an ERC721 NFT to represents a position and uses the LP's address as part of its ID (so 1 position has 1 owner at most always; therefore, if multiple users provide liquidity in the exact same range, multiple separate NFTs need to be minted one at a time). - -Having explained what the tokenId is and what it represents, we also mentioned its number of bits. Why? Well this is very important because it is here that the details of the option position lies. -Specifically, first we realize that the option position is with respect to a specific Uniswap V3 pool. Thus, we take the first 10 bytes (80 bits) of that address and use for the first 80 bits starting at the least significant bit (LSB, in a big endian format). - -Then, we follow that up with 4 bits per potential leg which consists of 3 bits quantifying the `optionRatio` and 1 bit for the `numeraire` identifier (the bit is 0/1 if token0/1 is the numeraire). -Thus, the optionRatio+numeraire segment takes up a total of 16 bits since we support up to 4 legs in one position. - -Following this, each leg takes up 40 bits and consists of the following details: The first bit signals whether the position is long or short. If long, liquidity is removed from Uniswap. The next bit signals the `tokenType` that is moved when deployed and therefore we are signaling whether it's a call or a put. Following this, we specify which potential risk partner this leg has (this would be another leg in the position). Then follows the strike tick for 24 bits stored as $(tickUpper + tickLower)/2$ of the Uniswap V3 liquidity that reprents the position. The width is stored next for 12 bits defined as $(tickUpper - tickLower) / 2$. - -As mentioned, up to four legs can be part of a position. A leg *index* specifies which of the four legs are meant and takes values in {0,1,2,3}. The leg takes values in {1,2,3,4}. -So leg $n$ has leg index $n-1$ in our nomenclature. - -## Diagram of the tokenId - -So, in summary the 256 bits of the uint256 is a fingerprint of the option position. A unique number representing the ID of the option position and it is broken down into the elements: - -(Insert figure) - -## Technical Details - -The following table gives a thorough technical overview of the tokenId composition - use the diagram above as a visual reference: - -|What is stored|Size in bits|Relative offset into ID from LSB|Description| -|---|---|---|---| -|**Uni V3 Pool ID**|80|0|The first 80 bits of the Uni V3 pool address.| -|**optionRatio Leg 1**|3|80|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| -|**Numeraire Leg 1**|1|83|Specify which token is the numeraire: 0/1 if token0/1 is.| -|**optionRatio Leg 2**|3|84|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| -|**Numeraire Leg 2**|1|87|Specify which token is the numeraire: 0/1 if token0/1 is.| -|**optionRatio Leg 3**|3|88|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| -|**Numeraire Leg 3**|1|91|Specify which token is the numeraire: 0/1 if token0/1 is.| -|**optionRatio Leg 4**|3|92|The optionRatio is how many times the user's ERC1155 balance is moved over in tokens| -|**Numeraire Leg 4**|1|95|Specify which token is the numeraire: 0/1 if token0/1 is.| -|**Leg 1 $(leg\_index=0)$**|||| -|** \\- isLong**|1|96|The first leg's first bit telling us whether this leg is long (1) or short (0).| -|** \\- tokenType**|1|97|The first leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| -|** \\- riskPartner**|2|98|The first leg's risk partner (another leg in this position).| -|** \\- strike**|24|100|The first leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| -|** \\- width**|12|124|The first leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| -|**Leg 2 $(leg\_index=1)$**|||| -|** \\- isLong**|1|136|The second leg's first bit telling us whether this leg is long (1) or short (0).| -|** \\- tokenType**|1|137|The second leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| -|** \\- riskPartner**|2|138|The second leg's risk partner (another leg in this position).| -|** \\- strike**|24|140|The second leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| -|** \\- width**|12|164|The second leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| -|**Leg 3 $(leg\_index=2)$**|||| -|** \\- isLong**|1|176|The third leg's first bit telling us whether this leg is long (1) or short (0).| -|** \\- tokenType**|1|177|The third leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| -|** \\- riskPartner**|2|178|The third leg's risk partner (another leg in this position).| -|** \\- strike**|24|180|The third leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| -|** \\- width**|12|204|The third leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| -|**Leg 4 $(leg\_index=3)$**|||| -|** \\- isLong**|1|216|The third leg's first bit telling us whether this leg is long (1) or short (0).| -|** \\- tokenType**|1|217|The third leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).| -|** \\- riskPartner**|2|218|The third leg's risk partner (another leg in this position).| -|** \\- strike**|24|220|The third leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as $(tickUpper + tickLower)/2$.| -|** \\- width**|12|244|The third leg's width of the liquidity deployed in the Uni v3 pool, defined as $(tickUpper - tickLower)/2$.| - -We note that the final piece of information starts at bit index 244 and takes up 12 bits thus ending at 256 (non-inclusive; to be sure, the final bit is located at 255 as expected). - -We use the tokenId library to store and extract the information from within this bit pattern. In other words, by leveraging bit shifting and other techniques, we can always extract any piece of information from the tokenId that we see fit. -Similarly, we can store into the pattern as well and thus we can both read and write to this. +# TokenId (ERC1155) + +In Panoptic, every option position is represented by a unique ID. This ID is a uint256 which contains 256 bits. In addition, an option position is represented internally as an ERC1155 semi-fungible NFT which is gas efficient for our users. + +Details about the position are encoded into the ID in the following format: + +```solidity num +/* +* This is how the token Id is packed into its bit-constituents containing position information. +* The following is a diagram to be read top-down in a little endian format +* (so (1) below occupies the first 80 least significant bits, e.g.): +* ===== 1 time (same for all legs) ============================================================== +* (1) univ3pool 80bits : first 10 bytes of the Uniswap v3 pool address (first 80 bits; little-endian) +* ===== 4 times (one for each leg) ============================================================== +* Property Size Comment +* (2) optionRatio 3+1 bits : 3 bits for number of contracts per leg, leftmost bit for specifying the numeraire +* ===== 4 times (one for each leg) ============================================================== +* Property Size Rel.Start Comment +* (3) isLong 1bit 0bits : long==1 means liquidity is removed, long==0 -> liquidity is added +* (4) tokenType 1bit 1bit : put/call: which token is moved when deployed (0 -> token0, 1 -> token1) +* (5) riskPartner 2bits 2bits : normally its own index. Partner in defined risk position otherwise +* (6) strike 24bits 4bits : strike price; defined as (tickUpper + tickLower) / 2 +* (7) width 12bits 28bits : width; defined as (tickUpper - tickLower) / 2 +* Total 40bits : Each leg takes up this many bits +* =============================================================================================== +* +* The bit pattern is therefore in general (the numbers in parentheses refer to the above table): +* +* (optionRatio of the third leg, e.g.) +* | +* (7)(6)(5)(4)(3) (7)(6)(5)(4)(3) (7)(6)(5)(4)(3) (7)(6)(5)(4)(3) (2)(2)(2)(2) (1) +* <-- 40 bits ---> <-- 40 bits ---> <-- 40 bits --> <-- 40 bits --> <- 16bits -> <- 80 bits -> +* Leg 4 Leg 3 Leg 2 Leg 1 optionRatios Univ3 Pool Address +* +* <--- most significant bit least significant bit ---> +*/ +``` + +This entire pattern is then, when interpreted as a uint256, a unique ID of that position. + +## Examples + +Let's cover a few examples to drive home the design. First, we can think of the bit pattern as an array starting at bit index 0 going to bit index 255 (so 256 total bits). +Second, in general leg number N (the Nth leg) has leg index N-1. + + +and so on. + +In general, Panoptic uses the tokenId library to store and extract information from within this bit pattern. Similarly, we can store into the pattern as well and thus we can both read and write to this.