Skip to content

Commit 0e73d21

Browse files
authored
PerpV2LeverageModule ABDK audit fixes (#190)
* 4. Fix event names * 7. Index event parameters * Revert changes introduced in #185 * 14. Refactor comment * 15. Implement getPositionUnitInfo using getPositionNotionalInfo * 16. Optimize fetching length * 21. Optimize if block condition * 16 & 23 Avoid multiple reads from mappings or arrays * 22 Avoid fetching array elements twice * 26. Optimize preciseDivCeil * Revert 4. Refactor event names to verbs instead of nouns * Pre calculate number of iterations in for loops * Update variable name to currentPosition * Update getPositionUnitInfo visibility to external * Refactor uint to uint256 * Replace .mul(-1) with neg() * Fix javadocs formatting * Add comments for registerToIssuanceModule try/catch block * Refactor neg() and add tests * Fix failing test * Remove failing test * Bump package to 0.1.11
1 parent 4892ef8 commit 0e73d21

File tree

7 files changed

+188
-98
lines changed

7 files changed

+188
-98
lines changed

contracts/interfaces/IPerpV2LeverageModule.sol

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,32 @@ interface IPerpV2LeverageModule {
108108
uint256 _amountWithdrawn
109109
);
110110

111+
/* ============ State Variable Getters ============ */
112+
113+
// PerpV2 contract which provides getters for base, quote, and owedRealizedPnl balances
114+
function perpAccountBalance() external view returns(IAccountBalance);
115+
116+
// PerpV2 contract which provides a trading API
117+
function perpClearingHouse() external view returns(IClearingHouse);
118+
119+
// PerpV2 contract which manages trading logic. Provides getters for UniswapV3 pools and pending funding balances
120+
function perpExchange() external view returns(IExchange);
121+
122+
// PerpV2 contract which handles deposits and withdrawals. Provides getter for collateral balances
123+
function perpVault() external view returns(IVault);
124+
125+
// PerpV2 contract which makes it possible to simulate a trade before it occurs
126+
function perpQuoter() external view returns(IQuoter);
127+
128+
// PerpV2 contract which provides a getter for baseToken UniswapV3 pools
129+
function perpMarketRegistry() external view returns(IMarketRegistry);
130+
131+
// Token (USDC) used as a vault deposit, Perp currently only supports USDC as it's settlement and collateral token
132+
function collateralToken() external view returns(IERC20);
133+
134+
// Decimals of collateral token. We set this in the constructor for later reading
135+
function collateralDecimals() external view returns(uint8);
136+
111137
/* ============ External Functions ============ */
112138

113139
/**
@@ -263,11 +289,4 @@ interface IPerpV2LeverageModule {
263289
* @return price Mid-point price of virtual token in UniswapV3 AMM market
264290
*/
265291
function getAMMSpotPrice(address _baseToken) external view returns (uint256 price);
266-
267-
/**
268-
* @dev Returns address of collateral token used by Perpetual Protocol (USDC)
269-
*
270-
* @return Address of Perpetual Protocol collateral token (USDC)
271-
*/
272-
function collateralToken() external view returns (IERC20);
273292
}

contracts/lib/PreciseUnitMath.sol

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,13 @@ library PreciseUnitMath {
144144
*/
145145
function preciseDivCeil(int256 a, int256 b) internal pure returns (int256) {
146146
require(b != 0, "Cant divide by 0");
147-
148-
if (a == 0 ) {
149-
return 0;
150-
} else if ((a > 0 && b > 0) || (a < 0 && b < 0)) {
151-
return a.mul(PRECISE_UNIT_INT).sub(1).div(b).add(1);
152-
} else {
153-
return a.mul(PRECISE_UNIT_INT).add(1).div(b).sub(1);
147+
148+
a = a.mul(PRECISE_UNIT_INT);
149+
int256 c = a.div(b);
150+
if (c * b != a) {
151+
c = c > 0 ? c + 1 : c - 1;
154152
}
153+
return c;
155154
}
156155

157156
/**
@@ -222,4 +221,12 @@ library PreciseUnitMath {
222221
function abs(int256 a) internal pure returns (uint) {
223222
return a >= 0 ? a.toUint256() : a.mul(-1).toUint256();
224223
}
224+
225+
/**
226+
* Returns the negation of a
227+
*/
228+
function neg(int256 a) internal pure returns (int256) {
229+
require(a > MIN_INT_256, "Inversion overflow");
230+
return -a;
231+
}
225232
}

contracts/mocks/PreciseUnitMathMock.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,8 @@ contract PreciseUnitMathMock {
9393
function abs(int256 a) external pure returns (uint256) {
9494
return a.abs();
9595
}
96+
97+
function neg(int256 a) external pure returns (int256) {
98+
return a.neg();
99+
}
96100
}

contracts/protocol/modules/PerpV2LeverageModule.sol

Lines changed: 77 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
118118
event PerpTraded(
119119
ISetToken indexed _setToken,
120120
address indexed _baseToken,
121-
uint256 _deltaBase,
121+
uint256 indexed _deltaBase,
122122
uint256 _deltaQuote,
123123
uint256 _protocolFee,
124124
bool _isBuy
@@ -132,8 +132,8 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
132132
*/
133133
event CollateralDeposited(
134134
ISetToken indexed _setToken,
135-
IERC20 _collateralToken,
136-
uint256 _amountDeposited
135+
IERC20 indexed _collateralToken,
136+
uint256 indexed _amountDeposited
137137
);
138138

139139
/**
@@ -144,8 +144,8 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
144144
*/
145145
event CollateralWithdrawn(
146146
ISetToken indexed _setToken,
147-
IERC20 _collateralToken,
148-
uint256 _amountWithdrawn
147+
IERC20 indexed _collateralToken,
148+
uint256 indexed _amountWithdrawn
149149
);
150150

151151
/* ============ Constants ============ */
@@ -166,25 +166,25 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
166166
uint8 internal immutable collateralDecimals;
167167

168168
// PerpV2 contract which provides getters for base, quote, and owedRealizedPnl balances
169-
IAccountBalance internal immutable perpAccountBalance;
169+
IAccountBalance public immutable perpAccountBalance;
170170

171171
// PerpV2 contract which provides a trading API
172-
IClearingHouse internal immutable perpClearingHouse;
172+
IClearingHouse public immutable perpClearingHouse;
173173

174174
// PerpV2 contract which manages trading logic. Provides getters for UniswapV3 pools and pending funding balances
175-
IExchange internal immutable perpExchange;
175+
IExchange public immutable perpExchange;
176176

177177
// PerpV2 contract which handles deposits and withdrawals. Provides getter for collateral balances
178-
IVault internal immutable perpVault;
178+
IVault public immutable perpVault;
179179

180180
// PerpV2 contract which makes it possible to simulate a trade before it occurs
181-
IQuoter internal immutable perpQuoter;
181+
IQuoter public immutable perpQuoter;
182182

183183
// PerpV2 contract which provides a getter for baseToken UniswapV3 pools
184-
IMarketRegistry internal immutable perpMarketRegistry;
184+
IMarketRegistry public immutable perpMarketRegistry;
185185

186186
// Mapping of SetTokens to an array of virtual token addresses the Set has open positions for.
187-
// Array is automatically updated when new positions are opened or old positions are zeroed out.
187+
// Array is updated when new positions are opened or old positions are zeroed out.
188188
mapping(ISetToken => address[]) internal positions;
189189

190190
/* ============ Constructor ============ */
@@ -249,7 +249,13 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
249249
// Try if register exists on any of the modules including the debt issuance module
250250
address[] memory modules = _setToken.getModules();
251251
for(uint256 i = 0; i < modules.length; i++) {
252-
try IDebtIssuanceModule(modules[i]).registerToIssuanceModule(_setToken) {} catch {}
252+
try IDebtIssuanceModule(modules[i]).registerToIssuanceModule(_setToken) {
253+
// This module registered itself on `modules[i]` issuance module.
254+
} catch {
255+
// Try will fail if `modules[i]` is not an instance of IDebtIssuanceModule and does not
256+
// implement the `registerToIssuanceModule` function, or if the `registerToIssuanceModule`
257+
// function call reverted. Irrespective of the reason for failure, continue to the next module.
258+
}
253259
}
254260
}
255261

@@ -384,7 +390,10 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
384390
"Account balance exists"
385391
);
386392

387-
delete positions[setToken]; // Should already be empty
393+
// `positions[setToken]` mapping stores an array of addresses. The base token addresses are removed from the array when the
394+
// corresponding base token positions are zeroed out. Since no positions exist when removing the module, the stored array should
395+
// already be empty, and the mapping can be deleted directly.
396+
delete positions[setToken];
388397

389398
// Try if unregister exists on any of the modules
390399
address[] memory modules = setToken.getModules();
@@ -599,10 +608,12 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
599608
* + quoteBalance: USDC quote asset balance as notional quantity (10**18)
600609
*/
601610
function getPositionNotionalInfo(ISetToken _setToken) public view returns (PositionNotionalInfo[] memory) {
602-
PositionNotionalInfo[] memory positionInfo = new PositionNotionalInfo[](positions[_setToken].length);
611+
address[] memory positionList = positions[_setToken];
612+
uint256 positionLength = positionList.length;
613+
PositionNotionalInfo[] memory positionInfo = new PositionNotionalInfo[](positionLength);
603614

604-
for(uint i = 0; i < positions[_setToken].length; i++){
605-
address baseToken = positions[_setToken][i];
615+
for(uint i = 0; i < positionLength; i++){
616+
address baseToken = positionList[i];
606617
positionInfo[i] = PositionNotionalInfo({
607618
baseToken: baseToken,
608619
baseBalance: perpAccountBalance.getBase(
@@ -630,26 +641,22 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
630641
* + baseUnit: baseToken balance as position unit (10**18)
631642
* + quoteUnit: USDC quote asset balance as position unit (10**18)
632643
*/
633-
function getPositionUnitInfo(ISetToken _setToken) public view returns (PositionUnitInfo[] memory) {
644+
function getPositionUnitInfo(ISetToken _setToken) external view returns (PositionUnitInfo[] memory) {
634645
int256 totalSupply = _setToken.totalSupply().toInt256();
635-
PositionUnitInfo[] memory positionInfo = new PositionUnitInfo[](positions[_setToken].length);
636-
637-
for(uint i = 0; i < positions[_setToken].length; i++){
638-
address baseToken = positions[_setToken][i];
639-
positionInfo[i] = PositionUnitInfo({
640-
baseToken: baseToken,
641-
baseUnit: perpAccountBalance.getBase(
642-
address(_setToken),
643-
baseToken
644-
).preciseDiv(totalSupply),
645-
quoteUnit: perpAccountBalance.getQuote(
646-
address(_setToken),
647-
baseToken
648-
).preciseDiv(totalSupply)
646+
PositionNotionalInfo[] memory positionNotionalInfo = getPositionNotionalInfo(_setToken);
647+
uint256 positionLength = positionNotionalInfo.length;
648+
PositionUnitInfo[] memory positionUnitInfo = new PositionUnitInfo[](positionLength);
649+
650+
for(uint i = 0; i < positionLength; i++){
651+
PositionNotionalInfo memory currentPosition = positionNotionalInfo[i];
652+
positionUnitInfo[i] = PositionUnitInfo({
653+
baseToken: currentPosition.baseToken,
654+
baseUnit: currentPosition.baseBalance.preciseDiv(totalSupply),
655+
quoteUnit: currentPosition.quoteBalance.preciseDiv(totalSupply)
649656
});
650657
}
651658

652-
return positionInfo;
659+
return positionUnitInfo;
653660
}
654661

655662

@@ -675,29 +682,11 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
675682
accountInfo = AccountInfo({
676683
collateralBalance: _getCollateralBalance(_setToken),
677684
owedRealizedPnl: owedRealizedPnl,
678-
pendingFundingPayments: perpExchange.getAllPendingFundingPayment(address(_setToken)).mul(-1),
685+
pendingFundingPayments: perpExchange.getAllPendingFundingPayment(address(_setToken)).neg(),
679686
netQuoteBalance: _getNetQuoteBalance(_setToken)
680687
});
681688
}
682689

683-
/**
684-
* @dev Returns important Perpetual Protocol addresses such as ClearingHouse, Vault, AccountBalance, etc. in an array.
685-
* Array is used in order to save bytecode vs a struct. Returned addresses are in the following order:
686-
* [AccountBalance, ClearingHouse, Exchange, Vault, Quoter, MarketRegistry]
687-
*
688-
* @return Array containing important Perpetual Protocol addresses
689-
*/
690-
function getPerpContracts() external view returns (address[6] memory) {
691-
return [
692-
address(perpAccountBalance),
693-
address(perpClearingHouse),
694-
address(perpExchange),
695-
address(perpVault),
696-
address(perpQuoter),
697-
address(perpMarketRegistry)
698-
];
699-
}
700-
701690
/* ============ Internal Functions ============ */
702691

703692
/**
@@ -749,17 +738,19 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
749738
// and variable may refer to the value which will be redeemed.
750739
int256 accountValueIssued = _calculatePartialAccountValuePositionUnit(_setToken).preciseMul(setTokenQuantityInt);
751740

752-
PositionUnitInfo[] memory positionInfo = getPositionUnitInfo(_setToken);
741+
PositionNotionalInfo[] memory positionInfo = getPositionNotionalInfo(_setToken);
742+
uint256 positionLength = positionInfo.length;
743+
int256 totalSupply = _setToken.totalSupply().toInt256();
753744

754-
for(uint i = 0; i < positionInfo.length; i++) {
755-
int256 baseTradeNotionalQuantity = positionInfo[i].baseUnit.preciseMul(setTokenQuantityInt);
745+
for(uint i = 0; i < positionLength; i++) {
746+
int256 baseTradeNotionalQuantity = positionInfo[i].baseBalance.preciseDiv(totalSupply).preciseMul(setTokenQuantityInt);
756747

757748
// When redeeming, we flip the sign of baseTradeNotionalQuantity because we are reducing the size of the position,
758749
// e.g selling base when long, buying base when short
759750
ActionInfo memory actionInfo = _createActionInfoNotional(
760751
_setToken,
761752
positionInfo[i].baseToken,
762-
_isIssue ? baseTradeNotionalQuantity : baseTradeNotionalQuantity.mul(-1),
753+
_isIssue ? baseTradeNotionalQuantity : baseTradeNotionalQuantity.neg(),
763754
0
764755
);
765756

@@ -1082,9 +1073,11 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
10821073
address[] memory positionList = positions[_setToken];
10831074
bool hasBaseToken = positionList.contains(_baseToken);
10841075

1085-
if (hasBaseToken && !_hasBaseBalance(_setToken, _baseToken)) {
1086-
positions[_setToken].removeStorage(_baseToken);
1087-
} else if (!hasBaseToken) {
1076+
if (hasBaseToken) {
1077+
if(!_hasBaseBalance(_setToken, _baseToken)) {
1078+
positions[_setToken].removeStorage(_baseToken);
1079+
}
1080+
} else {
10881081
positions[_setToken].push(_baseToken);
10891082
}
10901083
}
@@ -1098,10 +1091,12 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
10981091
*/
10991092
function _syncPositionList(ISetToken _setToken) internal {
11001093
address[] memory positionList = positions[_setToken];
1101-
1102-
for (uint256 i = 0; i < positionList.length; i++) {
1103-
if (!_hasBaseBalance(_setToken, positionList[i])) {
1104-
positions[_setToken].removeStorage(positionList[i]);
1094+
uint256 positionLength = positionList.length;
1095+
1096+
for (uint256 i = 0; i < positionLength; i++) {
1097+
address currPosition = positionList[i];
1098+
if (!_hasBaseBalance(_setToken, currPosition)) {
1099+
positions[_setToken].removeStorage(currPosition);
11051100
}
11061101
}
11071102
}
@@ -1144,9 +1139,10 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
11441139
*/
11451140
function _calculateExternalPositionUnit(ISetToken _setToken) internal view returns (int256) {
11461141
PositionNotionalInfo[] memory positionInfo = getPositionNotionalInfo(_setToken);
1142+
uint256 positionLength = positionInfo.length;
11471143
int256 totalPositionValue = 0;
11481144

1149-
for (uint i = 0; i < positionInfo.length; i++ ) {
1145+
for (uint i = 0; i < positionLength; i++ ) {
11501146
int256 spotPrice = _calculateAMMSpotPrice(positionInfo[i].baseToken).toInt256();
11511147
totalPositionValue = totalPositionValue.add(
11521148
positionInfo[i].baseBalance.preciseMul(spotPrice)
@@ -1159,22 +1155,29 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
11591155
return externalPositionUnitInPreciseUnits.fromPreciseUnitToDecimals(collateralDecimals);
11601156
}
11611157

1162-
// @dev Retrieves collateral balance as an 18 decimal vUSDC quote value
1163-
//
1164-
// @param _setToken Instance of SetToken
1165-
// @return int256 Collateral balance as an 18 decimal vUSDC quote value
1158+
/**
1159+
* @dev Retrieves collateral balance as an 18 decimal vUSDC quote value
1160+
*
1161+
* @param _setToken Instance of SetToken
1162+
* @return int256 Collateral balance as an 18 decimal vUSDC quote value
1163+
*/
11661164
function _getCollateralBalance(ISetToken _setToken) internal view returns (int256) {
11671165
return perpVault.getBalance(address(_setToken)).toPreciseUnitsFromDecimals(collateralDecimals);
11681166
}
11691167

1170-
// @dev Retrieves net quote balance of all open positions
1171-
//
1172-
// @param _setToken Instance of SetToken
1173-
// @return int256 Net quote balance of all open positions
1168+
/**
1169+
* @dev Retrieves net quote balance of all open positions
1170+
*
1171+
* @param _setToken Instance of SetToken
1172+
* @return netQuoteBalance Net quote balance of all open positions
1173+
*/
11741174
function _getNetQuoteBalance(ISetToken _setToken) internal view returns (int256 netQuoteBalance) {
1175-
for (uint256 i = 0; i < positions[_setToken].length; i++) {
1175+
address[] memory positionList = positions[_setToken];
1176+
uint256 positionLength = positionList.length;
1177+
1178+
for (uint256 i = 0; i < positionLength; i++) {
11761179
netQuoteBalance = netQuoteBalance.add(
1177-
perpAccountBalance.getQuote(address(_setToken), positions[_setToken][i])
1180+
perpAccountBalance.getQuote(address(_setToken), positionList[i])
11781181
);
11791182
}
11801183
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@setprotocol/set-protocol-v2",
3-
"version": "0.1.10",
3+
"version": "0.1.11",
44
"description": "",
55
"main": "dist",
66
"types": "dist/types",

0 commit comments

Comments
 (0)