diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 61bdeb4c..2f8deab6 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 }} @@ -193,13 +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 ./node_modules/@keep-network/keep-core - rm -rf ./external/npm - - name: Verify contracts on Etherscan env: ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} @@ -240,9 +230,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..5f1857b9 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -27,10 +27,8 @@ jobs: registry-url: "https://registry.npmjs.org" cache: "yarn" - - name: Resolve latest contracts - run: | - yarn upgrade --exact \ - @keep-network/keep-core + - 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. 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/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index fa5d60da..643ca085 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 @@ -79,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 @@ -174,41 +176,13 @@ 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 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 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 unstakeAll(address stakingProvider) external; + function unstakeT(address stakingProvider, uint96 amount) external; // // @@ -265,17 +239,11 @@ 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) + /// @notice Returns staked amount of T for the specified staking provider. + function stakeAmount(address stakingProvider) external view - returns ( - uint96 tStake, - uint96 keepInTStake, - uint96 nuInTStake - ); + returns (uint96); /// @notice Returns start staking timestamp. /// @dev This value is set at most once. @@ -290,9 +258,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 +278,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..7d67febe 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). +/// 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 @@ -43,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, @@ -86,9 +89,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; @@ -215,14 +215,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 { @@ -343,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 ]; @@ -372,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 @@ -395,11 +430,14 @@ 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 ]; - 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,21 +649,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { address application = stakingProviderStruct.authorizedApplications[ i ]; - 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); } } @@ -653,13 +677,12 @@ 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 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 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 override @@ -670,7 +693,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 +710,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 +849,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 stakeAmount(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 +880,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 +962,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 +986,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(); } @@ -1215,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. @@ -1231,9 +1063,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 +1135,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 +1150,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 +1304,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..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 @@ -296,37 +303,10 @@ 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. +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 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 @@ -369,16 +349,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)` +==== `stakeAmount(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 +364,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 +377,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/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, 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 080907b6..d1f7eab2 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -2,7 +2,7 @@ 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 @@ -11,6 +11,7 @@ const StakeTypes = { KEEP: 1, T: 2, } + const ApplicationStatus = { NOT_APPROVED: 0, APPROVED: 1, @@ -21,28 +22,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 +71,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 +268,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 () => { @@ -492,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 @@ -508,687 +495,801 @@ 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 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 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 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 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 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 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 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 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") - }) - }) + context("when authorize staked tokens in one tx", () => { + let tx + const authorizedAmount = amount.div(3) - context("when authorize staked tokens in one tx", () => { - let tx - const authorizedAmount = amount.div(3) + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + authorizedAmount + ) + }) - 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) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(authorizedAmount) + }) - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(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 min staked amount in T", 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 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(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + authorizedAmount1 + ) + tx2 = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + authorizedAmount2 + ) + }) - beforeEach(async () => { - tx1 = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount1 - ) - tx2 = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount2 - ) - }) - - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.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 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, - 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 increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(amount) + }) - 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 - ) - }) + 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 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 inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + Zero + ) }) - } - ) - 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 + 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(deployer) - .approveApplication(application1Mock.address) - - await tokenStaking.setLegacyStakingProvider( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - keepInTStake, - nuInTStake + .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 tToken.connect(staker).approve(tokenStaking.address, tStake) await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tStake) + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + amount + ) }) - 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") - }) + 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) }) + }) + }) + }) - 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 - ) - }) + describe("increaseAuthorization all applications", () => { + const amount = initialStakerBalance + let application3Mock - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(authorizedAmount) - }) + beforeEach(async () => { + const ApplicationMock = await ethers.getContractFactory("ApplicationMock") + application3Mock = await ApplicationMock.deploy(tokenStaking.address) + await application3Mock.deployed() - 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) - }) + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + }) - 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) - }) + context("when caller is not authorizer", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(staker) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Not authorizer") + }) + }) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - authorizedAmount, - Zero - ) - }) + context("when no approved applications", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Nothing to increase") + }) + }) - it("should emit AuthorizationIncreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - 0, - authorizedAmount - ) - }) + context("when applications were approved", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + }) - context("when authorize to the second application", () => { - let tx2 + context("when everything has authorized", () => { + it("should revert", async () => { + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Nothing to increase") + }) + }) - tx2 = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - tStake - ) - }) + 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") + }) + }) - 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) - }) + 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") + }) + }) - 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) - }) + context("when more applications than maximum allowed", () => { + it("should revert", async () => { + await tokenStaking.connect(deployer).setAuthorizationCeiling(1) - 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) - }) + await expect( + tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Too many applications") - it("should inform second application", async () => { - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - tStake, - Zero - ) - }) + 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") - it("should emit AuthorizationIncreased", async () => { - await expect(tx2) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application2Mock.address, - 0, - tStake - ) - }) - }) - }) + await tokenStaking + .connect(deployer) + .approveApplication(application3Mock.address) + await tokenStaking + .connect(deployer) + .disableApplication(application3Mock.address) - context("when authorize more than staked amount in several txs", () => { - it("should revert", async () => { - await tokenStaking + await expect( + 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") - }) + ["increaseAuthorization(address)"](stakingProvider.address) + ).to.be.revertedWith("Too many applications") }) - } - ) - }) + }) - describe("requestAuthorizationDecrease", () => { - context("when caller is not authorizer", () => { - it("should revert", async () => { - const amount = initialStakerBalance - await expect( - tokenStaking - .connect(staker) - ["requestAuthorizationDecrease(address,address,uint96)"]( + 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 ) - ).to.be.revertedWith("Not authorizer") + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + 0, + amount + ) + }) }) - }) - context( - "when caller is authorizer of staking provider with T stake", - () => { - const amount = initialStakerBalance + context("when existing staker authorizes everything", () => { + let tx + const authorized2 = amount.div(3) beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking - .connect(staker) - .stake( + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, - beneficiary.address, - authorizer.address, + application1Mock.address, amount ) - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, - application1Mock.address, - amount + application2Mock.address, + authorized2 ) - }) + await tokenStaking + .connect(deployer) + .approveApplication(application3Mock.address) - context("when application was paused", () => { - it("should revert", async () => { - const amount = initialStakerBalance - await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - await expect( - tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - amount - ) - ).to.be.revertedWith("Application is not approved") - }) + tx = await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address)"](stakingProvider.address) }) - context("when application is disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"]( - stakingProvider.address - ) - ).to.be.revertedWith("Application is not approved") - }) + 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", () => { + context("when caller is not authorizer", () => { + it("should revert", async () => { + const amount = initialStakerBalance + await expect( + tokenStaking + .connect(staker) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + ).to.be.revertedWith("Not authorizer") + }) + }) + + 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( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + }) + + context("when application was paused", () => { + it("should revert", async () => { + const amount = initialStakerBalance + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + ).to.be.revertedWith("Application is not approved") + }) + }) + + context("when application is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await expect( + tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"]( + stakingProvider.address + ) + ).to.be.revertedWith("Application is not approved") + }) }) context("when amount to decrease is zero", () => { @@ -1276,7 +1377,7 @@ describe("TokenStaking", () => { .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, amount @@ -1422,1064 +1523,317 @@ describe("TokenStaking", () => { await tokenStaking .connect(deployer) .approveApplication(application1Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - }) - - context("when application was not approved", () => { - it("should revert", async () => { - await expect( - application2Mock.approveAuthorizationDecrease(stakingProvider.address) - ).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( - application1Mock.approveAuthorizationDecrease(stakingProvider.address) - ).to.be.revertedWith("Application is not approved") - }) - }) - - context("when application is disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - application1Mock.approveAuthorizationDecrease(stakingProvider.address) - ).to.be.revertedWith("Application is not approved") - }) - }) - - context("when approve without request", () => { - it("should revert", async () => { - await expect( - application1Mock.approveAuthorizationDecrease(stakingProvider.address) - ).to.be.revertedWith("No deauthorizing in process") - }) - }) - - context("when approve twice", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - amount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"](stakingProvider.address) - application1Mock.approveAuthorizationDecrease(stakingProvider.address) - await expect( - application1Mock.approveAuthorizationDecrease(stakingProvider.address) - ).to.be.revertedWith("No deauthorizing in process") - }) - }) - - context("when approve after request of partial deauthorization", () => { - const amountToDecrease = amount.div(3) - const expectedFromAmount = amount - const expectedToAmount = amount.sub(amountToDecrease) - let tx - - beforeEach(async () => { - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - amountToDecrease - ) - tx = await application1Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - }) - - it("should decrease authorized amount", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedToAmount) - }) - - it("should decrease min staked amount in T", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).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 () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationDecreaseApproved") - .withArgs( - stakingProvider.address, - application1Mock.address, - expectedFromAmount, - expectedToAmount - ) - }) - }) - - context( - "when approve after request of full deauthorization for one app", - () => { - const otherAmount = amount.div(3) - let tx - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - otherAmount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - amount - ) - tx = await application1Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - }) - - it("should decrease authorized amount", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(otherAmount) - }) - - it("should decrease min staked amount in T", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).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 () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationDecreaseApproved") - .withArgs( - stakingProvider.address, - application1Mock.address, - amount, - Zero - ) - }) - } - ) - - context( - "when approve after request of full deauthorization for last app", - () => { - let tx - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - amount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"](stakingProvider.address) - await application1Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - tx = await application2Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - }) - - it("should decrease authorized amount", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(0) - }) - - it("should emit AuthorizationDecreaseApproved", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationDecreaseApproved") - .withArgs( - stakingProvider.address, - application2Mock.address, - amount, - Zero - ) - }) - } - ) - }) - - describe("forceDecreaseAuthorization", () => { - context("when application is not approved", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(auxiliaryAccount) - .forceDecreaseAuthorization( - stakingProvider.address, - application1Mock.address - ) - ).to.be.revertedWith("Application is not disabled") - }) - }) - - context("when application is approved", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await expect( - tokenStaking - .connect(deployer) - .forceDecreaseAuthorization( - stakingProvider.address, - application1Mock.address - ) - ).to.be.revertedWith("Application is not disabled") - }) - }) - - context("when application is paused", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - await expect( - tokenStaking - .connect(staker) - .forceDecreaseAuthorization( - stakingProvider.address, - application1Mock.address - ) - ).to.be.revertedWith("Application is not disabled") - }) - }) - - context("when application was not authorized and got disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - tokenStaking - .connect(deployer) - .forceDecreaseAuthorization( - stakingProvider.address, - application1Mock.address - ) - ).to.be.revertedWith("Application is not authorized") - }) - }) - - context("when application was authorized and got disabled", () => { - const amount = initialStakerBalance - let tx - - beforeEach(async () => { - await tokenStaking.connect(deployer).setAuthorizationCeiling(1) - 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 - ) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"](stakingProvider.address) - - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - - tx = await tokenStaking - .connect(deployer) - .forceDecreaseAuthorization( - stakingProvider.address, - application1Mock.address - ) - }) - - it("should set authorized amount to 0", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - }) - - it("should allow to authorize more applications", async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - amount - ) - }) - - it("should emit AuthorizationDecreaseApproved", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationDecreaseApproved") - .withArgs( - stakingProvider.address, - application1Mock.address, - amount, - 0 - ) - }) - }) - }) - - describe("pauseApplication", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - }) - - context("when caller is not the panic button address", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(deployer) - .pauseApplication(application1Mock.address) - ).to.be.revertedWith("Caller is not the panic button") - }) - }) - - context("when application is disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - ).to.be.revertedWith("Can't pause application") - }) - }) - - context("when application was paused", () => { - it("should revert", async () => { - await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - await expect( - tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - ).to.be.revertedWith("Can't pause application") - }) - }) - - context("when pause active application", () => { - let tx - - beforeEach(async () => { - tx = await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - }) - - it("should pause application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.PAUSED, panicButton.address]) - }) - - it("should keep list of all applications unchanged", async () => { - expect(await tokenStaking.getApplicationsLength()).to.equal(1) - expect(await tokenStaking.applications(0)).to.equal( - application1Mock.address - ) - }) - - it("should emit ApplicationStatusChanged", async () => { - await expect(tx) - .to.emit(tokenStaking, "ApplicationStatusChanged") - .withArgs(application1Mock.address, ApplicationStatus.PAUSED) - }) - }) - }) - - describe("disableApplication", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - }) - - context("when caller is not the governance", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(panicButton) - .disableApplication(application1Mock.address) - ).to.be.revertedWith("Caller is not the governance") - }) - }) - - context("when application is not approved", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(deployer) - .disableApplication(application2Mock.address) - ).to.be.revertedWith("Can't disable application") - }) - }) - - context("when application is disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - ).to.be.revertedWith("Can't disable application") - }) - }) - - const contextDisable = (preparation) => { - let tx - - beforeEach(async () => { - await preparation() - - tx = await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - }) - - it("should disable application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.DISABLED, panicButton.address]) - }) - - it("should keep list of all applications unchanged", async () => { - expect(await tokenStaking.getApplicationsLength()).to.equal(1) - expect(await tokenStaking.applications(0)).to.equal( - application1Mock.address - ) - }) - - it("should emit ApplicationStatusChanged", async () => { - await expect(tx) - .to.emit(tokenStaking, "ApplicationStatusChanged") - .withArgs(application1Mock.address, ApplicationStatus.DISABLED) - }) - } - - context("when disable approved application", () => { - contextDisable(() => {}) - }) - - context("when disable paused application", () => { - contextDisable(async () => { - await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - }) - }) - }) - - describe("setPanicButton", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - }) - - context("when caller is not the governance", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(staker) - .setPanicButton(application1Mock.address, panicButton.address) - ).to.be.revertedWith("Caller is not the governance") - }) - }) - - context("when application was not approved", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(deployer) - .setPanicButton(application2Mock.address, panicButton.address) - ).to.be.revertedWith("Application is not approved") - }) - }) - - context("when application is disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - ).to.be.revertedWith("Application is not approved") - }) - }) - - context("when set panic button address for approved application", () => { - let tx - - beforeEach(async () => { - tx = await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - }) - - it("should set address of panic button", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.APPROVED, panicButton.address]) - }) - - it("should emit PanicButtonSet", async () => { - await expect(tx) - .to.emit(tokenStaking, "PanicButtonSet") - .withArgs(application1Mock.address, panicButton.address) - }) - }) - }) - - describe("setAuthorizationCeiling", () => { - context("when caller is not the governance", () => { - it("should revert", async () => { - await expect( - tokenStaking.connect(staker).setAuthorizationCeiling(1) - ).to.be.revertedWith("Caller is not the governance") - }) - }) - - context("when caller is the governance", () => { - const ceiling = 10 - let tx - - beforeEach(async () => { - tx = await tokenStaking - .connect(deployer) - .setAuthorizationCeiling(ceiling) - }) - - it("should set authorization ceiling", async () => { - expect(await tokenStaking.authorizationCeiling()).to.equal(ceiling) - }) - - it("should emit AuthorizationCeilingSet", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationCeilingSet") - .withArgs(ceiling) - }) - }) - }) - - describe("topUp", () => { - context("when amount is zero", () => { - it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - staker.address, - staker.address, - initialStakerBalance - ) - await expect( - tokenStaking - .connect(stakingProvider) - .topUp(stakingProvider.address, 0) - ).to.be.revertedWith("Parameters must be specified") - }) - }) - - context("when staking provider has no delegated stake", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(stakingProvider) - .topUp(stakingProvider.address, initialStakerBalance) - ).to.be.revertedWith("Nothing to top-up") - }) - }) - - context("when staking provider has T stake", () => { - const amount = initialStakerBalance.div(3) - const topUpAmount = initialStakerBalance.mul(2) - const expectedAmount = amount.add(topUpAmount) - let tx - let blockTimestamp - - beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, amount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - staker.address, - staker.address, - amount - ) - blockTimestamp = await lastBlockTime() - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(staker) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - - 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 T staked amount", async () => { - await assertStakes(stakingProvider.address, expectedAmount, Zero, Zero) - }) - - 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 transfer tokens to the staking contract", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedAmount + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount ) - }) + }) - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(topUpAmount) + context("when application was not approved", () => { + it("should revert", async () => { + await expect( + application2Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("Application is not approved") }) + }) - it("should not increase min staked amount", 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) + 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( + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("Application is not approved") }) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) + context("when application is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await expect( + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("Application is not approved") }) + }) - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) + context("when approve without request", () => { + it("should revert", async () => { + await expect( + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("No deauthorizing in process") }) + }) - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) + context("when approve twice", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + amount + ) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"](stakingProvider.address) + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + await expect( + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("No deauthorizing in process") }) }) - context("when staking provider unstaked T previously", () => { - const amount = initialStakerBalance + context("when approve after request of partial deauthorization", () => { + const amountToDecrease = amount.div(3) + const expectedFromAmount = amount + const expectedToAmount = amount.sub(amountToDecrease) let tx - let blockTimestamp beforeEach(async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, amount.mul(2)) await tokenStaking - .connect(staker) - .stake( + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( stakingProvider.address, - staker.address, - staker.address, - amount + application1Mock.address, + amountToDecrease ) - blockTimestamp = await lastBlockTime() - await tokenStaking - .connect(staker) - .toggleAutoAuthorizationIncrease(stakingProvider.address) - - await increaseTime(86400) // +24h - - await tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - tx = await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, amount) - }) - - it("should update T staked amount", async () => { - await assertStakes(stakingProvider.address, amount, Zero, Zero) + tx = await application1Mock.approveAuthorizationDecrease( + stakingProvider.address + ) }) - it("should not update start staking timestamp", async () => { + it("should decrease authorized amount", async () => { expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(expectedToAmount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(expectedToAmount) }) - it("should emit ToppedUp event", async () => { + it("should emit AuthorizationDecreaseApproved", async () => { await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, amount) + .to.emit(tokenStaking, "AuthorizationDecreaseApproved") + .withArgs( + stakingProvider.address, + application1Mock.address, + expectedFromAmount, + expectedToAmount + ) }) }) - 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) - 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() + context( + "when approve after request of full deauthorization for one app", + () => { + const otherAmount = amount.div(3) + let tx - 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) - }) + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + otherAmount + ) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + tx = await application1Mock.approveAuthorizationDecrease( + stakingProvider.address + ) + }) - it("should update only T staked amount", async () => { - await assertStakes( - stakingProvider.address, - topUpAmount, - keepInTAmount, - Zero - ) - }) + it("should decrease authorized amount", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(otherAmount) + expect( + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(otherAmount) + }) - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) + it("should emit AuthorizationDecreaseApproved", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationDecreaseApproved") + .withArgs( + stakingProvider.address, + application1Mock.address, + amount, + Zero + ) + }) + } + ) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + context( + "when approve after request of full deauthorization for last app", + () => { + let tx - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + amount + ) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"](stakingProvider.address) + await application1Mock.approveAuthorizationDecrease( + stakingProvider.address ) - ).to.equal(topUpAmount) - }) - - it("should transfer tokens to the staking contract", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - topUpAmount - ) - }) + tx = await application2Mock.approveAuthorizationDecrease( + stakingProvider.address + ) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) - }) + it("should decrease authorized amount", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(0) + }) - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) - }) + it("should emit AuthorizationDecreaseApproved", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationDecreaseApproved") + .withArgs( + stakingProvider.address, + application2Mock.address, + amount, + Zero + ) + }) + } + ) + }) - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) + describe("forceDecreaseAuthorization", () => { + context("when application is not approved", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(auxiliaryAccount) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address + ) + ).to.be.revertedWith("Application is not disabled") }) }) - 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() - + context("when application is approved", () => { + it("should revert", async () => { 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) + .approveApplication(application1Mock.address) + await expect( + tokenStaking + .connect(deployer) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address + ) + ).to.be.revertedWith("Application is not disabled") }) + }) - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) + context("when application is paused", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + await expect( + tokenStaking + .connect(staker) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address + ) + ).to.be.revertedWith("Application is not disabled") }) + }) - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) + context("when application was not authorized and got disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await expect( + tokenStaking + .connect(deployer) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address + ) + ).to.be.revertedWith("Application is not authorized") }) }) - 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) + context("when application was authorized and got disabled", () => { + const amount = initialStakerBalance let tx beforeEach(async () => { + await tokenStaking.connect(deployer).setAuthorizationCeiling(1) + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking .connect(staker) @@ -2489,468 +1843,323 @@ describe("TokenStaking", () => { authorizer.address, amount ) - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized1 - ) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - authorized2 - ) - await tokenStaking - .connect(authorizer) - .toggleAutoAuthorizationIncrease(stakingProvider.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 T staked amount", async () => { - await assertStakes(stakingProvider.address, expectedAmount, Zero, Zero) - }) - - 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 min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(expectedAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, - StakeTypes.KEEP + application1Mock.address, + amount ) - ).to.equal(0) - }) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"](stakingProvider.address) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) - }) + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) - it("should increase authorized amounts", async () => { - expect( - await tokenStaking.authorizedStake( + tx = await tokenStaking + .connect(deployer) + .forceDecreaseAuthorization( stakingProvider.address, application1Mock.address ) - ).to.equal(expectedAmount) + }) + + it("should set authorized amount to 0", async () => { expect( await tokenStaking.authorizedStake( stakingProvider.address, - application2Mock.address + application1Mock.address ) - ).to.equal(authorized2.add(topUpAmount)) + ).to.equal(0) }) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - expectedAmount, - Zero - ) - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - authorized2.add(topUpAmount), - Zero - ) + it("should allow to authorize more applications", async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + amount + ) }) - it("should emit AuthorizationIncreased", async () => { + it("should emit AuthorizationDecreaseApproved", async () => { await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") + .to.emit(tokenStaking, "AuthorizationDecreaseApproved") .withArgs( stakingProvider.address, application1Mock.address, - authorized1, - expectedAmount - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application2Mock.address, - authorized2, - authorized2.add(topUpAmount) + amount, + 0 ) }) }) }) - describe("toggleAutoAuthorizationIncrease", () => { - const amount = initialStakerBalance - + describe("pauseApplication", () => { beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) }) - context("when caller is not authorizer", () => { + context("when caller is not the panic button address", () => { it("should revert", async () => { await expect( tokenStaking - .connect(stakingProvider) - .toggleAutoAuthorizationIncrease(stakingProvider.address) - ).to.be.revertedWith("Not authorizer") + .connect(deployer) + .pauseApplication(application1Mock.address) + ).to.be.revertedWith("Caller is not the panic button") }) }) - context("when method called first time", () => { + context("when application is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await expect( + tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + ).to.be.revertedWith("Can't pause application") + }) + }) + + context("when application was paused", () => { + it("should revert", async () => { + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + await expect( + tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + ).to.be.revertedWith("Can't pause application") + }) + }) + + context("when pause active application", () => { let tx beforeEach(async () => { tx = await tokenStaking - .connect(authorizer) - .toggleAutoAuthorizationIncrease(stakingProvider.address) + .connect(panicButton) + .pauseApplication(application1Mock.address) }) - it("should enable auto increase flag", async () => { + it("should pause application", async () => { expect( - await tokenStaking.getAutoIncreaseFlag(stakingProvider.address) - ).to.equal(true) + await tokenStaking.applicationInfo(application1Mock.address) + ).to.deep.equal([ApplicationStatus.PAUSED, panicButton.address]) }) - it("should emit AutoIncreaseToggled", async () => { + it("should keep list of all applications unchanged", async () => { + expect(await tokenStaking.getApplicationsLength()).to.equal(1) + expect(await tokenStaking.applications(0)).to.equal( + application1Mock.address + ) + }) + + it("should emit ApplicationStatusChanged", async () => { await expect(tx) - .to.emit(tokenStaking, "AutoIncreaseToggled") - .withArgs(stakingProvider.address, true) + .to.emit(tokenStaking, "ApplicationStatusChanged") + .withArgs(application1Mock.address, ApplicationStatus.PAUSED) }) }) + }) - context("when method called second time", () => { + describe("disableApplication", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + }) + + context("when caller is not the governance", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(panicButton) + .disableApplication(application1Mock.address) + ).to.be.revertedWith("Caller is not the governance") + }) + }) + + context("when application is not approved", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(deployer) + .disableApplication(application2Mock.address) + ).to.be.revertedWith("Can't disable application") + }) + }) + + context("when application is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await expect( + tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + ).to.be.revertedWith("Can't disable application") + }) + }) + + const contextDisable = (preparation) => { let tx beforeEach(async () => { - await tokenStaking - .connect(authorizer) - .toggleAutoAuthorizationIncrease(stakingProvider.address) + await preparation() + tx = await tokenStaking - .connect(authorizer) - .toggleAutoAuthorizationIncrease(stakingProvider.address) + .connect(deployer) + .disableApplication(application1Mock.address) }) - it("should enable auto increase flag", async () => { + it("should disable application", async () => { expect( - await tokenStaking.getAutoIncreaseFlag(stakingProvider.address) - ).to.equal(false) + await tokenStaking.applicationInfo(application1Mock.address) + ).to.deep.equal([ApplicationStatus.DISABLED, panicButton.address]) }) - it("should emit AutoIncreaseToggled", async () => { - await expect(tx) - .to.emit(tokenStaking, "AutoIncreaseToggled") - .withArgs(stakingProvider.address, false) + it("should keep list of all applications unchanged", async () => { + expect(await tokenStaking.getApplicationsLength()).to.equal(1) + expect(await tokenStaking.applications(0)).to.equal( + application1Mock.address + ) }) - }) - }) - describe("unstakeT", () => { - context("when staking provider has no stake", () => { - it("should revert", async () => { - await expect( - tokenStaking.unstakeT(deployer.address, 0) - ).to.be.revertedWith("Not owner or provider") + it("should emit ApplicationStatusChanged", async () => { + await expect(tx) + .to.emit(tokenStaking, "ApplicationStatusChanged") + .withArgs(application1Mock.address, ApplicationStatus.DISABLED) }) + } + + context("when disable approved application", () => { + contextDisable(() => {}) }) - context("when caller is not owner or staking provider", () => { - it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) + context("when disable paused application", () => { + contextDisable(async () => { await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) - await expect( - tokenStaking.connect(authorizer).unstakeT(stakingProvider.address, 0) - ).to.be.revertedWith("Not owner or provider") + .connect(panicButton) + .pauseApplication(application1Mock.address) }) }) + }) - context("when amount to unstake is zero", () => { + describe("setPanicButton", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + }) + + context("when caller is not the governance", () => { 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(staker).unstakeT(stakingProvider.address, 0) - ).to.be.revertedWith("Too much to unstake") + tokenStaking + .connect(staker) + .setPanicButton(application1Mock.address, panicButton.address) + ).to.be.revertedWith("Caller is not the governance") }) }) - context("when stake is only in Keep and Nu", () => { + context("when application was not approved", () => { 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") + .connect(deployer) + .setPanicButton(application2Mock.address, panicButton.address) + ).to.be.revertedWith("Application is not approved") }) }) - context("when amount to unstake is more than not authorized", () => { + context("when application is disabled", () => { 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 = amount.div(3) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - - const amountToUnstake = amount.sub(authorized).add(1) + .disableApplication(application1Mock.address) await expect( tokenStaking - .connect(stakingProvider) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + ).to.be.revertedWith("Application is not approved") }) }) - context("when unstake before minimum staking time passes", () => { - const amount = initialStakerBalance - const minAmount = initialStakerBalance.div(3) + context("when set panic button address for approved application", () => { + let tx beforeEach(async () => { - 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) - }) - - context("when the stake left would be above the minimum", () => { - it("should revert", async () => { - const amountToUnstake = amount.sub(minAmount).sub(1) - await expect( - tokenStaking - .connect(staker) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) + tx = await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) }) - context("when the stake left would be the minimum", () => { - it("should revert", async () => { - const amountToUnstake = amount.sub(minAmount) - await expect( - tokenStaking - .connect(staker) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) + it("should set address of panic button", async () => { + expect( + await tokenStaking.applicationInfo(application1Mock.address) + ).to.deep.equal([ApplicationStatus.APPROVED, panicButton.address]) }) - context("when the stake left would be below the minimum", () => { - it("should revert", async () => { - const amountToUnstake = amount.sub(minAmount).add(1) - await expect( - tokenStaking - .connect(staker) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) + it("should emit PanicButtonSet", async () => { + await expect(tx) + .to.emit(tokenStaking, "PanicButtonSet") + .withArgs(application1Mock.address, panicButton.address) }) + }) + }) - 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") - }) + describe("setAuthorizationCeiling", () => { + context("when caller is not the governance", () => { + it("should revert", async () => { + await expect( + tokenStaking.connect(staker).setAuthorizationCeiling(1) + ).to.be.revertedWith("Caller is not the governance") }) }) - context("when unstake after minimum staking time passes", () => { - const amount = initialStakerBalance - const minAmount = initialStakerBalance.div(3) + context("when caller is the governance", () => { + const ceiling = 10 let tx - let blockTimestamp beforeEach(async () => { - await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) - await tToken.connect(staker).approve(tokenStaking.address, amount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) - blockTimestamp = await lastBlockTime() - - await increaseTime(86400) // +24h - tx = await tokenStaking - .connect(stakingProvider) - .unstakeT(stakingProvider.address, amount) - }) - - it("should update T 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) + .connect(deployer) + .setAuthorizationCeiling(ceiling) }) - 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 set authorization ceiling", async () => { + expect(await tokenStaking.authorizationCeiling()).to.equal(ceiling) }) - it("should emit Unstaked", async () => { + it("should emit AuthorizationCeilingSet", async () => { await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, amount) + .to.emit(tokenStaking, "AuthorizationCeilingSet") + .withArgs(ceiling) }) }) }) - 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", () => { + describe("topUp", () => { + context("when amount is zero", () => { it("should revert", async () => { await tToken .connect(staker) @@ -2959,125 +2168,79 @@ describe("TokenStaking", () => { .connect(staker) .stake( stakingProvider.address, - beneficiary.address, - authorizer.address, + staker.address, + staker.address, initialStakerBalance ) - - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - initialStakerBalance - ) - await expect( tokenStaking .connect(stakingProvider) - .unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to unstake") + .topUp(stakingProvider.address, 0) + ).to.be.revertedWith("Parameters must be specified") }) }) - context("when authorized amount is more than non-Keep stake", () => { + context("when staking provider has no delegated 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") + tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, initialStakerBalance) + ).to.be.revertedWith("Nothing to top-up") }) }) - 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() + context("when staking provider has T stake", () => { + const amount = initialStakerBalance.div(3) + const topUpAmount = initialStakerBalance.mul(2) + const expectedAmount = amount.add(topUpAmount) + let tx + let blockTimestamp + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - - await tToken.connect(staker).approve(tokenStaking.address, tAmount) + .stake( + stakingProvider.address, + staker.address, + staker.address, + amount + ) + blockTimestamp = await lastBlockTime() await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) - + .connect(deployer) + .approveApplication(application1Mock.address) await tokenStaking - .connect(authorizer) - .increaseAuthorization( + .connect(staker) + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, - authorized + amount ) + 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) - .unstakeKeep(stakingProvider.address) + .topUp(stakingProvider.address, topUpAmount) }) - it("should set Keep staked amount to zero", async () => { - await assertStakes(stakingProvider.address, tAmount, Zero, Zero) + it("should update T staked amount", async () => { + await assertStake(stakingProvider.address, expectedAmount) }) it("should not update roles", async () => { expect( await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) + ).to.deep.equal([staker.address, staker.address, staker.address]) }) it("should not update start staking timestamp", async () => { @@ -3086,272 +2249,345 @@ describe("TokenStaking", () => { ).to.equal(blockTimestamp) }) - it("should decrease available amount to authorize", async () => { + it("should transfer tokens to the staking contract", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal( + expectedAmount + ) + }) + + it("should increase available amount to authorize", async () => { expect( await tokenStaking.getAvailableToAuthorize( stakingProvider.address, application1Mock.address ) - ).to.equal(tAmount.sub(authorized)) + ).to.equal(topUpAmount) }) - 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) + it("should not increase authorized amount", async () => { expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) + await tokenStaking.getMaxAuthorization(stakingProvider.address) + ).to.equal(amount) }) - it("should emit Unstaked", async () => { + it("should emit ToppedUp event", async () => { await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, keepInTAmount) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, topUpAmount) }) - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) + it("should increase the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal( + expectedAmount + ) }) - it("should decrease the total voting power", async () => { + it("should increase the total voting power", async () => { const lastBlock = await mineBlocks(1) expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount + expectedAmount ) }) }) - }) - 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 staking provider unstaked T previously", () => { + const amount = initialStakerBalance + let tx + let blockTimestamp + + beforeEach(async () => { + await tToken + .connect(staker) + .approve(tokenStaking.address, amount.mul(2)) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + staker.address, + staker.address, + amount + ) + blockTimestamp = await lastBlockTime() + await tokenStaking + .connect(staker) + .toggleAutoAuthorizationIncrease(stakingProvider.address) + + await increaseTime(86400) // +24h + + await tokenStaking + .connect(staker) + .unstakeT(stakingProvider.address, amount) + tx = await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, amount) }) - }) - 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") + it("should update T staked amount", async () => { + await assertStake(stakingProvider.address, amount) + }) + + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address) + ).to.equal(blockTimestamp) + }) + + it("should emit ToppedUp event", async () => { + await expect(tx) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, amount) }) }) - 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 - ) + 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 - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking .connect(staker) - .topUp(stakingProvider.address, initialStakerBalance) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + authorized1 + ) + await tokenStaking + .connect(authorizer) + ["increaseAuthorization(address,address,uint96)"]( + stakingProvider.address, + application2Mock.address, + authorized2 + ) + await tokenStaking + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) - await expect( - tokenStaking + await tToken + .connect(deployer) + .transfer(stakingProvider.address, topUpAmount) + await tToken + .connect(stakingProvider) + .approve(tokenStaking.address, topUpAmount) + }) + + 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) + + await expect( + tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, topUpAmount) + ).to.be.revertedWith("Application is not approved") + }) + }) + + context("when one of applications is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .disableApplication(application2Mock.address) + + await expect( + tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, topUpAmount) + ).to.be.revertedWith("Application is not approved") + }) + }) + + context("when all applications are approved", () => { + beforeEach(async () => { + tx = await tokenStaking .connect(stakingProvider) - .unstakeNu(stakingProvider.address) - ).to.be.revertedWith("Nothing to unstake") + .topUp(stakingProvider.address, topUpAmount) + }) + + it("should update T staked amount", async () => { + await assertStake(stakingProvider.address, expectedAmount) + }) + + 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, + expectedAmount, + Zero + ) + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + 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) + ) + }) }) }) + }) - 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( + describe("toggleAutoAuthorizationIncrease", () => { + const amount = initialStakerBalance + + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( stakingProvider.address, - staker.address, beneficiary.address, - authorizer.address - ) - await tokenStaking.addLegacyStake( - stakingProvider.address, - 0, - nuInTAmount + authorizer.address, + amount ) + }) - const authorized = nuInTAmount.div(3) - await tokenStaking - .connect(authorizer) - .forceIncreaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - + context("when caller is not authorizer", () => { + it("should revert", async () => { await expect( tokenStaking .connect(stakingProvider) - .unstakeNu(stakingProvider.address) - ).to.be.revertedWith("NU stake still authorized") + .toggleAutoAuthorizationIncrease(stakingProvider.address) + ).to.be.revertedWith("Not authorizer") }) }) - 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 + context("when method called first time", () => { 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 - ) + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) }) - it("should not update roles", async () => { + it("should enable auto increase flag", async () => { expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) + await tokenStaking.getAutoIncreaseFlag(stakingProvider.address) + ).to.equal(true) }) - it("should start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) + it("should emit AutoIncreaseToggled", async () => { + await expect(tx) + .to.emit(tokenStaking, "AutoIncreaseToggled") + .withArgs(stakingProvider.address, true) }) + }) - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) + context("when method called second time", () => { + let tx + + beforeEach(async () => { + await tokenStaking + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) + tx = await tokenStaking + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) }) - it("should update min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(tAmount) + it("should disable auto increase flag", async () => { expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) + await tokenStaking.getAutoIncreaseFlag(stakingProvider.address) + ).to.equal(false) }) - it("should emit Unstaked", async () => { + it("should emit AutoIncreaseToggled", 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 - ) + .to.emit(tokenStaking, "AutoIncreaseToggled") + .withArgs(stakingProvider.address, false) }) }) }) - describe("unstakeAll", () => { + describe("unstakeT", () => { context("when staking provider has no stake", () => { it("should revert", async () => { await expect( - tokenStaking.unstakeAll(deployer.address) + tokenStaking.unstakeT(deployer.address, 0) ).to.be.revertedWith("Not owner or provider") }) }) @@ -3370,12 +2606,31 @@ describe("TokenStaking", () => { initialStakerBalance ) await expect( - tokenStaking.connect(authorizer).unstakeAll(stakingProvider.address) + tokenStaking.connect(authorizer).unstakeT(stakingProvider.address, 0) ).to.be.revertedWith("Not owner or provider") }) }) - context("when authorized amount is not zero", () => { + context("when amount to unstake is zero", () => { + 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(staker).unstakeT(stakingProvider.address, 0) + ).to.be.revertedWith("Too much to unstake") + }) + }) + + context("when amount to unstake is more than not authorized", () => { it("should revert", async () => { const amount = initialStakerBalance await tokenStaking @@ -3390,27 +2645,29 @@ describe("TokenStaking", () => { authorizer.address, amount ) - const authorized = 1 + const authorized = amount.div(3) await tokenStaking .connect(authorizer) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized ) + const amountToUnstake = amount.sub(authorized).add(1) await expect( tokenStaking .connect(stakingProvider) - .unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Stake still authorized") + .unstakeT(stakingProvider.address, amountToUnstake) + ).to.be.revertedWith("Too much to unstake") }) }) - context("when unstake T before minimum staking time passes", () => { - it("should revert", async () => { - const amount = initialStakerBalance - const minAmount = 1 + context("when unstake before minimum staking time passes", () => { + const amount = initialStakerBalance + const minAmount = initialStakerBalance.div(3) + + beforeEach(async () => { await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking .connect(staker) @@ -3421,67 +2678,70 @@ describe("TokenStaking", () => { 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 the stake left would be above the minimum", () => { + it("should revert", async () => { + const amountToUnstake = amount.sub(minAmount).sub(1) + await expect( + tokenStaking + .connect(staker) + .unstakeT(stakingProvider.address, amountToUnstake) + ).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 - ) + context("when the stake left would be the minimum", () => { + it("should revert", async () => { + const amountToUnstake = amount.sub(minAmount) + await expect( + tokenStaking + .connect(staker) + .unstakeT(stakingProvider.address, amountToUnstake) + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") + context("when the stake left would be below the minimum", () => { + it("should revert", async () => { + const amountToUnstake = amount.sub(minAmount).add(1) + await expect( + tokenStaking + .connect(staker) + .unstakeT(stakingProvider.address, amountToUnstake) + ).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 + context("when unstake after minimum staking time passes", () => { + const amount = initialStakerBalance + const minAmount = initialStakerBalance.div(3) let tx let blockTimestamp beforeEach(async () => { - blockTimestamp = await preparation() + await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + blockTimestamp = await lastBlockTime() + + await increaseTime(86400) // +24h tx = await tokenStaking .connect(stakingProvider) - .unstakeAll(stakingProvider.address) + .unstakeT(stakingProvider.address, amount) }) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) + it("should update T staked amount", async () => { + await assertStake(stakingProvider.address, Zero) }) it("should not update roles", async () => { @@ -3499,148 +2759,18 @@ describe("TokenStaking", () => { 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 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, - nuInTAmount.add(keepInTAmount).add(tAmount) - ) + .withArgs(stakingProvider.address, amount) }) - } - - 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() - - // - // 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 - ) - } - ) + }) }) describe("setNotificationReward", () => { @@ -3861,7 +2991,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, amount @@ -3880,7 +3010,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, amountToSlash @@ -3922,7 +3052,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application1Mock.address, authorized @@ -3938,7 +3068,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, amount @@ -3986,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 @@ -4002,7 +3132,7 @@ describe("TokenStaking", () => { ) await tokenStaking .connect(otherStaker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( otherStaker.address, application1Mock.address, amount @@ -4274,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 @@ -4300,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 @@ -4336,12 +3466,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 () => { @@ -4408,7 +3533,7 @@ describe("TokenStaking", () => { await expect( tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, auxiliaryAccount.address, 1 @@ -4462,7 +3587,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 () => { @@ -4528,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 @@ -4587,12 +3712,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 () => { @@ -4649,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 @@ -4685,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 @@ -4693,7 +3818,7 @@ describe("TokenStaking", () => { await tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, application2Mock.address, authorized @@ -4705,7 +3830,7 @@ describe("TokenStaking", () => { await expect( tokenStaking .connect(staker) - .increaseAuthorization( + ["increaseAuthorization(address,address,uint96)"]( stakingProvider.address, auxiliaryAccount.address, authorized @@ -4723,10 +3848,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 +3965,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.stakeAmount(address), "invalid tStake").to.equal( + expectedTStake + ) } async function assertApplicationStakingProviders( 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"