From a0847486c86ce9dcb3cc213f414185f10701c240 Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Thu, 29 Aug 2024 00:17:36 -0300 Subject: [PATCH 01/11] Split net to total delegate and undelegate --- .../interfaces/ICollateralModule.sol | 11 +++++- .../contracts/interfaces/IVaultModule.sol | 22 ++++++++++++ .../modules/core/CollateralModule.sol | 23 ++++++++++--- .../contracts/modules/core/VaultModule.sol | 34 +++++++++++++++++-- .../synthetix/contracts/storage/Account.sol | 12 +++++-- .../storage/AccountDelegationIntents.sol | 23 ++++++++++--- 6 files changed, 109 insertions(+), 16 deletions(-) diff --git a/protocol/synthetix/contracts/interfaces/ICollateralModule.sol b/protocol/synthetix/contracts/interfaces/ICollateralModule.sol index a7a358a8a1..338801b7b7 100644 --- a/protocol/synthetix/contracts/interfaces/ICollateralModule.sol +++ b/protocol/synthetix/contracts/interfaces/ICollateralModule.sol @@ -102,11 +102,20 @@ interface ICollateralModule { * @return totalDeposited The total collateral deposited in the account, denominated with 18 decimals of precision. * @return totalAssigned The amount of collateral in the account that is delegated to pools, denominated with 18 decimals of precision. * @return totalLocked The amount of collateral in the account that cannot currently be undelegated from a pool, denominated with 18 decimals of precision. + * @return totalPendingToDelegate The amount of collateral in the account that cannot currently be undelegated from a pool because is intended to be delegated, denominated with 18 decimals of precision. */ function getAccountCollateral( uint128 accountId, address collateralType - ) external view returns (uint256 totalDeposited, uint256 totalAssigned, uint256 totalLocked); + ) + external + view + returns ( + uint256 totalDeposited, + uint256 totalAssigned, + uint256 totalLocked, + uint256 totalPendingToDelegate + ); /** * @notice Returns the amount of collateral of type `collateralType` deposited with account `accountId` that can be withdrawn or delegated to pools. diff --git a/protocol/synthetix/contracts/interfaces/IVaultModule.sol b/protocol/synthetix/contracts/interfaces/IVaultModule.sol index b42c7cc773..049c40452f 100644 --- a/protocol/synthetix/contracts/interfaces/IVaultModule.sol +++ b/protocol/synthetix/contracts/interfaces/IVaultModule.sol @@ -291,6 +291,28 @@ interface IVaultModule { uint32 processingEndTime ); + /** + * @notice Returns the total amount of collateral intended to be delegated to the vault by the account. + * @param accountId The id of the account owning the intents. + * @param collateralType The address of the collateral. + * @return delegatedPerCollateral The total amount of collateral intended to be delegated to the vault by the account, denominated with 18 decimals of precision. + */ + function getIntentDelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view returns (uint256 delegatedPerCollateral); + + /** + * @notice Returns the total amount of collateral intended to be undelegated to the vault by the account. + * @param accountId The id of the account owning the intents. + * @param collateralType The address of the collateral. + * @return undelegatedPerCollateral The total amount of collateral intended to be undelegated to the vault by the account, denominated with 18 decimals of precision. + */ + function getIntentUndelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view returns (uint256 undelegatedPerCollateral); + /** * @notice Returns the total (positive and negative) amount of collateral intended to be delegated to the vault by the account. * @param accountId The id of the account owning the intents. diff --git a/protocol/synthetix/contracts/modules/core/CollateralModule.sol b/protocol/synthetix/contracts/modules/core/CollateralModule.sol index c8ba90d742..5bedef92f3 100644 --- a/protocol/synthetix/contracts/modules/core/CollateralModule.sol +++ b/protocol/synthetix/contracts/modules/core/CollateralModule.sol @@ -82,12 +82,20 @@ contract CollateralModule is ICollateralModule { .load(collateralType) .convertTokenToSystemAmount(tokenAmount); - (uint256 totalDeposited, uint256 totalAssigned, uint256 totalLocked) = account - .getCollateralTotals(collateralType); + ( + uint256 totalDeposited, + uint256 totalAssigned, + uint256 totalLocked, + uint256 pendingToDelegate + ) = account.getCollateralTotals(collateralType); + + uint256 lockedPlusPending = totalLocked + pendingToDelegate; // The amount that cannot be withdrawn from the protocol is the max of either // locked collateral or delegated collateral. - uint256 unavailableCollateral = totalLocked > totalAssigned ? totalLocked : totalAssigned; + uint256 unavailableCollateral = lockedPlusPending > totalAssigned + ? lockedPlusPending + : totalAssigned; uint256 availableForWithdrawal = totalDeposited - unavailableCollateral; if (tokenAmountD18 > availableForWithdrawal) { @@ -111,7 +119,12 @@ contract CollateralModule is ICollateralModule { external view override - returns (uint256 totalDeposited, uint256 totalAssigned, uint256 totalLocked) + returns ( + uint256 totalDeposited, + uint256 totalAssigned, + uint256 totalLocked, + uint256 totalPendingToDelegate + ) { return Account.load(accountId).getCollateralTotals(collateralType); } @@ -212,7 +225,7 @@ contract CollateralModule is ICollateralModule { AccountRBAC._ADMIN_PERMISSION ); - (uint256 totalDeposited, , uint256 totalLocked) = account.getCollateralTotals( + (uint256 totalDeposited, , uint256 totalLocked, ) = account.getCollateralTotals( collateralType ); diff --git a/protocol/synthetix/contracts/modules/core/VaultModule.sol b/protocol/synthetix/contracts/modules/core/VaultModule.sol index 53dd96bb5a..cb9742eaee 100644 --- a/protocol/synthetix/contracts/modules/core/VaultModule.sol +++ b/protocol/synthetix/contracts/modules/core/VaultModule.sol @@ -493,16 +493,44 @@ contract VaultModule is IVaultModule { /** * @inheritdoc IVaultModule */ - function getNetDelegatedPerCollateral( + function getIntentDelegatedPerCollateral( uint128 accountId, address collateralType - ) external view override returns (int256) { + ) external view override returns (uint256) { + return + Account.load(accountId).getDelegationIntents().delegatedAmountPerCollateral[ + collateralType + ]; + } + + /** + * @inheritdoc IVaultModule + */ + function getIntentUndelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view override returns (uint256) { return - Account.load(accountId).getDelegationIntents().netDelegatedAmountPerCollateral[ + Account.load(accountId).getDelegationIntents().unDelegatedAmountPerCollateral[ collateralType ]; } + /** + * @inheritdoc IVaultModule + */ + function getNetDelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view override returns (int256) { + AccountDelegationIntents.Data storage accountIntents = Account + .load(accountId) + .getDelegationIntents(); + return + accountIntents.delegatedAmountPerCollateral[collateralType].toInt() - + accountIntents.unDelegatedAmountPerCollateral[collateralType].toInt(); + } + /** * @inheritdoc IVaultModule */ diff --git a/protocol/synthetix/contracts/storage/Account.sol b/protocol/synthetix/contracts/storage/Account.sol index d9c398a4c0..256c0132b7 100644 --- a/protocol/synthetix/contracts/storage/Account.sol +++ b/protocol/synthetix/contracts/storage/Account.sol @@ -112,15 +112,23 @@ library Account { ) internal view - returns (uint256 totalDepositedD18, uint256 totalAssignedD18, uint256 totalLockedD18) + returns ( + uint256 totalDepositedD18, + uint256 totalAssignedD18, + uint256 totalLockedD18, + uint256 totalIntendedToDelegateD18 + ) { totalAssignedD18 = getAssignedCollateral(self, collateralType); totalDepositedD18 = totalAssignedD18 + self.collaterals[collateralType].amountAvailableForDelegationD18; totalLockedD18 = self.collaterals[collateralType].getTotalLocked(); + totalIntendedToDelegateD18 = getDelegationIntents(self).delegatedAmountPerCollateral[ + collateralType + ]; - return (totalDepositedD18, totalAssignedD18, totalLockedD18); + return (totalDepositedD18, totalAssignedD18, totalLockedD18, totalIntendedToDelegateD18); } /** diff --git a/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol b/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol index eb99d655b7..4b85c78aa8 100644 --- a/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol +++ b/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol @@ -25,7 +25,8 @@ library AccountDelegationIntents { // accounting for the intents collateral delegated // Per Collateral SetUtil.AddressSet delegatedCollaterals; - mapping(address => int256) netDelegatedAmountPerCollateral; // collateralType => net delegatedCollateralAmount + mapping(address => uint256) delegatedAmountPerCollateral; // collateralType => sum of delegations (delegatedCollateralAmount) + mapping(address => uint256) unDelegatedAmountPerCollateral; // collateralType => sum of un-delegations (delegatedCollateralAmount) } function addIntent( @@ -42,8 +43,14 @@ library AccountDelegationIntents { ] .add(intentId); - self.netDelegatedAmountPerCollateral[delegationIntent.collateralType] += delegationIntent - .deltaCollateralAmountD18; + if (delegationIntent.deltaCollateralAmountD18 > 0) { + self.delegatedAmountPerCollateral[delegationIntent.collateralType] += delegationIntent + .deltaCollateralAmountD18 + .toUint(); + } else { + self.unDelegatedAmountPerCollateral[delegationIntent.collateralType] += (-1 * + delegationIntent.deltaCollateralAmountD18).toUint(); + } if (!self.delegatedCollaterals.contains(delegationIntent.collateralType)) { self.delegatedCollaterals.add(delegationIntent.collateralType); @@ -68,8 +75,14 @@ library AccountDelegationIntents { ] .remove(intentId); - self.netDelegatedAmountPerCollateral[delegationIntent.collateralType] -= delegationIntent - .deltaCollateralAmountD18; + if (delegationIntent.deltaCollateralAmountD18 > 0) { + self.delegatedAmountPerCollateral[delegationIntent.collateralType] -= delegationIntent + .deltaCollateralAmountD18 + .toUint(); + } else { + self.unDelegatedAmountPerCollateral[delegationIntent.collateralType] -= (-1 * + delegationIntent.deltaCollateralAmountD18).toUint(); + } } function getIntent( From 6005021ee17214d721e08353c48f04dee2fcdb75 Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Thu, 29 Aug 2024 10:49:59 -0300 Subject: [PATCH 02/11] use sided caches for pending intents --- .../contracts/interfaces/IVaultModule.sol | 13 +------ .../contracts/modules/core/VaultModule.sol | 38 ++++++++----------- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/protocol/synthetix/contracts/interfaces/IVaultModule.sol b/protocol/synthetix/contracts/interfaces/IVaultModule.sol index 049c40452f..21d495fe99 100644 --- a/protocol/synthetix/contracts/interfaces/IVaultModule.sol +++ b/protocol/synthetix/contracts/interfaces/IVaultModule.sol @@ -57,7 +57,7 @@ interface IVaultModule { */ error ExceedingUndelegateAmount( int256 deltaCollateralAmountD18, - int256 cachedDeltaCollateralAmountD18, + uint256 cachedDeltaCollateralAmountD18, int256 totalDeltaCollateralAmountD18, uint256 currentCollateralAmount ); @@ -313,17 +313,6 @@ interface IVaultModule { address collateralType ) external view returns (uint256 undelegatedPerCollateral); - /** - * @notice Returns the total (positive and negative) amount of collateral intended to be delegated to the vault by the account. - * @param accountId The id of the account owning the intents. - * @param collateralType The address of the collateral. - * @return netDelegatedPerCollateral The total amount of collateral intended to be delegated to the vault by the account, denominated with 18 decimals of precision. - */ - function getNetDelegatedPerCollateral( - uint128 accountId, - address collateralType - ) external view returns (int256 netDelegatedPerCollateral); - /** * @notice Returns the total executable (not expired) amount of collateral intended to be delegated to the vault by the account. * @param accountId The id of the account owning the intents. diff --git a/protocol/synthetix/contracts/modules/core/VaultModule.sol b/protocol/synthetix/contracts/modules/core/VaultModule.sol index cb9742eaee..ec9fe8334f 100644 --- a/protocol/synthetix/contracts/modules/core/VaultModule.sol +++ b/protocol/synthetix/contracts/modules/core/VaultModule.sol @@ -112,19 +112,28 @@ contract VaultModule is IVaultModule { Vault.Data storage vault = Pool.loadExisting(poolId).vaults[collateralType]; uint256 currentCollateralAmount = vault.currentAccountCollateral(accountId); - int256 accumulatedDelta = deltaCollateralAmountD18 + - accountIntents.netDelegatedAmountPerCollateral[collateralType]; - if (accumulatedDelta < 0 && currentCollateralAmount < (-1 * accumulatedDelta).toUint()) { + + uint256 accumulatedDelegatedDelta = deltaCollateralAmountD18 > 0 + ? accountIntents.delegatedAmountPerCollateral[collateralType] + + deltaCollateralAmountD18.toUint() + : accountIntents.delegatedAmountPerCollateral[collateralType]; + uint256 accumulatedUnDelegatedDelta = deltaCollateralAmountD18 < 0 + ? accountIntents.unDelegatedAmountPerCollateral[collateralType] + + (-1 * deltaCollateralAmountD18).toUint() + : accountIntents.unDelegatedAmountPerCollateral[collateralType]; + + if (deltaCollateralAmountD18 < 0 && currentCollateralAmount < accumulatedUnDelegatedDelta) { revert ExceedingUndelegateAmount( deltaCollateralAmountD18, - accountIntents.netDelegatedAmountPerCollateral[collateralType], - accumulatedDelta, + accountIntents.unDelegatedAmountPerCollateral[collateralType], + accumulatedUnDelegatedDelta.toInt() * -1, currentCollateralAmount ); } - uint256 newCollateralAmountD18 = (currentCollateralAmount.toInt() + accumulatedDelta) - .toUint(); + uint256 newCollateralAmountD18 = deltaCollateralAmountD18 > 0 + ? currentCollateralAmount + accumulatedDelegatedDelta + : currentCollateralAmount - accumulatedUnDelegatedDelta; // Each collateral type may specify a minimum collateral amount that can be delegated. // See CollateralConfiguration.minDelegationD18. @@ -516,21 +525,6 @@ contract VaultModule is IVaultModule { ]; } - /** - * @inheritdoc IVaultModule - */ - function getNetDelegatedPerCollateral( - uint128 accountId, - address collateralType - ) external view override returns (int256) { - AccountDelegationIntents.Data storage accountIntents = Account - .load(accountId) - .getDelegationIntents(); - return - accountIntents.delegatedAmountPerCollateral[collateralType].toInt() - - accountIntents.unDelegatedAmountPerCollateral[collateralType].toInt(); - } - /** * @inheritdoc IVaultModule */ From 9140d62f594f4bb8c1338e4633500cc663a49009 Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Thu, 29 Aug 2024 16:05:49 -0300 Subject: [PATCH 03/11] split VaultModule --- protocol/synthetix/cannonfile.test.toml | 1 + protocol/synthetix/cannonfile.toml | 4 + .../interfaces/IVaultIntentViewsModule.sol | 109 ++++++++++++ .../contracts/interfaces/IVaultModule.sol | 102 ----------- .../modules/core/VaultIntentViewsModule.sol | 158 ++++++++++++++++++ .../contracts/modules/core/VaultModule.sol | 134 --------------- 6 files changed, 272 insertions(+), 236 deletions(-) create mode 100644 protocol/synthetix/contracts/interfaces/IVaultIntentViewsModule.sol create mode 100644 protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol diff --git a/protocol/synthetix/cannonfile.test.toml b/protocol/synthetix/cannonfile.test.toml index 5e39a024df..08e6b37773 100644 --- a/protocol/synthetix/cannonfile.test.toml +++ b/protocol/synthetix/cannonfile.test.toml @@ -83,6 +83,7 @@ contracts = [ "RewardsManagerModule", "UtilsModule", "VaultModule", + "VaultIntentViewsModule", "TestableAccountStorage", "TestableAccountRBACStorage", "TestableCollateralStorage", diff --git a/protocol/synthetix/cannonfile.toml b/protocol/synthetix/cannonfile.toml index 8a9ebc9f81..3973442446 100644 --- a/protocol/synthetix/cannonfile.toml +++ b/protocol/synthetix/cannonfile.toml @@ -91,6 +91,9 @@ artifact = "contracts/modules/core/UtilsModule.sol:UtilsModule" [contract.VaultModule] artifact = "contracts/modules/core/VaultModule.sol:VaultModule" +[contract.VaultIntentViewsModule] +artifact = "contracts/modules/core/VaultIntentViewsModule.sol:VaultIntentViewsModule" + [contract.AccountTokenModule] artifact = "contracts/modules/account/AccountTokenModule.sol:AccountTokenModule" @@ -118,6 +121,7 @@ contracts = [ "RewardsManagerModule", "UtilsModule", "VaultModule", + "VaultIntentViewsModule", ] [contract.InitialCoreProxy] diff --git a/protocol/synthetix/contracts/interfaces/IVaultIntentViewsModule.sol b/protocol/synthetix/contracts/interfaces/IVaultIntentViewsModule.sol new file mode 100644 index 0000000000..195ba30ebe --- /dev/null +++ b/protocol/synthetix/contracts/interfaces/IVaultIntentViewsModule.sol @@ -0,0 +1,109 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.11 <0.9.0; + +/** + * @title Views for Delegation Intents for Vault. + */ +interface IVaultIntentViewsModule { + /** + * @notice Returns details of the requested intent. + * @param accountId The id of the account owning the intent. + * @param intentId The id of the intents. + * @return poolId The id of the pool associated with the position. + * @return collateralType The address of the collateral used in the position. + * @return deltaCollateralAmountD18 The delta amount of collateral delegated in the position, denominated with 18 decimals of precision. + * @return leverage The new leverage amount used in the position, denominated with 18 decimals of precision. + * @return declarationTime The time at which the intent was declared. + * @return processingStartTime The time at which the intent execution window starts. + * @return processingEndTime The time at which the intent execution window ends. + */ + function getAccountIntent( + uint128 accountId, + uint256 intentId + ) + external + view + returns ( + uint128 poolId, + address collateralType, + int256 deltaCollateralAmountD18, + uint256 leverage, + uint32 declarationTime, + uint32 processingStartTime, + uint32 processingEndTime + ); + + /** + * @notice Returns the total amount of collateral intended to be delegated to the vault by the account. + * @param accountId The id of the account owning the intents. + * @param collateralType The address of the collateral. + * @return delegatedPerCollateral The total amount of collateral intended to be delegated to the vault by the account, denominated with 18 decimals of precision. + */ + function getIntentDelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view returns (uint256 delegatedPerCollateral); + + /** + * @notice Returns the total amount of collateral intended to be undelegated to the vault by the account. + * @param accountId The id of the account owning the intents. + * @param collateralType The address of the collateral. + * @return undelegatedPerCollateral The total amount of collateral intended to be undelegated to the vault by the account, denominated with 18 decimals of precision. + */ + function getIntentUndelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view returns (uint256 undelegatedPerCollateral); + + /** + * @notice Returns the total executable (not expired) amount of collateral intended to be delegated to the vault by the account. + * @param accountId The id of the account owning the intents. + * @param poolId The id of the pool associated with the position. + * @param collateralType The address of the collateral. + * @return accumulatedIntentDelta The total amount of collateral intended to be delegated that is not expired, denominated with 18 decimals of precision. + */ + function getExecutableDelegationAccumulated( + uint128 accountId, + uint128 poolId, + address collateralType + ) external view returns (int256 accumulatedIntentDelta); + + /** + * @notice Returns the list of executable (by timing) intents for the account. + * @param accountId The id of the account owning the intents. + * @param maxProcessableIntent The maximum number of intents to process. + * @return intentIds The list of intents. + * @return foundIntents The number of found intents. + * + * @dev The array of intent ids might have empty items at the end, use `foundIntents` to know the actual number + * of valid intents. + */ + function getAccountExecutableIntentIds( + uint128 accountId, + uint256 maxProcessableIntent + ) external view returns (uint256[] memory intentIds, uint256 foundIntents); + + /** + * @notice Returns the list of expired (by timing) intents for the account. + * @param accountId The id of the account owning the intents. + * @param maxProcessableIntent The maximum number of intents to process. + * @return intentIds The list of intents. + * @return foundIntents The number of found intents. + * + * @dev The array of intent ids might have empty items at the end, use `foundIntents` to know the actual number + * of valid intents. + */ + function getAccountExpiredIntentIds( + uint128 accountId, + uint256 maxProcessableIntent + ) external view returns (uint256[] memory intentIds, uint256 foundIntents); + + /** + * @notice Returns the list of intents for the account. + * @param accountId The id of the account owning the intents. + * @return intentIds The list of intents. + */ + function getAccountIntentIds( + uint128 accountId + ) external view returns (uint256[] memory intentIds); +} diff --git a/protocol/synthetix/contracts/interfaces/IVaultModule.sol b/protocol/synthetix/contracts/interfaces/IVaultModule.sol index 21d495fe99..c2674e37bd 100644 --- a/protocol/synthetix/contracts/interfaces/IVaultModule.sol +++ b/protocol/synthetix/contracts/interfaces/IVaultModule.sol @@ -263,69 +263,6 @@ interface IVaultModule { */ function forceDeleteAllAccountIntents(uint128 accountId) external; - /** - * @notice Returns details of the requested intent. - * @param accountId The id of the account owning the intent. - * @param intentId The id of the intents. - * @return poolId The id of the pool associated with the position. - * @return collateralType The address of the collateral used in the position. - * @return deltaCollateralAmountD18 The delta amount of collateral delegated in the position, denominated with 18 decimals of precision. - * @return leverage The new leverage amount used in the position, denominated with 18 decimals of precision. - * @return declarationTime The time at which the intent was declared. - * @return processingStartTime The time at which the intent execution window starts. - * @return processingEndTime The time at which the intent execution window ends. - */ - function getAccountIntent( - uint128 accountId, - uint256 intentId - ) - external - view - returns ( - uint128 poolId, - address collateralType, - int256 deltaCollateralAmountD18, - uint256 leverage, - uint32 declarationTime, - uint32 processingStartTime, - uint32 processingEndTime - ); - - /** - * @notice Returns the total amount of collateral intended to be delegated to the vault by the account. - * @param accountId The id of the account owning the intents. - * @param collateralType The address of the collateral. - * @return delegatedPerCollateral The total amount of collateral intended to be delegated to the vault by the account, denominated with 18 decimals of precision. - */ - function getIntentDelegatedPerCollateral( - uint128 accountId, - address collateralType - ) external view returns (uint256 delegatedPerCollateral); - - /** - * @notice Returns the total amount of collateral intended to be undelegated to the vault by the account. - * @param accountId The id of the account owning the intents. - * @param collateralType The address of the collateral. - * @return undelegatedPerCollateral The total amount of collateral intended to be undelegated to the vault by the account, denominated with 18 decimals of precision. - */ - function getIntentUndelegatedPerCollateral( - uint128 accountId, - address collateralType - ) external view returns (uint256 undelegatedPerCollateral); - - /** - * @notice Returns the total executable (not expired) amount of collateral intended to be delegated to the vault by the account. - * @param accountId The id of the account owning the intents. - * @param poolId The id of the pool associated with the position. - * @param collateralType The address of the collateral. - * @return accumulatedIntentDelta The total amount of collateral intended to be delegated that is not expired, denominated with 18 decimals of precision. - */ - function getExecutableDelegationAccumulated( - uint128 accountId, - uint128 poolId, - address collateralType - ) external view returns (int256 accumulatedIntentDelta); - /** * @notice Returns the amount of debt that needs to be repaid, which allows execution of intents that aim at undelegating collalteral, ensuring complyiance with the issuance ratio requirements * @param accountId The id of the account owning the position. @@ -344,45 +281,6 @@ interface IVaultModule { uint256 collateralPrice ) external view returns (uint256 howMuchToRepayD18); - /** - * @notice Returns the list of executable (by timing) intents for the account. - * @param accountId The id of the account owning the intents. - * @param maxProcessableIntent The maximum number of intents to process. - * @return intentIds The list of intents. - * @return foundIntents The number of found intents. - * - * @dev The array of intent ids might have empty items at the end, use `foundIntents` to know the actual number - * of valid intents. - */ - function getAccountExecutableIntentIds( - uint128 accountId, - uint256 maxProcessableIntent - ) external view returns (uint256[] memory intentIds, uint256 foundIntents); - - /** - * @notice Returns the list of expired (by timing) intents for the account. - * @param accountId The id of the account owning the intents. - * @param maxProcessableIntent The maximum number of intents to process. - * @return intentIds The list of intents. - * @return foundIntents The number of found intents. - * - * @dev The array of intent ids might have empty items at the end, use `foundIntents` to know the actual number - * of valid intents. - */ - function getAccountExpiredIntentIds( - uint128 accountId, - uint256 maxProcessableIntent - ) external view returns (uint256[] memory intentIds, uint256 foundIntents); - - /** - * @notice Returns the list of intents for the account. - * @param accountId The id of the account owning the intents. - * @return intentIds The list of intents. - */ - function getAccountIntentIds( - uint128 accountId - ) external view returns (uint256[] memory intentIds); - /** * @notice Returns the collateralization ratio of the specified liquidity position. If debt is negative, this function will return 0. * @dev Call this function using `callStatic` to treat it as a view function. diff --git a/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol b/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol new file mode 100644 index 0000000000..7d8f1debf0 --- /dev/null +++ b/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol @@ -0,0 +1,158 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.11 <0.9.0; + +import "@synthetixio/core-contracts/contracts/ownership/OwnableStorage.sol"; +import "@synthetixio/core-contracts/contracts/utils/DecimalMath.sol"; +import "@synthetixio/core-contracts/contracts/utils/SafeCast.sol"; +import "@synthetixio/core-contracts/contracts/utils/ERC2771Context.sol"; + +import "../../storage/Account.sol"; +import "../../storage/Pool.sol"; +import "../../storage/AccountDelegationIntents.sol"; + +import "../../interfaces/IVaultIntentViewsModule.sol"; + +/** + * @title Views for Delegation Intents for Vault. + * @dev See IVaultIntentViewsModule. + */ +contract VaultIntentViewsModule is IVaultIntentViewsModule { + using SetUtil for SetUtil.UintSet; + using DecimalMath for uint256; + using Pool for Pool.Data; + using Vault for Vault.Data; + using VaultEpoch for VaultEpoch.Data; + using Collateral for Collateral.Data; + using CollateralConfiguration for CollateralConfiguration.Data; + using SafeCastU256 for uint256; + using SafeCastI256 for int256; + using AccountDelegationIntents for AccountDelegationIntents.Data; + using DelegationIntent for DelegationIntent.Data; + using Account for Account.Data; + + /** + * @inheritdoc IVaultIntentViewsModule + */ + function getAccountIntent( + uint128 accountId, + uint256 intentId + ) external view override returns (uint128, address, int256, uint256, uint32, uint32, uint32) { + DelegationIntent.Data storage intent = Account + .load(accountId) + .getDelegationIntents() + .getIntent(intentId); + return ( + intent.poolId, + intent.collateralType, + intent.deltaCollateralAmountD18, + intent.leverage, + intent.declarationTime, + intent.processingStartTime(), + intent.processingEndTime() + ); + } + + /** + * @inheritdoc IVaultIntentViewsModule + */ + function getAccountIntentIds( + uint128 accountId + ) external view override returns (uint256[] memory) { + return Account.load(accountId).getDelegationIntents().intentsId.values(); + } + + /** + * @inheritdoc IVaultIntentViewsModule + */ + function getAccountExpiredIntentIds( + uint128 accountId, + uint256 maxProcessableIntent + ) external view override returns (uint256[] memory expiredIntents, uint256 foundItems) { + uint256[] memory allIntents = Account + .load(accountId) + .getDelegationIntents() + .intentsId + .values(); + uint256 max = maxProcessableIntent > allIntents.length + ? allIntents.length + : maxProcessableIntent; + expiredIntents = new uint256[](max); + for (uint256 i = 0; i < max; i++) { + if (DelegationIntent.load(allIntents[i]).intentExpired()) { + expiredIntents[foundItems] = allIntents[i]; + foundItems++; + } + } + } + + /** + * @inheritdoc IVaultIntentViewsModule + */ + function getAccountExecutableIntentIds( + uint128 accountId, + uint256 maxProcessableIntent + ) external view override returns (uint256[] memory executableIntents, uint256 foundItems) { + uint256[] memory allIntents = Account + .load(accountId) + .getDelegationIntents() + .intentsId + .values(); + uint256 max = maxProcessableIntent > allIntents.length + ? allIntents.length + : maxProcessableIntent; + executableIntents = new uint256[](max); + for (uint256 i = 0; i < max; i++) { + if (DelegationIntent.load(allIntents[i]).isExecutable()) { + executableIntents[foundItems] = allIntents[i]; + foundItems++; + } + } + } + + /** + * @inheritdoc IVaultIntentViewsModule + */ + function getIntentDelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view override returns (uint256) { + return + Account.load(accountId).getDelegationIntents().delegatedAmountPerCollateral[ + collateralType + ]; + } + + /** + * @inheritdoc IVaultIntentViewsModule + */ + function getIntentUndelegatedPerCollateral( + uint128 accountId, + address collateralType + ) external view override returns (uint256) { + return + Account.load(accountId).getDelegationIntents().unDelegatedAmountPerCollateral[ + collateralType + ]; + } + + /** + * @inheritdoc IVaultIntentViewsModule + */ + function getExecutableDelegationAccumulated( + uint128 accountId, + uint128 poolId, + address collateralType + ) external view override returns (int256 accumulatedIntentDelta) { + uint256[] memory intentIds = Account.load(accountId).getDelegationIntents().intentIdsByPair( + poolId, + collateralType + ); + accumulatedIntentDelta = 0; + for (uint256 i = 0; i < intentIds.length; i++) { + DelegationIntent.Data storage intent = DelegationIntent.load(intentIds[i]); + if (!intent.intentExpired()) { + accumulatedIntentDelta += intent.deltaCollateralAmountD18; + } + } + } +} diff --git a/protocol/synthetix/contracts/modules/core/VaultModule.sol b/protocol/synthetix/contracts/modules/core/VaultModule.sol index ec9fe8334f..143cd7e1c3 100644 --- a/protocol/synthetix/contracts/modules/core/VaultModule.sol +++ b/protocol/synthetix/contracts/modules/core/VaultModule.sol @@ -20,21 +20,13 @@ import "../../interfaces/IVaultModule.sol"; */ contract VaultModule is IVaultModule { using SetUtil for SetUtil.UintSet; - using SetUtil for SetUtil.Bytes32Set; - using SetUtil for SetUtil.AddressSet; using DecimalMath for uint256; using Pool for Pool.Data; using Vault for Vault.Data; using VaultEpoch for VaultEpoch.Data; using Collateral for Collateral.Data; using CollateralConfiguration for CollateralConfiguration.Data; - using AccountRBAC for AccountRBAC.Data; - using Distribution for Distribution.Data; - using CollateralConfiguration for CollateralConfiguration.Data; - using ScalableMapping for ScalableMapping.Data; - using SafeCastU128 for uint128; using SafeCastU256 for uint256; - using SafeCastI128 for int128; using SafeCastI256 for int256; using AccountDelegationIntents for AccountDelegationIntents.Data; using DelegationIntent for DelegationIntent.Data; @@ -420,132 +412,6 @@ contract VaultModule is IVaultModule { return Pool.loadExisting(poolId).currentVaultDebt(collateralType); } - /** - * @inheritdoc IVaultModule - */ - function getAccountIntent( - uint128 accountId, - uint256 intentId - ) external view override returns (uint128, address, int256, uint256, uint32, uint32, uint32) { - DelegationIntent.Data storage intent = Account - .load(accountId) - .getDelegationIntents() - .getIntent(intentId); - return ( - intent.poolId, - intent.collateralType, - intent.deltaCollateralAmountD18, - intent.leverage, - intent.declarationTime, - intent.processingStartTime(), - intent.processingEndTime() - ); - } - - /** - * @inheritdoc IVaultModule - */ - function getAccountIntentIds( - uint128 accountId - ) external view override returns (uint256[] memory) { - return Account.load(accountId).getDelegationIntents().intentsId.values(); - } - - /** - * @inheritdoc IVaultModule - */ - function getAccountExpiredIntentIds( - uint128 accountId, - uint256 maxProcessableIntent - ) external view override returns (uint256[] memory expiredIntents, uint256 foundItems) { - uint256[] memory allIntents = Account - .load(accountId) - .getDelegationIntents() - .intentsId - .values(); - uint256 max = maxProcessableIntent > allIntents.length - ? allIntents.length - : maxProcessableIntent; - expiredIntents = new uint256[](max); - for (uint256 i = 0; i < max; i++) { - if (DelegationIntent.load(allIntents[i]).intentExpired()) { - expiredIntents[foundItems] = allIntents[i]; - foundItems++; - } - } - } - - /** - * @inheritdoc IVaultModule - */ - function getAccountExecutableIntentIds( - uint128 accountId, - uint256 maxProcessableIntent - ) external view override returns (uint256[] memory executableIntents, uint256 foundItems) { - uint256[] memory allIntents = Account - .load(accountId) - .getDelegationIntents() - .intentsId - .values(); - uint256 max = maxProcessableIntent > allIntents.length - ? allIntents.length - : maxProcessableIntent; - executableIntents = new uint256[](max); - for (uint256 i = 0; i < max; i++) { - if (DelegationIntent.load(allIntents[i]).isExecutable()) { - executableIntents[foundItems] = allIntents[i]; - foundItems++; - } - } - } - - /** - * @inheritdoc IVaultModule - */ - function getIntentDelegatedPerCollateral( - uint128 accountId, - address collateralType - ) external view override returns (uint256) { - return - Account.load(accountId).getDelegationIntents().delegatedAmountPerCollateral[ - collateralType - ]; - } - - /** - * @inheritdoc IVaultModule - */ - function getIntentUndelegatedPerCollateral( - uint128 accountId, - address collateralType - ) external view override returns (uint256) { - return - Account.load(accountId).getDelegationIntents().unDelegatedAmountPerCollateral[ - collateralType - ]; - } - - /** - * @inheritdoc IVaultModule - */ - function getExecutableDelegationAccumulated( - uint128 accountId, - uint128 poolId, - address collateralType - ) external view override returns (int256 accumulatedIntentDelta) { - uint256[] memory intentIds = Account.load(accountId).getDelegationIntents().intentIdsByPair( - poolId, - collateralType - ); - accumulatedIntentDelta = 0; - for (uint256 i = 0; i < intentIds.length; i++) { - DelegationIntent.Data storage intent = DelegationIntent.load(intentIds[i]); - if (!intent.intentExpired()) { - accumulatedIntentDelta += intent.deltaCollateralAmountD18; - } - } - } - /** * @inheritdoc IVaultModule */ From 6bcb5dbb5edaf3e2d51400304e56fdf8f2bb6711 Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Fri, 30 Aug 2024 13:59:52 -0300 Subject: [PATCH 04/11] update storage.dump --- markets/bfp-market/storage.dump.sol | 3 ++- markets/perps-market/storage.dump.sol | 3 ++- protocol/synthetix/storage.dump.sol | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/markets/bfp-market/storage.dump.sol b/markets/bfp-market/storage.dump.sol index 0c14278ffe..b15f9fd395 100644 --- a/markets/bfp-market/storage.dump.sol +++ b/markets/bfp-market/storage.dump.sol @@ -137,7 +137,8 @@ library AccountDelegationIntents { SetUtil.UintSet intentsId; mapping(bytes32 => SetUtil.UintSet) intentsByPair; SetUtil.AddressSet delegatedCollaterals; - mapping(address => int256) netDelegatedAmountPerCollateral; + mapping(address => uint256) delegatedAmountPerCollateral; + mapping(address => uint256) unDelegatedAmountPerCollateral; } } diff --git a/markets/perps-market/storage.dump.sol b/markets/perps-market/storage.dump.sol index c0bab4a01a..ae67d1cc0d 100644 --- a/markets/perps-market/storage.dump.sol +++ b/markets/perps-market/storage.dump.sol @@ -136,7 +136,8 @@ library AccountDelegationIntents { SetUtil.UintSet intentsId; mapping(bytes32 => SetUtil.UintSet) intentsByPair; SetUtil.AddressSet delegatedCollaterals; - mapping(address => int256) netDelegatedAmountPerCollateral; + mapping(address => uint256) delegatedAmountPerCollateral; + mapping(address => uint256) unDelegatedAmountPerCollateral; } } diff --git a/protocol/synthetix/storage.dump.sol b/protocol/synthetix/storage.dump.sol index 1ac3bbabb3..eeb5fe77ba 100644 --- a/protocol/synthetix/storage.dump.sol +++ b/protocol/synthetix/storage.dump.sol @@ -411,7 +411,8 @@ library AccountDelegationIntents { SetUtil.UintSet intentsId; mapping(bytes32 => SetUtil.UintSet) intentsByPair; SetUtil.AddressSet delegatedCollaterals; - mapping(address => int256) netDelegatedAmountPerCollateral; + mapping(address => uint256) delegatedAmountPerCollateral; + mapping(address => uint256) unDelegatedAmountPerCollateral; } } From 8e3b8d74338cc64af1dafa427459f100e26fad0d Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Fri, 30 Aug 2024 23:09:39 -0300 Subject: [PATCH 05/11] checkpoint: test (still wip) --- .../modules/core/CollateralModule.sol | 8 +- .../modules/core/VaultModule.test.ts | 219 ++++++++++++++++++ 2 files changed, 221 insertions(+), 6 deletions(-) diff --git a/protocol/synthetix/contracts/modules/core/CollateralModule.sol b/protocol/synthetix/contracts/modules/core/CollateralModule.sol index 5bedef92f3..54068603f0 100644 --- a/protocol/synthetix/contracts/modules/core/CollateralModule.sol +++ b/protocol/synthetix/contracts/modules/core/CollateralModule.sol @@ -89,15 +89,11 @@ contract CollateralModule is ICollateralModule { uint256 pendingToDelegate ) = account.getCollateralTotals(collateralType); - uint256 lockedPlusPending = totalLocked + pendingToDelegate; - // The amount that cannot be withdrawn from the protocol is the max of either // locked collateral or delegated collateral. - uint256 unavailableCollateral = lockedPlusPending > totalAssigned - ? lockedPlusPending - : totalAssigned; + uint256 unavailableCollateral = totalLocked > totalAssigned ? totalLocked : totalAssigned; - uint256 availableForWithdrawal = totalDeposited - unavailableCollateral; + uint256 availableForWithdrawal = totalDeposited - unavailableCollateral - pendingToDelegate; if (tokenAmountD18 > availableForWithdrawal) { revert InsufficientAccountCollateral(tokenAmountD18); } diff --git a/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts b/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts index b53c20f7ec..6d5828be52 100644 --- a/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts +++ b/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts @@ -13,6 +13,7 @@ import { expectedToDeltaDelegatedCollateral, } from '../../../common'; import { wei } from '@synthetixio/wei'; +import { fastForwardTo, getTime } from '@synthetixio/core-utils/utils/hardhat/rpc'; describe('VaultModule', function () { const { @@ -1068,4 +1069,222 @@ describe('VaultModule', function () { assertBn.equal(await systems().Core.getWithdrawableMarketUsd(marketId), 0); }); }); + + describe('transient locked collateral (flashloan)', async () => { + let intentId: BigNumber; + let declareDelegateIntentTime: number; + let preTotalDeposited: BigNumber; + let preTotalAssigned: BigNumber; + let toWithdraw: BigNumber; + + before(restore); + + before('set market window times', async () => { + const previousConfiguration = await MockMarket.getDelegationCollateralConfiguration(); + await MockMarket.setDelegationCollateralConfiguration( + 100, + 20, + previousConfiguration[2], + previousConfiguration[3] + ); + }); + + before('declare intent to delegate', async () => { + const collateralsPre = await systems() + .Core.connect(owner) + .getAccountCollateral(accountId, collateralAddress()); + preTotalAssigned = collateralsPre.totalAssigned; + preTotalDeposited = collateralsPre.totalDeposited; + toWithdraw = preTotalDeposited.sub(preTotalAssigned); + + intentId = await declareDelegateIntent( + systems, + owner, + user1, + accountId, + poolId, + collateralAddress(), + depositAmount.mul(2), + ethers.utils.parseEther('1') + ); + + declareDelegateIntentTime = await getTime(provider()); + }); + + const restoreToLocked = snapshotCheckpoint(provider); + + it('intended to delegate is transient locked', async () => { + const collaterals = await systems() + .Core.connect(owner) + .getAccountCollateral(accountId, collateralAddress()); + + assertBn.equal(collaterals.totalPendingToDelegate, depositAmount.mul(1)); + }); + + it('fails to withraw when collateral is reserved for the intent to delegate', async () => { + await assertRevert( + systems().Core.connect(user1).withdraw(accountId, collateralAddress(), toWithdraw), + 'InsufficientAccountCollateral("10000000000000000000")', + systems().Core + ); + }); + + describe('when the intent is processed and undelegated', async () => { + before(restoreToLocked); + + before('process the intent and undelegate the collateral', async () => { + await fastForwardTo(declareDelegateIntentTime + 110, provider()); + + await systems() + .Core.connect(user1) + .processIntentToDelegateCollateralByIntents(accountId, [intentId]); + + // Undelegate the collateral + await delegateCollateral( + systems, + owner, + user1, + accountId, + poolId, + collateralAddress(), + depositAmount.mul(1), + ethers.utils.parseEther('1') + ); + }); + + it('allows to withdraw the collateral', async () => { + await systems().Core.connect(user1).withdraw(accountId, collateralAddress(), toWithdraw); + }); + }); + + describe('when the intent is expired and removed', async () => { + before(restoreToLocked); + + before('remove the expired intent', async () => { + await fastForwardTo(declareDelegateIntentTime + 130, provider()); + + await systems() + .Core.connect(user1) + .processIntentToDelegateCollateralByIntents(accountId, [intentId]); + }); + + it('allows to withdraw the collateral', async () => { + await systems().Core.connect(user1).withdraw(accountId, collateralAddress(), toWithdraw); + }); + }); + }); + + describe('undelegate what is delegated', async () => { + let intentId: BigNumber; + // let preTotalDeposited: BigNumber; + // let preTotalAssigned: BigNumber; + + before(restore); + + before('declare intent to delegate', async () => { + const collateralsPre = await systems() + .Core.connect(owner) + .getAccountCollateral(accountId, collateralAddress()); + preTotalAssigned = collateralsPre.totalAssigned; + preTotalDeposited = collateralsPre.totalDeposited; + + intentId = await declareDelegateIntent( + systems, + owner, + user1, + accountId, + poolId, + collateralAddress(), + depositAmount.mul(2), + ethers.utils.parseEther('1') + ); + }); + + const restoreToLocked = snapshotCheckpoint(provider); + + it('intended to delegate is transient locked', async () => { + const collaterals = await systems() + .Core.connect(owner) + .getAccountCollateral(accountId, collateralAddress()); + + assertBn.equal(collaterals.totalPendingToDelegate, depositAmount.mul(1)); + }); + + it('fails to undelegate what is pending in a single undelegation', async () => { + const delta = await expectedToDeltaDelegatedCollateral( + systems, + accountId, + poolId, + collateralAddress(), + depositAmount.mul(0) + ); + await assertRevert( + systems() + .Core.connect(user1) + .declareIntentToDelegateCollateral( + accountId, + poolId, + collateralAddress(), + delta, + ethers.utils.parseEther('1') + ), + 'ExceedingUndelegateAmount', + systems().Core + ); + }); + + describe('fails to undelegate what is pending on a multiple shots', async () => { + before(restoreToLocked); + + before('add a previous undelegation intent', async () => { + // set the first undelegation (allowed, since is what is already delegated) + await declareDelegateIntent( + systems, + owner, + user1, + accountId, + poolId, + collateralAddress(), + depositAmount.mul(1), + ethers.utils.parseEther('1') + ); + }); + + it('fails to set the second intent to delegate', async () => { + const delta = await expectedToDeltaDelegatedCollateral( + systems, + accountId, + poolId, + collateralAddress(), + depositAmount.mul(0) + ); + // exceeds the amount that is delegated (still pending) + await assertRevert( + systems() + .Core.connect(user1) + .declareIntentToDelegateCollateral( + accountId, + poolId, + collateralAddress(), + delta, + ethers.utils.parseEther('1') + ), + 'ExceedingUndelegateAmount', + systems().Core + ); + }); + }); + + describe('can undelegate in a single shot if previous delegation is executed', async () => { + before(restoreToLocked); + + before('process the pending intent to delegate', async () => { + await systems() + .Core.connect(user1) + .processIntentToDelegateCollateralByIntents(accountId, [intentId]); + }); + + it('allows to withdraw the collateral in a single shot', async () => {}); + }); + }); }); From 15ddd691d34158bc5e8c7d0082a6478b98e46894 Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Mon, 2 Sep 2024 11:59:10 -0300 Subject: [PATCH 06/11] fix tests --- .../modules/core/VaultModule.test.ts | 104 +++++++++--------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts b/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts index 6d5828be52..c273bbdfd1 100644 --- a/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts +++ b/protocol/synthetix/test/integration/modules/core/VaultModule.test.ts @@ -1175,20 +1175,28 @@ describe('VaultModule', function () { }); describe('undelegate what is delegated', async () => { - let intentId: BigNumber; - // let preTotalDeposited: BigNumber; - // let preTotalAssigned: BigNumber; + let intentToDelegateDelta: BigNumber; + + let prePositionCollateral: BigNumber; before(restore); before('declare intent to delegate', async () => { - const collateralsPre = await systems() - .Core.connect(owner) - .getAccountCollateral(accountId, collateralAddress()); - preTotalAssigned = collateralsPre.totalAssigned; - preTotalDeposited = collateralsPre.totalDeposited; + prePositionCollateral = await systems().Core.getPositionCollateral( + accountId, + poolId, + collateralAddress() + ); - intentId = await declareDelegateIntent( + intentToDelegateDelta = await expectedToDeltaDelegatedCollateral( + systems, + accountId, + poolId, + collateralAddress(), + depositAmount.mul(2) + ); + + await declareDelegateIntent( systems, owner, user1, @@ -1198,39 +1206,46 @@ describe('VaultModule', function () { depositAmount.mul(2), ethers.utils.parseEther('1') ); + + // At this point the account has + // 1x depositAmount already delegated + // 1x depositAmount pending to delegate }); const restoreToLocked = snapshotCheckpoint(provider); - it('intended to delegate is transient locked', async () => { - const collaterals = await systems() - .Core.connect(owner) - .getAccountCollateral(accountId, collateralAddress()); - - assertBn.equal(collaterals.totalPendingToDelegate, depositAmount.mul(1)); + it('describes checkpoint', () => { + assert.ok(true); }); - it('fails to undelegate what is pending in a single undelegation', async () => { - const delta = await expectedToDeltaDelegatedCollateral( - systems, - accountId, - poolId, - collateralAddress(), - depositAmount.mul(0) - ); - await assertRevert( - systems() - .Core.connect(user1) - .declareIntentToDelegateCollateral( - accountId, - poolId, - collateralAddress(), - delta, - ethers.utils.parseEther('1') - ), - 'ExceedingUndelegateAmount', - systems().Core - ); + describe('when an intent is pending, the collateral is transiently locked', () => { + before(restoreToLocked); + + it('intended to delegate is transient locked', async () => { + const collaterals = await systems() + .Core.connect(owner) + .getAccountCollateral(accountId, collateralAddress()); + + assertBn.equal(collaterals.totalPendingToDelegate, depositAmount.mul(1)); + }); + + it('fails to undelegate what is pending in a single undelegation', async () => { + // Tries to undelegate what was delegated + what is pending + const delta = prePositionCollateral.add(intentToDelegateDelta).mul(-1); + await assertRevert( + systems() + .Core.connect(user1) + .declareIntentToDelegateCollateral( + accountId, + poolId, + collateralAddress(), + delta, + ethers.utils.parseEther('1') + ), + 'ExceedingUndelegateAmount', + systems().Core + ); + }); }); describe('fails to undelegate what is pending on a multiple shots', async () => { @@ -1238,6 +1253,8 @@ describe('VaultModule', function () { before('add a previous undelegation intent', async () => { // set the first undelegation (allowed, since is what is already delegated) + // notice, since we are using a fixed expected value based on current delegated, + // setting the expected amount to 0 will make an undelegation of 1x depositAmount await declareDelegateIntent( systems, owner, @@ -1245,7 +1262,7 @@ describe('VaultModule', function () { accountId, poolId, collateralAddress(), - depositAmount.mul(1), + depositAmount.mul(0), ethers.utils.parseEther('1') ); }); @@ -1258,6 +1275,7 @@ describe('VaultModule', function () { collateralAddress(), depositAmount.mul(0) ); + // exceeds the amount that is delegated (still pending) await assertRevert( systems() @@ -1274,17 +1292,5 @@ describe('VaultModule', function () { ); }); }); - - describe('can undelegate in a single shot if previous delegation is executed', async () => { - before(restoreToLocked); - - before('process the pending intent to delegate', async () => { - await systems() - .Core.connect(user1) - .processIntentToDelegateCollateralByIntents(accountId, [intentId]); - }); - - it('allows to withdraw the collateral in a single shot', async () => {}); - }); }); }); From db45b32ae9564c0fa54cf9ce475bf6e354cc5f7c Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Mon, 2 Sep 2024 12:30:58 -0300 Subject: [PATCH 07/11] Fix Intents not cleared on vault liquidation --- .../modules/core/VaultIntentViewsModule.sol | 3 +- .../contracts/modules/core/VaultModule.sol | 5 ++- .../contracts/storage/DelegationIntent.sol | 31 ++++++++++++------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol b/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol index 7d8f1debf0..5c1473d79f 100644 --- a/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol +++ b/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol @@ -92,6 +92,7 @@ contract VaultIntentViewsModule is IVaultIntentViewsModule { uint128 accountId, uint256 maxProcessableIntent ) external view override returns (uint256[] memory executableIntents, uint256 foundItems) { + uint128 accountEpochId = Account.load(accountId).currentDelegationIntentsEpoch; uint256[] memory allIntents = Account .load(accountId) .getDelegationIntents() @@ -102,7 +103,7 @@ contract VaultIntentViewsModule is IVaultIntentViewsModule { : maxProcessableIntent; executableIntents = new uint256[](max); for (uint256 i = 0; i < max; i++) { - if (DelegationIntent.load(allIntents[i]).isExecutable()) { + if (DelegationIntent.load(allIntents[i]).isExecutable(accountEpochId)) { executableIntents[foundItems] = allIntents[i]; foundItems++; } diff --git a/protocol/synthetix/contracts/modules/core/VaultModule.sol b/protocol/synthetix/contracts/modules/core/VaultModule.sol index 143cd7e1c3..86d2f7fc6b 100644 --- a/protocol/synthetix/contracts/modules/core/VaultModule.sol +++ b/protocol/synthetix/contracts/modules/core/VaultModule.sol @@ -163,6 +163,7 @@ contract VaultModule is IVaultModule { intent.deltaCollateralAmountD18 = deltaCollateralAmountD18; intent.leverage = leverage; intent.declarationTime = block.timestamp.to32(); + intent.epochId = account.currentDelegationIntentsEpoch; // Add intent to the account's delegation intents. accountIntents.addIntent(intent, intentId); @@ -198,6 +199,8 @@ contract VaultModule is IVaultModule { .load(accountId) .getDelegationIntents(); + uint128 accountEpochId = Account.load(accountId).currentDelegationIntentsEpoch; + for (uint256 i = 0; i < intentIds.length; i++) { uint256 intentId = intentIds[i]; DelegationIntent.Data storage intent = DelegationIntent.load(intentId); @@ -205,7 +208,7 @@ contract VaultModule is IVaultModule { revert DelegationIntentNotInCurrentEpoch(intentId); } - if (!intent.isExecutable()) { + if (!intent.isExecutable(accountEpochId)) { // emit an Skipped event emit DelegationIntentSkipped( intentId, diff --git a/protocol/synthetix/contracts/storage/DelegationIntent.sol b/protocol/synthetix/contracts/storage/DelegationIntent.sol index 13ba078e95..a862694cc3 100644 --- a/protocol/synthetix/contracts/storage/DelegationIntent.sol +++ b/protocol/synthetix/contracts/storage/DelegationIntent.sol @@ -60,6 +60,10 @@ library DelegationIntent { * @notice The timestamp at which the intent was declared */ uint32 declarationTime; + /** + * @notice Id of the epoch where this intent was created. Used to validate if the epoch is still valid + */ + uint128 epochId; } /** @@ -103,22 +107,25 @@ library DelegationIntent { return _processingEndTime; } - function checkIsExecutable(Data storage self) internal view { - (uint32 _processingStartTime, uint32 _processingEndTime) = getProcessingWindow(self); + // function checkIsExecutable(Data storage self) internal view { + // (uint32 _processingStartTime, uint32 _processingEndTime) = getProcessingWindow(self); - if (block.timestamp < _processingStartTime) - revert IVaultModule.DelegationIntentNotReady( - self.declarationTime, - _processingStartTime - ); - if (block.timestamp >= _processingEndTime) - revert IVaultModule.DelegationIntentExpired(self.declarationTime, _processingEndTime); - } + // if (block.timestamp < _processingStartTime) + // revert IVaultModule.DelegationIntentNotReady( + // self.declarationTime, + // _processingStartTime + // ); + // if (block.timestamp >= _processingEndTime) + // revert IVaultModule.DelegationIntentExpired(self.declarationTime, _processingEndTime); + // } - function isExecutable(Data storage self) internal view returns (bool) { + function isExecutable(Data storage self, uint128 currentEpochId) internal view returns (bool) { (uint32 _processingStartTime, uint32 _processingEndTime) = getProcessingWindow(self); - return block.timestamp >= _processingStartTime && block.timestamp < _processingEndTime; + return + currentEpochId == self.epochId && + block.timestamp >= _processingStartTime && + block.timestamp < _processingEndTime; } function intentExpired(Data storage self) internal view returns (bool) { From 0732f50a626996c47e80c30556744364f86ed2b2 Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Mon, 2 Sep 2024 12:33:36 -0300 Subject: [PATCH 08/11] Pending intents should be cleared when an account is transferred --- protocol/synthetix/contracts/modules/core/AccountModule.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/protocol/synthetix/contracts/modules/core/AccountModule.sol b/protocol/synthetix/contracts/modules/core/AccountModule.sol index 0dc853603f..204d8fb672 100644 --- a/protocol/synthetix/contracts/modules/core/AccountModule.sol +++ b/protocol/synthetix/contracts/modules/core/AccountModule.sol @@ -102,6 +102,9 @@ contract AccountModule is IAccountModule { account.rbac.revokeAllPermissions(permissionedAddresses[i]); } + // clean all pending intents + account.cleanAllIntents(); + account.rbac.setOwner(to); } From b852bc67f9c6d7ff6405123a42980a3f97915ec7 Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Mon, 2 Sep 2024 12:45:06 -0300 Subject: [PATCH 09/11] unnecesary set to 0 --- .../synthetix/contracts/modules/core/VaultIntentViewsModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol b/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol index 5c1473d79f..7a425b6641 100644 --- a/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol +++ b/protocol/synthetix/contracts/modules/core/VaultIntentViewsModule.sol @@ -148,7 +148,7 @@ contract VaultIntentViewsModule is IVaultIntentViewsModule { poolId, collateralType ); - accumulatedIntentDelta = 0; + for (uint256 i = 0; i < intentIds.length; i++) { DelegationIntent.Data storage intent = DelegationIntent.load(intentIds[i]); if (!intent.intentExpired()) { From 853cfe42a91d197784137de5ac7d6b06d2e3ebfa Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Mon, 2 Sep 2024 12:54:41 -0300 Subject: [PATCH 10/11] comments cleanup --- .../contracts/storage/AccountDelegationIntents.sol | 5 ++--- .../synthetix/contracts/storage/DelegationIntent.sol | 12 ------------ 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol b/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol index 4b85c78aa8..b2c749571a 100644 --- a/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol +++ b/protocol/synthetix/contracts/storage/AccountDelegationIntents.sol @@ -8,7 +8,7 @@ import "../interfaces/IVaultModule.sol"; import "./Account.sol"; /** - * @title Represents a delegation (or undelegation) intent. + * @title Contains the delegation intents for an account on an specific epoch. */ library AccountDelegationIntents { using SafeCastI256 for int256; @@ -107,8 +107,7 @@ library AccountDelegationIntents { } function isInCurrentEpoch(Data storage self, uint256 intentId) internal view returns (bool) { - // Notice: not checking that `self.delegationIntentsEpoch == account.currentDelegationIntentsEpoch` since - // it was loadValid and getValid use it at load time + // verifies the intent is in the current epoch return self.intentsId.contains(intentId); } diff --git a/protocol/synthetix/contracts/storage/DelegationIntent.sol b/protocol/synthetix/contracts/storage/DelegationIntent.sol index a862694cc3..8c136be4bd 100644 --- a/protocol/synthetix/contracts/storage/DelegationIntent.sol +++ b/protocol/synthetix/contracts/storage/DelegationIntent.sol @@ -107,18 +107,6 @@ library DelegationIntent { return _processingEndTime; } - // function checkIsExecutable(Data storage self) internal view { - // (uint32 _processingStartTime, uint32 _processingEndTime) = getProcessingWindow(self); - - // if (block.timestamp < _processingStartTime) - // revert IVaultModule.DelegationIntentNotReady( - // self.declarationTime, - // _processingStartTime - // ); - // if (block.timestamp >= _processingEndTime) - // revert IVaultModule.DelegationIntentExpired(self.declarationTime, _processingEndTime); - // } - function isExecutable(Data storage self, uint128 currentEpochId) internal view returns (bool) { (uint32 _processingStartTime, uint32 _processingEndTime) = getProcessingWindow(self); From ee1cc0d6ecea2ae35174783aae78dce8f467476b Mon Sep 17 00:00:00 2001 From: Leonardo Massazza Date: Mon, 2 Sep 2024 13:08:26 -0300 Subject: [PATCH 11/11] update storage.dump --- markets/bfp-market/storage.dump.sol | 1 + markets/perps-market/storage.dump.sol | 1 + protocol/synthetix/storage.dump.sol | 1 + 3 files changed, 3 insertions(+) diff --git a/markets/bfp-market/storage.dump.sol b/markets/bfp-market/storage.dump.sol index b15f9fd395..bb29f5a0a8 100644 --- a/markets/bfp-market/storage.dump.sol +++ b/markets/bfp-market/storage.dump.sol @@ -221,6 +221,7 @@ library DelegationIntent { int256 deltaCollateralAmountD18; uint256 leverage; uint32 declarationTime; + uint128 epochId; } function load(uint256 id) internal pure returns (Data storage delegationIntent) { bytes32 s = keccak256(abi.encode("io.synthetix.synthetix.DelegationIntent", id)); diff --git a/markets/perps-market/storage.dump.sol b/markets/perps-market/storage.dump.sol index ae67d1cc0d..d2ca37b9d5 100644 --- a/markets/perps-market/storage.dump.sol +++ b/markets/perps-market/storage.dump.sol @@ -220,6 +220,7 @@ library DelegationIntent { int256 deltaCollateralAmountD18; uint256 leverage; uint32 declarationTime; + uint128 epochId; } function load(uint256 id) internal pure returns (Data storage delegationIntent) { bytes32 s = keccak256(abi.encode("io.synthetix.synthetix.DelegationIntent", id)); diff --git a/protocol/synthetix/storage.dump.sol b/protocol/synthetix/storage.dump.sol index eeb5fe77ba..93064f6f0a 100644 --- a/protocol/synthetix/storage.dump.sol +++ b/protocol/synthetix/storage.dump.sol @@ -512,6 +512,7 @@ library DelegationIntent { int256 deltaCollateralAmountD18; uint256 leverage; uint32 declarationTime; + uint128 epochId; } function load(uint256 id) internal pure returns (Data storage delegationIntent) { bytes32 s = keccak256(abi.encode("io.synthetix.synthetix.DelegationIntent", id));