@@ -183,6 +183,10 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
183
183
// PerpV2 contract which provides a getter for baseToken UniswapV3 pools
184
184
IMarketRegistry public immutable perpMarketRegistry;
185
185
186
+ // PerpV2 operations are very gas intensive and there is a limit on the number of positions that can be opened in a single transaction
187
+ // during issuance/redemption. `maxPerpPositionsPerSet` is a safe limit set by governance taking Optimism's block gas limit into account.
188
+ uint256 public maxPerpPositionsPerSet;
189
+
186
190
// Mapping of SetTokens to an array of virtual token addresses the Set has open positions for.
187
191
// Array is updated when new positions are opened or old positions are zeroed out.
188
192
mapping (ISetToken => address []) internal positions;
@@ -202,7 +206,8 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
202
206
IController _controller ,
203
207
IVault _perpVault ,
204
208
IQuoter _perpQuoter ,
205
- IMarketRegistry _perpMarketRegistry
209
+ IMarketRegistry _perpMarketRegistry ,
210
+ uint256 _maxPerpPositionsPerSet
206
211
)
207
212
public
208
213
ModuleBase (_controller)
@@ -219,6 +224,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
219
224
perpVault = _perpVault;
220
225
perpQuoter = _perpQuoter;
221
226
perpMarketRegistry = _perpMarketRegistry;
227
+ maxPerpPositionsPerSet = _maxPerpPositionsPerSet;
222
228
}
223
229
224
230
/* ============ External Functions ============ */
@@ -268,6 +274,8 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
268
274
* NOTE: This method doesn't update the externalPositionUnit because it is a function of UniswapV3 virtual
269
275
* token market prices and needs to be generated on the fly to be meaningful.
270
276
*
277
+ * In the tables below, basePositionUnit = baseTokenBalance / setTotalSupply.
278
+ *
271
279
* As a user when levering, e.g increasing the magnitude of your position, you'd trade as below
272
280
* | ----------------------------------------------------------------------------------------------- |
273
281
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
@@ -276,13 +284,29 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
276
284
* | Short | Sell | get most amt. of vQuote | lower bound of output quote | negative |
277
285
* | ----------------------------------------------------------------------------------------------- |
278
286
*
279
- * As a user when delevering, e.g decreasing the magnitude of your position, you'd trade as below
280
- * | ----------------------------------------------------------------------------------------------- |
281
- * | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
282
- * | ----- |-------- | ------------------------- | --------------------------- | ------------------- |
283
- * | Long | Sell | get most amt. of vQuote | upper bound of input quote | negative |
284
- * | Short | Buy | pay least amt. of vQuote | lower bound of output quote | positive |
285
- * | ----------------------------------------------------------------------------------------------- |
287
+ * As a user when delevering by partially closing your position, you'd trade as below
288
+ * -----------------------------------------------------------------------------------------------------------------------------------
289
+ * | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
290
+ * | ----- |-------- | ------------------------- | --------------------------- | ----------------------------------------------------|
291
+ * | Long | Sell | get most amt. of vQuote | upper bound of input quote | negative, |baseQuantityUnits| < |basePositionUnit| |
292
+ * | Short | Buy | pay least amt. of vQuote | lower bound of output quote | positive, |baseQuantityUnits| < |basePositionUnit| |
293
+ * -----------------------------------------------------------------------------------------------------------------------------------
294
+ *
295
+ * As a user when completely closing a position, you'd trade as below
296
+ * -------------------------------------------------------------------------------------------------------------------------------------------
297
+ * | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
298
+ * | ----- |-----------------| ------------------------- | --------------------------- | ----------------------------------------------------|
299
+ * | Long | Sell to close | get most amt. of vQuote | upper bound of input quote | negative, baseQuantityUnits = -1 * basePositionUnit |
300
+ * | Short | Buy to close | pay least amt. of vQuote | lower bound of output quote | positive, baseQuantityUnits = -1 * basePositionUnit |
301
+ * -------------------------------------------------------------------------------------------------------------------------------------------
302
+ *
303
+ * As a user when reversing a position, e.g going from a long position to a short position in a single trade, you'd trade as below
304
+ * -------------------------------------------------------------------------------------------------------------------------------------------
305
+ * | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
306
+ * | ----- |-----------------|---------------------------| --------------------------- | ----------------------------------------------------|
307
+ * | Long | Sell to reverse | get most amt. of vQuote | upper bound of input quote | negative, |baseQuantityUnits| > |basePositionUnit| |
308
+ * | Short | Buy to reverse | pay least amt. of vQuote | lower bound of output quote | positive, |baseQuantityUnits| > |basePositionUnit| |
309
+ * -------------------------------------------------------------------------------------------------------------------------------------------
286
310
*
287
311
* @param _setToken Instance of the SetToken
288
312
* @param _baseToken Address virtual token being traded
@@ -537,6 +561,17 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
537
561
}
538
562
}
539
563
564
+ /* ============ External Setter Functions ============ */
565
+
566
+ /**
567
+ * @dev GOVERNANCE ONLY: Update max perpetual positions per SetToken. Only callable by governance.
568
+ *
569
+ * @param _maxPerpPositionsPerSet New max perpetual positons per set
570
+ */
571
+ function updateMaxPerpPositionsPerSet (uint256 _maxPerpPositionsPerSet ) external onlyOwner {
572
+ maxPerpPositionsPerSet = _maxPerpPositionsPerSet;
573
+ }
574
+
540
575
/* ============ External Getter Functions ============ */
541
576
542
577
/**
@@ -991,35 +1026,43 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
991
1026
992
1027
/**
993
1028
* @dev Construct the ActionInfo struct for trading. This method takes POSITION UNIT amounts and passes to
994
- * _createActionInfoNotional to create the struct. If the _baseTokenQuantity is zero then revert. This
995
- * method is only called from `trade` - the issue/redeem flow uses createActionInfoNotional directly.
1029
+ * _createActionInfoNotional to create the struct. If the _baseTokenQuantity is zero then revert. If
1030
+ * the _baseTokenQuantity = -(baseBalance/setSupply) then close the position entirely. This method is
1031
+ * only called from `trade` - the issue/redeem flow uses createActionInfoNotional directly.
996
1032
*
997
1033
* @param _setToken Instance of the SetToken
998
1034
* @param _baseToken Address of base token being traded into/out of
999
- * @param _baseTokenUnits Quantity of baseToken to trade in PositionUnits
1035
+ * @param _baseQuantityUnits Quantity of baseToken to trade in PositionUnits
1000
1036
* @param _quoteReceiveUnits Quantity of quote to receive if selling base and pay if buying, in PositionUnits
1001
1037
*
1002
1038
* @return ActionInfo Instance of constructed ActionInfo struct
1003
1039
*/
1004
1040
function _createAndValidateActionInfo (
1005
1041
ISetToken _setToken ,
1006
1042
address _baseToken ,
1007
- int256 _baseTokenUnits ,
1043
+ int256 _baseQuantityUnits ,
1008
1044
uint256 _quoteReceiveUnits
1009
1045
)
1010
1046
internal
1011
1047
view
1012
1048
returns (ActionInfo memory )
1013
1049
{
1014
- require (_baseTokenUnits != 0 , "Amount is 0 " );
1050
+ require (_baseQuantityUnits != 0 , "Amount is 0 " );
1015
1051
require (perpMarketRegistry.hasPool (_baseToken), "Base token does not exist " );
1016
1052
1017
1053
uint256 totalSupply = _setToken.totalSupply ();
1018
1054
1055
+ int256 baseBalance = perpAccountBalance.getBase (address (_setToken), _baseToken);
1056
+ int256 basePositionUnit = baseBalance.preciseDiv (totalSupply.toInt256 ());
1057
+
1058
+ int256 baseNotional = _baseQuantityUnits == basePositionUnit.neg ()
1059
+ ? baseBalance.neg () // To close position completely
1060
+ : _baseQuantityUnits.preciseMul (totalSupply.toInt256 ());
1061
+
1019
1062
return _createActionInfoNotional (
1020
1063
_setToken,
1021
1064
_baseToken,
1022
- _baseTokenUnits. preciseMul (totalSupply. toInt256 ()) ,
1065
+ baseNotional ,
1023
1066
_quoteReceiveUnits.preciseMul (totalSupply)
1024
1067
);
1025
1068
}
@@ -1078,6 +1121,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
1078
1121
positions[_setToken].removeStorage (_baseToken);
1079
1122
}
1080
1123
} else {
1124
+ require (positions[_setToken].length < maxPerpPositionsPerSet, "Exceeds max perpetual positions per set " );
1081
1125
positions[_setToken].push (_baseToken);
1082
1126
}
1083
1127
}
0 commit comments