diff --git a/README.md b/README.md index e1b8a1b9..e8f36a59 100644 --- a/README.md +++ b/README.md @@ -138,13 +138,31 @@ These functions verify balances and authorize the caller to retrieve their accum ### How to Deploy -**1)** Run the deployment script defined in `scripts/`: +**1)** Run the deployment script `DeployProxy.s.sol` defined in `scripts/`: __`❍ npm run deploy:holesky`__: verification is done automatically. __`❍ npm run deploy:hoodi`__: verification needs to be done manually for now. -### Public Testnet +### How to Update Module Contracts + +It is possible to update each one of the three modules: `StrategyManager`, `BasedAppsManager` and `ProtocolManager`. + +It is possible to update multiple modules at the same time. + +**1)** Go on the Proxy Contract on Etherscan, under "Write as Proxy" call the function: + +__`❍ updateModules`__: specifying the correct module id and the new module address. + +### How to Upgrade the Implementation Contract + +**1)** Go on the Proxy Contract on Etherscan, under "Write as Proxy" call the function: + +__`❍ upgradeToAndCall`__: specifying the new implementation address. The data field can be left empty in this case. + +There is also the `UpgradeProxy.s.sol` script file that can be run easily from the CLI. + +### Public Testnet Holesky | Name | Proxy | Implementation | Notes | | -------- | -------- | -------- | -------- | diff --git a/package.json b/package.json index 480a1c62..3cccce85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "based-applications", - "version": "0.0.0", + "version": "0.0.1", "description": "SSV Based Applications", "author": "SSV.Network", "repository": { diff --git a/scripts/DeployProxy.s.sol b/scripts/DeployProxy.s.sol index 8ef0810d..ad0e6bf8 100644 --- a/scripts/DeployProxy.s.sol +++ b/scripts/DeployProxy.s.sol @@ -32,6 +32,7 @@ contract DeployProxy is Script { withdrawalExpireTime: 3 days, obligationTimelockPeriod: 14 days, obligationExpireTime: 3 days, + tokenUpdateTimelockPeriod: 14 days, maxFeeIncrement: 500 }); @@ -76,6 +77,10 @@ contract DeployProxy is Script { config.obligationTimelockPeriod ); console.log("Obligation Expire Time:", config.obligationExpireTime); + console.log( + "Token Update Timelock Period:", + config.tokenUpdateTimelockPeriod + ); console.log("Max Shares:", config.maxShares); console.log("Max Fee Increment:", config.maxFeeIncrement); vm.stopBroadcast(); diff --git a/src/core/SSVBasedApps.sol b/src/core/SSVBasedApps.sol index cc2001fd..34b0dd35 100644 --- a/src/core/SSVBasedApps.sol +++ b/src/core/SSVBasedApps.sol @@ -72,7 +72,7 @@ contract SSVBasedApps is IBasedAppManager ssvBasedAppManger_, IStrategyManager ssvStrategyManager_, IProtocolManager protocolManager_, - ProtocolStorageLib.Data memory config + ProtocolStorageLib.Data calldata config ) external override initializer onlyProxy { __UUPSUpgradeable_init(); __Ownable_init_unchained(owner_); @@ -89,7 +89,7 @@ contract SSVBasedApps is IBasedAppManager ssvBasedAppManger_, IStrategyManager ssvStrategyManager_, IProtocolManager protocolManager_, - ProtocolStorageLib.Data memory config + ProtocolStorageLib.Data calldata config ) internal onlyInitializing { CoreStorageLib.Data storage s = CoreStorageLib.load(); ProtocolStorageLib.Data storage sp = ProtocolStorageLib.load(); @@ -114,6 +114,7 @@ contract SSVBasedApps is sp.withdrawalExpireTime = config.withdrawalExpireTime; sp.obligationTimelockPeriod = config.obligationTimelockPeriod; sp.obligationExpireTime = config.obligationExpireTime; + sp.tokenUpdateTimelockPeriod = config.tokenUpdateTimelockPeriod; sp.maxShares = config.maxShares; emit MaxFeeIncrementSet(sp.maxFeeIncrement); @@ -147,6 +148,12 @@ contract SSVBasedApps is _delegateTo(SSVCoreModules.SSV_BAPPS_MANAGER); } + function updateBAppsTokens( + ICore.TokenConfig[] calldata tokenConfigs + ) external { + _delegateTo(SSVCoreModules.SSV_BAPPS_MANAGER); + } + function createObligation( uint32 strategyId, address bApp, @@ -271,7 +278,7 @@ contract SSVBasedApps is uint32 strategyId, address bApp, address token, - uint256 amount, + uint32 percentage, bytes calldata data ) external { _delegateTo(SSVCoreModules.SSV_STRATEGY_MANAGER); @@ -323,6 +330,10 @@ contract SSVBasedApps is _delegateTo(SSVCoreModules.SSV_PROTOCOL_MANAGER); } + function updateTokenUpdateTimelockPeriod(uint32 value) external onlyOwner { + _delegateTo(SSVCoreModules.SSV_PROTOCOL_MANAGER); + } + function updateMaxShares(uint256 value) external onlyOwner { _delegateTo(SSVCoreModules.SSV_PROTOCOL_MANAGER); } @@ -364,6 +375,13 @@ contract SSVBasedApps is return (s.strategies[strategyId].owner, s.strategies[strategyId].fee); } + function ownedStrategies( + address owner + ) external view returns (uint32[] memory strategyIds) { + CoreStorageLib.Data storage s = CoreStorageLib.load(); + return s.strategyOwners[owner]; + } + function strategyAccountShares( uint32 strategyId, address account, @@ -420,22 +438,25 @@ contract SSVBasedApps is ); } - function usedTokens( - uint32 strategyId, - address token - ) external view returns (uint32) { - CoreStorageLib.Data storage s = CoreStorageLib.load(); - return s.usedTokens[strategyId][token]; - } - function bAppTokens( address bApp, address token - ) external view returns (uint32 value, bool isSet) { + ) + external + view + returns ( + uint32 currentValue, + bool isSet, + uint32 pendingValue, + uint32 effectTime + ) + { CoreStorageLib.Data storage s = CoreStorageLib.load(); return ( - s.bAppTokens[bApp][token].value, - s.bAppTokens[bApp][token].isSet + s.bAppTokens[bApp][token].currentValue, + s.bAppTokens[bApp][token].isSet, + s.bAppTokens[bApp][token].pendingValue, + s.bAppTokens[bApp][token].effectTime ); } @@ -533,6 +554,10 @@ contract SSVBasedApps is return ProtocolStorageLib.load().obligationExpireTime; } + function tokenUpdateTimelockPeriod() external view returns (uint32) { + return ProtocolStorageLib.load().tokenUpdateTimelockPeriod; + } + function getVersion() external pure returns (string memory) { return "0.0.1"; } diff --git a/src/core/interfaces/IBasedAppManager.sol b/src/core/interfaces/IBasedAppManager.sol index 6c30eccc..57e94954 100644 --- a/src/core/interfaces/IBasedAppManager.sol +++ b/src/core/interfaces/IBasedAppManager.sol @@ -1,17 +1,20 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.29; +import { ICore } from "@ssv/src/core/interfaces/ICore.sol"; + interface IBasedAppManager { - event BAppMetadataURIUpdated( - address indexed bAppAddress, - string metadataURI - ); + event BAppMetadataURIUpdated(address indexed bApp, string metadataURI); event BAppRegistered( - address indexed bAppAddress, + address indexed bApp, address[] tokens, uint32[] sharedRiskLevel, string metadataURI ); + event BAppTokensUpdated( + address indexed bApp, + ICore.TokenConfig[] tokenConfigs + ); function registerBApp( address[] calldata tokens, @@ -19,6 +22,9 @@ interface IBasedAppManager { string calldata metadataURI ) external; function updateBAppMetadataURI(string calldata metadataURI) external; + function updateBAppsTokens( + ICore.TokenConfig[] calldata tokenConfigs + ) external; error BAppAlreadyRegistered(); error BAppDoesNotSupportInterface(); diff --git a/src/core/interfaces/ICore.sol b/src/core/interfaces/ICore.sol index 86f9df0c..645306b9 100644 --- a/src/core/interfaces/ICore.sol +++ b/src/core/interfaces/ICore.sol @@ -7,9 +7,13 @@ interface ICore { /// @dev The shared risk level /// Encoding: The value is stored as a uint32. However, it represents a real (float) value. /// To get the actual real value (decode), divide by 10^6. - uint32 value; + uint32 currentValue; /// @dev if the shared risk level is set bool isSet; + /// @dev The value to be updated + uint32 pendingValue; + /// @dev The block time when the update request was sent + uint32 effectTime; } /// @notice Represents an Obligation @@ -67,4 +71,14 @@ interface ICore { /// @dev The account latest generation mapping(address => uint256) accountGeneration; } + + struct TokenUpdateRequest { + TokenConfig[] tokens; + uint32 requestTime; + } + + struct TokenConfig { + address token; + uint32 sharedRiskLevel; + } } diff --git a/src/core/interfaces/IProtocolManager.sol b/src/core/interfaces/IProtocolManager.sol index 7d22b049..fae64d56 100644 --- a/src/core/interfaces/IProtocolManager.sol +++ b/src/core/interfaces/IProtocolManager.sol @@ -6,6 +6,7 @@ interface IProtocolManager { event FeeTimelockPeriodUpdated(uint32 feeTimelockPeriod); event ObligationExpireTimeUpdated(uint32 obligationExpireTime); event ObligationTimelockPeriodUpdated(uint32 obligationTimelockPeriod); + event TokenUpdateTimelockPeriodUpdated(uint32 tokenUpdateTimelockPeriod); event StrategyMaxFeeIncrementUpdated(uint32 maxFeeIncrement); event StrategyMaxSharesUpdated(uint256 maxShares); event WithdrawalExpireTimeUpdated(uint32 withdrawalExpireTime); @@ -17,6 +18,7 @@ interface IProtocolManager { function updateMaxShares(uint256 value) external; function updateObligationExpireTime(uint32 value) external; function updateObligationTimelockPeriod(uint32 value) external; + function updateTokenUpdateTimelockPeriod(uint32 value) external; function updateWithdrawalExpireTime(uint32 value) external; function updateWithdrawalTimelockPeriod(uint32 value) external; } diff --git a/src/core/interfaces/IStrategyManager.sol b/src/core/interfaces/IStrategyManager.sol index fe4d770c..dfe61993 100644 --- a/src/core/interfaces/IStrategyManager.sol +++ b/src/core/interfaces/IStrategyManager.sol @@ -93,7 +93,7 @@ interface IStrategyManager { uint32 indexed strategyId, address indexed bApp, address token, - uint256 amount, + uint32 percentage, address receiver ); @@ -148,7 +148,7 @@ interface IStrategyManager { uint32 strategyId, address bApp, address token, - uint256 amount, + uint32 percentage, bytes calldata data ) external; function updateAccountMetadataURI(string calldata metadataURI) external; diff --git a/src/core/libraries/CoreStorageLib.sol b/src/core/libraries/CoreStorageLib.sol index 41d09964..c130ce43 100644 --- a/src/core/libraries/CoreStorageLib.sol +++ b/src/core/libraries/CoreStorageLib.sol @@ -21,6 +21,11 @@ library CoreStorageLib { * @dev The strategy ID is incremental and unique */ mapping(uint32 strategyId => ICore.Strategy) strategies; + /** + * @notice Tracks the owners of the strategies + * @dev The strategy ID is incremental and unique + */ + mapping(address owner => uint32[] strategyId) strategyOwners; /** * @notice Links an account to a single strategy for a specific bApp * @dev Guarantees that an account cannot have more than one strategy for a given bApp @@ -47,12 +52,6 @@ library CoreStorageLib { * @dev Uses a hash of the bApp and token to map the obligation percentage for the strategy. */ mapping(uint32 strategyId => mapping(address bApp => mapping(address token => ICore.Obligation))) obligations; - /** - * @notice Tracks unallocated tokens in a strategy. - * @dev Count the number of bApps that have one obligation set for the token. - * If the counter is 0, the token is unused and we can allow fast withdrawal. - */ - mapping(uint32 strategyId => mapping(address token => uint32 bAppsCounter)) usedTokens; /** * @notice Tracks all the withdrawal requests divided by token per strategy. * @dev User can have only one pending withdrawal request per token. @@ -87,6 +86,12 @@ library CoreStorageLib { * @dev The bApp is identified with its address */ mapping(address bApp => mapping(address token => ICore.SharedRiskLevel)) bAppTokens; + /** + * @notice Tracks the token update requests for a bApp + * @dev Only the bApp owner can submit one. + * Submitting a new request will overwrite the previous one and reset the timer. + */ + mapping(address bApp => ICore.TokenUpdateRequest) tokenUpdateRequests; } uint256 private constant SSV_BASED_APPS_STORAGE_POSITION = diff --git a/src/core/libraries/ProtocolStorageLib.sol b/src/core/libraries/ProtocolStorageLib.sol index 8fba0bd6..3ef850d1 100644 --- a/src/core/libraries/ProtocolStorageLib.sol +++ b/src/core/libraries/ProtocolStorageLib.sol @@ -11,6 +11,7 @@ library ProtocolStorageLib { uint32 withdrawalExpireTime; uint32 obligationTimelockPeriod; uint32 obligationExpireTime; + uint32 tokenUpdateTimelockPeriod; uint32 maxFeeIncrement; uint256 maxShares; } diff --git a/src/core/modules/BasedAppsManager.sol b/src/core/modules/BasedAppsManager.sol index 2a9e17c5..b0ad9980 100644 --- a/src/core/modules/BasedAppsManager.sol +++ b/src/core/modules/BasedAppsManager.sol @@ -4,16 +4,15 @@ pragma solidity 0.8.29; import { ICore } from "@ssv/src/core/interfaces/ICore.sol"; import { IBasedAppManager } from "@ssv/src/core/interfaces/IBasedAppManager.sol"; import { CoreStorageLib } from "@ssv/src/core/libraries/CoreStorageLib.sol"; +import { ProtocolStorageLib } from "@ssv/src/core/libraries/ProtocolStorageLib.sol"; import { ValidationLib } from "@ssv/src/core/libraries/ValidationLib.sol"; contract BasedAppsManager is IBasedAppManager { /// @notice Allow the function to be called only by a registered bApp - modifier onlyRegisteredBApp() { - CoreStorageLib.Data storage s = CoreStorageLib.load(); + function _onlyRegisteredBApp(CoreStorageLib.Data storage s) private view { if (!s.registeredBApps[msg.sender]) { revert IBasedAppManager.BAppNotRegistered(); } - _; } /// @notice Registers a bApp. @@ -42,12 +41,42 @@ contract BasedAppsManager is IBasedAppManager { /// @notice Function to update the metadata URI of the Based Application /// @param metadataURI The new metadata URI - function updateBAppMetadataURI( - string calldata metadataURI - ) external onlyRegisteredBApp { + function updateBAppMetadataURI(string calldata metadataURI) external { + _onlyRegisteredBApp(CoreStorageLib.load()); emit BAppMetadataURIUpdated(msg.sender, metadataURI); } + function updateBAppsTokens( + ICore.TokenConfig[] memory tokenConfigs + ) external { + CoreStorageLib.Data storage s = CoreStorageLib.load(); + + _onlyRegisteredBApp(s); + + uint32 requestTime = uint32(block.timestamp); + + address token; + ICore.SharedRiskLevel storage tokenData; + ProtocolStorageLib.Data storage sp = ProtocolStorageLib.load(); + + for (uint256 i = 0; i < tokenConfigs.length; ) { + token = tokenConfigs[i].token; + tokenData = s.bAppTokens[msg.sender][token]; + // Update current value if the previous effect time has passed + if (requestTime > tokenData.effectTime) { + tokenData.currentValue = tokenData.pendingValue; + } + tokenData.pendingValue = tokenConfigs[i].sharedRiskLevel; + tokenData.effectTime = requestTime + sp.tokenUpdateTimelockPeriod; + tokenData.isSet = true; + unchecked { + i++; + } + } + + emit BAppTokensUpdated(msg.sender, tokenConfigs); + } + /// @notice Function to add tokens to a bApp /// @param bApp The address of the bApp /// @param tokens The list of tokens to add @@ -67,26 +96,12 @@ contract BasedAppsManager is IBasedAppManager { if (s.bAppTokens[bApp][token].isSet) { revert IBasedAppManager.TokenAlreadyAddedToBApp(token); } - _setTokenRiskLevel(bApp, token, sharedRiskLevels[i]); + ICore.SharedRiskLevel storage tokenData = s.bAppTokens[bApp][token]; + tokenData.currentValue = sharedRiskLevels[i]; + tokenData.isSet = true; unchecked { i++; } } } - - /// @notice Internal function to set the shared risk level for a token - /// @param bApp The address of the bApp - /// @param token The address of the token - /// @param sharedRiskLevel The shared risk level - function _setTokenRiskLevel( - address bApp, - address token, - uint32 sharedRiskLevel - ) internal { - CoreStorageLib.Data storage s = CoreStorageLib.load(); - ICore.SharedRiskLevel storage tokenData = s.bAppTokens[bApp][token]; - - tokenData.value = sharedRiskLevel; - tokenData.isSet = true; - } } diff --git a/src/core/modules/ProtocolManager.sol b/src/core/modules/ProtocolManager.sol index db1a8146..3c93f001 100644 --- a/src/core/modules/ProtocolManager.sol +++ b/src/core/modules/ProtocolManager.sol @@ -43,6 +43,15 @@ contract ProtocolManager is IProtocolManager { emit ObligationExpireTimeUpdated(obligationExpireTime); } + function updateTokenUpdateTimelockPeriod( + uint32 tokenUpdateTimelockPeriod + ) external { + ProtocolStorageLib + .load() + .tokenUpdateTimelockPeriod = tokenUpdateTimelockPeriod; + emit TokenUpdateTimelockPeriodUpdated(tokenUpdateTimelockPeriod); + } + function updateMaxShares(uint256 maxShares) external { ProtocolStorageLib.load().maxShares = maxShares; emit StrategyMaxSharesUpdated(maxShares); diff --git a/src/core/modules/StrategyManager.sol b/src/core/modules/StrategyManager.sol index a5685030..1e0a01c6 100644 --- a/src/core/modules/StrategyManager.sol +++ b/src/core/modules/StrategyManager.sol @@ -144,6 +144,8 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { newStrategy.owner = msg.sender; newStrategy.fee = fee; + s.strategyOwners[msg.sender].push(strategyId); + emit StrategyCreated(strategyId, msg.sender, fee, metadataURI); } @@ -387,14 +389,7 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { sp.obligationExpireTime ); - if ( - percentage == 0 && - s.obligations[strategyId][bApp][address(token)].percentage > 0 - ) { - s.usedTokens[strategyId][address(token)] -= 1; - } - - _updateObligation(strategyId, bApp, address(token), percentage); + s.obligations[strategyId][bApp][address(token)].percentage = percentage; emit ObligationUpdated(strategyId, bApp, address(token), percentage); @@ -526,7 +521,6 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { } if (obligationPercentage != 0) { - s.usedTokens[strategyId][token] += 1; s .obligations[strategyId][bApp][token] .percentage = obligationPercentage; @@ -566,28 +560,6 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { } } - /// @notice Update a single obligation for a bApp - /// @param strategyId The ID of the strategy - /// @param bApp The address of the bApp - /// @param token The address of the token - function _updateObligation( - uint32 strategyId, - address bApp, - address token, - uint32 obligationPercentage - ) private { - CoreStorageLib.Data storage s = CoreStorageLib.load(); - - if ( - s.obligations[strategyId][bApp][token].percentage == 0 && - obligationPercentage > 0 - ) { - s.usedTokens[strategyId][token] += 1; - } - s - .obligations[strategyId][bApp][token].percentage = obligationPercentage; - } - /// @notice Check the timelocks /// @param requestTime The time of the request /// @param timelockPeriod The timelock period @@ -761,16 +733,17 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { /// @param strategyId The ID of the strategy /// @param bApp The address of the bApp /// @param token The address of the token - /// @param amount The amount to slash + /// @param percentage The amount to slash /// @param data Optional parameter that could be required by the service function slash( uint32 strategyId, address bApp, address token, - uint256 amount, + uint32 percentage, bytes calldata data ) external nonReentrant { - if (amount == 0) revert InvalidAmount(); + ValidationLib.validatePercentageAndNonZero(percentage); + CoreStorageLib.Data storage s = CoreStorageLib.load(); if (!s.registeredBApps[bApp]) { @@ -778,7 +751,10 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { } uint256 slashableBalance = getSlashableBalance(strategyId, bApp, token); - if (slashableBalance < amount) revert InsufficientBalance(); + if (slashableBalance == 0) revert InsufficientBalance(); + uint256 amount = (slashableBalance * percentage) / MAX_PERCENTAGE; + + // if (slashableBalance < amount) revert InsufficientBalance(); address receiver; bool exit; @@ -790,7 +766,8 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { (success, receiver, exit) = IBasedApp(bApp).slash( strategyId, token, - amount, + percentage, + msg.sender, data ); if (!success) revert IStrategyManager.BAppSlashingFailed(); @@ -817,7 +794,7 @@ contract StrategyManager is ReentrancyGuardTransient, IStrategyManager { strategyId, bApp, token, - amount, + percentage, receiver ); } diff --git a/src/middleware/interfaces/IBasedApp.sol b/src/middleware/interfaces/IBasedApp.sol index ddbe2863..787e89b5 100644 --- a/src/middleware/interfaces/IBasedApp.sol +++ b/src/middleware/interfaces/IBasedApp.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.29; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { ICore } from "@ssv/src/core/interfaces/ICore.sol"; interface IBasedApp is IERC165 { function optInToBApp( @@ -18,11 +19,15 @@ interface IBasedApp is IERC165 { function slash( uint32 strategyId, address token, - uint256 amount, + uint32 percentage, + address sender, bytes calldata data ) external returns (bool success, address receiver, bool exit); function supportsInterface(bytes4 interfaceId) external view returns (bool); function updateBAppMetadataURI(string calldata metadataURI) external; + function updateBAppTokens( + ICore.TokenConfig[] calldata tokenConfigs + ) external; error UnauthorizedCaller(); } diff --git a/src/middleware/modules/core/BasedAppCore.sol b/src/middleware/modules/core/BasedAppCore.sol index 21de53cd..abcab974 100644 --- a/src/middleware/modules/core/BasedAppCore.sol +++ b/src/middleware/modules/core/BasedAppCore.sol @@ -9,6 +9,8 @@ import { IBasedAppManager } from "@ssv/src/core/interfaces/IBasedAppManager.sol" import { IStrategyManager } from "@ssv/src/core/interfaces/IStrategyManager.sol"; +import { ICore } from "@ssv/src/core/interfaces/ICore.sol"; + // ===================================================================================== // ⚠️ WARNING: IMPLEMENT OWNER OR ACCESS ROLES ⚠️ // ------------------------------------------------------------------------------------- @@ -69,6 +71,16 @@ abstract contract BasedAppCore is IBasedApp { ); } + /// @notice Updates the tokens of a BApp + /// @param tokenConfigs new list of tokens and their shared risk levels + function updateBAppTokens( + ICore.TokenConfig[] calldata tokenConfigs + ) external virtual { + IBasedAppManager(SSV_BASED_APPS_NETWORK).updateBAppsTokens( + tokenConfigs + ); + } + function withdrawSlashingFund( address token, uint256 amount @@ -105,10 +117,18 @@ abstract contract BasedAppCore is IBasedApp { /*strategyId*/ address, /*token*/ - uint256, - /*amount*/ + uint32, + /*percentage*/ + address, + /*sender*/ bytes calldata - ) external virtual onlySSVBasedAppManager returns (bool, address, bool) { + ) + external + virtual + /*data*/ + onlySSVBasedAppManager + returns (bool, address, bool) + { ///@dev --- CORE LOGIC (TO BE IMPLEMENTED) --- ///@dev --- RETURN TRUE IF SUCCESS, FALSE OTHERWISE --- ///@dev --- RETURN RECEIVER ADDRESS FOR THE SLASHED FUNDS --- diff --git a/test/SSVBasedApps.t.sol b/test/SSVBasedApps.t.sol index 31439204..083aec83 100644 --- a/test/SSVBasedApps.t.sol +++ b/test/SSVBasedApps.t.sol @@ -163,6 +163,7 @@ contract SSVBasedAppsTest is Setup, Ownable2StepUpgradeable { withdrawalExpireTime: 3 days, obligationTimelockPeriod: 14 days, obligationExpireTime: 3 days, + tokenUpdateTimelockPeriod: 14 days, maxShares: 1e50 }); vm.expectRevert( @@ -192,6 +193,7 @@ contract SSVBasedAppsTest is Setup, Ownable2StepUpgradeable { withdrawalExpireTime: 3 days, obligationTimelockPeriod: 14 days, obligationExpireTime: 3 days, + tokenUpdateTimelockPeriod: 14 days, maxFeeIncrement: 10_001 }); vm.expectRevert( diff --git a/test/helpers/Setup.t.sol b/test/helpers/Setup.t.sol index 1ee4e99b..83ee86aa 100644 --- a/test/helpers/Setup.t.sol +++ b/test/helpers/Setup.t.sol @@ -109,6 +109,7 @@ contract Setup is Test { withdrawalExpireTime: 3 days, obligationTimelockPeriod: 14 days, obligationExpireTime: 3 days, + tokenUpdateTimelockPeriod: 14 days, maxShares: 1e50 }); diff --git a/test/helpers/Utils.t.sol b/test/helpers/Utils.t.sol index dc1f11a1..98cab438 100644 --- a/test/helpers/Utils.t.sol +++ b/test/helpers/Utils.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.29; import { SSVBasedApps } from "@ssv/src/core/SSVBasedApps.sol"; import { Setup } from "@ssv/test/helpers/Setup.t.sol"; +import { ICore } from "@ssv/src/core/interfaces/ICore.sol"; contract UtilsTest is Setup { function createSingleTokenAndSingleRiskLevel( @@ -53,10 +54,8 @@ contract UtilsTest is Setup { bool isRegistered = proxiedManager.registeredBApps(bApp); assertEq(isRegistered, true, "BApp registered"); for (uint32 i = 0; i < tokensInput.length; i++) { - (uint32 sharedRiskLevel, bool isSet) = proxiedManager.bAppTokens( - bApp, - tokensInput[i] - ); + (uint32 sharedRiskLevel, bool isSet, , ) = proxiedManager + .bAppTokens(bApp, tokensInput[i]); assertEq( riskLevelInput[i], sharedRiskLevel, @@ -73,7 +72,6 @@ contract UtilsTest is Setup { address token, uint32 percentage, SSVBasedApps proxiedManager, - uint32 expectedTokens, bool expectedIsSet ) internal view { uint32 id = proxiedManager.accountBAppStrategy(owner, bApp); @@ -85,8 +83,6 @@ contract UtilsTest is Setup { ); assertEq(isSet, expectedIsSet, "Obligation is set"); assertEq(obligationPercentage, percentage, "Obligation percentage"); - uint256 usedTokens = proxiedManager.usedTokens(strategyId, token); - assertEq(usedTokens, expectedTokens, "Used tokens"); (address strategyOwner, ) = proxiedManager.strategies(strategyId); if (strategyOwner != address(0)) { assertEq(owner, strategyOwner, "Strategy owner"); @@ -98,7 +94,6 @@ contract UtilsTest is Setup { address bApp, address token, uint32 expectedPercentage, - uint32 expectedUsedTokens, bool expectedIsSet, SSVBasedApps proxiedManager ) internal view { @@ -109,8 +104,6 @@ contract UtilsTest is Setup { ); assertEq(percentage, expectedPercentage, "Obligation percentage"); assertEq(isSet, expectedIsSet, "Obligation is set"); - uint32 usedTokens = proxiedManager.usedTokens(strategyId, token); - assertEq(usedTokens, expectedUsedTokens, "Used tokens"); } function checkSlashableBalance( @@ -302,19 +295,6 @@ contract UtilsTest is Setup { ); } - function checkUsedTokens( - uint32 strategyId, - address token, - uint32 expectedUsedTokens - ) internal view { - uint32 usedTokens = proxiedManager.usedTokens(strategyId, token); - assertEq( - usedTokens, - expectedUsedTokens, - "Should have set the correct used tokens" - ); - } - function checkAdjustedPercentage( address token, uint256 previousBalance, @@ -340,4 +320,38 @@ contract UtilsTest is Setup { ); return adjustedPercentage; } + + function checkBAppUpdatedTokens( + ICore.TokenConfig[] memory tokenConfigs, + address bApp + ) internal view { + bool isRegistered = proxiedManager.registeredBApps(bApp); + assertEq(isRegistered, true, "BApp registered"); + for (uint32 i = 0; i < tokenConfigs.length; i++) { + ( + , + bool isSet, + uint32 pendingValue, + uint32 effectiveTime + ) = proxiedManager.bAppTokens(bApp, tokenConfigs[i].token); + assertEq( + tokenConfigs[i].sharedRiskLevel, + pendingValue, + "BApp risk level percentage" + ); + assertNotEq(effectiveTime, 0); + assertEq(isSet, true, "BApp token set"); + } + } + + function calculateSlashAmount( + uint256 depositAmount, + uint32 obligationPercentage, + uint32 slashingPercentage + ) internal view returns (uint256) { + uint256 obligatedAmount = (depositAmount * obligationPercentage) / + proxiedManager.maxPercentage(); + return ((obligatedAmount * slashingPercentage) / + proxiedManager.maxPercentage()); + } } diff --git a/test/mocks/MockBApp2.sol b/test/mocks/MockBApp2.sol index c65ba6c6..847efbb7 100644 --- a/test/mocks/MockBApp2.sol +++ b/test/mocks/MockBApp2.sol @@ -18,12 +18,15 @@ contract BasedAppMock2 is BasedAppCore { } function slash( - uint32, /*strategyId*/ - address, + uint32, /*token*/ - uint256, - /*amount*/ + address, + /*percentage*/ + uint32, + /*sender*/ + address, + /*data*/ bytes calldata ) external diff --git a/test/mocks/MockBAppAccessControl.sol b/test/mocks/MockBAppAccessControl.sol index a5248181..5b81531d 100644 --- a/test/mocks/MockBAppAccessControl.sol +++ b/test/mocks/MockBAppAccessControl.sol @@ -25,13 +25,16 @@ contract BasedAppMock3 is AccessControlBasedApp { /*strategyId*/ address, /*token*/ - uint256, - /*amount*/ + uint32, + /*percentage*/ + address, + /*sender*/ bytes calldata ) external view override + /*data*/ onlySSVBasedAppManager returns (bool, address, bool) { diff --git a/test/mocks/MockNonCompliantBApp.sol b/test/mocks/MockNonCompliantBApp.sol index 9a76355c..aa9bc64d 100644 --- a/test/mocks/MockNonCompliantBApp.sol +++ b/test/mocks/MockNonCompliantBApp.sol @@ -11,7 +11,7 @@ interface ICustomBasedAppManager { uint32 strategyId, address bApp, address token, - uint256 amount, + uint32 percentage, bytes calldata data ) external; } @@ -44,12 +44,16 @@ contract NonCompliantBApp { ); } - function slash(uint32 strategyId, address token, uint256 amount) external { + function slash( + uint32 strategyId, + address token, + uint32 percentage + ) external { ICustomBasedAppManager(BASED_APP_MANAGER).slash( strategyId, address(this), token, - amount, + percentage, "" ); } diff --git a/test/modules/BasedAppsManager.t.sol b/test/modules/BasedAppsManager.t.sol index 8059d2f6..741b1b55 100644 --- a/test/modules/BasedAppsManager.t.sol +++ b/test/modules/BasedAppsManager.t.sol @@ -10,6 +10,7 @@ import { UtilsTest } from "@ssv/test/helpers/Utils.t.sol"; import { IBasedAppManager, IBasedApp } from "@ssv/test/helpers/Setup.t.sol"; import { IBasedAppManager } from "@ssv/src/core/interfaces/IBasedAppManager.sol"; import { ValidationLib } from "@ssv/src/core/libraries/ValidationLib.sol"; +import { ICore } from "@ssv/src/core/interfaces/ICore.sol"; contract BasedAppsManagerTest is UtilsTest { string public metadataURI = "http://metadata.com"; @@ -457,4 +458,31 @@ contract BasedAppsManagerTest is UtilsTest { } vm.stopPrank(); } + + function testUpdateBAppTokens() public { + testRegisterBApp(); + ( + address[] memory tokensInput, + uint32[] memory sharedRiskLevelInput + ) = createTwoTokenAndRiskInputs(); + ICore.TokenConfig[] memory tokenConfigs = new ICore.TokenConfig[]( + tokensInput.length + ); + for (uint256 i = 0; i < tokensInput.length; i++) { + tokenConfigs[i] = ICore.TokenConfig({ + token: tokensInput[i], + sharedRiskLevel: sharedRiskLevelInput[i] + 1000 + }); + } + for (uint256 i = 0; i < bApps.length; i++) { + vm.prank(USER1); + vm.expectEmit(true, true, false, false); + emit IBasedAppManager.BAppTokensUpdated( + address(bApps[i]), + tokenConfigs + ); + bApps[i].updateBAppTokens(tokenConfigs); + checkBAppUpdatedTokens(tokenConfigs, address(bApps[i])); + } + } } diff --git a/test/modules/ProtocolManager.t.sol b/test/modules/ProtocolManager.t.sol index d9d8ff97..134d8758 100644 --- a/test/modules/ProtocolManager.t.sol +++ b/test/modules/ProtocolManager.t.sol @@ -67,6 +67,16 @@ contract ProtocolManagerTest is Setup, Ownable2StepUpgradeable { ); } + function testUpdateTokenUpdateTimelockPeriod() public { + vm.prank(OWNER); + proxiedManager.updateTokenUpdateTimelockPeriod(7 days); + assertEq( + proxiedManager.tokenUpdateTimelockPeriod(), + 7 days, + "TokenUpdate timelock update failed" + ); + } + function testMaxPercentage() public view { assertEq( proxiedManager.maxPercentage(), @@ -170,6 +180,17 @@ contract ProtocolManagerTest is Setup, Ownable2StepUpgradeable { proxiedManager.updateObligationExpireTime(1 days); } + function testRevertUpdateTokenUpdateTimelockPeriodWithNonOwner() public { + vm.prank(ATTACKER); + vm.expectRevert( + abi.encodeWithSelector( + OwnableUnauthorizedAccount.selector, + address(ATTACKER) + ) + ); + proxiedManager.updateTokenUpdateTimelockPeriod(7 days); + } + function testRevertUpdateMaxSharesWithNonOwner() public { vm.prank(ATTACKER); vm.expectRevert( diff --git a/test/modules/SlashingManager.bapp.t.sol b/test/modules/SlashingManager.bapp.t.sol index 4d157454..7a71dd06 100644 --- a/test/modules/SlashingManager.bapp.t.sol +++ b/test/modules/SlashingManager.bapp.t.sol @@ -45,15 +45,20 @@ contract SlashingManagerTest is StrategyManagerTest { ); } - function testSlashBApp(uint256 slashAmount) public { + function testSlashBApp(uint32 slashPercentage) public { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); vm.assume( - slashAmount > 0 && - slashAmount <= - (depositAmount * percentage) / proxiedManager.maxPercentage() + slashPercentage > 0 && + slashPercentage <= proxiedManager.maxPercentage() ); + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); + testStrategyOptInToBApp(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -67,16 +72,17 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, address(bApp1) ); proxiedManager.slash( STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); + uint256 newStrategyBalance = depositAmount - slashAmount; checkTotalSharesAndTotalBalance( STRATEGY1, @@ -89,16 +95,22 @@ contract SlashingManagerTest is StrategyManagerTest { checkSlashingFund(address(bApp1), token, slashAmount); } - function testSlashBAppWithEth(uint256 slashAmount) public { + function testSlashBAppWithEth(uint32 slashPercentage) public { testStrategyOptInToBAppWithETH(); uint32 percentage = 10_000; uint256 depositAmount = 1 ether; address token = ETH_ADDRESS; vm.assume( - slashAmount > 0 && - slashAmount <= - (depositAmount * percentage) / proxiedManager.maxPercentage() + slashPercentage > 0 && + slashPercentage <= proxiedManager.maxPercentage() ); + + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); + vm.prank(USER2); proxiedManager.depositETH{ value: depositAmount }(STRATEGY1); vm.prank(USER1); @@ -107,14 +119,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, address(bApp1) ); proxiedManager.slash( STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -129,15 +141,17 @@ contract SlashingManagerTest is StrategyManagerTest { checkSlashingFund(address(bApp1), token, slashAmount); } - function testSlashBAppButInternalSlashRevert(uint256 slashAmount) public { + function testSlashBAppButInternalSlashRevert( + uint32 slashPercentage + ) public { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); vm.assume( - slashAmount > 0 && - slashAmount <= - (depositAmount * percentage) / proxiedManager.maxPercentage() + slashPercentage > 0 && + slashPercentage <= proxiedManager.maxPercentage() ); + testStrategyOptInToBApp(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -153,7 +167,7 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp2), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); checkTotalSharesAndTotalBalance( @@ -181,7 +195,7 @@ contract SlashingManagerTest is StrategyManagerTest { depositAmount ); vm.prank(USER1); - uint256 slashAmount = 1; + uint32 slashPercentage = 100; vm.expectRevert( abi.encodeWithSelector(IBasedAppManager.BAppNotRegistered.selector) ); @@ -189,7 +203,7 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, USER1, address(erc20mock), - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); } @@ -198,7 +212,7 @@ contract SlashingManagerTest is StrategyManagerTest { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); - uint256 slashAmount = 1000; + uint32 slashPercentage = 100; testStrategyOptInToBApp(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -214,27 +228,32 @@ contract SlashingManagerTest is StrategyManagerTest { bApps[i].slash( STRATEGY1, token, - slashAmount, + slashPercentage, + address(bApps[i]), abi.encodePacked("0x00") ); } } function testWithdrawSlashingFundErc20() public { - uint256 slashAmount = 100; - testSlashBApp(slashAmount); + uint32 slashPercentage = 9000; + testSlashBApp(slashPercentage); vm.expectEmit(true, true, true, true); - emit IStrategyManager.SlashingFundWithdrawn( - address(erc20mock), - slashAmount - ); + emit IStrategyManager.SlashingFundWithdrawn(address(erc20mock), 1); vm.prank(USER1); - bApp1.withdrawSlashingFund(address(erc20mock), slashAmount); + bApp1.withdrawSlashingFund(address(erc20mock), 1); } function testWithdrawSlashingFundEth() public { - uint256 slashAmount = 0.2 ether; - testSlashBAppWithEth(slashAmount); + uint32 slashPercentage = 9000; + uint32 percentage = 10000; + uint256 depositAmount = 1 ether; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); + testSlashBAppWithEth(slashPercentage); vm.expectEmit(true, true, true, true); emit IStrategyManager.SlashingFundWithdrawn(ETH_ADDRESS, slashAmount); vm.prank(USER1); @@ -242,8 +261,13 @@ contract SlashingManagerTest is StrategyManagerTest { } function testRevertWithdrawSlashingFundErc20WithEth() public { - uint256 slashAmount = 100; - testSlashBApp(slashAmount); + uint32 slashPercentage = 100; + uint256 slashAmount = calculateSlashAmount( + 1 ether, + proxiedManager.maxPercentage(), + slashPercentage + ); + testSlashBApp(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector(IStrategyManager.InvalidToken.selector) @@ -251,26 +275,9 @@ contract SlashingManagerTest is StrategyManagerTest { proxiedManager.withdrawSlashingFund(ETH_ADDRESS, slashAmount); } - function testRevertWithdrawSlashingFundErc20WithInsufficientBalance() - public - { - uint256 slashAmount = 100; - testSlashBApp(slashAmount); - vm.prank(address(bApp1)); - vm.expectRevert( - abi.encodeWithSelector( - IStrategyManager.InsufficientBalance.selector - ) - ); - proxiedManager.withdrawSlashingFund( - address(erc20mock), - slashAmount + 1 - ); - } - function testRevertWithdrawSlashingFundErc20WithZeroAmount() public { - uint256 slashAmount = 100; - testSlashBApp(slashAmount); + uint32 slashPercentage = 100; + testSlashBApp(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector(IStrategyManager.InvalidAmount.selector) @@ -282,7 +289,8 @@ contract SlashingManagerTest is StrategyManagerTest { public { uint256 slashAmount = 100; - testSlashBApp(slashAmount); + uint32 slashPercentage = 10000; + testSlashBApp(slashPercentage); vm.prank(address(bApp1)); vm.expectRevert( abi.encodeWithSelector( @@ -293,8 +301,8 @@ contract SlashingManagerTest is StrategyManagerTest { } function testRevertWithdrawETHSlashingFundErc20WithZeroAmount() public { - uint256 slashAmount = 100; - testSlashBApp(slashAmount); + uint32 slashPercentage = 100; + testSlashBApp(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector(IStrategyManager.InvalidAmount.selector) @@ -303,11 +311,16 @@ contract SlashingManagerTest is StrategyManagerTest { } function testFinalizeWithdrawalAfterSlashingRedeemsLowerAmount() public { - uint256 slashAmount = 100; + uint32 slashPercentage = 100; uint32 percentage = 9000; uint256 depositAmount = 100_000; uint256 withdrawalAmount = (depositAmount * 50) / 100; address token = address(erc20mock); + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); testStrategyOptInToBApp(percentage); @@ -334,14 +347,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, address(bApp1) ); proxiedManager.slash( STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -361,17 +374,23 @@ contract SlashingManagerTest is StrategyManagerTest { vm.prank(USER2); proxiedManager.finalizeWithdrawal(STRATEGY1, IERC20(erc20mock)); - checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_950); + checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_550); checkAccountShares(STRATEGY1, USER2, token, 50_000); checkSlashableBalance(STRATEGY1, address(bApp1), token, 0); checkSlashingFund(address(bApp1), token, slashAmount); } function testFinalizeWithdrawalETHAfterSlashingRedeemsLowerAmount() public { - uint256 slashAmount = 100; + uint32 slashPercentage = 100; uint256 depositAmount = 100_000; uint256 withdrawalAmount = (depositAmount * 50) / 100; address token = ETH_ADDRESS; + uint32 percentage = 10000; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); testStrategyOptInToBAppWithETH(); @@ -394,14 +413,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, address(bApp1) ); proxiedManager.slash( STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -421,7 +440,7 @@ contract SlashingManagerTest is StrategyManagerTest { vm.prank(USER2); proxiedManager.finalizeWithdrawalETH(STRATEGY1); - checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_950); + checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_500); checkAccountShares(STRATEGY1, USER2, token, 50_000); checkSlashableBalance(STRATEGY1, address(bApp1), token, 0); checkSlashingFund(address(bApp1), token, slashAmount); @@ -429,11 +448,17 @@ contract SlashingManagerTest is StrategyManagerTest { function testSlashBAppTotalBalance(uint256 depositAmount) public { uint32 percentage = 10_000; + uint32 slashPercentage = proxiedManager.maxPercentage(); vm.assume( depositAmount > 0 && depositAmount <= proxiedManager.maxShares() ); address token = address(erc20mock); - uint256 slashAmount = depositAmount; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); + testStrategyOptInToBApp(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -447,7 +472,7 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, address(bApp1) ); checkGeneration(STRATEGY1, token, 0); @@ -455,7 +480,7 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -510,7 +535,8 @@ contract SlashingManagerTest is StrategyManagerTest { function testSlashBAppWhenObligationIsZero() public { uint256 depositAmount = 100_000 * 10 ** 18; address token = address(erc20mock); - uint256 slashAmount = depositAmount / 2; + uint32 slashPercentage = 100; + testStrategyOptInToBAppWithMultipleTokensWithPercentageZero(); vm.prank(USER2); proxiedManager.depositERC20( @@ -529,7 +555,7 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); checkTotalSharesAndTotalBalance( @@ -547,10 +573,16 @@ contract SlashingManagerTest is StrategyManagerTest { function testFinalizeWithdrawalAfterPartialSlashBAppWithdrawSmallerAmount() public { - testStrategyOptInToBApp(proxiedManager.maxPercentage()); + uint32 percentage = proxiedManager.maxPercentage(); + testStrategyOptInToBApp(percentage); uint256 depositAmount = 1000; uint256 withdrawalAmount = 800; - uint256 slashAmount = 300; + uint32 slashPercentage = 10_00; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); address token = address(erc20mock); vm.startPrank(USER1); proxiedManager.depositERC20( @@ -563,10 +595,11 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp1), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); - uint256 newStrategyBalance = depositAmount - slashAmount; // 700 + + uint256 newStrategyBalance = depositAmount - slashAmount; checkTotalSharesAndTotalBalance( STRATEGY1, token, @@ -577,9 +610,10 @@ contract SlashingManagerTest is StrategyManagerTest { checkSlashableBalance(STRATEGY1, address(bApp1), token, 0); checkSlashingFund(address(bApp1), token, slashAmount); checkGeneration(STRATEGY1, token, 0); + vm.warp(block.timestamp + proxiedManager.withdrawalTimelockPeriod()); - proxiedManager.finalizeWithdrawal(STRATEGY1, IERC20(token)); // this ends up withdrawing 560 (800 * 70% since 30% was slashed) - uint256 effectiveWithdrawalAmount = 560; + proxiedManager.finalizeWithdrawal(STRATEGY1, IERC20(token)); // this ends up withdrawing 720 + uint256 effectiveWithdrawalAmount = 720; checkTotalSharesAndTotalBalance( STRATEGY1, token, @@ -601,7 +635,12 @@ contract SlashingManagerTest is StrategyManagerTest { uint32 percentage = 10_000; uint256 depositAmount = 100_000; address token = address(erc20mock); - uint256 slashAmount = 10_000; + uint32 slashPercentage = 100; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); testStrategyOptInToBApp(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -615,14 +654,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, address(0) ); proxiedManager.slash( STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); (uint32 adjustedPercentage, ) = proxiedManager.obligations( @@ -630,7 +669,7 @@ contract SlashingManagerTest is StrategyManagerTest { address(bApp3), token ); - assertEq(adjustedPercentage, 9000, "Obligation percentage"); + assertEq(adjustedPercentage, 9900, "Obligation percentage"); uint256 newStrategyBalance = depositAmount - slashAmount; checkTotalSharesAndTotalBalance( STRATEGY1, @@ -639,12 +678,12 @@ contract SlashingManagerTest is StrategyManagerTest { newStrategyBalance ); checkAccountShares(STRATEGY1, USER2, token, depositAmount); - checkSlashableBalance(STRATEGY1, address(bApp3), token, 81_000); + checkSlashableBalance(STRATEGY1, address(bApp3), token, 98010); checkSlashingFund(address(0), token, slashAmount); } function testSlashBAppAdjust( - uint256 slashAmount, + uint32 slashPercentage, uint256 depositAmount ) public { uint32 percentage = 10_000; @@ -654,10 +693,13 @@ contract SlashingManagerTest is StrategyManagerTest { depositAmount <= proxiedManager.maxShares() && percentage > 0 && percentage <= proxiedManager.maxPercentage() && - slashAmount > 0 && - slashAmount != depositAmount && - slashAmount <= - (depositAmount * percentage) / proxiedManager.maxPercentage() + slashPercentage > 0 && + slashPercentage <= proxiedManager.maxPercentage() + ); + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage ); testStrategyOptInToBApp(percentage); vm.prank(USER2); @@ -672,14 +714,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, address(0) ); proxiedManager.slash( STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint32 adjustedPercentage = checkAdjustedPercentage( @@ -712,7 +754,12 @@ contract SlashingManagerTest is StrategyManagerTest { vm.assume( depositAmount > 0 && depositAmount <= proxiedManager.maxShares() ); - uint256 slashAmount = depositAmount; + uint32 slashPercentage = 10_000; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); testStrategyOptInToBApp(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -726,14 +773,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, address(0) ); proxiedManager.slash( STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); (uint32 adjustedPercentage, ) = proxiedManager.obligations( @@ -755,7 +802,13 @@ contract SlashingManagerTest is StrategyManagerTest { function testSlashBAppAdjustBasicETHWithAdjust() public { uint256 depositAmount = 100 ether; address token = ETH_ADDRESS; - uint256 slashAmount = 10 ether; + uint32 slashPercentage = 100; + uint32 percentage = 10_000; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); testStrategyOptInToBAppWithETH(); vm.deal(USER2, depositAmount); vm.prank(USER2); @@ -766,14 +819,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, address(0) ); proxiedManager.slash( STRATEGY1, address(bApp3), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); (uint32 adjustedPercentage, ) = proxiedManager.obligations( @@ -781,7 +834,7 @@ contract SlashingManagerTest is StrategyManagerTest { address(bApp3), token ); - assertEq(adjustedPercentage, 9000, "Obligation percentage"); + assertEq(adjustedPercentage, 9900, "Obligation percentage"); uint256 newStrategyBalance = depositAmount - slashAmount; checkTotalSharesAndTotalBalance( STRATEGY1, @@ -790,14 +843,22 @@ contract SlashingManagerTest is StrategyManagerTest { newStrategyBalance ); checkAccountShares(STRATEGY1, USER2, token, depositAmount); - checkSlashableBalance(STRATEGY1, address(bApp3), token, 81 ether); + checkSlashableBalance(STRATEGY1, address(bApp3), token, 98.01 ether); checkSlashingFund(address(0), token, slashAmount); } function testSlashBAppAdjustBasicETH() public { uint256 depositAmount = 100 ether; address token = ETH_ADDRESS; - uint256 slashAmount = 10 ether; + // uint256 slashAmount = 10 ether; + uint32 slashPercentage = 100; + uint32 percentage = 10_000; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); + testStrategyOptInToBAppWithETH(); vm.deal(USER2, depositAmount); vm.prank(USER2); @@ -808,14 +869,14 @@ contract SlashingManagerTest is StrategyManagerTest { STRATEGY1, address(bApp4), token, - slashAmount, + slashPercentage, address(bApp4) ); proxiedManager.slash( STRATEGY1, address(bApp4), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); (uint32 adjustedPercentage, ) = proxiedManager.obligations( diff --git a/test/modules/SlashingManager.eoa.t.sol b/test/modules/SlashingManager.eoa.t.sol index 379705bd..cc5ea2d4 100644 --- a/test/modules/SlashingManager.eoa.t.sol +++ b/test/modules/SlashingManager.eoa.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.29; import { IERC20, IStrategyManager, IBasedAppManager } from "@ssv/test/helpers/Setup.t.sol"; import { StrategyManagerTest } from "@ssv/test/modules/StrategyManager.t.sol"; +import { ValidationLib } from "@ssv/src/core/libraries/ValidationLib.sol"; contract SlashingManagerEOATest is StrategyManagerTest { function testGetSlashableBalanceBasic() public { @@ -42,7 +43,10 @@ contract SlashingManagerEOATest is StrategyManagerTest { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); - uint256 slashAmount = 1000; + uint32 slashPercentage = 100; + uint256 slashAmount = (((depositAmount * percentage) / + proxiedManager.maxPercentage()) * slashPercentage) / + proxiedManager.maxPercentage(); testStrategyOptInToBAppEOA(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -55,10 +59,10 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); - uint256 newStrategyBalance = depositAmount - slashAmount; // 100,000 - 1,000 = 99,000 ERC20 + uint256 newStrategyBalance = depositAmount - slashAmount; checkAccountShares(STRATEGY1, USER2, token, depositAmount); checkTotalSharesAndTotalBalance( STRATEGY1, @@ -66,18 +70,20 @@ contract SlashingManagerEOATest is StrategyManagerTest { depositAmount, newStrategyBalance ); - checkSlashableBalance(STRATEGY1, USER1, token, 0); // 99,000 * 90% = 89,100 ERC20 + checkSlashableBalance(STRATEGY1, USER1, token, 0); checkSlashingFund(USER1, token, slashAmount); } - function testSlashEOA(uint256 slashAmount) public { + function testSlashEOA(uint32 slashPercentage) public { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); + uint256 slashAmount = (depositAmount * percentage * slashPercentage) / + proxiedManager.maxPercentage() / + proxiedManager.maxPercentage(); vm.assume( - slashAmount > 0 && - slashAmount <= - (depositAmount * percentage) / proxiedManager.maxPercentage() + slashPercentage > 0 && + slashPercentage <= proxiedManager.maxPercentage() ); testStrategyOptInToBAppEOA(percentage); vm.prank(USER2); @@ -98,7 +104,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -113,17 +119,17 @@ contract SlashingManagerEOATest is StrategyManagerTest { checkSlashingFund(USER1, token, slashAmount); } - function testSlashEOAWithEth(uint256 slashAmount) public { + function testSlashEOAWithEth(uint32 slashPercentage) public { uint32 percentage = 9000; uint256 depositAmount = 3 ether; address token = ETH_ADDRESS; - vm.assume( - slashAmount > 0 && - slashAmount <= - (depositAmount * percentage) / proxiedManager.maxPercentage() + slashPercentage > 0 && + slashPercentage <= proxiedManager.maxPercentage() ); - + uint256 slashAmount = (depositAmount * percentage * slashPercentage) / + proxiedManager.maxPercentage() / + proxiedManager.maxPercentage(); testStrategyOptInToBAppEOAWithETH(percentage); vm.prank(USER2); proxiedManager.depositETH{ value: depositAmount }(STRATEGY1); @@ -140,7 +146,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -155,18 +161,19 @@ contract SlashingManagerEOATest is StrategyManagerTest { checkSlashingFund(USER1, token, slashAmount); } - function testRevertSlashEOAWithZeroAmount() public { + function testRevertSlashEOAWithZeroPercentage() public { testStrategyOptInToBAppEOA(1000); vm.prank(USER1); - uint256 slashAmount = 0; + uint32 slashPercentage = 0; + vm.expectRevert( - abi.encodeWithSelector(IStrategyManager.InvalidAmount.selector) + abi.encodeWithSelector(ValidationLib.InvalidPercentage.selector) ); proxiedManager.slash( STRATEGY1, USER1, address(erc20mock), - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); } @@ -174,7 +181,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { function testRevertSlashEOAWithInsufficientBalance() public { uint32 percentage = 9000; address token = address(erc20mock); - uint256 slashAmount = 1; + uint32 slashPercentage = 100; + testStrategyOptInToBAppEOA(percentage); vm.prank(USER1); vm.expectRevert( @@ -186,7 +194,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); } @@ -195,7 +203,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); - uint256 slashAmount = 1000; + uint32 slashPercentage = 100; + testStrategyOptInToBAppEOA(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -209,14 +218,15 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); } function testWithdrawSlashingFundErc20FromEOA() public { uint256 slashAmount = 100; - testSlashEOA(slashAmount); + uint32 slashPercentage = 9000; + testSlashEOA(slashPercentage); vm.expectEmit(true, true, true, true); emit IStrategyManager.SlashingFundWithdrawn( address(erc20mock), @@ -228,7 +238,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { function testWithdrawSlashingFundEthFromEOA() public { uint256 slashAmount = 0.2 ether; - testSlashEOAWithEth(slashAmount); + uint32 slashPercentage = 1000; + testSlashEOAWithEth(slashPercentage); vm.expectEmit(true, true, true, true); emit IStrategyManager.SlashingFundWithdrawn(ETH_ADDRESS, slashAmount); vm.prank(USER1); @@ -236,16 +247,17 @@ contract SlashingManagerEOATest is StrategyManagerTest { } // Slash Non Compatible BApp - - function testSlashNonCompatibleBApp(uint256 slashAmount) public { + function testSlashNonCompatibleBApp(uint32 slashPercentage) public { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); vm.assume( - slashAmount > 0 && - slashAmount <= - (depositAmount * percentage) / proxiedManager.maxPercentage() + slashPercentage > 0 && + slashPercentage <= proxiedManager.maxPercentage() ); + uint256 slashAmount = (depositAmount * percentage * slashPercentage) / + proxiedManager.maxPercentage() / + proxiedManager.maxPercentage(); testStrategyOptInToBAppNonCompliant(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -259,10 +271,10 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, address(nonCompliantBApp), token, - slashAmount, + slashPercentage, address(nonCompliantBApp) ); - nonCompliantBApp.slash(STRATEGY1, token, slashAmount); + nonCompliantBApp.slash(STRATEGY1, token, slashPercentage); uint256 newStrategyBalance = depositAmount - slashAmount; checkTotalSharesAndTotalBalance( STRATEGY1, @@ -279,7 +291,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { uint32 percentage = 9000; uint256 depositAmount = 100_000; address token = address(erc20mock); - uint256 slashAmount = 2; + uint32 slashPercentage = 100; + testStrategyOptInToBAppNonCompliant(percentage); vm.startPrank(USER2); proxiedManager.depositERC20( @@ -298,7 +311,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, address(nonCompliantBApp), token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); vm.stopPrank(); @@ -306,6 +319,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { function testRevertSlashEOANotRegistered() public { uint256 depositAmount = 100_000; + uint32 slashPercentage = 100; + vm.prank(USER2); proxiedManager.depositERC20( STRATEGY1, @@ -313,7 +328,6 @@ contract SlashingManagerEOATest is StrategyManagerTest { depositAmount ); vm.prank(USER1); - uint256 slashAmount = 1; vm.expectRevert( abi.encodeWithSelector(IBasedAppManager.BAppNotRegistered.selector) ); @@ -321,14 +335,15 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, address(erc20mock), - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); } function testRevertWithdrawSlashingFundErc20WithEthEOA() public { uint256 slashAmount = 100; - testSlashEOA(slashAmount); + uint32 slashPercentage = 100; + testSlashEOA(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector(IStrategyManager.InvalidToken.selector) @@ -336,26 +351,9 @@ contract SlashingManagerEOATest is StrategyManagerTest { proxiedManager.withdrawSlashingFund(ETH_ADDRESS, slashAmount); } - function testRevertWithdrawSlashingFundErc20WithInsufficientBalanceEOA() - public - { - uint256 slashAmount = 100; - testSlashEOA(slashAmount); - vm.prank(USER1); - vm.expectRevert( - abi.encodeWithSelector( - IStrategyManager.InsufficientBalance.selector - ) - ); - proxiedManager.withdrawSlashingFund( - address(erc20mock), - slashAmount + 1 - ); - } - function testRevertWithdrawSlashingFundErc20WithZeroAmountEOA() public { - uint256 slashAmount = 100; - testSlashEOA(slashAmount); + uint32 slashPercentage = 100; + testSlashEOA(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector(IStrategyManager.InvalidAmount.selector) @@ -367,7 +365,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { public { uint256 slashAmount = 100; - testSlashEOA(slashAmount); + uint32 slashPercentage = 100; + testSlashEOA(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector( @@ -378,8 +377,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { } function testRevertWithdrawETHSlashingFundErc20WithZeroAmountEOA() public { - uint256 slashAmount = 100; - testSlashEOA(slashAmount); + uint32 slashPercentage = 100; + testSlashEOA(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector(IStrategyManager.InvalidAmount.selector) @@ -388,11 +387,16 @@ contract SlashingManagerEOATest is StrategyManagerTest { } function testFinalizeWithdrawalAfterSlashingRedeemsLowerAmountEOA() public { - uint256 slashAmount = 100; uint32 percentage = 9000; uint256 depositAmount = 100_000; uint256 withdrawalAmount = (depositAmount * 50) / 100; address token = address(erc20mock); + uint32 slashPercentage = 100; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); testStrategyOptInToBAppEOA(percentage); @@ -419,14 +423,14 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, USER1 ); proxiedManager.slash( STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -446,7 +450,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { vm.prank(USER2); proxiedManager.finalizeWithdrawal(STRATEGY1, IERC20(erc20mock)); - checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_950); + checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_550); checkAccountShares(STRATEGY1, USER2, token, 50_000); checkSlashableBalance(STRATEGY1, USER1, token, 0); checkSlashingFund(USER1, token, slashAmount); @@ -455,11 +459,16 @@ contract SlashingManagerEOATest is StrategyManagerTest { function testFinalizeWithdrawalETHAfterSlashingRedeemsLowerAmountEOA() public { - uint256 slashAmount = 100; uint32 percentage = 10_000; uint256 depositAmount = 100_000; uint256 withdrawalAmount = (depositAmount * 50) / 100; address token = ETH_ADDRESS; + uint32 slashPercentage = 100; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); testStrategyOptInToBAppEOAWithETH(percentage); @@ -482,14 +491,14 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, USER1 ); proxiedManager.slash( STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; @@ -509,7 +518,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { vm.prank(USER2); proxiedManager.finalizeWithdrawalETH(STRATEGY1); - checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_950); + checkTotalSharesAndTotalBalance(STRATEGY1, token, 50_000, 49_500); checkAccountShares(STRATEGY1, USER2, token, 50_000); checkSlashableBalance(STRATEGY1, USER1, token, 0); checkSlashingFund(USER1, token, slashAmount); @@ -521,7 +530,13 @@ contract SlashingManagerEOATest is StrategyManagerTest { ); uint32 percentage = 10_000; address token = address(erc20mock); - uint256 slashAmount = depositAmount; + uint32 slashPercentage = proxiedManager.maxPercentage(); + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); + testStrategyOptInToBAppEOA(percentage); vm.prank(USER2); proxiedManager.depositERC20( @@ -535,7 +550,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); checkGeneration(STRATEGY1, token, 1); @@ -616,10 +631,16 @@ contract SlashingManagerEOATest is StrategyManagerTest { function testFinalizeWithdrawalAfterPartialSlashBAppWithdrawSmallerAmountEOA() public { - testStrategyOptInToBAppEOA(proxiedManager.maxPercentage()); + uint32 percentage = proxiedManager.maxPercentage(); + testStrategyOptInToBAppEOA(percentage); uint256 depositAmount = 1000; uint256 withdrawalAmount = 800; - uint256 slashAmount = 300; + uint32 slashPercentage = 10_00; + uint256 slashAmount = calculateSlashAmount( + depositAmount, + percentage, + slashPercentage + ); address token = address(erc20mock); vm.startPrank(USER1); proxiedManager.depositERC20( @@ -632,7 +653,7 @@ contract SlashingManagerEOATest is StrategyManagerTest { STRATEGY1, USER1, token, - slashAmount, + slashPercentage, abi.encodePacked("0x00") ); uint256 newStrategyBalance = depositAmount - slashAmount; // 700 @@ -647,8 +668,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { checkSlashingFund(USER1, token, slashAmount); checkGeneration(STRATEGY1, token, 0); vm.warp(block.timestamp + proxiedManager.withdrawalTimelockPeriod()); - proxiedManager.finalizeWithdrawal(STRATEGY1, IERC20(token)); // this ends up withdrawing 560 (800 * 70% since 30% was slashed) - uint256 effectiveWithdrawalAmount = 560; + proxiedManager.finalizeWithdrawal(STRATEGY1, IERC20(token)); // this ends up withdrawing 720 + uint256 effectiveWithdrawalAmount = 720; checkTotalSharesAndTotalBalance( STRATEGY1, token, @@ -668,7 +689,8 @@ contract SlashingManagerEOATest is StrategyManagerTest { function testRevertWithdrawSlashingFundErc20WithEthOnEOA() public { uint256 slashAmount = 100; - testSlashEOA(slashAmount); + uint32 slashPercentage = 100; + testSlashEOA(slashPercentage); vm.prank(USER1); vm.expectRevert( abi.encodeWithSelector(IStrategyManager.InvalidToken.selector) diff --git a/test/modules/StrategyManager.t.sol b/test/modules/StrategyManager.t.sol index f27fe6c3..1e27c20e 100644 --- a/test/modules/StrategyManager.t.sol +++ b/test/modules/StrategyManager.t.sol @@ -46,9 +46,36 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { STRATEGY1_INITIAL_FEE, "" ); + uint32[] memory strategies = proxiedManager.ownedStrategies(USER1); + assertEq( + strategies.length, + 1, + "Should have created 1 strategy for USER1" + ); + assertEq( + strategies[0], + strategyId1, + "Should have the correct ID for Strategy 1" + ); proxiedManager.createStrategy(STRATEGY2_INITIAL_FEE, ""); proxiedManager.createStrategy(STRATEGY3_INITIAL_FEE, ""); - + strategies = proxiedManager.ownedStrategies(USER1); + assertEq(strategies.length, 3, "Should have created the strategy"); + assertEq( + strategies[0], + strategyId1, + "Should have the correct ID for Strategy 1" + ); + assertEq( + strategies[1], + STRATEGY2, + "Should have the correct ID for Strategy 2" + ); + assertEq( + strategies[2], + STRATEGY3, + "Should have the correct ID for Strategy 3" + ); assertEq( strategyId1, STRATEGY1, @@ -70,6 +97,17 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { STRATEGY4_INITIAL_FEE, "" ); + strategies = proxiedManager.ownedStrategies(USER2); + assertEq( + strategies.length, + 1, + "Should have created one strategy for USER2" + ); + assertEq( + strategies[0], + strategyId4, + "Should have the correct ID for Strategy 4" + ); assertEq( strategyId4, STRATEGY4, @@ -390,7 +428,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), percentage, proxiedManager, - uint32(i) + 1, true ); } @@ -442,7 +479,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), percentage, proxiedManager, - 1, true ); uint32 counter = bApp1.counter(); @@ -485,7 +521,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), percentage, proxiedManager, - 1, true ); vm.stopPrank(); @@ -526,7 +561,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { ETH_ADDRESS, percentage, proxiedManager, - 1, true ); vm.stopPrank(); @@ -567,7 +601,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), percentage, proxiedManager, - 1, true ); vm.stopPrank(); @@ -600,7 +633,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), 0, proxiedManager, - 0, false ); uint32 counter = bApp1.counter(); @@ -645,7 +677,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), 0, proxiedManager, - 0, false ); } @@ -689,7 +720,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), percentage, proxiedManager, - 1, true ); checkStrategyInfo( @@ -699,7 +729,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock2), percentage, proxiedManager, - 1, true ); vm.stopPrank(); @@ -776,7 +805,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), percentage, proxiedManager, - uint32(i) + 1, true ); } @@ -848,7 +876,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(erc20mock), percentage, proxiedManager, - 0, true ); } @@ -890,7 +917,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { ETH_ADDRESS, percentage, proxiedManager, - uint32(i) + 1, true ); } @@ -1226,7 +1252,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(bApps[i]), token, percentage, - uint32(i) + 1, true, proxiedManager ); @@ -1586,7 +1611,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { ); vm.startPrank(USER1); - checkUsedTokens(STRATEGY1, address(erc20mock), uint32(bApps.length)); vm.warp( block.timestamp + @@ -1616,7 +1640,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { true ); } - checkUsedTokens(STRATEGY1, address(erc20mock), 0); vm.stopPrank(); } @@ -1998,7 +2021,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(bApps[i]), address(erc20mock), percentage, - uint32(i) + 1, true, proxiedManager ); @@ -2021,7 +2043,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(bApps[i]), ETH_ADDRESS, proxiedManager.maxPercentage(), - uint32(i) + 1, true, proxiedManager ); @@ -2062,7 +2083,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(bApps[i]), address(erc20mock), 0, - 0, true, proxiedManager ); @@ -2085,7 +2105,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(bApps[i]), ETH_ADDRESS, 0, - 0, true, proxiedManager ); @@ -2116,7 +2135,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { address(bApps[i]), ETH_ADDRESS, 5000, - uint32(i) + 1, true, proxiedManager ); @@ -2207,346 +2225,6 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { vm.stopPrank(); } - function testUpdateUsedTokensCorrectly() public { - testCreateNewObligationSuccessful(); - vm.startPrank(USER1); - address token = address(erc20mock2); - - updateObligation(STRATEGY1, address(bApp1), token, 9700); - checkObligationInfo( - STRATEGY1, - address(bApp1), - token, - 9700, - uint32(bApps.length), - true, - proxiedManager - ); - - proxiedManager.proposeUpdateObligation( - STRATEGY1, - address(bApp1), - token, - 0 - ); - checkProposedObligation( - STRATEGY1, - address(bApp1), - token, - 9700, - 0, - block.timestamp, - true - ); - vm.warp(block.timestamp + proxiedManager.obligationTimelockPeriod()); - proxiedManager.finalizeUpdateObligation( - STRATEGY1, - address(bApp1), - token - ); - checkProposedObligation( - STRATEGY1, - address(bApp1), - token, - 0, - 0, - 0, - true - ); - checkObligationInfo( - STRATEGY1, - address(bApp1), - token, - 0, - uint32(bApps.length) - 1, - true, - proxiedManager - ); - - vm.expectRevert( - abi.encodeWithSelector( - IStrategyManager.ObligationAlreadySet.selector - ) - ); - proxiedManager.createObligation(STRATEGY1, address(bApp1), token, 1000); - updateObligation(STRATEGY1, address(bApp1), token, 1000); - checkObligationInfo( - STRATEGY1, - address(bApp1), - token, - 1000, - uint32(bApps.length), - true, - proxiedManager - ); - vm.stopPrank(); - } - - function testUpdateUsedTokens() public { - uint32 percentage = 10_000; - testCreateStrategies(); - testRegisterBAppWith2Tokens(); - - vm.startPrank(USER1); - proxiedManager.depositERC20(STRATEGY1, IERC20(erc20mock), 100_000); - ( - address[] memory tokensInput, - uint32[] memory obligationPercentagesInput - ) = createSingleTokenAndSingleObligationPercentage( - address(erc20mock), - percentage - ); - - proxiedManager.optInToBApp( - STRATEGY1, - address(bApp1), - tokensInput, - obligationPercentagesInput, - abi.encodePacked("0x00") - ); - proxiedManager.optInToBApp( - STRATEGY1, - address(bApp2), - tokensInput, - obligationPercentagesInput, - abi.encodePacked("0x00") - ); - - uint32 strategyId = proxiedManager.accountBAppStrategy( - USER1, - address(bApp1) - ); - assertEq( - strategyId, - STRATEGY1, - "BApp1 should have the strategy ID set correctly" - ); - - strategyId = proxiedManager.accountBAppStrategy(USER1, address(bApp2)); - assertEq( - strategyId, - STRATEGY1, - "BApp2 should have the strategy ID set correctly" - ); - - (uint256 obligationPercentage, bool isSet) = proxiedManager.obligations( - strategyId, - address(bApp1), - address(erc20mock) - ); - assertEq(isSet, true, "BApp1 Should have the obligation set"); - assertEq( - obligationPercentage, - percentage, - "Should have the obligation percentage set correctly" - ); - - (obligationPercentage, isSet) = proxiedManager.obligations( - strategyId, - address(bApp2), - address(erc20mock) - ); - assertEq(isSet, true, "BApp2 Should have the obligation set"); - assertEq( - obligationPercentage, - percentage, - "Should have the obligation percentage set correctly" - ); - - checkUsedTokens(strategyId, address(erc20mock), 2); - - proxiedManager.proposeUpdateObligation( - STRATEGY1, - address(bApp2), - address(erc20mock), - 0 - ); - checkUsedTokens(strategyId, address(erc20mock), 2); - - vm.warp(block.timestamp + proxiedManager.obligationTimelockPeriod()); - proxiedManager.finalizeUpdateObligation( - STRATEGY1, - address(bApp2), - address(erc20mock) - ); - checkUsedTokens(strategyId, address(erc20mock), 1); - - proxiedManager.proposeUpdateObligation( - STRATEGY1, - address(bApp1), - address(erc20mock), - 0 - ); - checkUsedTokens(strategyId, address(erc20mock), 1); - - vm.warp(block.timestamp + proxiedManager.obligationTimelockPeriod()); - proxiedManager.finalizeUpdateObligation( - STRATEGY1, - address(bApp1), - address(erc20mock) - ); - checkUsedTokens(strategyId, address(erc20mock), 0); - - proxiedManager.proposeUpdateObligation( - STRATEGY1, - address(bApp1), - address(erc20mock), - 1 - ); - checkUsedTokens(strategyId, address(erc20mock), 0); - - vm.warp(block.timestamp + proxiedManager.obligationTimelockPeriod()); - proxiedManager.finalizeUpdateObligation( - STRATEGY1, - address(bApp1), - address(erc20mock) - ); - checkUsedTokens(strategyId, address(erc20mock), 1); - - proxiedManager.proposeUpdateObligation( - STRATEGY1, - address(bApp2), - address(erc20mock), - 1 - ); - checkUsedTokens(strategyId, address(erc20mock), 1); - - vm.warp(block.timestamp + proxiedManager.obligationTimelockPeriod()); - proxiedManager.finalizeUpdateObligation( - STRATEGY1, - address(bApp2), - address(erc20mock) - ); - checkUsedTokens(strategyId, address(erc20mock), 2); - vm.stopPrank(); - } - - function testUpdateUsedTokensETH() public { - uint32 percentage = 10_000; - testCreateStrategies(); - testRegisterBAppWithETH(); - vm.startPrank(USER1); - proxiedManager.depositETH{ value: 1 ether }(STRATEGY1); - ( - address[] memory tokensInput, - uint32[] memory obligationPercentagesInput - ) = createSingleTokenAndSingleObligationPercentage( - ETH_ADDRESS, - percentage - ); - - proxiedManager.optInToBApp( - STRATEGY1, - address(bApp1), - tokensInput, - obligationPercentagesInput, - abi.encodePacked("0x00") - ); - checkObligationInfo( - STRATEGY1, - address(bApp1), - ETH_ADDRESS, - percentage, - 1, - true, - proxiedManager - ); - - proxiedManager.optInToBApp( - STRATEGY1, - address(bApp2), - tokensInput, - obligationPercentagesInput, - abi.encodePacked("0x00") - ); - checkObligationInfo( - STRATEGY1, - address(bApp2), - ETH_ADDRESS, - percentage, - 2, - true, - proxiedManager - ); - - proxiedManager.proposeUpdateObligation( - STRATEGY1, - address(bApp2), - ETH_ADDRESS, - 0 - ); - checkObligationInfo( - STRATEGY1, - address(bApp2), - ETH_ADDRESS, - percentage, - 2, - true, - proxiedManager - ); - - vm.warp(block.timestamp + proxiedManager.obligationTimelockPeriod()); - - proxiedManager.finalizeUpdateObligation( - STRATEGY1, - address(bApp2), - ETH_ADDRESS - ); - checkObligationInfo( - STRATEGY1, - address(bApp2), - ETH_ADDRESS, - 0, - 1, - true, - proxiedManager - ); - - proxiedManager.proposeUpdateObligation( - STRATEGY1, - address(bApp1), - ETH_ADDRESS, - 0 - ); - checkObligationInfo( - STRATEGY1, - address(bApp1), - ETH_ADDRESS, - percentage, - 1, - true, - proxiedManager - ); - - vm.warp(block.timestamp + proxiedManager.obligationTimelockPeriod()); - - proxiedManager.finalizeUpdateObligation( - STRATEGY1, - address(bApp1), - ETH_ADDRESS - ); - checkObligationInfo( - STRATEGY1, - address(bApp1), - ETH_ADDRESS, - 0, - 0, - true, - proxiedManager - ); - checkObligationInfo( - STRATEGY1, - address(bApp2), - ETH_ADDRESS, - 0, - 0, - true, - proxiedManager - ); - - vm.stopPrank(); - } - function testRevertProposeWithdrawInsufficientLiquidity() public { testCreateObligationETHWithZeroPercentage(); // Registers BApp and does the opt in vm.prank(USER1); @@ -2615,11 +2293,13 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { vm.startPrank(USER1); proxiedManager.depositETH{ value: 1 ether }(STRATEGY1); proxiedManager.proposeWithdrawalETH(STRATEGY1, 1 ether); + uint32 slashPercentage = 5000; proxiedManager.slash( STRATEGY1, address(bApp1), ETH_ADDRESS, - 0.5 ether, + // 0.5 ether, + slashPercentage, abi.encode("0x00") ); vm.warp(block.timestamp + proxiedManager.withdrawalTimelockPeriod()); @@ -2634,11 +2314,12 @@ contract StrategyManagerTest is UtilsTest, BasedAppsManagerTest { vm.startPrank(USER1); proxiedManager.depositETH{ value: 1 ether }(STRATEGY1); proxiedManager.proposeWithdrawalETH(STRATEGY1, 1 ether); + uint32 slashPercentage = 10_000; proxiedManager.slash( STRATEGY1, address(bApp1), ETH_ADDRESS, - 1 ether, + slashPercentage, abi.encode("0x00") ); vm.warp(block.timestamp + proxiedManager.withdrawalTimelockPeriod());