Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contracts/interfaces/external/perp-v2/IAccountBalance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ interface IAccountBalance {
function getBase(address trader, address baseToken) external view returns (int256);
function getQuote(address trader, address baseToken) external view returns (int256);
function getNetQuoteBalanceAndPendingFee(address trader) external view returns (int256, uint256);
function getPositionSize(address trader, address baseToken) external view returns (int256);
function getPositionValue(address trader, address baseToken) external view returns (int256);
function getTotalPositionSize(address trader, address baseToken) external view returns (int256);
function getTotalPositionValue(address trader, address baseToken) external view returns (int256);
function getTotalAbsPositionValue(address trader) external view returns (uint256);
function getClearingHouseConfig() external view returns (address);
function getExchange() external view returns (address);
Expand Down
14 changes: 7 additions & 7 deletions contracts/protocol/lib/PositionV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
* CHANGELOG:
* - `Position` library has all internal functions which are inlined to the module contract during compilation.
* Inlining functions increases bytecode size of the module contract. This library contains the same functions
* as `Position` library but all the functions have public/external access modifier. Thus, making this version
* as `Position` library but all the functions have public/external access modifier. Thus, making this version
* linkable which helps in reducing bytecode size of the module contract.
*/
library PositionV2 {
Expand All @@ -62,7 +62,7 @@ library PositionV2 {
function hasExternalPosition(ISetToken _setToken, address _component) public view returns(bool) {
return _setToken.getExternalPositionModules(_component).length > 0;
}

/**
* Returns whether the SetToken component default position real unit is greater than or equal to units passed in.
*/
Expand All @@ -71,7 +71,7 @@ library PositionV2 {
}

/**
* Returns whether the SetToken component external position is greater than or equal to the real units passed in.
* Returns whether the SetToken component's external position is greater than or equal to the real units passed in.
*/
function hasSufficientExternalUnits(
ISetToken _setToken,
Expand All @@ -83,12 +83,12 @@ library PositionV2 {
view
returns(bool)
{
return _setToken.getExternalPositionRealUnit(_component, _positionModule) >= _unit.toInt256();
return _setToken.getExternalPositionRealUnit(_component, _positionModule) >= _unit.toInt256();
}

/**
* If the position does not exist, create a new Position and add to the SetToken. If it already exists,
* then set the position units. If the new units is 0, remove the position. Handles adding/removing of
* then set the position units. If the new units is 0, remove the position. Handles adding/removing of
* components where needed (in light of potential external positions).
*
* @param _setToken Address of SetToken being modified
Expand All @@ -114,7 +114,7 @@ library PositionV2 {

/**
* Update an external position and remove and external positions or components if necessary. The logic flows as follows:
* 1) If component is not already added then add component and external position.
* 1) If component is not already added then add component and external position.
* 2) If component is added but no existing external position using the passed module exists then add the external position.
* 3) If the existing position is being added to then just update the unit and data
* 4) If the position is being closed and no other external positions or default positions are associated with the component
Expand Down Expand Up @@ -191,7 +191,7 @@ library PositionV2 {
* @return Notional tracked balance
*/
function getDefaultTrackedBalance(ISetToken _setToken, address _component) external view returns(uint256) {
int256 positionUnit = _setToken.getDefaultPositionRealUnit(_component);
int256 positionUnit = _setToken.getDefaultPositionRealUnit(_component);
return _setToken.totalSupply().preciseMul(positionUnit.toUint256());
}

Expand Down
158 changes: 109 additions & 49 deletions contracts/protocol/modules/v2/PerpV2BasisTradingModule.sol

Large diffs are not rendered by default.

80 changes: 32 additions & 48 deletions contracts/protocol/modules/v2/PerpV2LeverageModuleV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol";
import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol";
import { UnitConversionUtils } from "../../../lib/UnitConversionUtils.sol";


/**
* @title PerpV2LeverageModuleV2
* @author Set Protocol
Expand All @@ -60,8 +61,8 @@ import { UnitConversionUtils } from "../../../lib/UnitConversionUtils.sol";
* Any pending funding costs or PnL is carried by the current token holders. To be used safely this module MUST issue using the
* SlippageIssuanceModule or else issue and redeem transaction could be sandwich attacked.
*
* NOTE: The external position unit is only updated on an as-needed basis during issuance/redemption. It does not reflect the current
* value of the Set's perpetual position. The current value can be calculated from getPositionNotionalInfo.
* NOTE: The external position unit is only updated on an as-needed basis during issuance, redemption, deposit and withdraw. It does not
* reflect the current value of the Set's perpetual position. The current value can be calculated from getPositionNotionalInfo.
*
* CHANGELOG:
* - This contract has the same functionality as `PerpV2LeverageModule` but smaller bytecode size. It extends ModuleBaseV2 (which uses
Expand Down Expand Up @@ -352,6 +353,10 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
onlyManagerAndValidSet(_setToken)
{
require(_collateralQuantityUnits > 0, "Deposit amount is 0");
require(
_collateralQuantityUnits <= _setToken.getDefaultPositionRealUnit(address(collateralToken)).toUint256(),
"Amount too high"
);

uint256 notionalDepositedQuantity = _depositAndUpdatePositions(_setToken, _collateralQuantityUnits);

Expand All @@ -374,6 +379,7 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
uint256 _collateralQuantityUnits
)
public
virtual
nonReentrant
onlyManagerAndValidSet(_setToken)
{
Expand All @@ -385,7 +391,7 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
}

/**
* @dev MANAGER ONLY: Removes this module from the SetToken, via call by the SetToken. Deletes
* @dev SETTOKEN ONLY: Removes this module from the SetToken, via call by the SetToken. Deletes
* position mappings associated with SetToken.
*
* NOTE: Function will revert if there is greater than a position unit amount of USDC of account value.
Expand Down Expand Up @@ -555,7 +561,7 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
/**
* @dev GOVERNANCE ONLY: Update max perpetual positions per SetToken. Only callable by governance.
*
* @param _maxPerpPositionsPerSet New max perpetual positons per set
* @param _maxPerpPositionsPerSet New max perpetual positions per set
*/
function updateMaxPerpPositionsPerSet(uint256 _maxPerpPositionsPerSet) external onlyOwner {
maxPerpPositionsPerSet = _maxPerpPositionsPerSet;
Expand Down Expand Up @@ -830,12 +836,7 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
initialCollateralPositionBalance
);

_setToken.editExternalPosition(
address(collateralToken),
address(this),
_calculateExternalPositionUnit(_setToken),
""
);
_updateExternalPositionUnit(_setToken);

return collateralNotionalQuantity;
}
Expand Down Expand Up @@ -885,12 +886,7 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
initialCollateralPositionBalance
);

_setToken.editExternalPosition(
address(collateralToken),
address(this),
_calculateExternalPositionUnit(_setToken),
""
);
_updateExternalPositionUnit(_setToken);

return collateralNotionalQuantity;
}
Expand Down Expand Up @@ -1058,44 +1054,32 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
}

/**
* @dev Gets the mid-point price of a virtual asset from UniswapV3 markets maintained by Perp Protocol
* @dev Sets the external position unit based on PerpV2 Vault collateral balance. If there is >= 1
* position unit of USDC of collateral value, add the component and/or the external position to the
* SetToken. Else, untracks the component and/or removes external position from the SetToken. Refer
* to PositionV2#editExternalPosition for detailed flow.
*
* @param _baseToken Address of virtual token to price
* @return price Mid-point price of virtual token in UniswapV3 AMM market
*/
function _calculateAMMSpotPrice(address _baseToken) internal view returns (uint256 price) {
address pool = perpMarketRegistry.getPool(_baseToken);
(uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0();
uint256 priceX96 = sqrtPriceX96.formatSqrtPriceX96ToPriceX96();
return priceX96.formatX96ToX10_18();
}

/**
* @dev Calculates the sum of collateralToken denominated market-prices of assets and debt for the Perp account per
* SetToken
* NOTE: Setting external position unit to the collateral balance is acceptable because, as noted above, the
* external position unit is only updated on an as-needed basis during issuance, redemption, deposit and
* withdraw. It does not reflect the current value of the Set's perpetual position. The true current value can
* be calculated from getPositionNotionalInfo.
*
* @param _setToken Instance of SetToken
* @return int256 External position unit
*/
function _calculateExternalPositionUnit(ISetToken _setToken) internal view returns (int256) {
PerpV2Positions.PositionNotionalInfo[] memory positionInfo = getPositionNotionalInfo(_setToken);
uint256 positionLength = positionInfo.length;
int256 totalPositionValue = 0;

for (uint i = 0; i < positionLength; i++ ) {
int256 spotPrice = _calculateAMMSpotPrice(positionInfo[i].baseToken).toInt256();
totalPositionValue = totalPositionValue.add(
positionInfo[i].baseBalance.preciseMul(spotPrice)
);
}
function _updateExternalPositionUnit(ISetToken _setToken) internal {
int256 collateralValueUnit = perpVault.getBalance(address(_setToken))
.toPreciseUnitsFromDecimals(collateralDecimals)
.preciseDiv(_setToken.totalSupply().toInt256())
.fromPreciseUnitToDecimals(collateralDecimals);

int256 externalPositionUnitInPreciseUnits = _calculatePartialAccountValuePositionUnit(_setToken)
.add(totalPositionValue.preciseDiv(_setToken.totalSupply().toInt256()));

return externalPositionUnitInPreciseUnits.fromPreciseUnitToDecimals(collateralDecimals);
_setToken.editExternalPosition(
address(collateralToken),
address(this),
collateralValueUnit,
""
);
}


/**
* @dev Returns issuance or redemption adjustments in the format expected by `SlippageIssuanceModule`.
* The last recorded externalPositionUnit (current) is subtracted from a dynamically generated
Expand All @@ -1107,7 +1091,7 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo
* @param _setToken Instance of the SetToken
* @param _newExternalPositionUnit Dynamically calculated externalPositionUnit
* @return int256[] Components-length array with equity adjustment value at appropriate index
* @return int256[] Components-length array of zeroes (debt adjustements)
* @return int256[] Components-length array of zeroes (debt adjustments)
*/
function _formatAdjustments(
ISetToken _setToken,
Expand Down
18 changes: 9 additions & 9 deletions deprecated/PerpV2LeverageModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
* token market prices and needs to be generated on the fly to be meaningful.
*
* In the tables below, basePositionUnit = baseTokenBalance / setTotalSupply.
*
*
* As a user when levering, e.g increasing the magnitude of your position, you'd trade as below
* | ----------------------------------------------------------------------------------------------- |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
Expand Down Expand Up @@ -318,7 +318,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
public
nonReentrant
onlyManagerAndValidSet(_setToken)
{
{
ActionInfo memory actionInfo = _createAndValidateActionInfo(
_setToken,
_baseToken,
Expand Down Expand Up @@ -413,7 +413,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
// `positions[setToken]` mapping stores an array of addresses. The base token addresses are removed from the array when the
// corresponding base token positions are zeroed out. Since no positions exist when removing the module, the stored array should
// already be empty, and the mapping can be deleted directly.
delete positions[setToken];
delete positions[setToken];

// Try if unregister exists on any of the modules
address[] memory modules = setToken.getModules();
Expand Down Expand Up @@ -563,8 +563,8 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA

/**
* @dev GOVERNANCE ONLY: Update max perpetual positions per SetToken. Only callable by governance.
*
* @param _maxPerpPositionsPerSet New max perpetual positons per set
*
* @param _maxPerpPositionsPerSet New max perpetual positions per set
*/
function updateMaxPerpPositionsPerSet(uint256 _maxPerpPositionsPerSet) external onlyOwner {
maxPerpPositionsPerSet = _maxPerpPositionsPerSet;
Expand Down Expand Up @@ -1052,8 +1052,8 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA

int256 baseBalance = perpAccountBalance.getBase(address(_setToken), _baseToken);
int256 basePositionUnit = baseBalance.preciseDiv(totalSupply.toInt256());
int256 baseNotional = _baseQuantityUnits == basePositionUnit.neg()

int256 baseNotional = _baseQuantityUnits == basePositionUnit.neg()
? baseBalance.neg() // To close position completely
: _baseQuantityUnits.preciseMul(totalSupply.toInt256());

Expand Down Expand Up @@ -1134,7 +1134,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
function _syncPositionList(ISetToken _setToken) internal {
address[] memory positionList = positions[_setToken];
uint256 positionLength = positionList.length;

for (uint256 i = 0; i < positionLength; i++) {
address currPosition = positionList[i];
if (!_hasBaseBalance(_setToken, currPosition)) {
Expand Down Expand Up @@ -1236,7 +1236,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
* @param _components Array of components held by the SetToken
* @param _newExternalPositionUnit Dynamically calculated externalPositionUnit
* @return int256[] Components-length array with equity adjustment value at appropriate index
* @return int256[] Components-length array of zeroes (debt adjustements)
* @return int256[] Components-length array of zeroes (debt adjustments)
*/
function _formatAdjustments(
ISetToken _setToken,
Expand Down
16 changes: 14 additions & 2 deletions test/integration/perpV2BasisTradingSlippageIssuance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => {
setToken.address,
{
feeRecipient: owner.address,
performanceFeePercentage: ether(.0),
performanceFeePercentage: ether(.1),
maxPerformanceFeePercentage: ether(.2)
}
);
Expand Down Expand Up @@ -294,6 +294,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => {
baseToken,
2,
ether(.02),
true,
true
);

Expand Down Expand Up @@ -508,6 +509,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => {
baseToken,
2,
ether(.02),
true,
true
);

Expand Down Expand Up @@ -542,7 +544,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => {
// initialExternalPositionUnit = 10_000_000
// finalExternalPositionUnit = 9_597_857

const expectedExternalPositionUnit = preciseDiv(usdcTransferOutQuantity, subjectQuantity);
const expectedExternalPositionUnit = preciseDiv(usdcTransferOutQuantity, subjectQuantity);;
expect(initialExternalPositionUnit).eq(usdcDefaultPositionUnit);
expect(finalExternalPositionUnit).to.be.closeTo(expectedExternalPositionUnit, 1);
});
Expand Down Expand Up @@ -855,6 +857,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => {
baseToken,
6,
ether(.02),
true,
true
);

Expand Down Expand Up @@ -909,7 +912,12 @@ describe("PerpV2BasisTradingSlippageIssuance", () => {

describe("when liquidation results in negative account value", () => {
beforeEach(async () => {
// Move oracle price down, wait one day
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(10.5));
await increaseTimeAsync(ONE_DAY_IN_SECONDS);

// Calculated leverage = ~8.5X = 8_654_438_822_995_683_587
// Lever again to track funding as `settled`
await leverUp(
setToken,
perpBasisTradingModule,
Expand All @@ -918,9 +926,13 @@ describe("PerpV2BasisTradingSlippageIssuance", () => {
baseToken,
6,
ether(.02),
true,
true
);

// Freeze funding changes
await perpSetup.clearingHouseConfig.setMaxFundingRate(ZERO);

// Move oracle price down to 5 USDC to enable liquidation
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(5.0));

Expand Down
2 changes: 1 addition & 1 deletion test/integration/perpV2LeverageV2SlippageIssuance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ describe("PerpV2LeverageSlippageIssuance", () => {
// initialExternalPositionUnit = 10_000_000
// finalExternalPositionUnit = 9_597_857

const expectedExternalPositionUnit = preciseDiv(usdcTransferOutQuantity, subjectQuantity);
const expectedExternalPositionUnit = preciseDiv(usdcTransferOutQuantity, subjectQuantity);;
expect(initialExternalPositionUnit).eq(usdcDefaultPositionUnit);
expect(finalExternalPositionUnit).to.be.closeTo(expectedExternalPositionUnit, 1);
});
Expand Down
Loading