From b181f2a44079669e1efabeb260e91d2dbb8b039b Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Mon, 27 Nov 2023 17:21:04 -0500 Subject: [PATCH 1/5] TokenStaking: removes all functions related to legacy stakes --- contracts/staking/IStaking.sol | 75 +- contracts/staking/TokenStaking.sol | 305 +---- contracts/test/TokenStakingTestSet.sol | 73 +- deploy/07_deploy_token_staking.ts | 6 +- docs/rfc-1-staking-contract.adoc | 63 +- test/staking/TokenStaking.test.js | 1746 +----------------------- 6 files changed, 93 insertions(+), 2175 deletions(-) diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index fa5d60da..8857b5ce 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -27,12 +27,6 @@ pragma solidity ^0.8.9; /// delegation optimizes the network throughput without compromising the /// security of the owners’ stake. interface IStaking { - enum StakeType { - NU, - KEEP, - T - } - // // // Delegating a stake @@ -176,39 +170,11 @@ interface IStaking { /// @notice Reduces the liquid T stake amount by the provided amount and /// withdraws T to the owner. Reverts if there is at least one - /// authorization higher than the sum of the legacy stake and - /// remaining liquid T stake or if the unstake amount is higher than - /// the liquid T stake amount. Can be called only by the delegation - /// owner or the staking provider. - function unstakeT(address stakingProvider, uint96 amount) external; - - /// @notice Sets the legacy KEEP staking contract active stake amount cached - /// in T staking contract to 0. Reverts if the amount of liquid T - /// staked in T staking contract is lower than the highest - /// application authorization. This function allows to unstake from - /// KEEP staking contract and still being able to operate in T - /// network and earning rewards based on the liquid T staked. Can be - /// called only by the delegation owner or the staking provider. - function unstakeKeep(address stakingProvider) external; - - /// @notice Sets to 0 the amount of T that is cached from the legacy - /// NU staking contract. Reverts if there is at least one - /// authorization higher than the sum of remaining legacy NU stake - /// and native T stake for that staking provider or if the unstaked - /// amount is higher than the cached legacy stake amount. If succeeded, - /// the legacy NU stake can be partially or fully undelegated on - /// the legacy NU staking contract. This function allows to unstake - /// from NU staking contract while still being able to operate in - /// T network and earning rewards based on the native T staked. - /// Can be called only by the stake owner or the staking provider. - function unstakeNu(address stakingProvider) external; - - /// @notice Sets cached legacy stake amount to 0, sets the liquid T stake - /// amount to 0 and withdraws all liquid T from the stake to the - /// owner. Reverts if there is at least one non-zero authorization. + /// authorization higher than the remaining liquid T stake or + /// if the unstake amount is higher than the liquid T stake amount. /// Can be called only by the delegation owner or the staking /// provider. - function unstakeAll(address stakingProvider) external; + function unstakeT(address stakingProvider, uint96 amount) external; // // @@ -265,17 +231,8 @@ interface IStaking { view returns (uint96); - /// @notice Returns staked amount of T, Keep and Nu for the specified - /// staking provider. - /// @dev All values are in T denomination - function stakes(address stakingProvider) - external - view - returns ( - uint96 tStake, - uint96 keepInTStake, - uint96 nuInTStake - ); + /// @notice Returns staked amount of T for the specified staking provider. + function tStake(address stakingProvider) external view returns (uint96); /// @notice Returns start staking timestamp. /// @dev This value is set at most once. @@ -290,9 +247,6 @@ interface IStaking { view returns (bool); - /// @notice Returns staked amount of NU for the specified staking provider. - function stakedNu(address stakingProvider) external view returns (uint256); - /// @notice Gets the stake owner, the beneficiary and the authorizer /// for the specified staking provider address. /// @return owner Stake owner address. @@ -313,23 +267,8 @@ interface IStaking { /// @notice Returns length of slashing queue function getSlashingQueueLength() external view returns (uint256); - /// @notice Returns minimum possible stake for T, KEEP or NU in T - /// denomination. - /// @dev For example, suppose the given staking provider has 10 T, 20 T worth - /// of KEEP, and 30 T worth of NU all staked, and the maximum - /// application authorization is 40 T, then `getMinStaked` for - /// that staking provider returns: - /// * 0 T if KEEP stake type specified i.e. - /// min = 40 T max - (10 T) = 30 T - /// * 10 T if NU stake type specified i.e. - /// min = 40 T max - (10 T) = 30 T - /// * 0 T if T stake type specified i.e. - /// min = 40 T max = 40 T - /// In other words, the minimum stake amount for the specified - /// stake type is the minimum amount of stake of the given type - /// needed to satisfy the maximum application authorization given - /// the staked amounts of the T stake types for that staking provider. - function getMinStaked(address stakingProvider, StakeType stakeTypes) + /// @notice Returns the maximum application authorization + function getMaxAuthorization(address stakingProvider) external view returns (uint96); diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index 29f50cd3..f7aefc4a 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -28,12 +28,8 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; /// @notice TokenStaking is the main staking contract of the Threshold Network. -/// Apart from the basic usage of enabling T stakes, it also acts as a -/// sort of "meta-staking" contract, accepting existing legacy NU/KEEP -/// stakes. Additionally, it serves as application manager for the apps -/// that run on the Threshold Network. Note that legacy NU/KEEP staking -/// contracts see TokenStaking as an application (e.g., slashing is -/// requested by TokenStaking and performed by the legacy contracts). +/// Additionally, it serves as application manager for the apps +/// that run on the Threshold Network. /// @dev TokenStaking is upgradeable, using OpenZeppelin's Upgradeability /// framework. As such, it is required to satisfy OZ's guidelines, like /// restrictions on constructors, immutable variables, base contracts and @@ -51,9 +47,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } struct StakingProviderInfo { - uint96 nuInTStake; + uint96 legacyNuInTStake; address owner; - uint96 keepInTStake; + uint96 legacyKeepInTStake; address payable beneficiary; uint96 tStake; address authorizer; @@ -86,9 +82,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable T internal immutable token; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - uint256 internal immutable nucypherRatio; - address public governance; uint96 public minTStakeAmount; uint256 public authorizationCeiling; @@ -108,7 +101,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { uint256 public slashingQueueIndex; event Staked( - StakeType indexed stakeType, address indexed owner, address indexed stakingProvider, address beneficiary, @@ -215,14 +207,11 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } /// @param _token Address of T token contract - /// @param _nucypherVendingMachine Address of NuCypher vending machine /// @custom:oz-upgrades-unsafe-allow constructor - constructor(T _token, VendingMachine _nucypherVendingMachine) { + constructor(T _token) { // calls to check contracts are working require(_token.totalSupply() > 0, "Wrong input parameters"); token = _token; - - nucypherRatio = _nucypherVendingMachine.ratio(); } function initialize() external initializer { @@ -274,7 +263,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { increaseStakeCheckpoint(stakingProvider, amount); emit Staked( - StakeType.T, msg.sender, stakingProvider, beneficiary, @@ -655,11 +643,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Reduces the liquid T stake amount by the provided amount and /// withdraws T to the owner. Reverts if there is at least one - /// authorization higher than the sum of the legacy stake and - /// remaining liquid T stake or if the unstake amount is higher than - /// the liquid T stake amount. Can be called only by the owner or - /// the staking provider. Can only be called when 24h passed since - /// the stake has been delegated. + /// authorization higher than the remaining liquid T stake or + /// if the unstake amount is higher than the liquid T stake amount. + /// Can be called only by the delegation owner or the staking + /// provider. function unstakeT(address stakingProvider, uint96 amount) external override @@ -670,7 +657,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { ]; require( amount > 0 && - amount + getMinStaked(stakingProvider, StakeType.T) <= + amount + getMaxAuthorization(stakingProvider) <= stakingProviderStruct.tStake, "Too much to unstake" ); @@ -687,121 +674,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { token.safeTransfer(stakingProviderStruct.owner, amount); } - /// @notice Sets the legacy KEEP staking contract active stake amount cached - /// in T staking contract to 0. Reverts if the amount of liquid T - /// staked in T staking contract is lower than the highest - /// application authorization. This function allows to unstake from - /// KEEP staking contract and still being able to operate in T - /// network and earning rewards based on the liquid T staked. Can be - /// called only by the delegation owner or the staking provider. - /// Can only be called when 24h passed since the stake has been - /// delegated. - /// @dev This function (or `unstakeAll`) must be called before - /// `undelegate`/`undelegateAt` in Keep staking contract. Otherwise - /// provider can be slashed by `notifyKeepStakeDiscrepancy` method. - function unstakeKeep(address stakingProvider) - external - override - onlyOwnerOrStakingProvider(stakingProvider) - { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - uint96 keepInTStake = stakingProviderStruct.keepInTStake; - require(keepInTStake != 0, "Nothing to unstake"); - require( - getMinStaked(stakingProvider, StakeType.KEEP) == 0, - "Keep stake still authorized" - ); - - emit Unstaked(stakingProvider, keepInTStake); - stakingProviderStruct.keepInTStake = 0; - decreaseStakeCheckpoint(stakingProvider, keepInTStake); - } - - /// @notice Sets to 0 the amount of T that is cached from the legacy - /// NU staking contract. Reverts if there is at least one - /// authorization higher than the sum of remaining legacy NU stake - /// and native T stake for that staking provider or if the unstaked - /// amount is higher than the cached legacy stake amount. If succeeded, - /// the legacy NU stake can be partially or fully undelegated on - /// the legacy NU staking contract. This function allows to unstake - /// from NU staking contract while still being able to operate in - /// T network and earning rewards based on the native T staked. - /// Can be called only by the stake owner or the staking provider. - /// @dev This function (or `unstakeAll`) must be called before `withdraw` - /// in NuCypher staking contract. Otherwise NU tokens can't be - /// unlocked. - /// @param stakingProvider Staking provider address - function unstakeNu(address stakingProvider) - external - override - onlyOwnerOrStakingProvider(stakingProvider) - { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - uint96 nuInTStake = stakingProviderStruct.nuInTStake; - require(nuInTStake != 0, "Nothing to unstake"); - require( - getMinStaked(stakingProvider, StakeType.NU) == 0, - "NU stake still authorized" - ); - - stakingProviderStruct.nuInTStake = 0; - decreaseStakeCheckpoint(stakingProvider, nuInTStake); - emit Unstaked(stakingProvider, nuInTStake); - } - - /// @notice Sets cached legacy stake amount to 0, sets the liquid T stake - /// amount to 0 and withdraws all liquid T from the stake to the - /// owner. Reverts if there is at least one non-zero authorization. - /// Can be called only by the delegation owner or the staking - /// provider. Can only be called when 24h passed since the stake - /// has been delegated. - function unstakeAll(address stakingProvider) - external - override - onlyOwnerOrStakingProvider(stakingProvider) - { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - require( - stakingProviderStruct.authorizedApplications.length == 0, - "Stake still authorized" - ); - require( - stakingProviderStruct.startStakingTimestamp + MIN_STAKE_TIME <= - /* solhint-disable-next-line not-rely-on-time */ - block.timestamp, - "Can't unstake earlier than 24h" - ); - - uint96 unstaked = stakingProviderStruct.tStake + - stakingProviderStruct.keepInTStake + - stakingProviderStruct.nuInTStake; - emit Unstaked(stakingProvider, unstaked); - uint96 amount = stakingProviderStruct.tStake; - stakingProviderStruct.tStake = 0; - stakingProviderStruct.keepInTStake = 0; - stakingProviderStruct.nuInTStake = 0; - decreaseStakeCheckpoint(stakingProvider, unstaked); - - if (amount > 0) { - token.safeTransfer(stakingProviderStruct.owner, amount); - } - } - - /// @notice Involuntary decrease authorization for all application up to T - /// stake amount for all staking providers in the list. - /// Sets cached legacy stake amount to 0. Can be called by anyone - function forceUnstakeLegacy(address[] memory _stakingProviders) external { - for (uint256 i = 0; i < _stakingProviders.length; i++) { - forceUnstakeLegacy(_stakingProviders[i]); - } - } - // // // Keeping information in sync @@ -941,25 +813,14 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { .authorized; } - /// @notice Returns staked amount of T, Keep and Nu for the specified - /// staking provider. - /// @dev All values are in T denomination - function stakes(address stakingProvider) + /// @notice Returns staked amount of T for the specified staking provider. + function tStake(address stakingProvider) external view override - returns ( - uint96 tStake, - uint96 keepInTStake, - uint96 nuInTStake - ) + returns (uint96) { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - tStake = stakingProviderStruct.tStake; - keepInTStake = stakingProviderStruct.keepInTStake; - nuInTStake = stakingProviderStruct.nuInTStake; + return stakingProviders[stakingProvider].tStake; } /// @notice Returns start staking timestamp. @@ -983,19 +844,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { return stakingProviders[stakingProvider].autoIncrease; } - /// @notice Returns staked amount of NU for the specified staking provider. - function stakedNu(address stakingProvider) - external - view - override - returns (uint256 nuAmount) - { - (nuAmount, ) = convertFromT( - stakingProviders[stakingProvider].nuInTStake, - nucypherRatio - ); - } - /// @notice Gets the stake owner, the beneficiary and the authorizer /// for the specified staking provider address. /// @return owner Stake owner address. @@ -1078,88 +926,8 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { ); } - /// @notice Involuntary decrease authorization for all application up to T - /// stake amount. Sets cached legacy stake amount to 0. - /// Can be called by anyone - function forceUnstakeLegacy(address stakingProvider) public { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - uint96 legacyStake = stakingProviderStruct.keepInTStake + - stakingProviderStruct.nuInTStake; - require(legacyStake > 0, "No legacy stake"); - - // similar to authorizationDecrease method - uint256 applicationsToDelete = 0; - for ( - uint256 i = 0; - i < stakingProviderStruct.authorizedApplications.length; - i++ - ) { - address authorizedApplication = stakingProviderStruct - .authorizedApplications[i]; - AppAuthorization storage authorization = stakingProviderStruct - .authorizations[authorizedApplication]; - uint96 fromAmount = authorization.authorized; - - if (fromAmount <= stakingProviderStruct.tStake) { - continue; - } - authorization.authorized = stakingProviderStruct.tStake; - - bool successful = true; - //slither-disable-next-line calls-loop - try - IApplication(authorizedApplication) - .involuntaryAuthorizationDecrease{ - gas: GAS_LIMIT_AUTHORIZATION_DECREASE - }(stakingProvider, fromAmount, authorization.authorized) - {} catch { - successful = false; - } - if (authorization.deauthorizing > authorization.authorized) { - authorization.deauthorizing = authorization.authorized; - } - emit AuthorizationInvoluntaryDecreased( - stakingProvider, - authorizedApplication, - fromAmount, - authorization.authorized, - successful - ); - if (authorization.authorized == 0) { - applicationsToDelete++; - } - } - if (applicationsToDelete > 0) { - cleanAuthorizedApplications( - stakingProviderStruct, - applicationsToDelete - ); - } - - emit Unstaked(stakingProvider, legacyStake); - stakingProviderStruct.keepInTStake = 0; - stakingProviderStruct.nuInTStake = 0; - decreaseStakeCheckpoint(stakingProvider, legacyStake); - } - - /// @notice Returns minimum possible stake for T, KEEP or NU in T denomination - /// @dev For example, suppose the given staking provider has 10 T, 20 T worth - /// of KEEP, and 30 T worth of NU all staked, and the maximum - /// application authorization is 40 T, then `getMinStaked` for - /// that staking provider returns: - /// * 0 T if KEEP stake type specified i.e. - /// min = 40 T max - (10 T) = 30 T - /// * 10 T if NU stake type specified i.e. - /// min = 40 T max - (10 T) = 30 T - /// * 0 T if T stake type specified i.e. - /// min = 40 T max = 40 T - /// In other words, the minimum stake amount for the specified - /// stake type is the minimum amount of stake of the given type - /// needed to satisfy the maximum application authorization given - /// the staked amounts of the T stake types for that staking provider. - function getMinStaked(address stakingProvider, StakeType stakeTypes) + /// @notice Returns the maximum application authorization + function getMaxAuthorization(address stakingProvider) public view override @@ -1182,16 +950,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { stakingProviderStruct.authorizations[application].authorized ); } - - if (maxAuthorization == 0) { - return 0; - } - if (stakeTypes != StakeType.T) { - maxAuthorization -= MathUpgradeable.min( - maxAuthorization, - stakingProviderStruct.tStake - ); - } return maxAuthorization.toUint96(); } @@ -1231,9 +989,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; - uint96 stakingProviderBalance = stakingProviderStruct.tStake + - stakingProviderStruct.keepInTStake + - stakingProviderStruct.nuInTStake; + uint96 stakingProviderBalance = stakingProviderStruct.tStake; address oldDelegatee = delegates(stakingProvider); _delegates[stakingProvider] = delegatee; emit DelegateChanged(stakingProvider, oldDelegatee, delegatee); @@ -1305,9 +1061,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { slashing.stakingProvider ]; uint96 tAmountToSlash = slashing.amount; - uint96 oldStake = stakingProviderStruct.tStake + - stakingProviderStruct.keepInTStake + - stakingProviderStruct.nuInTStake; + uint96 oldStake = stakingProviderStruct.tStake; // slash T tAmountToBurn = MathUpgradeable .min(tAmountToSlash, stakingProviderStruct.tStake) @@ -1322,21 +1076,18 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { stakingProviderStruct, slashedAmount ); - uint96 newStake = stakingProviderStruct.tStake + - stakingProviderStruct.keepInTStake + - stakingProviderStruct.nuInTStake; + uint96 newStake = stakingProviderStruct.tStake; decreaseStakeCheckpoint(slashing.stakingProvider, oldStake - newStake); } /// @notice Synchronize authorizations (if needed) after slashing stake + //slither-disable-next-line dead-code function authorizationDecrease( address stakingProvider, StakingProviderInfo storage stakingProviderStruct, uint96 slashedAmount ) internal { - uint96 totalStake = stakingProviderStruct.tStake + - stakingProviderStruct.nuInTStake + - stakingProviderStruct.keepInTStake; + uint96 totalStake = stakingProviderStruct.tStake; uint256 applicationsToDelete = 0; for ( uint256 i = 0; @@ -1479,18 +1230,4 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { governance = newGuvnor; emit GovernanceTransferred(oldGuvnor, newGuvnor); } - - /// @notice Returns the amount of legacy tokens that's obtained from - /// `tAmount` T tokens for the given `ratio`, and the T remainder - /// that can't be converted. - function convertFromT(uint96 tAmount, uint256 ratio) - internal - pure - returns (uint256 amount, uint96 tRemainder) - { - //slither-disable-next-line weak-prng - tRemainder = (tAmount % ratio).toUint96(); - uint256 convertibleAmount = tAmount - tRemainder; - amount = (convertibleAmount * CONVERSION_DIVISOR) / ratio; - } } diff --git a/contracts/test/TokenStakingTestSet.sol b/contracts/test/TokenStakingTestSet.sol index abdadc2c..eb54e0b9 100644 --- a/contracts/test/TokenStakingTestSet.sol +++ b/contracts/test/TokenStakingTestSet.sol @@ -149,9 +149,7 @@ contract ManagedGrantMock { } contract ExtendedTokenStaking is TokenStaking { - constructor(T _token, VendingMachine _nucypherVendingMachine) - TokenStaking(_token, _nucypherVendingMachine) - {} + constructor(T _token) TokenStaking(_token) {} function cleanAuthorizedApplications( address stakingProvider, @@ -192,72 +190,3 @@ contract ExtendedTokenStaking is TokenStaking { return stakingProviders[stakingProvider].authorizedApplications; } } - -contract LegacyTokenStaking is TokenStaking { - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(T _token, VendingMachine _nucypherVendingMachine) - TokenStaking(_token, _nucypherVendingMachine) - {} - - function setLegacyStakingProviderDefault(address stakingProvider) external { - setLegacyStakingProvider( - stakingProvider, - stakingProvider, - payable(stakingProvider), - stakingProvider - ); - } - - function addLegacyStake( - address stakingProvider, - uint96 keepInTStake, - uint96 nuInTStake - ) external { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - stakingProviderStruct.keepInTStake += keepInTStake; - stakingProviderStruct.nuInTStake += nuInTStake; - if (stakingProviderStruct.startStakingTimestamp == 0) { - /* solhint-disable-next-line not-rely-on-time */ - stakingProviderStruct.startStakingTimestamp = block.timestamp; - } - increaseStakeCheckpoint(stakingProvider, keepInTStake + nuInTStake); - } - - function forceIncreaseAuthorization( - address stakingProvider, - address application, - uint96 amount - ) external { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - AppAuthorization storage authorization = stakingProviderStruct - .authorizations[application]; - uint96 fromAmount = authorization.authorized; - if (fromAmount == 0) { - stakingProviderStruct.authorizedApplications.push(application); - } - authorization.authorized += amount; - IApplication(application).authorizationIncreased( - stakingProvider, - fromAmount, - authorization.authorized - ); - } - - function setLegacyStakingProvider( - address stakingProvider, - address owner, - address payable beneficiary, - address authorizer - ) public { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - stakingProviderStruct.owner = owner; - stakingProviderStruct.authorizer = authorizer; - stakingProviderStruct.beneficiary = beneficiary; - } -} diff --git a/deploy/07_deploy_token_staking.ts b/deploy/07_deploy_token_staking.ts index 2820f162..bb5c5b26 100644 --- a/deploy/07_deploy_token_staking.ts +++ b/deploy/07_deploy_token_staking.ts @@ -9,12 +9,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployer } = await getNamedAccounts() const T = await deployments.get("T") - const VendingMachineNuCypher = await deployments.get("VendingMachineNuCypher") - const tokenStakingConstructorArgs = [ - T.address, - VendingMachineNuCypher.address, - ] + const tokenStakingConstructorArgs = [T.address] const tokenStakingInitializerArgs = [] // TODO: Consider upgradable deployment also for goerli/sepolia. diff --git a/docs/rfc-1-staking-contract.adoc b/docs/rfc-1-staking-contract.adoc index 6a244fa9..e1b944c7 100644 --- a/docs/rfc-1-staking-contract.adoc +++ b/docs/rfc-1-staking-contract.adoc @@ -297,36 +297,9 @@ will be added to already authorized applications. ==== `unstakeT(address stakingProvider, uint96 amount) external onlyOwnerOrStakingProvider(stakingProvider)` Reduces the liquid T stake amount by `amount` and withdraws `amount` of T -to the owner. Reverts if there is at least one authorization higher than the sum -of a legacy stake and remaining liquid T stake or if the `amount` is higher than -the liquid T stake amount. Can be called only by the owner or the staking provider. - -==== `unstakeKeep(address stakingProvider) external onlyOwnerOrStakingProvider(stakingProvider)` - -Sets the legacy staking contract active stake amount cached in T staking -contract to 0. Reverts if the amount of liquid T staked in T staking contract is -lower than the highest application authorization. This function allows to -unstake from Keep staking contract and sill being able to operate in T network -and earning rewards based on the liquid T staked. Can be called only by the -delegation owner or the staking provider. - -==== `unstakeNu(address stakingProvider, uint96 amount) external onlyOwnerOrStakingProvider(stakingProvider)` - -Sets to 0 the amount of T that is cached from the legacy NU staking contract. -Reverts if there is at least one authorization higher than the sum of remaining -legacy NU stake and native T stake for that staking provider or if the unstaked -amount is higher than the cached legacy stake amount. If succeeded, the legacy -NU stake can be partially or fully undelegated on the legacy NU staking contract. -This function allows to unstake from NU staking contract while still being able -to operate in T network and earning rewards based on the native T staked. -Can be called only by the stake owner or the staking provider. - -==== `unstakeAll(address stakingProvider) external onlyOwnerOrStakingProvider(stakingProvider)` - -Sets cached legacy stake amount to 0, sets the liquid T stake amount to 0 and -withdraws all liquid T from the stake to the owner. Reverts if there is at least one -non-zero authorization. Can be called only by the delegation owner or the -staking provider. +to the owner. Reverts if there is at least one authorization higher than the +remaining liquid T stake or if the `amount` is higher than the liquid T stake amount. +Can be called only by the owner or the staking provider. === Keeping information in sync @@ -369,16 +342,14 @@ each affected application. Returns the authorized stake amount of the staking provider for the application. -==== `stakes(address stakingProvider) external view returns (uint96 tStake, uint96 keepInTStake, uint96 nuInTStake)` +==== `tStake(address stakingProvider) external view returns (uint96)` -Returns staked amount of T, Keep and Nu for the specified staking provider. All values -are in T denomination. +Returns staked amount of T for the specified staking provider. ==== `getStartStakingTimestamp(address stakingProvider) external view returns (uint256)` -Returns start staking timestamp for T/NU stake. This value is set at most once, -and only when a stake is created with T or NU tokens. If a stake is created -from a legacy KEEP stake, this value will remain as zero. +Returns start staking timestamp for T stake. This value is set at most once, +and only when a stake is created with. ==== `getAutoIncreaseFlag(address stakingProvider) external view returns (bool)` @@ -386,10 +357,6 @@ from a legacy KEEP stake, this value will remain as zero. Returns auto-increase flag. If flag is true then any topped up amount will be added to existing authorizations. -==== `stakedNu(address stakingProvider) external view returns (uint256)` - -Returns staked amount of NU for the specified staking provider - ==== `rolesOf(address stakingProvider) external view returns (address owner, address payable beneficiary, address authorizer)` Gets the stake owner, the beneficiary and the authorizer for the specified @@ -403,21 +370,9 @@ Returns length of application array Returns length of slashing queue -==== `getMinStaked(address stakingProvider, StakeType stakeTypes) external view returns (uint96)` - -Returns minimum possible stake for T, KEEP or NU (stake type) in T denomination. -For example, suppose the given staking provider has 10 T, 20 T worth of KEEP, -and 30 T worth of NU all staked, and the maximum application authorization is -40 T, then `getMinStaked` for that staking provider returns: - -* 0 T if KEEP stake type specified i.e. min = 40 T max - (10 T) = 30 T -* 10 T if NU stake type specified i.e. min = 40 T max - (10 T) = 30 T -* 0 T if T stake type specified i.e. min = 40 T max = 40 T +==== `getMaxAuthorization(address stakingProvider) external view returns (uint96)` -In other words, the minimum stake amount for -the specified stake type is the minimum amount of stake of the given type needed -to satisfy the maximum application authorization given the staked amounts of the -T stake type for that staking provider. +Returns the maximum application authorization ==== `getAvailableToAuthorize(address stakingProvider, address application) external view returns (uint96)` diff --git a/test/staking/TokenStaking.test.js b/test/staking/TokenStaking.test.js index 080907b6..abfe7891 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -2,15 +2,10 @@ const { expect } = require("chai") const { helpers } = require("hardhat") const { lastBlockTime, mineBlocks, increaseTime } = helpers.time -const { to1e18, to1ePrecision } = helpers.number +const { to1e18 } = helpers.number const { AddressZero, Zero } = ethers.constants -const StakeTypes = { - NU: 0, - KEEP: 1, - T: 2, -} const ApplicationStatus = { NOT_APPROVED: 0, APPROVED: 1, @@ -21,28 +16,10 @@ const { upgrades } = require("hardhat") describe("TokenStaking", () => { let tToken - let nucypherVendingMachine let application1Mock let application2Mock - const floatingPointDivisor = to1ePrecision(1, 15) const tAllocation = to1e18("4500000000") // 4.5 Billion - const maxKeepWrappedTokens = to1e18("1100000000") // 1.1 Billion - const maxNuWrappedTokens = to1e18("900000000") // 0.9 Billion - const keepRatio = floatingPointDivisor - .mul(tAllocation) - .div(maxKeepWrappedTokens) - const nuRatio = floatingPointDivisor.mul(tAllocation).div(maxNuWrappedTokens) - - function convertToT(amount, ratio) { - amount = ethers.BigNumber.from(amount) - const wrappedRemainder = amount.mod(floatingPointDivisor) - amount = amount.sub(wrappedRemainder) - return { - result: amount.mul(ratio).div(floatingPointDivisor), - remainder: wrappedRemainder, - } - } function rewardFromPenalty(penalty, rewardMultiplier) { return penalty.mul(5).div(100).mul(rewardMultiplier).div(100) @@ -88,20 +65,13 @@ describe("TokenStaking", () => { .connect(deployer) .transfer(otherStaker.address, initialStakerBalance) - const VendingMachine = await ethers.getContractFactory("VendingMachineMock") - nucypherVendingMachine = await VendingMachine.deploy( - maxNuWrappedTokens, - tAllocation - ) - await nucypherVendingMachine.deployed() - - const TokenStaking = await ethers.getContractFactory("LegacyTokenStaking") + const TokenStaking = await ethers.getContractFactory("TokenStaking") const tokenStakingInitializerArgs = [] tokenStaking = await upgrades.deployProxy( TokenStaking, tokenStakingInitializerArgs, { - constructorArgs: [tToken.address, nucypherVendingMachine.address], + constructorArgs: [tToken.address], } ) await tokenStaking.deployed() @@ -292,10 +262,7 @@ describe("TokenStaking", () => { }) it("should set value of stakes", async () => { - await assertStakes(stakingProvider.address, amount, Zero, Zero) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - 0 - ) + await assertStake(stakingProvider.address, amount) }) it("should start staking timestamp", async () => { @@ -321,7 +288,6 @@ describe("TokenStaking", () => { await expect(tx) .to.emit(tokenStaking, "Staked") .withArgs( - StakeTypes.T, staker.address, stakingProvider.address, beneficiary.address, @@ -643,27 +609,9 @@ describe("TokenStaking", () => { application1Mock.address ) ).to.equal(authorizedAmount) - }) - - it("should increase min staked amount in T", async () => { expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) + await tokenStaking.getMaxAuthorization(stakingProvider.address) ).to.equal(authorizedAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) }) it("should decrease available amount to authorize for one application", async () => { @@ -756,6 +704,9 @@ describe("TokenStaking", () => { application1Mock.address ) ).to.equal(amount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(amount) }) it("should decrease available amount to authorize for one application", async () => { @@ -773,27 +724,6 @@ describe("TokenStaking", () => { ).to.equal(amount) }) - it("should increase min staked amount in T", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).to.equal(amount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - it("should inform application", async () => { await assertApplicationStakingProviders( application1Mock, @@ -871,245 +801,6 @@ describe("TokenStaking", () => { }) } ) - - context( - "when caller is authorizer of staking provider with mixed stake", - () => { - const tStake = initialStakerBalance - const keepStake = initialStakerBalance - const keepInTStake = convertToT(keepStake, keepRatio).result - const nuStake = initialStakerBalance - const nuInTStake = convertToT(nuStake, nuRatio).result - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTStake, - nuInTStake - ) - - await tToken.connect(staker).approve(tokenStaking.address, tStake) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tStake) - }) - - context("when authorize more than not legacy staked amount", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - tStake.add(1) - ) - ).to.be.revertedWith("Not enough stake to authorize") - }) - }) - - context("when authorize staked tokens in one tx", () => { - let tx - const notAuthorized = tStake.sub(to1e18(1)) - const authorizedAmount = tStake.sub(notAuthorized) - - beforeEach(async () => { - tx = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount - ) - }) - - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(authorizedAmount) - }) - - it("should increase min staked amount in T only", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).to.equal(authorizedAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should decrease available amount to authorize for one application", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(notAuthorized) - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(tStake) - }) - - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - authorizedAmount, - Zero - ) - }) - - it("should emit AuthorizationIncreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - 0, - authorizedAmount - ) - }) - - context("when authorize to the second application", () => { - let tx2 - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - - tx2 = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - tStake - ) - }) - - it("should increase only one authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(authorizedAmount) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(tStake) - }) - - it("should set min staked amount equal to T stake", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).to.equal(tStake) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should decrease available amount to authorize for the second application", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(notAuthorized) - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(0) - }) - - it("should inform second application", async () => { - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - tStake, - Zero - ) - }) - - it("should emit AuthorizationIncreased", async () => { - await expect(tx2) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application2Mock.address, - 0, - tStake - ) - }) - }) - }) - - context("when authorize more than staked amount in several txs", () => { - it("should revert", async () => { - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - tStake.sub(1) - ) - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - 2 - ) - ).to.be.revertedWith("Not enough stake to authorize") - }) - }) - } - ) }) describe("requestAuthorizationDecrease", () => { @@ -1520,24 +1211,9 @@ describe("TokenStaking", () => { application1Mock.address ) ).to.equal(expectedToAmount) - }) - - it("should decrease min staked amount in T", async () => { expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) + await tokenStaking.getMaxAuthorization(stakingProvider.address) ).to.equal(expectedToAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) }) it("should emit AuthorizationDecreaseApproved", async () => { @@ -1594,27 +1270,9 @@ describe("TokenStaking", () => { application2Mock.address ) ).to.equal(otherAmount) - }) - - it("should decrease min staked amount in T", async () => { expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) + await tokenStaking.getMaxAuthorization(stakingProvider.address) ).to.equal(otherAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) }) it("should emit AuthorizationDecreaseApproved", async () => { @@ -2166,7 +1824,7 @@ describe("TokenStaking", () => { }) it("should update T staked amount", async () => { - await assertStakes(stakingProvider.address, expectedAmount, Zero, Zero) + await assertStake(stakingProvider.address, expectedAmount) }) it("should not update roles", async () => { @@ -2196,22 +1854,10 @@ describe("TokenStaking", () => { ).to.equal(topUpAmount) }) - it("should not increase min staked amount", async () => { + it("should not increase authorized amount", async () => { expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) + await tokenStaking.getMaxAuthorization(stakingProvider.address) ).to.equal(amount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) }) it("should emit ToppedUp event", async () => { @@ -2258,14 +1904,16 @@ describe("TokenStaking", () => { await increaseTime(86400) // +24h - await tokenStaking.connect(staker).unstakeAll(stakingProvider.address) + await tokenStaking + .connect(staker) + .unstakeT(stakingProvider.address, amount) tx = await tokenStaking .connect(staker) .topUp(stakingProvider.address, amount) }) it("should update T staked amount", async () => { - await assertStakes(stakingProvider.address, amount, Zero, Zero) + await assertStake(stakingProvider.address, amount) }) it("should not update start staking timestamp", async () => { @@ -2281,206 +1929,16 @@ describe("TokenStaking", () => { }) }) - context("when staking provider has Keep stake", () => { - const keepAmount = initialStakerBalance - const keepInTAmount = convertToT(keepAmount, keepRatio).result - const topUpAmount = initialStakerBalance.mul(2) - const expectedAmount = keepInTAmount.add(topUpAmount) + context("when auto increase flag is enabled", () => { + const amount = initialStakerBalance.div(2) + const topUpAmount = initialStakerBalance + const expectedAmount = amount.add(topUpAmount) + const authorized1 = amount + const authorized2 = amount.div(2) let tx - let blockTimestamp beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setMinimumStakeAmount(topUpAmount.add(1)) - - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTAmount, - 0 - ) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - await tToken.connect(deployer).transfer(authorizer.address, topUpAmount) - await tToken - .connect(authorizer) - .approve(tokenStaking.address, topUpAmount) - tx = await tokenStaking - .connect(authorizer) - .topUp(stakingProvider.address, topUpAmount) - }) - - it("should update only T staked amount", async () => { - await assertStakes( - stakingProvider.address, - topUpAmount, - keepInTAmount, - Zero - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(topUpAmount) - }) - - it("should transfer tokens to the staking contract", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - topUpAmount - ) - }) - - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) - }) - - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) - }) - - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) - }) - }) - - context("when staking provider has NuCypher stake", () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const topUpAmount = initialStakerBalance.mul(2) - const expectedAmount = nuInTAmount.add(topUpAmount) - let tx - let blockTimestamp - - beforeEach(async () => { - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - staker.address, - staker.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - nuInTAmount - ) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - await tToken - .connect(deployer) - .transfer(stakingProvider.address, topUpAmount) - await tToken - .connect(stakingProvider) - .approve(tokenStaking.address, topUpAmount) - tx = await tokenStaking - .connect(stakingProvider) - .topUp(stakingProvider.address, topUpAmount) - }) - - it("should update only T staked amount", async () => { - await assertStakes( - stakingProvider.address, - topUpAmount, - Zero, - nuInTAmount - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([staker.address, staker.address, staker.address]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(topUpAmount) - }) - - it("should transfer tokens to the staking contract", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - topUpAmount - ) - }) - - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) - }) - - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) - }) - - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) - }) - }) - - context("when auto increase flag is enabled", () => { - const amount = initialStakerBalance.div(2) - const topUpAmount = initialStakerBalance - const expectedAmount = amount.add(topUpAmount) - const authorized1 = amount - const authorized2 = amount.div(2) - let tx - - beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, amount) + await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking .connect(staker) .stake( @@ -2525,7 +1983,7 @@ describe("TokenStaking", () => { }) it("should update T staked amount", async () => { - await assertStakes(stakingProvider.address, expectedAmount, Zero, Zero) + await assertStake(stakingProvider.address, expectedAmount) }) it("should not increase available amount to authorize", async () => { @@ -2543,22 +2001,10 @@ describe("TokenStaking", () => { ).to.equal(amount.sub(authorized2)) }) - it("should increase min staked amount", async () => { + it("should increase authorized amount", async () => { expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) + await tokenStaking.getMaxAuthorization(stakingProvider.address) ).to.equal(expectedAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) }) it("should emit ToppedUp event", async () => { @@ -2738,29 +2184,6 @@ describe("TokenStaking", () => { }) }) - context("when stake is only in Keep and Nu", () => { - it("should revert", async () => { - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - initialStakerBalance, - initialStakerBalance - ) - - const amountToUnstake = 1 - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") - }) - }) - context("when amount to unstake is more than not authorized", () => { it("should revert", async () => { const amount = initialStakerBalance @@ -2843,24 +2266,6 @@ describe("TokenStaking", () => { ).to.be.revertedWith("Can't unstake earlier than 24h") }) }) - - context("when another stake type was topped-up", () => { - it("should revert", async () => { - const nuAmount = initialStakerBalance - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - nuAmount - ) - - const amountToUnstake = amount - await expect( - tokenStaking - .connect(staker) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) - }) }) context("when unstake after minimum staking time passes", () => { @@ -2890,7 +2295,7 @@ describe("TokenStaking", () => { }) it("should update T staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) + await assertStake(stakingProvider.address, Zero) }) it("should not update roles", async () => { @@ -2901,746 +2306,25 @@ describe("TokenStaking", () => { beneficiary.address, authorizer.address, ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should transfer tokens to the staker address", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal(0) - expect(await tToken.balanceOf(staker.address)).to.equal(amount) - }) - - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, amount) - }) - }) - }) - - describe("unstakeKeep", () => { - context("when staking provider has no stake", () => { - it("should revert", async () => { - await expect( - tokenStaking.unstakeKeep(deployer.address) - ).to.be.revertedWith("Not owner or provider") - }) - }) - - context("when caller is not owner or staking provider", () => { - it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) - await expect( - tokenStaking.connect(authorizer).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") - }) - }) - - context("when stake is only in T and Nu", () => { - it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) - - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - initialStakerBalance - ) - - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to unstake") - }) - }) - - context("when authorized amount is more than non-Keep stake", () => { - it("should revert", async () => { - const tAmount = initialStakerBalance - const keepAmount = initialStakerBalance - - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepAmount, - 0 - ) - - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) - - const authorized = tAmount.add(1) - await tokenStaking - .connect(authorizer) - .forceIncreaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - - await expect( - tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Keep stake still authorized") - }) - }) - - context("when authorized amount is less than non-Keep stake", () => { - const tAmount = initialStakerBalance - const keepAmount = initialStakerBalance - const keepInTAmount = convertToT(keepAmount, keepRatio).result - const authorized = tAmount - let tx - let blockTimestamp - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTAmount, - 0 - ) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) - - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - - tx = await tokenStaking - .connect(stakingProvider) - .unstakeKeep(stakingProvider.address) - }) - - it("should set Keep staked amount to zero", async () => { - await assertStakes(stakingProvider.address, tAmount, Zero, Zero) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount.sub(authorized)) - }) - - it("should update min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(tAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, keepInTAmount) - }) - - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) - }) - - it("should decrease the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount - ) - }) - }) - }) - - describe("unstakeNu", () => { - context("when staking provider has no stake", () => { - it("should revert", async () => { - await expect( - tokenStaking.unstakeNu(deployer.address) - ).to.be.revertedWith("Not owner or provider") - }) - }) - - context("when caller is not owner or staking provider", () => { - it("should revert", async () => { - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - initialStakerBalance - ) - await expect( - tokenStaking.connect(authorizer).unstakeNu(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") - }) - }) - - context("when stake is only in Keep and T", () => { - it("should revert", async () => { - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - initialStakerBalance, - 0 - ) - - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, initialStakerBalance) - - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address) - ).to.be.revertedWith("Nothing to unstake") - }) - }) - - context("when amount to unstake is more than not authorized", () => { - it("should revert", async () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - nuInTAmount - ) - - const authorized = nuInTAmount.div(3) - await tokenStaking - .connect(authorizer) - .forceIncreaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address) - ).to.be.revertedWith("NU stake still authorized") - }) - }) - - context("when amount to unstake is less than not authorized", () => { - const tAmount = initialStakerBalance - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const authorized = tAmount - const expectedNuAmount = 0 - const expectedNuInTAmount = 0 - const expectedUnstaked = nuInTAmount - let tx - let blockTimestamp - - beforeEach(async () => { - await tokenStaking.connect(deployer).setMinimumStakeAmount(1) - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - nuInTAmount - ) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) - - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - - await increaseTime(86400) // +24h - tx = await tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address) - }) - - it("should update Nu staked amount", async () => { - await assertStakes( - stakingProvider.address, - tAmount, - Zero, - expectedNuInTAmount - ) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - expectedNuAmount - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - }) - - it("should update min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(tAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, expectedUnstaked) - }) - - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) - }) - - it("should decrease the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount - ) - }) - }) - }) - - describe("unstakeAll", () => { - context("when staking provider has no stake", () => { - it("should revert", async () => { - await expect( - tokenStaking.unstakeAll(deployer.address) - ).to.be.revertedWith("Not owner or provider") - }) - }) - - context("when caller is not owner or staking provider", () => { - it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) - await expect( - tokenStaking.connect(authorizer).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") - }) - }) - - context("when authorized amount is not zero", () => { - it("should revert", async () => { - const amount = initialStakerBalance - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tToken.connect(staker).approve(tokenStaking.address, amount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) - const authorized = 1 - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Stake still authorized") - }) - }) - - context("when unstake T before minimum staking time passes", () => { - it("should revert", async () => { - const amount = initialStakerBalance - const minAmount = 1 - await tToken.connect(staker).approve(tokenStaking.address, amount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) - await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) - - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) - }) - - context("when unstake Nu before minimum staking time passes", () => { - it("should revert", async () => { - const nuAmount = initialStakerBalance - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake(stakingProvider.address, 0, nuAmount) - - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) - }) - - context("when unstake Keep before minimum time passes", () => { - it("should revert", async () => { - const keepAmount = initialStakerBalance - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepAmount, - 0 - ) - - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) - }) - - const contextUnstakeAll = (preparation, tAmount, nuAmount, keepAmount) => { - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const keepInTAmount = convertToT(keepAmount, keepRatio).result - let tx - let blockTimestamp - - beforeEach(async () => { - blockTimestamp = await preparation() - - tx = await tokenStaking - .connect(stakingProvider) - .unstakeAll(stakingProvider.address) - }) - - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should transfer tokens to the staker address", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal(0) - expect(await tToken.balanceOf(staker.address)).to.equal( - initialStakerBalance - ) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - }) - - it("should update min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs( - stakingProvider.address, - nuInTAmount.add(keepInTAmount).add(tAmount) - ) - }) - } - - context( - "when unstake after minimum staking time passes for T stake", - () => { - // subtracting arbitrary values just to keep them different - const tAmount = initialStakerBalance.sub(1) - const nuAmount = initialStakerBalance.sub(2) - const keepAmount = initialStakerBalance.sub(3) - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const keepInTAmount = convertToT(keepAmount, keepRatio).result - - contextUnstakeAll( - async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking.connect(deployer).setMinimumStakeAmount(1) - - // - // stake T - // - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - tAmount - ) - const blockTimestamp = await lastBlockTime() - - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTAmount, - nuInTAmount - ) - - await increaseTime(86400) // +24h - return blockTimestamp - }, - tAmount, - nuAmount, - keepAmount - ) - } - ) - - context( - "when unstake after minimum staking time passes for NU and KEEP stake", - () => { - // subtracting arbitrary values just to keep them different - const tAmount = initialStakerBalance.sub(3) - const nuAmount = initialStakerBalance.sub(1) - const keepAmount = initialStakerBalance.sub(2) - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const keepInTAmount = convertToT(keepAmount, keepRatio).result - - contextUnstakeAll( - async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking.connect(deployer).setMinimumStakeAmount(1) + }) - // - // legacy stake NU and KEEP - // - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTAmount, - nuInTAmount - ) - const blockTimestamp = await lastBlockTime() + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address) + ).to.equal(blockTimestamp) + }) - // - // top-up T - // - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) - - await increaseTime(86400) // +24h - return blockTimestamp - }, - tAmount, - nuAmount, - keepAmount - ) - } - ) + it("should transfer tokens to the staker address", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal(0) + expect(await tToken.balanceOf(staker.address)).to.equal(amount) + }) + + it("should emit Unstaked", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, amount) + }) + }) }) describe("setNotificationReward", () => { @@ -4336,12 +3020,7 @@ describe("TokenStaking", () => { }) it("should update staked amount", async () => { - await assertStakes( - stakingProvider.address, - expectedAmount, - Zero, - Zero - ) + await assertStake(stakingProvider.address, expectedAmount) }) it("should decrease the delegatee voting power", async () => { @@ -4462,7 +3141,7 @@ describe("TokenStaking", () => { }) it("should update staked amount", async () => { - await assertStakes(otherStaker.address, Zero, Zero, Zero) + await assertStake(otherStaker.address, Zero) }) it("should update index of queue", async () => { @@ -4587,12 +3266,12 @@ describe("TokenStaking", () => { await increaseTime(86400) // +24h await tokenStaking .connect(stakingProvider) - .unstakeAll(stakingProvider.address) + .unstakeT(stakingProvider.address, tAmount) tx = await tokenStaking.connect(auxiliaryAccount).processSlashing(1) }) it("should not update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) + await assertStake(stakingProvider.address, Zero) }) it("should update index of queue", async () => { @@ -4723,10 +3402,7 @@ describe("TokenStaking", () => { const ExtendedTokenStaking = await ethers.getContractFactory( "ExtendedTokenStaking" ) - extendedTokenStaking = await ExtendedTokenStaking.deploy( - tToken.address, - nucypherVendingMachine.address - ) + extendedTokenStaking = await ExtendedTokenStaking.deploy(tToken.address) await extendedTokenStaking.deployed() }) @@ -4843,324 +3519,10 @@ describe("TokenStaking", () => { ) }) - describe("forceUnstakeLegacy", () => { - const tAmount = initialStakerBalance - const keepInTStake = convertToT(initialStakerBalance, keepRatio).result - const nuInTStake = convertToT(initialStakerBalance, nuRatio).result - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .stake(stakingProvider.address, staker.address, staker.address, tAmount) - }) - - context("when no legacy stake", () => { - it("should revert", async () => { - await tokenStaking - .connect(staker) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - tAmount - ) - await expect( - tokenStaking["forceUnstakeLegacy(address)"](stakingProvider.address) - ).to.be.revertedWith("No legacy stake") - }) - }) - - context("when authorized only T stake", () => { - let tx - - beforeEach(async () => { - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTStake, - nuInTStake - ) - await tokenStaking - .connect(staker) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - tAmount - ) - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - - tx = await tokenStaking["forceUnstakeLegacy(address)"]( - stakingProvider.address - ) - }) - - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, tAmount, Zero, Zero) - }) - - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) - }) - - it("should not decrease authorized amounts", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount) - }) - - it("should not inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - tAmount, - Zero - ) - }) - - it("should emit Unstaked event", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, nuInTStake.add(keepInTStake)) - }) - }) - - context("when authorized T, KEEP and NU stakes", () => { - const authorized1 = tAmount.add(keepInTStake).add(nuInTStake) - const authorized2 = nuInTStake.add(keepInTStake) - const deauth = authorized1.sub(tAmount).sub(1) - let tx - - beforeEach(async () => { - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTStake, - nuInTStake - ) - await tokenStaking - .connect(staker) - .forceIncreaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized1 - ) - await tokenStaking - .connect(staker) - .forceIncreaseAuthorization( - stakingProvider.address, - application2Mock.address, - authorized2 - ) - await tokenStaking - .connect(staker) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - deauth - ) - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - - tx = await tokenStaking["forceUnstakeLegacy(address)"]( - stakingProvider.address - ) - }) - - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, tAmount, Zero, Zero) - }) - - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) - }) - - it("should decrease authorized amounts", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(tAmount) - }) - - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - tAmount, - Zero - ) - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - tAmount, - Zero - ) - }) - - it("should emit Unstaked and AuthorizationInvoluntaryDecreased event", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, nuInTStake.add(keepInTStake)) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - authorized1, - tAmount, - true - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - application2Mock.address, - authorized2, - tAmount, - true - ) - }) - }) - - context("when unstake multiple legacy stakes", () => { - let tx - - beforeEach(async () => { - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTStake, - 0 - ) - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake(otherStaker.address, 0, nuInTStake) - - await tokenStaking - .connect(staker) - .forceIncreaseAuthorization( - stakingProvider.address, - application1Mock.address, - tAmount.add(keepInTStake) - ) - await tokenStaking - .connect(staker) - .forceIncreaseAuthorization( - otherStaker.address, - application1Mock.address, - nuInTStake - ) - - tx = await tokenStaking["forceUnstakeLegacy(address[])"]([ - stakingProvider.address, - otherStaker.address, - ]) - }) - - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, tAmount, Zero, Zero) - await assertStakes(otherStaker.address, Zero, Zero, Zero) - }) - - it("should decrease authorized amounts", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount) - expect( - await tokenStaking.authorizedStake( - otherStaker.address, - application1Mock.address - ) - ).to.equal(Zero) - }) - - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - tAmount, - Zero - ) - await assertApplicationStakingProviders( - application1Mock, - otherStaker.address, - Zero, - Zero - ) - }) - - it("should emit Unstaked and AuthorizationInvoluntaryDecreased event", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, keepInTStake) - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(otherStaker.address, nuInTStake) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - tAmount.add(keepInTStake), - tAmount, - true - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - otherStaker.address, - application1Mock.address, - nuInTStake, - Zero, - true - ) - }) - }) - }) - - async function assertStakes( - address, - expectedTStake, - expectedKeepInTStake, - expectedNuInTStake - ) { - expect( - (await tokenStaking.stakes(address)).tStake, - "invalid tStake" - ).to.equal(expectedTStake) - expect( - (await tokenStaking.stakes(address)).keepInTStake, - "invalid keepInTStake" - ).to.equal(expectedKeepInTStake) - expect( - (await tokenStaking.stakes(address)).nuInTStake, - "invalid nuInTStake" - ).to.equal(expectedNuInTStake) + async function assertStake(address, expectedTStake) { + expect(await tokenStaking.tStake(address), "invalid tStake").to.equal( + expectedTStake + ) } async function assertApplicationStakingProviders( From 5fb485ec28ae6abc8544b2ad0ede1fc83b663537 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Tue, 28 Nov 2023 10:38:06 -0500 Subject: [PATCH 2/5] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez Co-authored-by: Michalina --- .github/workflows/contracts.yml | 7 ------- .github/workflows/npm.yml | 5 ----- contracts/staking/IStaking.sol | 11 +++++++---- contracts/staking/TokenStaking.sol | 25 +++++++++++++++++-------- docs/rfc-1-staking-contract.adoc | 6 +++--- package.json | 4 ---- test/staking/TokenStaking.test.js | 9 ++++++++- yarn.lock | 8 -------- 8 files changed, 35 insertions(+), 40 deletions(-) diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 61bdeb4c..109bdf9a 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -115,9 +115,6 @@ jobs: - name: Install dependencies run: yarn install --frozen-lockfile - - name: Resolve latest contracts - run: yarn upgrade @keep-network/keep-core@${{ github.event.inputs.environment }} - - name: Configure tenderly env: TENDERLY_TOKEN: ${{ secrets.TENDERLY_TOKEN }} @@ -197,7 +194,6 @@ jobs: # `etherscan-verify` plugins tries to verify them, which is not desired. - name: Prepare for verification on Etherscan run: | - rm -rf ./node_modules/@keep-network/keep-core rm -rf ./external/npm - name: Verify contracts on Etherscan @@ -240,9 +236,6 @@ jobs: - name: Install dependencies run: yarn install --frozen-lockfile - - name: Resolve latest contracts - run: yarn upgrade @keep-network/keep-core@${{ github.event.inputs.environment }} - - name: Deploy contracts env: # Using fake ternary expressions to decide which credentials to use, diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 3c054b81..8c414694 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -27,11 +27,6 @@ jobs: registry-url: "https://registry.npmjs.org" cache: "yarn" - - name: Resolve latest contracts - run: | - yarn upgrade --exact \ - @keep-network/keep-core - # Deploy contracts to a local network to generate deployment artifacts that # are required by dashboard compilation. - name: Deploy contracts diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index 8857b5ce..e15f1126 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -168,10 +168,10 @@ interface IStaking { // // - /// @notice Reduces the liquid T stake amount by the provided amount and + /// @notice Reduces the T stake amount by the provided amount and /// withdraws T to the owner. Reverts if there is at least one - /// authorization higher than the remaining liquid T stake or - /// if the unstake amount is higher than the liquid T stake amount. + /// authorization higher than the remaining T stake or + /// if the unstake amount is higher than the T stake amount. /// Can be called only by the delegation owner or the staking /// provider. function unstakeT(address stakingProvider, uint96 amount) external; @@ -232,7 +232,10 @@ interface IStaking { returns (uint96); /// @notice Returns staked amount of T for the specified staking provider. - function tStake(address stakingProvider) external view returns (uint96); + function stakeAmount(address stakingProvider) + external + view + returns (uint96); /// @notice Returns start staking timestamp. /// @dev This value is set at most once. diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index f7aefc4a..31212028 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -28,8 +28,8 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; /// @notice TokenStaking is the main staking contract of the Threshold Network. -/// Additionally, it serves as application manager for the apps -/// that run on the Threshold Network. +/// It serves as application manager for the apps that run on +/// the Threshold Network. /// @dev TokenStaking is upgradeable, using OpenZeppelin's Upgradeability /// framework. As such, it is required to satisfy OZ's guidelines, like /// restrictions on constructors, immutable variables, base contracts and @@ -39,6 +39,13 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { using PercentUtils for uint256; using SafeCastUpgradeable for uint256; + // enum is used for Staked event to have backward compatibility + enum StakeType { + NU, + KEEP, + T + } + enum ApplicationStatus { NOT_APPROVED, APPROVED, @@ -47,9 +54,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } struct StakingProviderInfo { - uint96 legacyNuInTStake; + uint96 nuInTStake; address owner; - uint96 legacyKeepInTStake; + uint96 keepInTStake; address payable beneficiary; uint96 tStake; address authorizer; @@ -101,6 +108,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { uint256 public slashingQueueIndex; event Staked( + StakeType indexed stakeType, address indexed owner, address indexed stakingProvider, address beneficiary, @@ -263,6 +271,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { increaseStakeCheckpoint(stakingProvider, amount); emit Staked( + StakeType.T, msg.sender, stakingProvider, beneficiary, @@ -641,10 +650,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { // // - /// @notice Reduces the liquid T stake amount by the provided amount and + /// @notice Reduces the T stake amount by the provided amount and /// withdraws T to the owner. Reverts if there is at least one - /// authorization higher than the remaining liquid T stake or - /// if the unstake amount is higher than the liquid T stake amount. + /// authorization higher than the remaining T stake or + /// if the unstake amount is higher than the T stake amount. /// Can be called only by the delegation owner or the staking /// provider. function unstakeT(address stakingProvider, uint96 amount) @@ -814,7 +823,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } /// @notice Returns staked amount of T for the specified staking provider. - function tStake(address stakingProvider) + function stakeAmount(address stakingProvider) external view override diff --git a/docs/rfc-1-staking-contract.adoc b/docs/rfc-1-staking-contract.adoc index e1b944c7..fda9adc1 100644 --- a/docs/rfc-1-staking-contract.adoc +++ b/docs/rfc-1-staking-contract.adoc @@ -296,9 +296,9 @@ will be added to already authorized applications. ==== `unstakeT(address stakingProvider, uint96 amount) external onlyOwnerOrStakingProvider(stakingProvider)` -Reduces the liquid T stake amount by `amount` and withdraws `amount` of T +Reduces the T stake amount by `amount` and withdraws `amount` of T to the owner. Reverts if there is at least one authorization higher than the -remaining liquid T stake or if the `amount` is higher than the liquid T stake amount. +remaining T stake or if the `amount` is higher than the T stake amount. Can be called only by the owner or the staking provider. === Keeping information in sync @@ -342,7 +342,7 @@ each affected application. Returns the authorized stake amount of the staking provider for the application. -==== `tStake(address stakingProvider) external view returns (uint96)` +==== `stakeAmount(address stakingProvider) external view returns (uint96)` Returns staked amount of T for the specified staking provider. diff --git a/package.json b/package.json index dd7f6ff9..b21ae812 100644 --- a/package.json +++ b/package.json @@ -58,12 +58,8 @@ "typescript": "^4.4.4" }, "dependencies": { - "@keep-network/keep-core": ">1.8.1-dev <1.8.1-goerli", "@openzeppelin/contracts": "~4.5.0", "@openzeppelin/contracts-upgradeable": "~4.5.2", "@thesis/solidity-contracts": "github:thesis/solidity-contracts#4985bcf" - }, - "peerDependencies": { - "@keep-network/keep-core": ">1.8.1-dev <1.8.1-goerli" } } diff --git a/test/staking/TokenStaking.test.js b/test/staking/TokenStaking.test.js index abfe7891..bf91e4ee 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -6,6 +6,12 @@ const { to1e18 } = helpers.number const { AddressZero, Zero } = ethers.constants +const StakeTypes = { + NU: 0, + KEEP: 1, + T: 2, +} + const ApplicationStatus = { NOT_APPROVED: 0, APPROVED: 1, @@ -288,6 +294,7 @@ describe("TokenStaking", () => { await expect(tx) .to.emit(tokenStaking, "Staked") .withArgs( + StakeTypes.T, staker.address, stakingProvider.address, beneficiary.address, @@ -3520,7 +3527,7 @@ describe("TokenStaking", () => { }) async function assertStake(address, expectedTStake) { - expect(await tokenStaking.tStake(address), "invalid tStake").to.equal( + expect(await tokenStaking.stakeAmount(address), "invalid tStake").to.equal( expectedTStake ) } diff --git a/yarn.lock b/yarn.lock index 0f302329..e488deef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1269,14 +1269,6 @@ resolved "https://registry.yarnpkg.com/@keep-network/hardhat-helpers/-/hardhat-helpers-0.6.0-pre.8.tgz#6e0722889a0132dabed5182fb32f6424ff4a77d0" integrity sha512-51oLHceBubutBYxfVk2pLjgyhvpcDC1ahKM3V9lOiTa9lbWyY18Dza7rnM9V04kq+8DbweQRM0M9/f+K26nl9g== -"@keep-network/keep-core@>1.8.1-dev <1.8.1-goerli": - version "1.8.1-dev.0" - resolved "https://registry.yarnpkg.com/@keep-network/keep-core/-/keep-core-1.8.1-dev.0.tgz#d95864b25800214de43d8840376a68336cb12055" - integrity sha512-gFXkgN4PYOYCZ14AskL7fZHEFW5mu3BDd+TJKBuKZc1q9CgRMOK+dxpJnSctxmSH1tV+Ln9v9yqlSkfPCoiBHw== - dependencies: - "@openzeppelin/upgrades" "^2.7.2" - openzeppelin-solidity "2.4.0" - "@keep-network/prettier-config-keep@github:keep-network/prettier-config-keep": version "0.0.1" resolved "https://codeload.github.com/keep-network/prettier-config-keep/tar.gz/a1a333e7ac49928a0f6ed39421906dd1e46ab0f3" From c05d8d0a70a4dd1cf1a5d4f3546ac68fec4d3595 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 29 Nov 2023 12:56:37 -0500 Subject: [PATCH 3/5] Updates github workflow and hardhat config, removes "legacy" info from governance contracts Co-authored-by: Michalina --- .github/workflows/contracts.yml | 6 ------ .github/workflows/npm.yml | 3 +++ contracts/governance/StakerGovernorVotes.sol | 6 ++---- contracts/governance/TokenholderGovernorVotes.sol | 11 ++--------- hardhat.config.ts | 7 ------- 5 files changed, 7 insertions(+), 26 deletions(-) diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 109bdf9a..2f8deab6 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -190,12 +190,6 @@ jobs: - name: Install needed dependencies run: yarn install --frozen-lockfile - # If we don't remove the `keep-core` contracts from `node-modules`, the - # `etherscan-verify` plugins tries to verify them, which is not desired. - - name: Prepare for verification on Etherscan - run: | - rm -rf ./external/npm - - name: Verify contracts on Etherscan env: ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 8c414694..5f1857b9 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -27,6 +27,9 @@ jobs: registry-url: "https://registry.npmjs.org" cache: "yarn" + - name: Install needed dependencies + run: yarn install --frozen-lockfile + # Deploy contracts to a local network to generate deployment artifacts that # are required by dashboard compilation. - name: Deploy contracts diff --git a/contracts/governance/StakerGovernorVotes.sol b/contracts/governance/StakerGovernorVotes.sol index c55c1413..a97228a9 100644 --- a/contracts/governance/StakerGovernorVotes.sol +++ b/contracts/governance/StakerGovernorVotes.sol @@ -19,8 +19,7 @@ import "./GovernorParameters.sol"; import "./IVotesHistory.sol"; /// @title StakerGovernorVotes -/// @notice Staker DAO voting power extraction from staked T positions, -// including legacy stakes (NU/KEEP). +/// @notice Staker DAO voting power extraction from staked T positions. abstract contract StakerGovernorVotes is GovernorParameters { IVotesHistory public immutable staking; @@ -29,8 +28,7 @@ abstract contract StakerGovernorVotes is GovernorParameters { } /// @notice Read the voting weight from the snapshot mechanism in the T - /// staking contracts. Note that this also tracks legacy stakes - /// (NU/KEEP). + /// staking contracts. /// @param account Delegate account with T staking voting power /// @param blockNumber The block number to get the vote balance at /// @dev See {IGovernor-getVotes} diff --git a/contracts/governance/TokenholderGovernorVotes.sol b/contracts/governance/TokenholderGovernorVotes.sol index 69a17ba8..79be03e6 100644 --- a/contracts/governance/TokenholderGovernorVotes.sol +++ b/contracts/governance/TokenholderGovernorVotes.sol @@ -20,7 +20,7 @@ import "./IVotesHistory.sol"; /// @title TokenholderGovernorVotes /// @notice Tokenholder DAO voting power extraction from both liquid and staked -/// T token positions, including legacy stakes (NU/KEEP). +/// T token positions. abstract contract TokenholderGovernorVotes is GovernorParameters { IVotesHistory public immutable token; IVotesHistory public immutable staking; @@ -35,10 +35,6 @@ abstract contract TokenholderGovernorVotes is GovernorParameters { /// two voting power sources: /// - Liquid T, tracked by the T token contract /// - Stakes in the T network, tracked by the T staking contract. - /// Note that this also tracks legacy stakes (NU/KEEP); legacy - /// stakes count for tokenholders' voting power, but not for the - /// total voting power of the Tokenholder DAO - /// (see {_getPastTotalSupply}). /// @param account Tokenholder account in the T network /// @param blockNumber The block number to get the vote balance at /// @dev See {IGovernor-getVotes} @@ -57,10 +53,7 @@ abstract contract TokenholderGovernorVotes is GovernorParameters { /// @notice Compute the total voting power for Tokenholder DAO. Note how it /// only uses the token total supply as source, as native T tokens /// that are staked continue existing, but as deposits in the - /// staking contract. However, legacy stakes can't contribute to the - /// total voting power as they're already implicitly counted as part - /// of Vending Machines' liquid balance; hence, we only need to read - /// total voting power from the token. + /// staking contract. /// @param blockNumber The block number to get the vote power at function _getPastTotalSupply(uint256 blockNumber) internal diff --git a/hardhat.config.ts b/hardhat.config.ts index 299a897a..7cbbe25e 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -91,8 +91,6 @@ const config: HardhatUserConfig = { // For hardhat environment we can fork the mainnet, so we need to point it // to the contract artifacts. hardhat: process.env.FORKING_URL ? ["./external/mainnet"] : [], - goerli: ["./external/goerli"], - sepolia: ["./external/sepolia"], mainnet: ["./external/mainnet"], }, }, @@ -106,11 +104,6 @@ const config: HardhatUserConfig = { thresholdCouncil: { mainnet: "0x9F6e831c8F8939DC0C830C6e492e7cEf4f9C2F5f", }, - keepRegistryKeeper: { - default: 1, // same as the deployer - goerli: "0x68ad60CC5e8f3B7cC53beaB321cf0e6036962dBc", - sepolia: "0x68ad60CC5e8f3B7cC53beaB321cf0e6036962dBc", - }, }, mocha: { timeout: 60000, From 0bef9ada8c26abdb4003d55d86d22d0273125a0c Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Wed, 29 Nov 2023 13:47:20 -0500 Subject: [PATCH 4/5] TokenStaking: restricts `topUp` auto increase to only approved apps (not paused, not disabled) --- contracts/staking/TokenStaking.sol | 11 +- test/staking/TokenStaking.test.js | 176 +++++++++++++++++------------ 2 files changed, 116 insertions(+), 71 deletions(-) diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index 31212028..407f8fd7 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -392,7 +392,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// application. /// @dev Calls `authorizationDecreaseRequested` callback /// for each authorized application. See `IApplication`. - function requestAuthorizationDecrease(address stakingProvider) external { + function requestAuthorizationDecrease(address stakingProvider) + external + override + { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; @@ -608,6 +611,12 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { address application = stakingProviderStruct.authorizedApplications[ i ]; + require( + applicationInfo[application].status == + ApplicationStatus.APPROVED, + "Application is not approved" + ); + AppAuthorization storage authorization = stakingProviderStruct .authorizations[application]; uint96 fromAmount = authorization.authorized; diff --git a/test/staking/TokenStaking.test.js b/test/staking/TokenStaking.test.js index bf91e4ee..dd74d9aa 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -1984,89 +1984,125 @@ describe("TokenStaking", () => { await tToken .connect(stakingProvider) .approve(tokenStaking.address, topUpAmount) - tx = await tokenStaking - .connect(stakingProvider) - .topUp(stakingProvider.address, topUpAmount) }) - it("should update T staked amount", async () => { - await assertStake(stakingProvider.address, expectedAmount) - }) + context("when one of applications is paused", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) - it("should not increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(amount.sub(authorized2)) + await expect( + tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, topUpAmount) + ).to.be.revertedWith("Application is not approved") + }) }) - it("should increase authorized amount", async () => { - expect( - await tokenStaking.getMaxAuthorization(stakingProvider.address) - ).to.equal(expectedAmount) - }) + context("when one of applications is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application2Mock.address) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) + await expect( + tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, topUpAmount) + ).to.be.revertedWith("Application is not approved") + }) }) - it("should increase authorized amounts", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedAmount) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(authorized2.add(topUpAmount)) - }) + context("when all applications are approved", () => { + beforeEach(async () => { + tx = await tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, topUpAmount) + }) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - expectedAmount, - Zero - ) - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - authorized2.add(topUpAmount), - Zero - ) - }) + it("should update T staked amount", async () => { + await assertStake(stakingProvider.address, expectedAmount) + }) - it("should emit AuthorizationIncreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( + it("should not increase available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount.sub(authorized2)) + }) + + it("should increase authorized amount", async () => { + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(expectedAmount) + }) + + it("should emit ToppedUp event", async () => { + await expect(tx) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, topUpAmount) + }) + + it("should increase authorized amounts", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(expectedAmount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(authorized2.add(topUpAmount)) + }) + + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, stakingProvider.address, - application1Mock.address, - authorized1, - expectedAmount + expectedAmount, + Zero ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( + await assertApplicationStakingProviders( + application2Mock, stakingProvider.address, - application2Mock.address, - authorized2, - authorized2.add(topUpAmount) + authorized2.add(topUpAmount), + Zero ) + }) + + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + authorized1, + expectedAmount + ) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + authorized2, + authorized2.add(topUpAmount) + ) + }) }) }) }) @@ -2130,7 +2166,7 @@ describe("TokenStaking", () => { .toggleAutoAuthorizationIncrease(stakingProvider.address) }) - it("should enable auto increase flag", async () => { + it("should disable auto increase flag", async () => { expect( await tokenStaking.getAutoIncreaseFlag(stakingProvider.address) ).to.equal(false) From 4d501ce1d80328c04143968f913e4d7fff6b4788 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Thu, 30 Nov 2023 19:04:05 -0500 Subject: [PATCH 5/5] TokenStaking: method to increase authorization for all available apps --- contracts/staking/IStaking.sol | 8 + contracts/staking/TokenStaking.sol | 142 ++-- docs/rfc-1-staking-contract.adoc | 7 + test/staking/TokenStaking.test.js | 1069 +++++++++++++++++++--------- 4 files changed, 850 insertions(+), 376 deletions(-) diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index e15f1126..643ca085 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -73,6 +73,14 @@ interface IStaking { uint96 amount ) external; + /// @notice Increases the authorization of the given staking provider for + /// all applications by all stake amount. Can only be called by + /// the given staking provider’s authorizer. + /// @dev Calls `authorizationIncreased` callback on each application to + /// notify the applications about authorization change. + /// See `IApplication`. + function increaseAuthorization(address stakingProvider) external; + /// @notice Requests decrease of the authorization for the given staking /// provider on the given application by the provided amount. /// It may not change the authorized amount immediatelly. When diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index 407f8fd7..7d67febe 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -340,14 +340,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { uint96 amount ) external override onlyAuthorizerOf(stakingProvider) { require(amount > 0, "Parameters must be specified"); - ApplicationInfo storage applicationStruct = applicationInfo[ - application - ]; - require( - applicationStruct.status == ApplicationStatus.APPROVED, - "Application is not approved" - ); - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; @@ -369,18 +361,64 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { application ); require(availableTValue >= amount, "Not enough stake to authorize"); - authorization.authorized += amount; - emit AuthorizationIncreased( - stakingProvider, - application, - fromAmount, - authorization.authorized - ); - IApplication(application).authorizationIncreased( - stakingProvider, - fromAmount, - authorization.authorized + increaseAuthorizationInternal(stakingProvider, application, amount); + } + + /// @notice Increases the authorization of the given staking provider for + /// all applications by all stake amount. Can only be called by + /// the given staking provider’s authorizer. + /// @dev Calls `authorizationIncreased` callback on each application to + /// notify the applications about authorization change. + /// See `IApplication`. + function increaseAuthorization(address stakingProvider) + external + override + onlyAuthorizerOf(stakingProvider) + { + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + bool increased = false; + uint96 tStake = stakingProviderStruct.tStake; + uint256 authorizedApplications = stakingProviderStruct + .authorizedApplications + .length; + + for (uint256 i = 0; i < applications.length; i++) { + address application = applications[i]; + if ( + applicationInfo[application].status == + ApplicationStatus.DISABLED + ) { + continue; + } + + uint96 amount = getAvailableToAuthorize( + stakingProvider, + application + ); + + // new application + if (amount == tStake) { + stakingProviderStruct.authorizedApplications.push(application); + authorizedApplications++; + } + if (amount > 0) { + increaseAuthorizationInternal( + stakingProvider, + application, + amount + ); + increased = true; + } + } + + require( + authorizationCeiling == 0 || + authorizedApplications <= authorizationCeiling, + "Too many applications" ); + require(increased, "Nothing to increase"); } /// @notice Requests decrease of all authorizations for the given staking @@ -399,7 +437,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; - uint96 deauthorizing = 0; + bool deauthorizing = false; for ( uint256 i = 0; i < stakingProviderStruct.authorizedApplications.length; @@ -417,11 +455,11 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { application, authorized ); - deauthorizing += authorized; + deauthorizing = true; } } - require(deauthorizing > 0, "Nothing was authorized"); + require(deauthorizing, "Nothing was authorized"); } /// @notice Called by the application at its discretion to approve the @@ -611,27 +649,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { address application = stakingProviderStruct.authorizedApplications[ i ]; - require( - applicationInfo[application].status == - ApplicationStatus.APPROVED, - "Application is not approved" - ); - - AppAuthorization storage authorization = stakingProviderStruct - .authorizations[application]; - uint96 fromAmount = authorization.authorized; - authorization.authorized += amount; - emit AuthorizationIncreased( - stakingProvider, - application, - fromAmount, - authorization.authorized - ); - IApplication(application).authorizationIncreased( - stakingProvider, - fromAmount, - authorization.authorized - ); + increaseAuthorizationInternal(stakingProvider, application, amount); } } @@ -991,6 +1009,44 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } } + /// @notice Increases the authorization of the given staking provider for + /// the given application by the given amount. + /// @dev Calls `authorizationIncreased` callback on the given application to + /// notify the application about authorization change. + /// See `IApplication`. + function increaseAuthorizationInternal( + address stakingProvider, + address application, + uint96 amount + ) internal { + ApplicationInfo storage applicationStruct = applicationInfo[ + application + ]; + require( + applicationStruct.status == ApplicationStatus.APPROVED, + "Application is not approved" + ); + + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + AppAuthorization storage authorization = stakingProviderStruct + .authorizations[application]; + uint96 fromAmount = authorization.authorized; + authorization.authorized += amount; + emit AuthorizationIncreased( + stakingProvider, + application, + fromAmount, + authorization.authorized + ); + IApplication(application).authorizationIncreased( + stakingProvider, + fromAmount, + authorization.authorized + ); + } + /// @notice Delegate voting power from the stake associated to the /// `stakingProvider` to a `delegatee` address. Caller must be the owner /// of this stake. diff --git a/docs/rfc-1-staking-contract.adoc b/docs/rfc-1-staking-contract.adoc index fda9adc1..b161c1b0 100644 --- a/docs/rfc-1-staking-contract.adoc +++ b/docs/rfc-1-staking-contract.adoc @@ -222,6 +222,13 @@ the given amount. Calls `authorizationIncreased(address stakingProvider, uint96 callback on the given application to notify the application. Can only be called by the given provider's authorizer. +==== `increaseAuthorization(address stakingProvider) external onlyAuthorizerOf(stakingProvider)` + +Increases the authorization of the given staking provider for all applications by all +stake amount. Calls `authorizationIncreased` callback on each application to notify +the applications about authorization change. Can only be called by the given staking +provider’s authorizer. + ==== `requestAuthorizationDecrease(address stakingProvider, address application, uint96 amount) external onlyAuthorizerOf(stakingProvider)` Requests decrease of the authorization for the given staking provider on the given diff --git a/test/staking/TokenStaking.test.js b/test/staking/TokenStaking.test.js index dd74d9aa..d1f7eab2 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -465,14 +465,28 @@ describe("TokenStaking", () => { }) }) - describe("increaseAuthorization", () => { + describe("increaseAuthorization one application", () => { + const amount = initialStakerBalance + + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + }) + context("when caller is not authorizer", () => { it("should revert", async () => { const amount = initialStakerBalance await expect( tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, amount @@ -481,333 +495,722 @@ describe("TokenStaking", () => { }) }) - context( - "when caller is authorizer of staking provider with T stake", - () => { - const amount = initialStakerBalance - - beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, amount) - await tokenStaking - .connect(staker) - .stake( + context("when application was not approved", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, - beneficiary.address, - authorizer.address, + application1Mock.address, amount ) - }) + ).to.be.revertedWith("Application is not approved") + }) + }) - context("when application was not approved", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - ).to.be.revertedWith("Application is not approved") - }) + context("when application was approved", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + }) + + context("when application was paused", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + ).to.be.revertedWith("Application is not approved") }) + }) - context("when application was approved", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - }) + context("when application is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + ).to.be.revertedWith("Application is not approved") + }) + }) - context("when application was paused", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - ).to.be.revertedWith("Application is not approved") - }) - }) + context("when already authorized maximum applications", () => { + it("should revert", async () => { + await tokenStaking.connect(deployer).setAuthorizationCeiling(1) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + amount + ) + ).to.be.revertedWith("Too many applications") + }) + }) - context("when application is disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - ).to.be.revertedWith("Application is not approved") - }) - }) + context("when authorize more than staked amount", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount.add(1) + ) + ).to.be.revertedWith("Not enough stake to authorize") + }) + }) - context("when already authorized maximum applications", () => { - it("should revert", async () => { - await tokenStaking.connect(deployer).setAuthorizationCeiling(1) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - amount - ) - ).to.be.revertedWith("Too many applications") - }) - }) + context("when authorize staked tokens in one tx", () => { + let tx + const authorizedAmount = amount.div(3) - context("when authorize more than staked amount", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount.add(1) - ) - ).to.be.revertedWith("Not enough stake to authorize") - }) - }) + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + authorizedAmount + ) + }) - context("when authorize staked tokens in one tx", () => { - let tx - const authorizedAmount = amount.div(3) + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(authorizedAmount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(authorizedAmount) + }) - beforeEach(async () => { - tx = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount - ) - }) + it("should decrease available amount to authorize for one application", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount.sub(authorizedAmount)) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount) + }) - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(authorizedAmount) - expect( - await tokenStaking.getMaxAuthorization(stakingProvider.address) - ).to.equal(authorizedAmount) - }) + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + authorizedAmount, + Zero + ) + }) - it("should decrease available amount to authorize for one application", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(amount.sub(authorizedAmount)) - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(amount) - }) + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + 0, + authorizedAmount + ) + }) + }) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, + context("when authorize more than staked amount in several txs", () => { + it("should revert", async () => { + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount.sub(1) + ) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, - authorizedAmount, - Zero + application1Mock.address, + 2 ) - }) - - it("should emit AuthorizationIncreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - 0, - authorizedAmount - ) - }) - }) + ).to.be.revertedWith("Not enough stake to authorize") + }) + }) - context( - "when authorize more than staked amount in several txs", - () => { - it("should revert", async () => { - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount.sub(1) - ) - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - 2 - ) - ).to.be.revertedWith("Not enough stake to authorize") - }) - } - ) - - context("when authorize staked tokens in several txs", () => { - let tx1 - let tx2 - const authorizedAmount1 = amount.sub(1) - const authorizedAmount2 = 1 + context("when authorize staked tokens in several txs", () => { + let tx1 + let tx2 + const authorizedAmount1 = amount.sub(1) + const authorizedAmount2 = 1 - beforeEach(async () => { - tx1 = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount1 - ) - tx2 = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount2 - ) - }) + beforeEach(async () => { + tx1 = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + authorizedAmount1 + ) + tx2 = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + authorizedAmount2 + ) + }) - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(amount) - expect( - await tokenStaking.getMaxAuthorization(stakingProvider.address) - ).to.equal(amount) - }) + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(amount) + }) - it("should decrease available amount to authorize for one application", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(amount) - }) + it("should decrease available amount to authorize for one application", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount) + }) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - amount, - Zero - ) - }) + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + Zero + ) + }) - it("should emit two AuthorizationIncreased", async () => { - await expect(tx1) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - 0, - authorizedAmount1 - ) - await expect(tx2) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - authorizedAmount1, - authorizedAmount1.add(authorizedAmount2) - ) - }) - }) + it("should emit two AuthorizationIncreased", async () => { + await expect(tx1) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + 0, + authorizedAmount1 + ) + await expect(tx2) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + authorizedAmount1, + authorizedAmount1.add(authorizedAmount2) + ) + }) + }) - context("when authorize after full deauthorization", () => { - beforeEach(async () => { - await tokenStaking.connect(deployer).setAuthorizationCeiling(1) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"]( - stakingProvider.address - ) - await application1Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - amount - ) - }) + context("when authorize after full deauthorization", () => { + beforeEach(async () => { + await tokenStaking.connect(deployer).setAuthorizationCeiling(1) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"](stakingProvider.address) + await application1Mock.approveAuthorizationDecrease( + stakingProvider.address + ) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + amount + ) + }) - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(amount) - }) - }) + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount) }) - } - ) + }) + }) + }) + + describe("increaseAuthorization all applications", () => { + const amount = initialStakerBalance + let application3Mock + + beforeEach(async () => { + const ApplicationMock = await ethers.getContractFactory("ApplicationMock") + application3Mock = await ApplicationMock.deploy(tokenStaking.address) + await application3Mock.deployed() + + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + }) + + context("when caller is not authorizer", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(staker) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Not authorizer") + }) + }) + + context("when no approved applications", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Nothing to increase") + }) + }) + + context("when applications were approved", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + }) + + context("when everything has authorized", () => { + it("should revert", async () => { + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Nothing to increase") + }) + }) + + context("when application was paused", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Application is not approved") + }) + }) + + context("when all application are disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .disableApplication(application2Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Nothing to increase") + }) + }) + + context("when more applications than maximum allowed", () => { + it("should revert", async () => { + await tokenStaking.connect(deployer).setAuthorizationCeiling(1) + + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Too many applications") + + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Too many applications") + + await tokenStaking + .connect(deployer) + .approveApplication(application3Mock.address) + await tokenStaking + .connect(deployer) + .disableApplication(application3Mock.address) + + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Too many applications") + }) + }) + + context("when new staker authorizes everything", () => { + let tx + + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + }) + + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(amount) + }) + + it("should decrease available amount to authorize for all applications", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(0) + }) + + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + Zero + ) + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + amount, + Zero + ) + }) + + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + 0, + amount + ) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + 0, + amount + ) + }) + }) + + context("when existing staker authorizes everything", () => { + let tx + const authorized2 = amount.div(3) + + beforeEach(async () => { + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + authorized2 + ) + await tokenStaking + .connect(deployer) + .approveApplication(application3Mock.address) + + tx = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + }) + + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application3Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(amount) + }) + + it("should decrease available amount to authorize for all applications", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application3Mock.address + ) + ).to.equal(0) + }) + + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + Zero + ) + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + amount, + Zero + ) + await assertApplicationStakingProviders( + application3Mock, + stakingProvider.address, + amount, + Zero + ) + }) + + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + authorized2, + amount + ) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application3Mock.address, + 0, + amount + ) + }) + }) + + context("when authorize after full deauthorization", () => { + const authorized3 = amount.div(3) + + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application3Mock.address) + + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"](stakingProvider.address) + await application1Mock.approveAuthorizationDecrease( + stakingProvider.address + ) + await application2Mock.approveAuthorizationDecrease( + stakingProvider.address + ) + await application3Mock.approveAuthorizationDecrease( + stakingProvider.address + ) + + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application3Mock.address, + authorized3 + ) + await tokenStaking + .connect(deployer) + .disableApplication(application3Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + + tx = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + }) + + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application3Mock.address + ) + ).to.equal(authorized3) + }) + + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + 0, + amount + ) + }) + }) + }) }) describe("requestAuthorizationDecrease", () => { @@ -846,7 +1249,7 @@ describe("TokenStaking", () => { .approveApplication(application1Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, amount @@ -974,7 +1377,7 @@ describe("TokenStaking", () => { .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, amount @@ -1122,7 +1525,7 @@ describe("TokenStaking", () => { .approveApplication(application1Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, amount @@ -1177,7 +1580,7 @@ describe("TokenStaking", () => { .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, amount @@ -1247,7 +1650,7 @@ describe("TokenStaking", () => { .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, otherAmount @@ -1306,7 +1709,7 @@ describe("TokenStaking", () => { .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, amount @@ -1442,7 +1845,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, amount @@ -1478,7 +1881,7 @@ describe("TokenStaking", () => { .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, amount @@ -1810,7 +2213,7 @@ describe("TokenStaking", () => { .approveApplication(application1Mock.address) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, amount @@ -1962,14 +2365,14 @@ describe("TokenStaking", () => { .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized1 ) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, authorized2 @@ -2245,7 +2648,7 @@ describe("TokenStaking", () => { const authorized = amount.div(3) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized @@ -2588,7 +2991,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, amount @@ -2607,7 +3010,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, amountToSlash @@ -2649,7 +3052,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized @@ -2665,7 +3068,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, amount @@ -2713,7 +3116,7 @@ describe("TokenStaking", () => { .stake(stakingProvider.address, staker.address, staker.address, amount) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized @@ -2729,7 +3132,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, amount @@ -3001,14 +3404,14 @@ describe("TokenStaking", () => { .delegateVoting(stakingProvider.address, delegatee.address) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, provider1Authorized1 ) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, provider1Authorized2 @@ -3027,14 +3430,14 @@ describe("TokenStaking", () => { await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, provider2Authorized1 ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application2Mock.address, provider2Authorized2 @@ -3130,7 +3533,7 @@ describe("TokenStaking", () => { await expect( tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, auxiliaryAccount.address, 1 @@ -3250,14 +3653,14 @@ describe("TokenStaking", () => { await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, tAmount ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application2Mock.address, tAmount @@ -3371,14 +3774,14 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, authorized ) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized @@ -3407,7 +3810,7 @@ describe("TokenStaking", () => { it("should allow to authorize one more application", async () => { await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized @@ -3415,7 +3818,7 @@ describe("TokenStaking", () => { await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, authorized @@ -3427,7 +3830,7 @@ describe("TokenStaking", () => { await expect( tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, auxiliaryAccount.address, authorized