Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into gated-launch
Browse files Browse the repository at this point in the history
  • Loading branch information
dyedm1 committed Nov 22, 2023
2 parents aec92e7 + c3fae98 commit f5af63a
Show file tree
Hide file tree
Showing 10 changed files with 601 additions and 219 deletions.
65 changes: 50 additions & 15 deletions contracts/CollateralTracker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity =0.8.18;
// Interfaces
import {PanopticFactory} from "./PanopticFactory.sol";
import {PanopticPool} from "./PanopticPool.sol";
import {IERC20Partial} from "@tokens/interfaces/IERC20Partial.sol";
import {IUniswapV3Pool} from "univ3-core/interfaces/IUniswapV3Pool.sol";
// Inherited implementations
import {ERC20Minimal} from "@tokens/ERC20Minimal.sol";
Expand Down Expand Up @@ -546,7 +547,9 @@ contract CollateralTracker is ERC20Minimal, Multicall {
/// @return maxShares The maximum amount of shares that can be minted.
function maxMint(address) external view returns (uint256 maxShares) {
unchecked {
return (convertToShares(type(uint104).max) * 1000) / 1001;
return
(convertToShares(type(uint104).max) * DECIMALS) /
(DECIMALS + uint128(s_commissionFee));
}
}

Expand Down Expand Up @@ -849,15 +852,13 @@ contract CollateralTracker is ERC20Minimal, Multicall {
/// - 10% if the position is liquidated when the price is between 950 and 1000, or if it is between 1050 and 1100
/// - 5% if the price is between 900 and 950 or (1100, 1150)
/// - 2.5% if between (850, 900) or (1150, 1200)
/// @param account Address of the exercised account.
/// @param currentTick The current price tick.
/// @param medianTick The median price tick.
/// @param positionId The position to be exercised
/// @param positionBalance The balance in `account` of the position to be exercised
/// @param longAmounts The amount of longs in the position.
/// @return exerciseFees The fees for exercising the option position.
function exerciseCost(
address account,
int24 currentTick,
int24 medianTick,
uint256 positionId,
Expand Down Expand Up @@ -1278,6 +1279,7 @@ contract CollateralTracker is ERC20Minimal, Multicall {
/// @param positionBalanceArray The list of all historical positions held by the 'optionOwner', stored as [[tokenId, balance/poolUtilizationAtMint], ...].
/// @return utilization The utilization of the Panoptic Pool.
/// @return tokenData LeftRight encoding with tokenbalance, ie assets, (in the right slot) and amount required in collateral (left slot).
/// @return realizedPremium The final premium paid/collected after accounting for available funds.
function takeCommissionAddData(
uint256 environmentContext,
int128 longAmount,
Expand All @@ -1286,19 +1288,39 @@ contract CollateralTracker is ERC20Minimal, Multicall {
int128 oldPositionPremia,
int128 swappedAmount,
uint256[2][] memory positionBalanceArray
) external onlyPanopticPool returns (int128 utilization, uint256 tokenData) {
)
external
onlyPanopticPool
returns (int128 utilization, uint256 tokenData, int128 realizedPremium)
{
unchecked {
// current available assets belonging to PLPs (updated after settlement) excluding any premium paid
int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;

// constrict premium to only assets not belonging to PLPs (i.e premium paid by sellers or collected from the pool earlier)
int256 exchangedAmount = _getExchangedAmount(longAmount, shortAmount, swappedAmount);

realizedPremium = int128(
Math.min(
oldPositionPremia,
int256(IERC20Partial(s_underlyingToken).balanceOf(address(s_panopticPool))) -
updatedAssets
)
);

// pay/collect premium of burnt option if rolling
// add intrinsic value of option + commission/ITM spread fees to settle
int256 tokenToPay = exchangedAmount - oldPositionPremia;
int256 tokenToPay = exchangedAmount - realizedPremium;

// compute tokens to be paid due to swap
// mint or burn tokens due to minting in-the-money
if (tokenToPay > 0) {
// if user must pay tokens, burn them from user balance
uint256 sharesToBurn = convertToShares(uint256(tokenToPay));
uint256 sharesToBurn = Math.mulDivUp(
uint256(tokenToPay),
totalSupply,
totalAssets()
);
_burn(environmentContext.caller(), sharesToBurn);
} else if (tokenToPay < 0) {
// if user must receive tokens, mint them
Expand All @@ -1310,9 +1332,7 @@ contract CollateralTracker is ERC20Minimal, Multicall {
// the inflow or outflow of pool assets is defined by the swappedAmount: it includes both the ITM swap amounts and the short/long amounts used to create the position
// however, any intrinsic value is paid for by the users, so we only add the portion that comes from PLPs: the short/long amounts
// premia is not included in the balance since it is the property of options buyers and sellers, not PLPs
s_poolAssets = uint128(
uint256(int256(uint256(s_poolAssets)) - swappedAmount + oldPositionPremia)
);
s_poolAssets = uint128(uint256(updatedAssets + realizedPremium));
s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount)));

{
Expand Down Expand Up @@ -1363,16 +1383,29 @@ contract CollateralTracker is ERC20Minimal, Multicall {
/// @param shortAmount The amount of shorts to be exercised (if any).
/// @param swappedAmount The amount of tokens potentially swapped.
/// @param currentPositionPremium The position premium.
/// @return realizedPremium The final premium paid/collected after accounting for available funds.
function exercise(
address optionOwner,
int128 longAmount,
int128 shortAmount,
int128 swappedAmount,
int128 currentPositionPremium
) external onlyPanopticPool {
) external onlyPanopticPool returns (int128 realizedPremium) {
unchecked {
// current available assets belonging to PLPs (updated after settlement) excluding any premium paid
int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;

// constrict premium to only assets not belonging to PLPs (i.e premium paid by sellers or collected from the pool earlier)
realizedPremium = int128(
Math.min(
currentPositionPremium,
int256(IERC20Partial(s_underlyingToken).balanceOf(address(s_panopticPool))) -
updatedAssets
)
);

// add premium to be paid/collected on position close
int256 tokenToPay = -currentPositionPremium;
int256 tokenToPay = -realizedPremium;

// if burning ITM and swap occurred, compute tokens to be paid through exercise and add swap fees
int256 intrinsicValue = swappedAmount - (longAmount - shortAmount);
Expand All @@ -1386,7 +1419,11 @@ contract CollateralTracker is ERC20Minimal, Multicall {

if (tokenToPay > 0) {
// if user must pay tokens, burn them from user balance (revert if balance too small)
uint256 sharesToBurn = convertToShares(uint256(tokenToPay));
uint256 sharesToBurn = Math.mulDivUp(
uint256(tokenToPay),
totalSupply,
totalAssets()
);
_burn(optionOwner, sharesToBurn);
} else if (tokenToPay < 0) {
// if user must receive tokens, mint them
Expand All @@ -1397,9 +1434,7 @@ contract CollateralTracker is ERC20Minimal, Multicall {
// update stored asset balances with net moved amounts
// any intrinsic value is paid for by the users, so we do not add it to s_inAMM
// premia is not included in the balance since it is the property of options buyers and sellers, not PLPs
s_poolAssets = uint128(
uint256(int256(uint256(s_poolAssets)) - swappedAmount + currentPositionPremium)
);
s_poolAssets = uint128(uint256(updatedAssets + realizedPremium));
s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount)));
}
}
Expand Down
102 changes: 53 additions & 49 deletions contracts/PanopticPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -428,20 +428,17 @@ contract PanopticPool is ERC1155Holder, Multicall {
/// @notice Calculate the accumulated premia owed from the option buyer to the option seller.
/// @param user The holder of options.
/// @param positionIdList The list of all option positions held by user.
/// @param collateralCalculation If true do not compute premium of short options - these are liquidity chunks in the AMM currently.
/// This is because the contracts only consider long premium as part of the collateral,
/// so setting it as true will compute all the long premia and deduct it from the collateral balance.
/// @param atTick Tick at which the accumulated premia is evaluated.
/// @param computeAllPremia Whether to compute accumulated premia for all legs held by the user (true), or just owed premia for long legs (false).
/// @return portfolioPremium The computed premia of the user's positions, where premia contains the accumulated premia for token0 in the right slot and for token1 in the left slot.
/// @return balances A list of balances and pool utilization for each position, of the form [[tokenId0, balances0], [tokenId1, balances1], ...].
function _calculateAccumulatedPremia(
address user,
uint256[] calldata positionIdList,
bool collateralCalculation,
bool computeAllPremia,
int24 atTick
) internal view returns (int256 portfolioPremium, uint256[2][] memory balances) {
uint256 pLength = positionIdList.length;
uint256[2][] memory balances = new uint256[2][](pLength);
balances = new uint256[2][](pLength);

address c_user = user;
// loop through each option position/tokenId
Expand All @@ -459,7 +456,7 @@ contract PanopticPool is ERC1155Holder, Multicall {
tokenId,
positionSize,
c_user,
collateralCalculation,
computeAllPremia,
atTick
);
portfolioPremium = portfolioPremium.add(positionPremia);
Expand Down Expand Up @@ -694,7 +691,7 @@ contract PanopticPool is ERC1155Holder, Multicall {

// pay commission based on total moved amount (long + short)
// write data about inAMM in collateralBase
poolUtilizations = _payCommissionAndWriteData(
(poolUtilizations, ) = _payCommissionAndWriteData(
tickStateCallContext.updateCurrentTick(newTick),
0,
tokenId,
Expand All @@ -717,6 +714,7 @@ contract PanopticPool is ERC1155Holder, Multicall {
/// @return poolUtilizations Packing of the pool utilization (how much funds are in the Panoptic pool versus the AMM pool at the time of minting),
/// right 64bits for token0 and left 64bits for token1, defined as (inAMM * 10_000) / totalAssets().
/// Where totalAssets is the total tracked assets in the AMM and PanopticPool minus fees and donations to the Panoptic pool.
/// @return realizedPremium The final premium paid/collected after accounting for available funds.
function _payCommissionAndWriteData(
uint256 tickStateCallContext,
uint256 oldTokenId,
Expand All @@ -725,7 +723,7 @@ contract PanopticPool is ERC1155Holder, Multicall {
int256 totalSwapped,
int256 oldPositionPremia,
uint256[] calldata positionIdList
) internal returns (uint128 poolUtilizations) {
) internal returns (uint128 poolUtilizations, int256 realizedPremium) {
// update storage data, take commission IMPORTANT: use post minting utilizations!

int256 portfolioPremium;
Expand Down Expand Up @@ -764,7 +762,7 @@ contract PanopticPool is ERC1155Holder, Multicall {
);

// update storage data, take commission
poolUtilizations = takeCommission(
(poolUtilizations, realizedPremium) = takeCommission(
positionBalanceArray,
tickStateCallContext,
longAmounts,
Expand All @@ -785,6 +783,7 @@ contract PanopticPool is ERC1155Holder, Multicall {
/// @param portfolioPremium Value of the long premia owed for all position in positionIdList.
/// @param totalSwapped Amount of tokens that were swapped during minting/rolling. Only happens when minting ITM positions.
/// @param oldPositionPremia Premia accumulated for the position that was closed during a roll.
/// @return realizedPremium The final premium paid/collected after accounting for available funds.
function takeCommission(
uint256[2][] memory positionBalanceArray,
uint256 tickStateCallContext,
Expand All @@ -793,7 +792,7 @@ contract PanopticPool is ERC1155Holder, Multicall {
int256 portfolioPremium,
int256 totalSwapped,
int256 oldPositionPremia
) internal returns (uint128) {
) internal returns (uint128, int256 realizedPremium) {
uint256 tokenData0;
uint256 tokenData1;
int128 utilization0;
Expand All @@ -807,31 +806,35 @@ contract PanopticPool is ERC1155Holder, Multicall {
int128 _portfolioPremium = portfolioPremium.rightSlot();
int128 _swapped = totalSwapped.rightSlot();
int128 _oldPositionPremia = oldPositionPremia.rightSlot();
(utilization0, tokenData0) = s_collateralToken0.takeCommissionAddData(
_ct,
_longAmount,
_shortAmount,
_portfolioPremium,
_oldPositionPremia,
_swapped,
_positionBalanceArray
);
(utilization0, tokenData0, _oldPositionPremia) = s_collateralToken0
.takeCommissionAddData(
_ct,
_longAmount,
_shortAmount,
_portfolioPremium,
_oldPositionPremia,
_swapped,
_positionBalanceArray
);
realizedPremium = int256(0).toRightSlot(_oldPositionPremia);
}
{
int128 _longAmount = longAmounts.leftSlot();
int128 _shortAmount = shortAmounts.leftSlot();
int128 _portfolioPremium = portfolioPremium.leftSlot();
int128 _swapped = totalSwapped.leftSlot();
int128 _oldPositionPremia = oldPositionPremia.leftSlot();
(utilization1, tokenData1) = s_collateralToken1.takeCommissionAddData(
_ct,
_longAmount,
_shortAmount,
_portfolioPremium,
_oldPositionPremia,
_swapped,
_positionBalanceArray
);
(utilization1, tokenData1, _oldPositionPremia) = s_collateralToken1
.takeCommissionAddData(
_ct,
_longAmount,
_shortAmount,
_portfolioPremium,
_oldPositionPremia,
_swapped,
_positionBalanceArray
);
realizedPremium = realizedPremium.toLeftSlot(_oldPositionPremia);
}

unchecked {
Expand All @@ -857,7 +860,7 @@ contract PanopticPool is ERC1155Holder, Multicall {

// return pool utilizations as a uint128 (pool Utilization is always < 10000)
unchecked {
return uint128(utilization0) + uint128(utilization1 << 64);
return (uint128(utilization0) + uint128(utilization1 << 64), realizedPremium);
}
}

Expand Down Expand Up @@ -1087,20 +1090,24 @@ contract PanopticPool is ERC1155Holder, Multicall {
);

// exercise the option and take the commission and addData
s_collateralToken0.exercise(
owner,
longAmounts.rightSlot(),
shortAmounts.rightSlot(),
totalSwapped.rightSlot(),
currentPositionPremia.rightSlot()
int256 realizedPremium = int256(0).toRightSlot(
s_collateralToken0.exercise(
owner,
longAmounts.rightSlot(),
shortAmounts.rightSlot(),
totalSwapped.rightSlot(),
currentPositionPremia.rightSlot()
)
);

s_collateralToken1.exercise(
owner,
longAmounts.leftSlot(),
shortAmounts.leftSlot(),
totalSwapped.leftSlot(),
currentPositionPremia.leftSlot()
currentPositionPremia = realizedPremium.toLeftSlot(
s_collateralToken1.exercise(
owner,
longAmounts.leftSlot(),
shortAmounts.leftSlot(),
totalSwapped.leftSlot(),
currentPositionPremia.leftSlot()
)
);
}

Expand Down Expand Up @@ -1199,7 +1206,7 @@ contract PanopticPool is ERC1155Holder, Multicall {
);

// pay commission based on total moved amount (long + short), write data about inAMM and premia in collateralBase
poolUtilizations = _payCommissionAndWriteData(
(poolUtilizations, oldPositionPremia) = _payCommissionAndWriteData(
tickStateCallContext,
oldTokenId,
newTokenId,
Expand Down Expand Up @@ -1421,7 +1428,6 @@ contract PanopticPool is ERC1155Holder, Multicall {
// Compute the exerciseFee, this will decrease the further away the price is from the forcedExercised position
/// @dev use the medianTick to prevent price manipulations based on swaps.
exerciseFees = s_collateralToken0.exerciseCost(
account,
currentTick,
getMedian(),
touchedId[0],
Expand Down Expand Up @@ -1735,23 +1741,21 @@ contract PanopticPool is ERC1155Holder, Multicall {
/// @param tokenId The option position.
/// @param positionSize The number of contracts (size) of the option position.
/// @param owner The holder of the tokenId option.
/// @param collateralCalculation If true do not compute premium of short options - these are liquidity chunks in the AMM currently.
/// This is because the contracts only consider long premium as part of the collateral,
/// so setting it as true will compute all the long premia and deduct it from the collateral balance.
/// @param computeAllPremia Whether to compute accumulated premia for all legs held by the user (true), or just owed premia for long legs (false).
/// @param atTick The tick at which the premia is calculated -> use (atTick < type(int24).max) to compute it
/// up to current block. atTick = type(int24).max will only consider fees as of the last on-chain transaction.
/// @return premia The computed premia (LeftRight-packed) of the option position for tokens 0 (right slot) and 1 (left slot).
function _getPremia(
uint256 tokenId,
uint128 positionSize,
address owner,
bool collateralCalculation,
bool computeAllPremia,
int24 atTick
) internal view returns (int256 premia) {
uint256 numLegs = tokenId.countLegs();
for (uint256 leg = 0; leg < numLegs; ) {
uint256 isLong = tokenId.isLong(leg);
if ((isLong == 1) || collateralCalculation) {
if ((isLong == 1) || computeAllPremia) {
uint256 tokenType = TokenId.tokenType(tokenId, leg);
uint256 liquidityChunk = PanopticMath.getLiquidityChunk(
tokenId,
Expand Down
Loading

0 comments on commit f5af63a

Please sign in to comment.