diff --git a/contracts/staking/ILegacyTokenStaking.sol b/contracts/staking/ILegacyTokenStaking.sol deleted file mode 100644 index bcfa0f33..00000000 --- a/contracts/staking/ILegacyTokenStaking.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -// ██████████████ ▐████▌ ██████████████ -// ██████████████ ▐████▌ ██████████████ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ██████████████ ▐████▌ ██████████████ -// ██████████████ ▐████▌ ██████████████ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ - -pragma solidity 0.8.9; - -/// @title IKeepTokenStaking -/// @notice Interface for Keep TokenStaking contract -interface IKeepTokenStaking { - /// @notice Seize provided token amount from every member in the misbehaved - /// operators array. The tattletale is rewarded with 5% of the total seized - /// amount scaled by the reward adjustment parameter and the rest 95% is burned. - /// @param amountToSeize Token amount to seize from every misbehaved operator. - /// @param rewardMultiplier Reward adjustment in percentage. Min 1% and 100% max. - /// @param tattletale Address to receive the 5% reward. - /// @param misbehavedOperators Array of addresses to seize the tokens from. - function seize( - uint256 amountToSeize, - uint256 rewardMultiplier, - address tattletale, - address[] memory misbehavedOperators - ) external; - - /// @notice Gets stake delegation info for the given operator. - /// @param operator Operator address. - /// @return amount The amount of tokens the given operator delegated. - /// @return createdAt The time when the stake has been delegated. - /// @return undelegatedAt The time when undelegation has been requested. - /// If undelegation has not been requested, 0 is returned. - function getDelegationInfo(address operator) - external - view - returns ( - uint256 amount, - uint256 createdAt, - uint256 undelegatedAt - ); - - /// @notice Gets the stake owner for the specified operator address. - /// @return Stake owner address. - function ownerOf(address operator) external view returns (address); - - /// @notice Gets the beneficiary for the specified operator address. - /// @return Beneficiary address. - function beneficiaryOf(address operator) - external - view - returns (address payable); - - /// @notice Gets the authorizer for the specified operator address. - /// @return Authorizer address. - function authorizerOf(address operator) external view returns (address); - - /// @notice Gets the eligible stake balance of the specified address. - /// An eligible stake is a stake that passed the initialization period - /// and is not currently undelegating. Also, the operator had to approve - /// the specified operator contract. - /// - /// Operator with a minimum required amount of eligible stake can join the - /// network and participate in new work selection. - /// - /// @param operator address of stake operator. - /// @param operatorContract address of operator contract. - /// @return balance an uint256 representing the eligible stake balance. - function eligibleStake(address operator, address operatorContract) - external - view - returns (uint256 balance); -} - -/// @title INuCypherStakingEscrow -/// @notice Interface for NuCypher StakingEscrow contract -interface INuCypherStakingEscrow { - /// @notice Slash the staker's stake and reward the investigator - /// @param staker Staker's address - /// @param penalty Penalty - /// @param investigator Investigator - /// @param reward Reward for the investigator - function slashStaker( - address staker, - uint256 penalty, - address investigator, - uint256 reward - ) external; - - /// @notice Request merge between NuCypher staking contract and T staking contract. - /// Returns amount of staked tokens - function requestMerge(address staker, address stakingProvider) - external - returns (uint256); - - /// @notice Get all tokens belonging to the staker - function getAllTokens(address staker) external view returns (uint256); -} diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index 44d1907f..24657a02 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -51,25 +51,6 @@ interface IStaking { uint96 amount ) external; - /// @notice Copies delegation from the legacy KEEP staking contract to T - /// staking contract. No tokens are transferred. Caches the active - /// stake amount from KEEP staking contract. Can be called by - /// anyone. - /// @dev The staking provider in T staking contract is the legacy KEEP - /// staking contract operator. - function stakeKeep(address stakingProvider) external; - - /// @notice Copies delegation from the legacy NU staking contract to T - /// staking contract, additionally appointing staking provider, - /// beneficiary and authorizer roles. Caches the amount staked in NU - /// staking contract. Can be called only by the original delegation - /// owner. - function stakeNu( - address stakingProvider, - address payable beneficiary, - address authorizer - ) external; - /// @notice Allows the Governance to set the minimum required stake amount. /// This amount is required to protect against griefing the staking /// contract and individual applications are allowed to require @@ -131,9 +112,9 @@ interface IStaking { /// called by the application that was previously requested to /// decrease the authorization for that staking provider. /// Returns resulting authorized amount for the application. - function approveAuthorizationDecrease(address stakingProvider) - external - returns (uint96); + function approveAuthorizationDecrease( + address stakingProvider + ) external returns (uint96); /// @notice Decreases the authorization for the given `stakingProvider` on /// the given disabled `application`, for all authorized amount. @@ -181,16 +162,6 @@ interface IStaking { /// transfer to the staking contract. function topUp(address stakingProvider, uint96 amount) external; - /// @notice Propagates information about stake top-up from the legacy KEEP - /// staking contract to T staking contract. Can be called only by - /// the owner or the staking provider. - function topUpKeep(address stakingProvider) external; - - /// @notice Propagates information about stake top-up from the legacy NU - /// staking contract to T staking contract. Can be called only by - /// the owner or the staking provider. - function topUpNu(address stakingProvider) external; - // // // Undelegating a stake (unstaking) @@ -214,17 +185,17 @@ interface IStaking { /// called only by the delegation owner or the staking provider. function unstakeKeep(address stakingProvider) external; - /// @notice Reduces cached legacy NU stake amount by the provided amount. - /// Reverts if there is at least one authorization higher than the - /// sum of remaining legacy NU stake and liquid T stake for that - /// staking provider or if the untaked amount is higher than the - /// cached legacy stake amount. If succeeded, the legacy NU stake - /// can be partially or fully undelegated on the legacy staking - /// contract. This function allows to unstake from NU 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 unstakeNu(address stakingProvider, uint96 amount) external; + /// @notice Sets the legacy NU staking contract active stake amount cached + /// in T staking contract to 0. Reverts if there is at least one + /// authorization higher than the sum of remaining legacy NU stake + /// and liquid T stake for that staking provider or if the untaked + /// amount is higher than the cached legacy stake amount. If succeeded, + /// the legacy NU stake can be partially or fully undelegated on + /// the legacy staking contract. This function allows to unstake + /// from NU 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 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 @@ -239,34 +210,6 @@ interface IStaking { // // - /// @notice Notifies about the discrepancy between legacy KEEP active stake - /// and the amount cached in T staking contract. Slashes the staking - /// provider in case the amount cached is higher than the actual - /// active stake amount in KEEP staking contract. Needs to update - /// authorizations of all affected applications and execute an - /// involuntary allocation decrease on all affected applications. - /// Can be called by anyone, notifier receives a reward. - function notifyKeepStakeDiscrepancy(address stakingProvider) external; - - /// @notice Notifies about the discrepancy between legacy NU active stake - /// and the amount cached in T staking contract. Slashes the - /// staking provider in case the amount cached is higher than the - /// actual active stake amount in NU staking contract. Needs to - /// update authorizations of all affected applications and execute - /// an involuntary allocation decrease on all affected applications. - /// Can be called by anyone, notifier receives a reward. - function notifyNuStakeDiscrepancy(address stakingProvider) external; - - /// @notice Sets the penalty amount for stake discrepancy and reward - /// multiplier for reporting it. The penalty is seized from the - /// delegated stake, and 5% of the penalty, scaled by the - /// multiplier, is given to the notifier. The rest of the tokens are - /// burned. Can only be called by the Governance. See `seize` function. - function setStakeDiscrepancyPenalty( - uint96 penalty, - uint256 rewardMultiplier - ) external; - /// @notice Sets reward in T tokens for notification of misbehaviour /// of one staking provider. Can only be called by the governance. function setNotificationReward(uint96 reward) external; @@ -277,8 +220,10 @@ interface IStaking { /// @notice Withdraw some amount of T tokens from notifiers treasury. /// Can only be called by the governance. - function withdrawNotificationReward(address recipient, uint96 amount) - external; + function withdrawNotificationReward( + address recipient, + uint96 amount + ) external; /// @notice Adds staking providers to the slashing queue along with the /// amount that should be slashed from each one of them. Can only be @@ -311,29 +256,26 @@ interface IStaking { /// @notice Returns the authorized stake amount of the staking provider for /// the application. - function authorizedStake(address stakingProvider, address application) - external - view - returns (uint96); + function authorizedStake( + address stakingProvider, + address application + ) external 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) + function stakes( + address stakingProvider + ) external view - returns ( - uint96 tStake, - uint96 keepInTStake, - uint96 nuInTStake - ); + returns (uint96 tStake, uint96 keepInTStake, uint96 nuInTStake); /// @notice Returns start staking timestamp. /// @dev This value is set at most once. - function getStartStakingTimestamp(address stakingProvider) - external - view - returns (uint256); + function getStartStakingTimestamp( + address stakingProvider + ) external view returns (uint256); /// @notice Returns staked amount of NU for the specified staking provider. function stakedNu(address stakingProvider) external view returns (uint256); @@ -343,7 +285,9 @@ interface IStaking { /// @return owner Stake owner address. /// @return beneficiary Beneficiary address. /// @return authorizer Authorizer address. - function rolesOf(address stakingProvider) + function rolesOf( + address stakingProvider + ) external view returns ( @@ -374,10 +318,10 @@ interface IStaking { /// 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 other stake types for that staking provider. - function getMinStaked(address stakingProvider, StakeType stakeTypes) - external - view - returns (uint96); + function getMinStaked( + address stakingProvider, + StakeType stakeTypes + ) external view returns (uint96); /// @notice Returns available amount to authorize for the specified application function getAvailableToAuthorize( diff --git a/contracts/staking/KeepStake.sol b/contracts/staking/KeepStake.sol deleted file mode 100644 index 50e0068d..00000000 --- a/contracts/staking/KeepStake.sol +++ /dev/null @@ -1,318 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -// ██████████████ ▐████▌ ██████████████ -// ██████████████ ▐████▌ ██████████████ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ██████████████ ▐████▌ ██████████████ -// ██████████████ ▐████▌ ██████████████ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ -// ▐████▌ ▐████▌ - -pragma solidity 0.8.9; - -import "./ILegacyTokenStaking.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -/// @title KEEP ManagedGrant contract interface -interface IManagedGrant { - function grantee() external view returns (address); -} - -/// @title KEEP stake owner resolver -/// @notice T network staking contract supports existing KEEP stakes by allowing -/// KEEP stakers to use their stakes in T network and weights them based -/// on KEEP<>T token ratio. KEEP stake owner is cached in T staking -/// contract and used to restrict access to all functions only owner or -/// operator should call. To cache KEEP stake owner in T staking -/// contract, T staking contract first needs to resolve the owner. -/// -/// Resolving liquid KEEP stake owner is easy. Resolving token grant -/// stake owner is complicated and not possible to do on-chain from -/// a contract external to KEEP TokenStaking contract. Keep TokenStaking -/// knows the grant ID but does not expose it externally. -/// -/// KeepStake contract addresses this problem by exposing -/// operator-owner mappings snapshotted off-chain based on events and -/// information publicly available from KEEP TokenStaking contract and -/// KEEP TokenGrant contract. Additionally, it gives the Governance -/// ability to add new mappings in case they are ever needed; in -/// practice, this will be needed only if someone decides to stake their -/// KEEP token grant in KEEP network after 2021-11-11 when the snapshot -/// was taken. -/// -/// Operator-owner pairs were snapshotted 2021-11-11 in the following -/// way: -/// 1. Fetch all TokenStaking events from KEEP staking contract. -/// 2. Filter out undelegated operators. -/// 3. Filter out canceled delegations. -/// 4. Fetch grant stake information from KEEP TokenGrant for that -/// operator to determine if we are dealing with grant delegation. -/// 5. Fetch grantee address from KEEP TokenGrant contract. -/// 6. Check if we are dealing with ManagedGrant by looking for all -/// created ManagedGrants and comparing their address against grantee -/// address fetched from TokenGrant contract. -contract KeepStake is Ownable { - IKeepTokenStaking public immutable keepTokenStaking; - - mapping(address => address) public operatorToManagedGrant; - mapping(address => address) public operatorToGrantee; - - constructor(IKeepTokenStaking _keepTokenStaking) { - keepTokenStaking = _keepTokenStaking; - } - - /// @notice Allows the Governance to set new operator-managed grant pair. - /// This function should only be called for managed grants if - /// the snapshot does include this pair. - function setManagedGrant(address operator, address managedGrant) - external - onlyOwner - { - operatorToManagedGrant[operator] = managedGrant; - } - - /// @notice Allows the Governance to set new operator-grantee pair. - /// This function should only be called for non-managed grants if - /// the snapshot does include this pair. - function setGrantee(address operator, address grantee) external onlyOwner { - operatorToGrantee[operator] = grantee; - } - - /// @notice Resolves KEEP stake owner for the provided operator address. - /// Reverts if could not resolve the owner. - function resolveOwner(address operator) external view returns (address) { - address owner = operatorToManagedGrant[operator]; - if (owner != address(0)) { - return IManagedGrant(owner).grantee(); - } - - owner = operatorToGrantee[operator]; - if (owner != address(0)) { - return owner; - } - - owner = resolveSnapshottedManagedGrantees(operator); - if (owner != address(0)) { - return owner; - } - - owner = resolveSnapshottedGrantees(operator); - if (owner != address(0)) { - return owner; - } - - owner = keepTokenStaking.ownerOf(operator); - require(owner != address(0), "Could not resolve the owner"); - - return owner; - } - - function resolveSnapshottedManagedGrantees(address operator) - internal - view - returns (address) - { - if (operator == 0x855A951162B1B93D70724484d5bdc9D00B56236B) { - return - IManagedGrant(0xFADbF758307A054C57B365Db1De90acA71feaFE5) - .grantee(); - } - if (operator == 0xF1De9490Bf7298b5F350cE74332Ad7cf8d5cB181) { - return - IManagedGrant(0xAEd493Aaf3E76E83b29E151848b71eF4544f92f1) - .grantee(); - } - if (operator == 0x39d2aCBCD80d80080541C6eed7e9feBb8127B2Ab) { - return - IManagedGrant(0xA2fa09D6f8C251422F5fde29a0BAd1C53dEfAe66) - .grantee(); - } - if (operator == 0xd66cAE89FfBc6E50e6b019e45c1aEc93Dec54781) { - return - IManagedGrant(0x306309f9d105F34132db0bFB3Ce3f5B0245Cd386) - .grantee(); - } - if (operator == 0x2eBE08379f4fD866E871A9b9E1d5C695154C6A9F) { - return - IManagedGrant(0xd00c0d43b747C33726B3f0ff4BDA4b72dc53c6E9) - .grantee(); - } - if (operator == 0xA97c34278162b556A527CFc01B53eb4DDeDFD223) { - return - IManagedGrant(0xB3E967355c456B1Bd43cB0188A321592D410D096) - .grantee(); - } - if (operator == 0x6C76d49322C9f8761A1623CEd89A31490cdB649d) { - return - IManagedGrant(0xB3E967355c456B1Bd43cB0188A321592D410D096) - .grantee(); - } - if (operator == 0x4a41c7a884d119eaaefE471D0B3a638226408382) { - return - IManagedGrant(0xcdf3d216d82a463Ce82971F2F5DA3d8f9C5f093A) - .grantee(); - } - if (operator == 0x9c06Feb7Ebc8065ee11Cd5E8EEdaAFb2909A7087) { - return - IManagedGrant(0x45119cd98d145283762BA9eBCAea75F72D188733) - .grantee(); - } - if (operator == 0x9bD818Ab6ACC974f2Cf2BD2EBA7a250126Accb9F) { - return - IManagedGrant(0x6E535043377067621954ee84065b0bd7357e7aBa) - .grantee(); - } - if (operator == 0x1d803c89760F8B4057DB15BCb3B8929E0498D310) { - return - IManagedGrant(0xB3E967355c456B1Bd43cB0188A321592D410D096) - .grantee(); - } - if (operator == 0x3101927DEeC27A2bfA6c4a6316e3A221f631dB91) { - return - IManagedGrant(0x178Bf1946feD0e2362fdF8bcD3f91F0701a012C6) - .grantee(); - } - if (operator == 0x9d9b187E478bC62694A7bED216Fc365de87F280C) { - return - IManagedGrant(0xFBad17CFad6cb00D726c65501D69FdC13Ca5477c) - .grantee(); - } - if (operator == 0xd977144724Bc77FaeFAe219F958AE3947205d0b5) { - return - IManagedGrant(0x087B442BFd4E42675cf2df5fa566F87d7A96Fb12) - .grantee(); - } - if (operator == 0x045E511f53DeBF55c9C0B4522f14F602f7C7cA81) { - return - IManagedGrant(0xFcfe8C036C414a15cF871071c483687095caF7D6) - .grantee(); - } - if (operator == 0x3Dd301b3c96A282d8092E1e6f6846f24172D45C1) { - return - IManagedGrant(0xb5Bdd2D9B3541fc8f581Af37430D26527e59aeF8) - .grantee(); - } - if (operator == 0x5d84DEB482E770479154028788Df79aA7C563aA4) { - return - IManagedGrant(0x9D1a179c469a8BdD0b683A9f9250246cc47e8fBE) - .grantee(); - } - if (operator == 0x1dF927B69A97E8140315536163C029d188e8573b) { - return - IManagedGrant(0xb5Bdd2D9B3541fc8f581Af37430D26527e59aeF8) - .grantee(); - } - if (operator == 0x617daCE069Fbd41993491de211b4DfccdAcbd348) { - return - IManagedGrant(0xb5Bdd2D9B3541fc8f581Af37430D26527e59aeF8) - .grantee(); - } - if (operator == 0x650A9eD18Df873cad98C88dcaC8170531cAD2399) { - return - IManagedGrant(0x1Df7324A3aD20526DFa02Cc803eD2D97Cac81F3b) - .grantee(); - } - if (operator == 0x07C9a8f8264221906b7b8958951Ce4753D39628B) { - return - IManagedGrant(0x305D12b4d70529Cd618dA7399F5520701E510041) - .grantee(); - } - if (operator == 0x63eB4c3DD0751F9BE7070A01156513C227fa1eF6) { - return - IManagedGrant(0x306309f9d105F34132db0bFB3Ce3f5B0245Cd386) - .grantee(); - } - if (operator == 0xc6349eEC31048787676b6297ba71721376A8DdcF) { - return - IManagedGrant(0xac1a985E75C6a0b475b9c807Ad0705a988Be2D99) - .grantee(); - } - if (operator == 0x3B945f9C0C8737e44f8e887d4F04B5B3A491Ac4d) { - return - IManagedGrant(0x82e17477726E8D9D2C237745cA9989631582eE98) - .grantee(); - } - if (operator == 0xF35343299a4f80Dd5D917bbe5ddd54eBB820eBd4) { - return - IManagedGrant(0xCC88c15506251B62ccCeebA193e100d6bBC9a30D) - .grantee(); - } - if (operator == 0x3B9e5ae72d068448bB96786989c0d86FBC0551D1) { - return - IManagedGrant(0x306309f9d105F34132db0bFB3Ce3f5B0245Cd386) - .grantee(); - } - if (operator == 0xB2D53Be158Cb8451dFc818bD969877038c1BdeA1) { - return - IManagedGrant(0xaE55e3800f0A3feaFdcE535A8C0fab0fFdB90DEe) - .grantee(); - } - if (operator == 0xF6dbF7AFe05b8Bb6f198eC7e69333c98D3C4608C) { - return - IManagedGrant(0xbb8D24a20c20625f86739824014C3cBAAAb26700) - .grantee(); - } - if (operator == 0xB62Fc1ADfFb2ab832041528C8178358338d85f76) { - return - IManagedGrant(0x9ED98fD1C29018B9342CB8F57A3073B9695f0c02) - .grantee(); - } - if (operator == 0x9bC8d30d971C9e74298112803036C05db07D73e3) { - return - IManagedGrant(0x66beda757939f8e505b5Eb883cd02C8d4a11Bca2) - .grantee(); - } - - return address(0); - } - - function resolveSnapshottedGrantees(address operator) - internal - pure - returns (address) - { - if (operator == 0x1147ccFB4AEFc6e587a23b78724Ef20Ec6e474D4) { - return 0x3FB49dA4375Ef9019f17990D04c6d5daD482D80a; - } - if (operator == 0x4c21541f95a00C03C75F38C71DC220bd27cbbEd9) { - return 0xC897cfeE43a8d827F76D4226994D5CE5EBBe2571; - } - if (operator == 0x7E6332d18719a5463d3867a1a892359509589a3d) { - return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da; - } - if (operator == 0x8Bd660A764Ca14155F3411a4526a028b6316CB3E) { - return 0xf6f372DfAeCC1431186598c304e91B79Ce115766; - } - if (operator == 0x4F4f0D0dfd93513B3f4Cb116Fe9d0A005466F725) { - return 0x8b055ac1c4dd287E2a46D4a52d61FE76FB551bD0; - } - if (operator == 0x1DF0250027fEC876d8876d1ac7A392c9098F1a1e) { - return 0xE408fFa969707Ce5d7aA3e5F8d44674Fa4b26219; - } - if (operator == 0x860EF3f83B6adFEF757F98345c3B8DdcFCA9d152) { - return 0x08a3633AAb8f3E436DEA204288Ee26Fe094406b0; - } - if (operator == 0xe3a2d16dA142E6B190A5d9F7e0C07cc460B58A5F) { - return 0x875f8fFCDDeD63B5d8Cf54be4E4b82FE6c6E249C; - } - if (operator == 0xBDE07f1cA107Ef319b0Bb26eBF1d0a5b4c97ffc1) { - return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da; - } - if (operator == 0xE86181D6b672d78D33e83029fF3D0ef4A601B4C4) { - return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da; - } - if (operator == 0xb7c561e2069aCaE2c4480111B1606790BB4E13fE) { - return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da; - } - if (operator == 0x526c013f8382B050d32d86e7090Ac84De22EdA4D) { - return 0x61C6E5DDacded540CD08066C08cbc096d22D91f4; - } - - return address(0); - } -} diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index eea0b78a..02b959c7 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -16,9 +16,7 @@ pragma solidity 0.8.9; import "./IApplication.sol"; -import "./ILegacyTokenStaking.sol"; import "./IStaking.sol"; -import "./KeepStake.sol"; import "../governance/Checkpoints.sol"; import "../token/T.sol"; import "../utils/PercentUtils.sol"; @@ -82,27 +80,19 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { uint256 internal constant SLASHING_REWARD_PERCENT = 5; uint256 internal constant MIN_STAKE_TIME = 24 hours; uint256 internal constant GAS_LIMIT_AUTHORIZATION_DECREASE = 250000; - uint256 internal constant CONVERSION_DIVISOR = 10**(18 - 3); + uint256 internal constant CONVERSION_DIVISOR = 10 ** (18 - 3); /// @custom:oz-upgrades-unsafe-allow state-variable-immutable T internal immutable token; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - IKeepTokenStaking internal immutable keepStakingContract; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - KeepStake internal immutable keepStake; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - INuCypherStakingEscrow internal immutable nucypherStakingContract; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - uint256 internal immutable keepRatio; /// @custom:oz-upgrades-unsafe-allow state-variable-immutable uint256 internal immutable nucypherRatio; address public governance; uint96 public minTStakeAmount; uint256 public authorizationCeiling; - uint96 public stakeDiscrepancyPenalty; - uint256 public stakeDiscrepancyRewardMultiplier; + uint96 public legacyStakeDiscrepancyPenalty; + uint256 public legacyStakeDiscrepancyRewardMultiplier; uint256 public notifiersTreasury; uint256 public notificationReward; @@ -164,7 +154,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { uint96 amount, bool indexed discrepancy ); - event StakeDiscrepancyPenaltySet(uint96 penalty, uint256 rewardMultiplier); event NotificationRewardSet(uint96 reward); event NotificationRewardPushed(uint96 reward); event NotificationRewardWithdrawn(address recipient, uint96 amount); @@ -174,11 +163,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { uint256 count, uint256 tAmount ); - event OwnerRefreshed( - address indexed stakingProvider, - address indexed oldOwner, - address indexed newOwner - ); event GovernanceTransferred(address oldGovernance, address newGovernance); modifier onlyGovernance() { @@ -224,34 +208,13 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } /// @param _token Address of T token contract - /// @param _keepStakingContract Address of Keep staking contract - /// @param _nucypherStakingContract Address of NuCypher staking contract - /// @param _keepVendingMachine Address of Keep vending machine /// @param _nucypherVendingMachine Address of NuCypher vending machine - /// @param _keepStake Address of Keep contract with grant owners /// @custom:oz-upgrades-unsafe-allow constructor - constructor( - T _token, - IKeepTokenStaking _keepStakingContract, - INuCypherStakingEscrow _nucypherStakingContract, - VendingMachine _keepVendingMachine, - VendingMachine _nucypherVendingMachine, - KeepStake _keepStake - ) { + constructor(T _token, VendingMachine _nucypherVendingMachine) { // calls to check contracts are working - require( - _token.totalSupply() > 0 && - _keepStakingContract.ownerOf(address(0)) == address(0) && - _nucypherStakingContract.getAllTokens(address(0)) == 0 && - AddressUpgradeable.isContract(address(_keepStake)), - "Wrong input parameters" - ); + require(_token.totalSupply() > 0, "Wrong input parameters"); token = _token; - keepStakingContract = _keepStakingContract; - keepStake = _keepStake; - nucypherStakingContract = _nucypherStakingContract; - keepRatio = _keepVendingMachine.ratio(); nucypherRatio = _nucypherVendingMachine.ratio(); } @@ -285,11 +248,8 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; - (, uint256 createdAt, ) = keepStakingContract.getDelegationInfo( - stakingProvider - ); require( - createdAt == 0 && stakingProviderStruct.owner == address(0), + stakingProviderStruct.owner == address(0), "Provider is already in use" ); require( @@ -317,98 +277,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { token.safeTransferFrom(msg.sender, address(this), amount); } - /// @notice Copies delegation from the legacy KEEP staking contract to T - /// staking contract. No tokens are transferred. Caches the active - /// stake amount from KEEP staking contract. Can be called by - /// anyone. - /// @dev The staking provider in T staking contract is the legacy KEEP - /// staking contract operator. - function stakeKeep(address stakingProvider) external override { - require(stakingProvider != address(0), "Parameters must be specified"); - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - - require( - stakingProviderStruct.owner == address(0), - "Provider is already in use" - ); - - uint96 tAmount = getKeepAmountInT(stakingProvider); - require(tAmount != 0, "Nothing to sync"); - - stakingProviderStruct.keepInTStake = tAmount; - stakingProviderStruct.owner = keepStake.resolveOwner(stakingProvider); - stakingProviderStruct.authorizer = keepStakingContract.authorizerOf( - stakingProvider - ); - stakingProviderStruct.beneficiary = keepStakingContract.beneficiaryOf( - stakingProvider - ); - - /* solhint-disable-next-line not-rely-on-time */ - stakingProviderStruct.startStakingTimestamp = block.timestamp; - - increaseStakeCheckpoint(stakingProvider, tAmount); - - emit Staked( - StakeType.KEEP, - stakingProviderStruct.owner, - stakingProvider, - stakingProviderStruct.beneficiary, - stakingProviderStruct.authorizer, - tAmount - ); - } - - /// @notice Copies delegation from the legacy NU staking contract to T - /// staking contract, additionally appointing beneficiary and - /// authorizer roles. Caches the amount staked in NU staking - /// contract. Can be called only by the original delegation owner. - function stakeNu( - address stakingProvider, - address payable beneficiary, - address authorizer - ) external override { - require( - stakingProvider != address(0) && - beneficiary != address(0) && - authorizer != address(0), - "Parameters must be specified" - ); - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - (, uint256 createdAt, ) = keepStakingContract.getDelegationInfo( - stakingProvider - ); - require( - createdAt == 0 && stakingProviderStruct.owner == address(0), - "Provider is already in use" - ); - - uint96 tAmount = getNuAmountInT(msg.sender, stakingProvider); - require(tAmount > 0, "Nothing to sync"); - - stakingProviderStruct.nuInTStake = tAmount; - stakingProviderStruct.owner = msg.sender; - stakingProviderStruct.authorizer = authorizer; - stakingProviderStruct.beneficiary = beneficiary; - /* solhint-disable-next-line not-rely-on-time */ - stakingProviderStruct.startStakingTimestamp = block.timestamp; - - increaseStakeCheckpoint(stakingProvider, tAmount); - - emit Staked( - StakeType.NU, - msg.sender, - stakingProvider, - beneficiary, - authorizer, - tAmount - ); - } - /// @notice Allows the Governance to set the minimum required stake amount. /// This amount is required to protect against griefing the staking /// contract and individual applications are allowed to require @@ -419,11 +287,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// is just to protect against griefing stake operation. Please note /// that each application may have its own minimum authorization though /// and the authorization can not be higher than the stake. - function setMinimumStakeAmount(uint96 amount) - external - override - onlyGovernance - { + function setMinimumStakeAmount( + uint96 amount + ) external override onlyGovernance { minTStakeAmount = amount; emit MinimumStakeAmountSet(amount); } @@ -436,11 +302,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Allows the Governance to approve the particular application /// before individual stake authorizers are able to authorize it. - function approveApplication(address application) - external - override - onlyGovernance - { + function approveApplication( + address application + ) external override onlyGovernance { require(application != address(0), "Parameters must be specified"); ApplicationInfo storage info = applicationInfo[application]; require( @@ -554,11 +418,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// called by the application that was previously requested to /// decrease the authorization for that staking provider. /// Returns resulting authorized amount for the application. - function approveAuthorizationDecrease(address stakingProvider) - external - override - returns (uint96) - { + function approveAuthorizationDecrease( + address stakingProvider + ) external override returns (uint96) { ApplicationInfo storage applicationStruct = applicationInfo[msg.sender]; require( applicationStruct.status == ApplicationStatus.APPROVED, @@ -627,11 +489,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// application. The paused application can not slash stakes until /// it is approved again by the Governance using `approveApplication` /// function. Should be used only in case of an emergency. - function pauseApplication(address application) - external - override - onlyPanicButtonOf(application) - { + function pauseApplication( + address application + ) external override onlyPanicButtonOf(application) { ApplicationInfo storage applicationStruct = applicationInfo[ application ]; @@ -649,11 +509,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// `forceDecreaseAuthorization` at any moment. Can be called only /// by the governance. The disabled application can't be approved /// again. Should be used only in case of an emergency. - function disableApplication(address application) - external - override - onlyGovernance - { + function disableApplication( + address application + ) external override onlyGovernance { ApplicationInfo storage applicationStruct = applicationInfo[ application ]; @@ -670,11 +528,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// provided address. Can only be called by the Governance. If the /// Panic Button for the given application should be disabled, the /// role address should be set to 0x0 address. - function setPanicButton(address application, address panicButton) - external - override - onlyGovernance - { + function setPanicButton( + address application, + address panicButton + ) external override onlyGovernance { ApplicationInfo storage applicationStruct = applicationInfo[ application ]; @@ -689,11 +546,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Sets the maximum number of applications one staking provider can /// have authorized. Used to protect against DoSing slashing queue. /// Can only be called by the Governance. - function setAuthorizationCeiling(uint256 ceiling) - external - override - onlyGovernance - { + function setAuthorizationCeiling( + uint256 ceiling + ) external override onlyGovernance { authorizationCeiling = ceiling; emit AuthorizationCeilingSet(ceiling); } @@ -722,55 +577,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { token.safeTransferFrom(msg.sender, address(this), amount); } - /// @notice Propagates information about stake top-up from the legacy KEEP - /// staking contract to T staking contract. Can be called only by - /// the owner or the staking provider. - function topUpKeep(address stakingProvider) - external - override - onlyOwnerOrStakingProvider(stakingProvider) - { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - uint96 tAmount = getKeepAmountInT(stakingProvider); - require( - tAmount > stakingProviderStruct.keepInTStake, - "Nothing to top-up" - ); - - uint96 toppedUp = tAmount - stakingProviderStruct.keepInTStake; - emit ToppedUp(stakingProvider, toppedUp); - stakingProviderStruct.keepInTStake = tAmount; - increaseStakeCheckpoint(stakingProvider, toppedUp); - } - - /// @notice Propagates information about stake top-up from the legacy NU - /// staking contract to T staking contract. Can be called only by - /// the owner or the staking provider. - function topUpNu(address stakingProvider) - external - override - onlyOwnerOf(stakingProvider) - { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - uint96 tAmount = getNuAmountInT( - stakingProviderStruct.owner, - stakingProvider - ); - require( - tAmount > stakingProviderStruct.nuInTStake, - "Nothing to top-up" - ); - - uint96 toppedUp = tAmount - stakingProviderStruct.nuInTStake; - emit ToppedUp(stakingProvider, toppedUp); - stakingProviderStruct.nuInTStake = tAmount; - increaseStakeCheckpoint(stakingProvider, toppedUp); - } - // // // Undelegating a stake (unstaking) @@ -784,11 +590,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// 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. - function unstakeT(address stakingProvider, uint96 amount) - external - override - onlyOwnerOrStakingProvider(stakingProvider) - { + function unstakeT( + address stakingProvider, + uint96 amount + ) external override onlyOwnerOrStakingProvider(stakingProvider) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; @@ -823,11 +628,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @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) - { + function unstakeKeep( + address stakingProvider + ) external override onlyOwnerOrStakingProvider(stakingProvider) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; @@ -837,62 +640,42 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { getMinStaked(stakingProvider, StakeType.KEEP) == 0, "Keep 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" - ); emit Unstaked(stakingProvider, keepInTStake); stakingProviderStruct.keepInTStake = 0; decreaseStakeCheckpoint(stakingProvider, keepInTStake); } - /// @notice Reduces cached legacy NU stake amount by the provided amount. - /// Reverts if there is at least one authorization higher than the - /// sum of remaining legacy NU stake and liquid T stake for that - /// staking provider or if the untaked amount is higher than the - /// cached legacy stake amount. If succeeded, the legacy NU stake - /// can be partially or fully undelegated on the legacy staking - /// contract. This function allows to unstake from NU 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. + /// @notice Sets the legacy NU staking contract active stake amount cached + /// in T staking contract to 0. Reverts if there is at least one + /// authorization higher than the sum of remaining legacy NU stake + /// and liquid T stake for that staking provider or if the untaked + /// amount is higher than the cached legacy stake amount. If succeeded, + /// the legacy NU stake can be partially or fully undelegated on + /// the legacy staking contract. This function allows to unstake + /// from NU 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. /// @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 - /// @param amount Amount of NU to unstake in T denomination - function unstakeNu(address stakingProvider, uint96 amount) - external - override - onlyOwnerOrStakingProvider(stakingProvider) - { + function unstakeNu( + address stakingProvider + ) external override onlyOwnerOrStakingProvider(stakingProvider) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; - // rounding amount to guarantee exact T<>NU conversion in both ways, - // so there's no remainder after unstaking - (, uint96 tRemainder) = convertFromT(amount, nucypherRatio); - amount -= tRemainder; + uint96 nuInTStake = stakingProviderStruct.nuInTStake; + require(nuInTStake != 0, "Nothing to unstake"); require( - amount > 0 && - amount + getMinStaked(stakingProvider, StakeType.NU) <= - stakingProviderStruct.nuInTStake, - "Too much to unstake" - ); - require( - stakingProviderStruct.startStakingTimestamp + MIN_STAKE_TIME <= - /* solhint-disable-next-line not-rely-on-time */ - block.timestamp, - "Can't unstake earlier than 24h" + getMinStaked(stakingProvider, StakeType.NU) == 0, + "NU stake still authorized" ); - stakingProviderStruct.nuInTStake -= amount; - decreaseStakeCheckpoint(stakingProvider, amount); - emit Unstaked(stakingProvider, amount); + stakingProviderStruct.nuInTStake = 0; + decreaseStakeCheckpoint(stakingProvider, nuInTStake); + emit Unstaked(stakingProvider, nuInTStake); } /// @notice Sets cached legacy stake amount to 0, sets the liquid T stake @@ -901,11 +684,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// 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) - { + function unstakeAll( + address stakingProvider + ) external override onlyOwnerOrStakingProvider(stakingProvider) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; @@ -941,127 +722,11 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { // // - /// @notice Notifies about the discrepancy between legacy KEEP active stake - /// and the amount cached in T staking contract. Slashes the staking - /// provider in case the amount cached is higher than the actual - /// active stake amount in KEEP staking contract. Needs to update - /// authorizations of all affected applications and execute an - /// involuntary authorization decrease on all affected applications. - /// Can be called by anyone, notifier receives a reward. - function notifyKeepStakeDiscrepancy(address stakingProvider) - external - override - { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - require(stakingProviderStruct.keepInTStake > 0, "Nothing to slash"); - - (uint256 keepStakeAmount, , uint256 undelegatedAt) = keepStakingContract - .getDelegationInfo(stakingProvider); - - (uint96 realKeepInTStake, ) = convertToT(keepStakeAmount, keepRatio); - uint96 oldKeepInTStake = stakingProviderStruct.keepInTStake; - - require( - oldKeepInTStake > realKeepInTStake || undelegatedAt != 0, - "There is no discrepancy" - ); - stakingProviderStruct.keepInTStake = realKeepInTStake; - seizeKeep( - stakingProviderStruct, - stakingProvider, - stakeDiscrepancyPenalty, - stakeDiscrepancyRewardMultiplier - ); - - uint96 slashedAmount = realKeepInTStake - - stakingProviderStruct.keepInTStake; - emit TokensSeized(stakingProvider, slashedAmount, true); - if (undelegatedAt != 0) { - stakingProviderStruct.keepInTStake = 0; - } - - decreaseStakeCheckpoint( - stakingProvider, - oldKeepInTStake - stakingProviderStruct.keepInTStake - ); - - authorizationDecrease( - stakingProvider, - stakingProviderStruct, - slashedAmount - ); - } - - /// @notice Notifies about the discrepancy between legacy NU active stake - /// and the amount cached in T staking contract. Slashes the - /// staking provider in case the amount cached is higher than the - /// actual active stake amount in NU staking contract. Needs to - /// update authorizations of all affected applications and execute an - /// involuntary authorization decrease on all affected applications. - /// Can be called by anyone, notifier receives a reward. - /// @dev Real discrepancy between T and Nu is impossible. - /// This method is a safeguard in case of bugs in NuCypher staking - /// contract - function notifyNuStakeDiscrepancy(address stakingProvider) - external - override - { - StakingProviderInfo storage stakingProviderStruct = stakingProviders[ - stakingProvider - ]; - require(stakingProviderStruct.nuInTStake > 0, "Nothing to slash"); - - uint256 nuStakeAmount = nucypherStakingContract.getAllTokens( - stakingProviderStruct.owner - ); - (uint96 realNuInTStake, ) = convertToT(nuStakeAmount, nucypherRatio); - uint96 oldNuInTStake = stakingProviderStruct.nuInTStake; - require(oldNuInTStake > realNuInTStake, "There is no discrepancy"); - - stakingProviderStruct.nuInTStake = realNuInTStake; - seizeNu( - stakingProviderStruct, - stakeDiscrepancyPenalty, - stakeDiscrepancyRewardMultiplier - ); - - uint96 slashedAmount = realNuInTStake - - stakingProviderStruct.nuInTStake; - emit TokensSeized(stakingProvider, slashedAmount, true); - authorizationDecrease( - stakingProvider, - stakingProviderStruct, - slashedAmount - ); - decreaseStakeCheckpoint( - stakingProvider, - oldNuInTStake - stakingProviderStruct.nuInTStake - ); - } - - /// @notice Sets the penalty amount for stake discrepancy and reward - /// multiplier for reporting it. The penalty is seized from the - /// delegated stake, and 5% of the penalty, scaled by the - /// multiplier, is given to the notifier. The rest of the tokens are - /// burned. Can only be called by the Governance. See `seize` function. - function setStakeDiscrepancyPenalty( - uint96 penalty, - uint256 rewardMultiplier - ) external override onlyGovernance { - stakeDiscrepancyPenalty = penalty; - stakeDiscrepancyRewardMultiplier = rewardMultiplier; - emit StakeDiscrepancyPenaltySet(penalty, rewardMultiplier); - } - /// @notice Sets reward in T tokens for notification of misbehaviour /// of one staking provider. Can only be called by the governance. - function setNotificationReward(uint96 reward) - external - override - onlyGovernance - { + function setNotificationReward( + uint96 reward + ) external override onlyGovernance { notificationReward = reward; emit NotificationRewardSet(reward); } @@ -1077,11 +742,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Withdraw some amount of T tokens from notifiers treasury. /// Can only be called by the governance. - function withdrawNotificationReward(address recipient, uint96 amount) - external - override - onlyGovernance - { + function withdrawNotificationReward( + address recipient, + uint96 amount + ) external override onlyGovernance { require(amount <= notifiersTreasury, "Not enough tokens"); notifiersTreasury -= amount; emit NotificationRewardWithdrawn(recipient, amount); @@ -1095,10 +759,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @dev This method doesn't emit events for providers that are added to /// the queue. If necessary events can be added to the application /// level. - function slash(uint96 amount, address[] memory _stakingProviders) - external - override - { + function slash( + uint96 amount, + address[] memory _stakingProviders + ) external override { notify(amount, 0, address(0), _stakingProviders); } @@ -1153,18 +817,17 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Delegate voting power from the stake associated to the /// `stakingProvider` to a `delegatee` address. Caller must be the /// owner of this stake. - function delegateVoting(address stakingProvider, address delegatee) - external - { + function delegateVoting( + address stakingProvider, + address delegatee + ) external { delegate(stakingProvider, delegatee); } /// @notice Transfers ownership of the contract to `newGuvnor`. - function transferGovernance(address newGuvnor) - external - virtual - onlyGovernance - { + function transferGovernance( + address newGuvnor + ) external virtual onlyGovernance { _transferGovernance(newGuvnor); } @@ -1176,12 +839,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Returns the authorized stake amount of the staking provider for /// the application. - function authorizedStake(address stakingProvider, address application) - external - view - override - returns (uint96) - { + function authorizedStake( + address stakingProvider, + address application + ) external view override returns (uint96) { return stakingProviders[stakingProvider] .authorizations[application] @@ -1191,15 +852,13 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @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) + function stakes( + address stakingProvider + ) external view override - returns ( - uint96 tStake, - uint96 keepInTStake, - uint96 nuInTStake - ) + returns (uint96 tStake, uint96 keepInTStake, uint96 nuInTStake) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider @@ -1211,22 +870,16 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Returns start staking timestamp. /// @dev This value is set at most once. - function getStartStakingTimestamp(address stakingProvider) - external - view - override - returns (uint256) - { + function getStartStakingTimestamp( + address stakingProvider + ) external view override returns (uint256) { return stakingProviders[stakingProvider].startStakingTimestamp; } /// @notice Returns staked amount of NU for the specified staking provider. - function stakedNu(address stakingProvider) - external - view - override - returns (uint256 nuAmount) - { + function stakedNu( + address stakingProvider + ) external view override returns (uint256 nuAmount) { (nuAmount, ) = convertFromT( stakingProviders[stakingProvider].nuInTStake, nucypherRatio @@ -1238,15 +891,13 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @return owner Stake owner address. /// @return beneficiary Beneficiary address. /// @return authorizer Authorizer address. - function rolesOf(address stakingProvider) + function rolesOf( + address stakingProvider + ) external view override - returns ( - address owner, - address payable beneficiary, - address authorizer - ) + returns (address owner, address payable beneficiary, address authorizer) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider @@ -1331,12 +982,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// needed to satisfy the maximum application authorization given /// the staked amounts of the other stake types for that staking /// provider. - function getMinStaked(address stakingProvider, StakeType stakeTypes) - public - view - override - returns (uint96) - { + function getMinStaked( + address stakingProvider, + StakeType stakeTypes + ) public view override returns (uint96) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; @@ -1364,18 +1013,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { stakingProviderStruct.tStake ); } - if (stakeTypes != StakeType.NU) { - maxAuthorization -= MathUpgradeable.min( - maxAuthorization, - stakingProviderStruct.nuInTStake - ); - } - if (stakeTypes != StakeType.KEEP) { - maxAuthorization -= MathUpgradeable.min( - maxAuthorization, - stakingProviderStruct.keepInTStake - ); - } return maxAuthorization.toUint96(); } @@ -1388,13 +1025,15 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; - availableTValue = - stakingProviderStruct.tStake + - stakingProviderStruct.keepInTStake + - stakingProviderStruct.nuInTStake; - availableTValue -= stakingProviderStruct + availableTValue = stakingProviderStruct.tStake; + uint96 authorized = stakingProviderStruct .authorizations[application] .authorized; + if (authorized <= availableTValue) { + availableTValue -= authorized; + } else { + availableTValue = 0; + } } /// @notice Delegate voting power from the stake associated to the @@ -1404,12 +1043,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// parameters, `delegator` and `delegatee`. Here we override it and /// comply with the same signature but the semantics of the first /// parameter changes to the `stakingProvider` address. - function delegate(address stakingProvider, address delegatee) - internal - virtual - override - onlyOwnerOf(stakingProvider) - { + function delegate( + address stakingProvider, + address delegatee + ) internal virtual override onlyOwnerOf(stakingProvider) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ stakingProvider ]; @@ -1479,10 +1116,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// Executes `involuntaryAuthorizationDecrease` function on each /// affected application. //slither-disable-next-line dead-code - function processSlashing(SlashingEvent storage slashing) - internal - returns (uint96 tAmountToBurn) - { + function processSlashing( + SlashingEvent storage slashing + ) internal returns (uint96 tAmountToBurn) { StakingProviderInfo storage stakingProviderStruct = stakingProviders[ slashing.stakingProvider ]; @@ -1491,40 +1127,13 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { stakingProviderStruct.keepInTStake + stakingProviderStruct.nuInTStake; // slash T - if (stakingProviderStruct.tStake > 0) { - if (tAmountToSlash <= stakingProviderStruct.tStake) { - tAmountToBurn = tAmountToSlash; - } else { - tAmountToBurn = stakingProviderStruct.tStake; - } - stakingProviderStruct.tStake -= tAmountToBurn; - tAmountToSlash -= tAmountToBurn; - } - - // slash KEEP - if (tAmountToSlash > 0 && stakingProviderStruct.keepInTStake > 0) { - (uint256 keepStakeAmount, , ) = keepStakingContract - .getDelegationInfo(slashing.stakingProvider); - (uint96 tAmount, ) = convertToT(keepStakeAmount, keepRatio); - stakingProviderStruct.keepInTStake = tAmount; - - tAmountToSlash = seizeKeep( - stakingProviderStruct, - slashing.stakingProvider, - tAmountToSlash, - 100 - ); - } - - // slash NU - if (tAmountToSlash > 0 && stakingProviderStruct.nuInTStake > 0) { - // synchronization skipped due to impossibility of real discrepancy - tAmountToSlash = seizeNu( - stakingProviderStruct, - tAmountToSlash, - 100 - ); + if (tAmountToSlash <= stakingProviderStruct.tStake) { + tAmountToBurn = tAmountToSlash; + } else { + tAmountToBurn = stakingProviderStruct.tStake; } + stakingProviderStruct.tStake -= tAmountToBurn; + tAmountToSlash -= tAmountToBurn; uint96 slashedAmount = slashing.amount - tAmountToSlash; emit TokensSeized(slashing.stakingProvider, slashedAmount, false); @@ -1600,90 +1209,6 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } } - /// @notice Convert amount from T to Keep and call `seize` in Keep staking contract. - /// Returns remainder of slashing amount in T - /// @dev Note this internal function doesn't update stake checkpoints - function seizeKeep( - StakingProviderInfo storage stakingProviderStruct, - address stakingProvider, - uint96 tAmountToSlash, - uint256 rewardMultiplier - ) internal returns (uint96) { - if (stakingProviderStruct.keepInTStake == 0) { - return tAmountToSlash; - } - - uint96 tPenalty; - if (tAmountToSlash <= stakingProviderStruct.keepInTStake) { - tPenalty = tAmountToSlash; - } else { - tPenalty = stakingProviderStruct.keepInTStake; - } - - (uint256 keepPenalty, uint96 tRemainder) = convertFromT( - tPenalty, - keepRatio - ); - if (keepPenalty == 0) { - return tAmountToSlash; - } - tPenalty -= tRemainder; - stakingProviderStruct.keepInTStake -= tPenalty; - tAmountToSlash -= tPenalty; - - address[] memory stakingProviderWrapper = new address[](1); - stakingProviderWrapper[0] = stakingProvider; - keepStakingContract.seize( - keepPenalty, - rewardMultiplier, - msg.sender, - stakingProviderWrapper - ); - return tAmountToSlash; - } - - /// @notice Convert amount from T to NU and call `slashStaker` in NuCypher staking contract. - /// Returns remainder of slashing amount in T - /// @dev Note this internal function doesn't update the stake checkpoints - function seizeNu( - StakingProviderInfo storage stakingProviderStruct, - uint96 tAmountToSlash, - uint256 rewardMultiplier - ) internal returns (uint96) { - if (stakingProviderStruct.nuInTStake == 0) { - return tAmountToSlash; - } - - uint96 tPenalty; - if (tAmountToSlash <= stakingProviderStruct.nuInTStake) { - tPenalty = tAmountToSlash; - } else { - tPenalty = stakingProviderStruct.nuInTStake; - } - - (uint256 nuPenalty, uint96 tRemainder) = convertFromT( - tPenalty, - nucypherRatio - ); - if (nuPenalty == 0) { - return tAmountToSlash; - } - tPenalty -= tRemainder; - stakingProviderStruct.nuInTStake -= tPenalty; - tAmountToSlash -= tPenalty; - - uint256 nuReward = nuPenalty.percent(SLASHING_REWARD_PERCENT).percent( - rewardMultiplier - ); - nucypherStakingContract.slashStaker( - stakingProviderStruct.owner, - nuPenalty, - msg.sender, - nuReward - ); - return tAmountToSlash; - } - /// @notice Removes application with zero authorization from authorized /// applications array function cleanAuthorizedApplications( @@ -1708,8 +1233,8 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { 0 ) { stakingProviderStruct.authorizedApplications[ - index - ] = stakingProviderStruct.authorizedApplications[ + index + ] = stakingProviderStruct.authorizedApplications[ length - deleted - 1 ]; deleted++; @@ -1754,79 +1279,36 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @notice Creates new checkpoints due to an increment of a stakers' stake /// @param _delegator Address of the staking provider acting as delegator /// @param _amount Amount of T to increment - function increaseStakeCheckpoint(address _delegator, uint96 _amount) - internal - { + function increaseStakeCheckpoint( + address _delegator, + uint96 _amount + ) internal { newStakeCheckpoint(_delegator, _amount, true); } /// @notice Creates new checkpoints due to a decrease of a stakers' stake /// @param _delegator Address of the stake owner acting as delegator /// @param _amount Amount of T to decrease - function decreaseStakeCheckpoint(address _delegator, uint96 _amount) - internal - { + function decreaseStakeCheckpoint( + address _delegator, + uint96 _amount + ) internal { newStakeCheckpoint(_delegator, _amount, false); } - /// @notice Returns amount of Nu stake in the NuCypher staking contract for - /// the specified staking provider. - /// Resulting value in T denomination - function getNuAmountInT(address owner, address stakingProvider) - internal - returns (uint96) - { - uint256 nuStakeAmount = nucypherStakingContract.requestMerge( - owner, - stakingProvider - ); - (uint96 tAmount, ) = convertToT(nuStakeAmount, nucypherRatio); - return tAmount; - } - function _transferGovernance(address newGuvnor) internal virtual { address oldGuvnor = governance; governance = newGuvnor; emit GovernanceTransferred(oldGuvnor, newGuvnor); } - /// @notice Returns amount of Keep stake in the Keep staking contract for - /// the specified staking provider. - /// Resulting value in T denomination - function getKeepAmountInT(address stakingProvider) - internal - view - returns (uint96) - { - uint256 keepStakeAmount = keepStakingContract.eligibleStake( - stakingProvider, - address(this) - ); - (uint96 tAmount, ) = convertToT(keepStakeAmount, keepRatio); - return tAmount; - } - - /// @notice Returns the T token amount that's obtained from `amount` legacy - /// tokens for the given `ratio`, and the remainder that can't be - /// converted. - function convertToT(uint256 amount, uint256 ratio) - internal - pure - returns (uint96 tAmount, uint256 remainder) - { - remainder = amount % CONVERSION_DIVISOR; - uint256 convertibleAmount = amount - remainder; - tAmount = ((convertibleAmount * ratio) / CONVERSION_DIVISOR).toUint96(); - } - /// @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) - { + 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; diff --git a/contracts/test/IKeepManagedGrant.sol b/contracts/test/IKeepManagedGrant.sol deleted file mode 100644 index fac75ced..00000000 --- a/contracts/test/IKeepManagedGrant.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity 0.8.9; - -import "../staking/KeepStake.sol"; - -interface IKeepManagedGrant is IManagedGrant { - function stake( - address stakingContract, - uint256 amount, - bytes memory extraData - ) external; -} diff --git a/contracts/test/IKeepRegistry.sol b/contracts/test/IKeepRegistry.sol deleted file mode 100644 index 8dae13b4..00000000 --- a/contracts/test/IKeepRegistry.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity 0.8.9; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface IKeepRegistry { - function approveOperatorContract(address operatorContract) external; - - function registryKeeper() external view returns (address); -} diff --git a/contracts/test/IKeepToken.sol b/contracts/test/IKeepToken.sol deleted file mode 100644 index e054dc45..00000000 --- a/contracts/test/IKeepToken.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity 0.8.9; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface IKeepToken is IERC20 { - function approveAndCall( - address spender, - uint256 value, - bytes memory extraData - ) external returns (bool success); -} diff --git a/contracts/test/IKeepTokenGrant.sol b/contracts/test/IKeepTokenGrant.sol deleted file mode 100644 index 4bdcf881..00000000 --- a/contracts/test/IKeepTokenGrant.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity 0.8.9; - -import "../staking/ILegacyTokenStaking.sol"; - -interface IKeepTokenGrant { - function stake( - uint256 id, - address stakingContract, - uint256 amount, - bytes memory extraData - ) external; -} diff --git a/contracts/test/ITestKeepTokenStaking.sol b/contracts/test/ITestKeepTokenStaking.sol deleted file mode 100644 index b2bb62a8..00000000 --- a/contracts/test/ITestKeepTokenStaking.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity 0.8.9; - -import "../staking/ILegacyTokenStaking.sol"; - -interface ITestKeepTokenStaking is IKeepTokenStaking { - function authorizeOperatorContract( - address operator, - address operatorContract - ) external; - - function commitTopUp(address operator) external; - - function undelegate(address operator) external; - - function getLocks(address operator) - external - view - returns (address[] memory creators, uint256[] memory expirations); -} diff --git a/contracts/test/KeepRegistryStub.sol b/contracts/test/KeepRegistryStub.sol deleted file mode 100644 index a9beb036..00000000 --- a/contracts/test/KeepRegistryStub.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity 0.8.9; - -import "./IKeepRegistry.sol"; - -contract KeepRegistryStub is IKeepRegistry { - address public registryKeeper; - - event OperatorContractApproved(address operatorContract); - - constructor() { - registryKeeper = msg.sender; - } - - function approveOperatorContract(address operatorContract) external { - emit OperatorContractApproved(operatorContract); - } -} diff --git a/contracts/test/TokenStakingTestSet.sol b/contracts/test/TokenStakingTestSet.sol index cd34030a..003f4c95 100644 --- a/contracts/test/TokenStakingTestSet.sol +++ b/contracts/test/TokenStakingTestSet.sol @@ -2,195 +2,9 @@ pragma solidity 0.8.9; -import "../staking/ILegacyTokenStaking.sol"; import "../staking/IApplication.sol"; import "../staking/TokenStaking.sol"; -contract KeepTokenStakingMock is IKeepTokenStaking { - using PercentUtils for uint256; - - struct OperatorStruct { - address owner; - address payable beneficiary; - address authorizer; - uint256 createdAt; - uint256 undelegatedAt; - uint256 amount; - mapping(address => bool) eligibility; - } - - mapping(address => OperatorStruct) internal operators; - mapping(address => uint256) public tattletales; - - function setOperator( - address operator, - address owner, - address payable beneficiary, - address authorizer, - uint256 createdAt, - uint256 undelegatedAt, - uint256 amount - ) external { - OperatorStruct storage operatorStrut = operators[operator]; - operatorStrut.owner = owner; - operatorStrut.beneficiary = beneficiary; - operatorStrut.authorizer = authorizer; - operatorStrut.createdAt = createdAt; - operatorStrut.undelegatedAt = undelegatedAt; - operatorStrut.amount = amount; - } - - function setEligibility( - address operator, - address application, - bool isEligible - ) external { - operators[operator].eligibility[application] = isEligible; - } - - function setAmount(address operator, uint256 amount) external { - operators[operator].amount = amount; - } - - function setUndelegatedAt(address operator, uint256 undelegatedAt) - external - { - operators[operator].undelegatedAt = undelegatedAt; - } - - function seize( - uint256 amountToSeize, - uint256 rewardMultiplier, - address tattletale, - address[] memory misbehavedOperators - ) external override { - require(amountToSeize > 0, "Amount to slash must be greater than zero"); - // assumed only one will be slashed (per call) - require( - misbehavedOperators.length == 1, - "Only one operator per call in tests" - ); - address operator = misbehavedOperators[0]; - operators[operator].amount -= amountToSeize; - tattletales[tattletale] += amountToSeize.percent(5).percent( - rewardMultiplier - ); - } - - function getDelegationInfo(address operator) - external - view - override - returns ( - uint256 amount, - uint256 createdAt, - uint256 undelegatedAt - ) - { - amount = operators[operator].amount; - createdAt = operators[operator].createdAt; - undelegatedAt = operators[operator].undelegatedAt; - } - - function ownerOf(address operator) - external - view - override - returns (address) - { - return operators[operator].owner; - } - - function beneficiaryOf(address operator) - external - view - override - returns (address payable) - { - return operators[operator].beneficiary; - } - - function authorizerOf(address operator) - external - view - override - returns (address) - { - return operators[operator].authorizer; - } - - function eligibleStake(address operator, address operatorContract) - external - view - override - returns (uint256 balance) - { - OperatorStruct storage operatorStrut = operators[operator]; - if (operatorStrut.eligibility[operatorContract]) { - return operatorStrut.amount; - } - return 0; - } -} - -contract NuCypherTokenStakingMock is INuCypherStakingEscrow { - struct StakerStruct { - uint256 value; - address stakingProvider; - } - - mapping(address => StakerStruct) public stakers; - mapping(address => uint256) public investigators; - - function setStaker(address staker, uint256 value) external { - stakers[staker].value = value; - } - - function slashStaker( - address staker, - uint256 penalty, - address investigator, - uint256 reward - ) external override { - require(penalty > 0, "Amount to slash must be greater than zero"); - stakers[staker].value -= penalty; - investigators[investigator] += reward; - } - - function requestMerge(address staker, address stakingProvider) - external - override - returns (uint256) - { - StakerStruct storage stakerStruct = stakers[staker]; - require( - stakerStruct.stakingProvider == address(0) || - stakerStruct.stakingProvider == stakingProvider, - "Another provider was already set for this staker" - ); - if (stakerStruct.stakingProvider == address(0)) { - stakerStruct.stakingProvider = stakingProvider; - } - return stakers[staker].value; - } - - function getAllTokens(address staker) - external - view - override - returns (uint256) - { - return stakers[staker].value; - } - - function stakerInfo(address staker) - public - view - returns (StakerStruct memory) - { - return stakers[staker]; - } -} contract VendingMachineMock { uint256 public constant FLOATING_POINT_DIVISOR = 10**15; @@ -338,19 +152,11 @@ contract ManagedGrantMock { contract ExtendedTokenStaking is TokenStaking { constructor( T _token, - IKeepTokenStaking _keepStakingContract, - INuCypherStakingEscrow _nucypherStakingContract, - VendingMachine _keepVendingMachine, - VendingMachine _nucypherVendingMachine, - KeepStake _keepStake + VendingMachine _nucypherVendingMachine ) TokenStaking( _token, - _keepStakingContract, - _nucypherStakingContract, - _keepVendingMachine, - _nucypherVendingMachine, - _keepStake + _nucypherVendingMachine ) {} @@ -393,3 +199,52 @@ 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 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; + } + + function addLegacyStake(address stakingProvider, uint96 keepInTStake, uint96 nuInTStake) external { + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + stakingProviderStruct.keepInTStake += keepInTStake; + stakingProviderStruct.nuInTStake += nuInTStake; + if (stakingProviderStruct.startStakingTimestamp == 0) { + 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]; + stakingProviderStruct.authorizedApplications.push(application); + authorization.authorized += amount; + } + +} \ No newline at end of file diff --git a/test/staking/KeepStake.test.js b/test/staking/KeepStake.test.js deleted file mode 100644 index 523bd3b1..00000000 --- a/test/staking/KeepStake.test.js +++ /dev/null @@ -1,152 +0,0 @@ -const { expect } = require("chai") - -describe("KeepStake", () => { - let deployer - let governance - let thirdParty - - let keepStake - - let keepTokenStakingMock - let managedGrantMock - - beforeEach(async () => { - ;[deployer, governance, thirdParty] = await ethers.getSigners() - - const KeepTokenStakingMock = await ethers.getContractFactory( - "KeepTokenStakingMock" - ) - keepTokenStakingMock = await KeepTokenStakingMock.deploy() - await keepTokenStakingMock.deployed() - - const KeepStake = await ethers.getContractFactory("KeepStake") - keepStake = await KeepStake.deploy(keepTokenStakingMock.address) - await keepStake.deployed() - - keepStake.connect(deployer).transferOwnership(governance.address) - - const ManagedGrantMock = await ethers.getContractFactory("ManagedGrantMock") - managedGrantMock = await ManagedGrantMock.deploy() - await managedGrantMock.deployed() - }) - - describe("resolveOwner", () => { - context("for snapshotted operator", () => { - it("should return grantee address", async () => { - const grantee1 = await keepStake.resolveOwner( - "0x1147ccFB4AEFc6e587a23b78724Ef20Ec6e474D4" - ) - const grantee2 = await keepStake.resolveOwner( - "0x526c013f8382B050d32d86e7090Ac84De22EdA4D" - ) - - expect(grantee1).to.equal("0x3FB49dA4375Ef9019f17990D04c6d5daD482D80a") - expect(grantee2).to.equal("0x61C6E5DDacded540CD08066C08cbc096d22D91f4") - }) - }) - - context("for managed grant set by governance", () => { - const operator = "0xbDe54bDf60a7a5f748dA3e15fF029d2D7C4E078f" - const grantee = "0x0068B9e3cdCccBb3f101FA90beC864890789d444" - - beforeEach(async () => { - await managedGrantMock.setGrantee(grantee) - await keepStake - .connect(governance) - .setManagedGrant(operator, managedGrantMock.address) - }) - - it("should return grantee address", async () => { - expect(await keepStake.resolveOwner(operator)).to.equal(grantee) - }) - }) - - context("for grantee set by governance", () => { - const operator = "0xbDe54bDf60a7a5f748dA3e15fF029d2D7C4E078f" - const grantee = "0x3EaCc4EcF687A999b72cC4bd72b2Ff969681034A" - - beforeEach(async () => { - await keepStake.connect(governance).setGrantee(operator, grantee) - }) - - it("should return grantee address", async () => { - expect(await keepStake.resolveOwner(operator)).to.equal(grantee) - }) - }) - - context("for liquid token operator", () => { - const operator = "0xbDe54bDf60a7a5f748dA3e15fF029d2D7C4E078f" - const owner = "0x3f78eC9999Bbf47b4eefBf1058BDE4CeDA3eaa8A" - const beneficiary = "0x3A654A853eC8BAfc1b147B21342C1118d2DF6ffe" - const authorizer = "0xbC04D5301C0Cd565f8Fe1cDbA7def6e5de4EB2c4" - - beforeEach(async () => { - await keepTokenStakingMock.setOperator( - operator, - owner, - beneficiary, - authorizer, - 1, - 0, - 1 - ) - }) - - it("should fallback to Keep staking contract", async () => { - expect(await keepStake.resolveOwner(operator)).to.equal(owner) - }) - - it("should revert if Keep staking does not know the operator", async () => { - await expect(keepStake.resolveOwner(authorizer)).to.be.revertedWith( - "Could not resolve the owner" - ) - }) - }) - }) - - describe("setManagedGrant", () => { - const operator = "0xbDe54bDf60a7a5f748dA3e15fF029d2D7C4E078f" - const managedGrant = "0xCc83cae99c1e6a16dFB2D2Aba9cA25082AeB9537" - - context("when called by governance", () => { - it("should set managed grant", async () => { - await keepStake - .connect(governance) - .setManagedGrant(operator, managedGrant) - - expect(await keepStake.operatorToManagedGrant(operator)).to.equal( - managedGrant - ) - }) - }) - - context("when called by a third party", () => { - it("should revert", async () => { - await expect( - keepStake.connect(thirdParty).setManagedGrant(operator, managedGrant) - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - }) - }) - - describe("setGrantee", () => { - const operator = "0xbDe54bDf60a7a5f748dA3e15fF029d2D7C4E078f" - const grantee = "0x12e298bDd84A19968980efDc1dEC91Af357824c7" - - context("when called by governance", () => { - it("should set grantee", async () => { - await keepStake.connect(governance).setGrantee(operator, grantee) - - expect(await keepStake.operatorToGrantee(operator)).to.equal(grantee) - }) - }) - - context("when called by a third party", () => { - it("should revert", async () => { - await expect( - keepStake.connect(thirdParty).setGrantee(operator, grantee) - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - }) - }) -}) diff --git a/test/staking/TokenStaking.test.js b/test/staking/TokenStaking.test.js index a99bb3d7..43040219 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -21,11 +21,7 @@ const { upgrades } = require("hardhat") describe("TokenStaking", () => { let tToken - let keepVendingMachine let nucypherVendingMachine - let keepStakingMock - let keepStake - let nucypherStakingMock let application1Mock let application2Mock @@ -48,16 +44,6 @@ describe("TokenStaking", () => { } } - function convertFromT(amount, ratio) { - amount = ethers.BigNumber.from(amount) - const tRemainder = amount.mod(ratio) - amount = amount.sub(tRemainder) - return { - result: amount.mul(floatingPointDivisor).div(ratio), - remainder: tRemainder, - } - } - function rewardFromPenalty(penalty, rewardMultiplier) { return penalty.mul(5).div(100).mul(rewardMultiplier).div(100) } @@ -103,46 +89,20 @@ describe("TokenStaking", () => { .transfer(otherStaker.address, initialStakerBalance) const VendingMachine = await ethers.getContractFactory("VendingMachineMock") - keepVendingMachine = await VendingMachine.deploy( - maxKeepWrappedTokens, - tAllocation - ) - await keepVendingMachine.deployed() nucypherVendingMachine = await VendingMachine.deploy( maxNuWrappedTokens, - tAllocation + tAllocation, ) await nucypherVendingMachine.deployed() - const KeepTokenStakingMock = await ethers.getContractFactory( - "KeepTokenStakingMock" - ) - keepStakingMock = await KeepTokenStakingMock.deploy() - await keepStakingMock.deployed() - const KeepStake = await ethers.getContractFactory("KeepStake") - keepStake = await KeepStake.deploy(keepStakingMock.address) - await keepStake.deployed() - const NuCypherTokenStakingMock = await ethers.getContractFactory( - "NuCypherTokenStakingMock" - ) - nucypherStakingMock = await NuCypherTokenStakingMock.deploy() - await nucypherStakingMock.deployed() - - const TokenStaking = await ethers.getContractFactory("TokenStaking") + const TokenStaking = await ethers.getContractFactory("LegacyTokenStaking") const tokenStakingInitializerArgs = [] tokenStaking = await upgrades.deployProxy( TokenStaking, tokenStakingInitializerArgs, { - constructorArgs: [ - tToken.address, - keepStakingMock.address, - nucypherStakingMock.address, - keepVendingMachine.address, - nucypherVendingMachine.address, - keepStake.address, - ], - } + constructorArgs: [tToken.address, nucypherVendingMachine.address], + }, ) await tokenStaking.deployed() @@ -159,7 +119,7 @@ describe("TokenStaking", () => { context("when caller is not the governance", () => { it("should revert", async () => { await expect( - tokenStaking.connect(staker).setMinimumStakeAmount(amount) + tokenStaking.connect(staker).setMinimumStakeAmount(amount), ).to.be.revertedWith("Caller is not the governance") }) }) @@ -190,7 +150,12 @@ describe("TokenStaking", () => { await expect( tokenStaking .connect(staker) - .stake(AddressZero, beneficiary.address, authorizer.address, amount) + .stake( + AddressZero, + beneficiary.address, + authorizer.address, + amount, + ), ).to.be.revertedWith("Parameters must be specified") }) }) @@ -205,8 +170,8 @@ describe("TokenStaking", () => { stakingProvider.address, AddressZero, authorizer.address, - amount - ) + amount, + ), ).to.be.revertedWith("Parameters must be specified") }) }) @@ -221,8 +186,8 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, AddressZero, - amount - ) + amount, + ), ).to.be.revertedWith("Parameters must be specified") }) }) @@ -242,7 +207,7 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount + amount, ) await tToken.connect(staker).approve(tokenStaking.address, amount) await expect( @@ -252,40 +217,11 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount - ) - ).to.be.revertedWith("Provider is already in use") - }) - } - ) - - context( - "when staking provider is in use in Keep staking contract", - () => { - it("should revert", async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - otherStaker.address, - AddressZero, - AddressZero, - createdAt, - 0, - 0 - ) - const amount = 0 - await expect( - tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) + amount, + ), ).to.be.revertedWith("Provider is already in use") }) - } + }, ) }) @@ -300,8 +236,8 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount - ) + amount, + ), ).to.be.revertedWith("Amount is less than minimum") }) }) @@ -320,8 +256,8 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount - ) + amount, + ), ).to.be.revertedWith("Amount is less than minimum") }) }) @@ -345,14 +281,14 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount + amount, ) blockTimestamp = await lastBlockTime() }) it("should set roles equal to the provided values", async () => { expect( - await tokenStaking.rolesOf(stakingProvider.address) + await tokenStaking.rolesOf(stakingProvider.address), ).to.deep.equal([ staker.address, beneficiary.address, @@ -363,13 +299,15 @@ 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 + 0, ) }) it("should start staking timestamp", async () => { expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) + await tokenStaking.getStartStakingTimestamp( + stakingProvider.address, + ), ).to.equal(blockTimestamp) }) @@ -381,8 +319,8 @@ describe("TokenStaking", () => { expect( await tokenStaking.getAvailableToAuthorize( stakingProvider.address, - application1Mock.address - ) + application1Mock.address, + ), ).to.equal(amount) }) @@ -395,20 +333,20 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount + amount, ) }) it("should create a new checkpoint for staked total supply", async () => { const lastBlock = await mineBlocks(1) expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - amount + amount, ) }) it("shouldn't create a new checkpoint for any stake role", async () => { expect(await tokenStaking.getVotes(staker.address)).to.equal(0) expect(await tokenStaking.getVotes(stakingProvider.address)).to.equal( - 0 + 0, ) expect(await tokenStaking.getVotes(beneficiary.address)).to.equal(0) expect(await tokenStaking.getVotes(authorizer.address)).to.equal(0) @@ -424,826 +362,421 @@ describe("TokenStaking", () => { it("checkpoint for staked total supply should remain constant", async () => { const lastBlock = await mineBlocks(1) expect( - await tokenStaking.getPastTotalSupply(lastBlock - 1) + await tokenStaking.getPastTotalSupply(lastBlock - 1), ).to.equal(amount) }) it("should create a new checkpoint for staker's delegatee", async () => { expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - amount + amount, ) }) it("shouldn't create a new checkpoint for any stake role", async () => { expect(await tokenStaking.getVotes(staker.address)).to.equal(0) expect( - await tokenStaking.getVotes(stakingProvider.address) + await tokenStaking.getVotes(stakingProvider.address), ).to.equal(0) expect(await tokenStaking.getVotes(beneficiary.address)).to.equal(0) expect(await tokenStaking.getVotes(authorizer.address)).to.equal(0) }) }) - } + }, ) }) - describe("stakeKeep", () => { - context("when caller did not provide staking provider", () => { + describe("approveApplication", () => { + context("when caller is not the governance", () => { it("should revert", async () => { - await expect(tokenStaking.stakeKeep(AddressZero)).to.be.revertedWith( - "Parameters must be specified" - ) + await expect( + tokenStaking.connect(staker).approveApplication(AddressZero), + ).to.be.revertedWith("Caller is not the governance") }) }) - context("when staking provider is in use", () => { + context("when caller did not provide application", () => { + it("should revert", async () => { + await expect( + tokenStaking.connect(deployer).approveApplication(AddressZero), + ).to.be.revertedWith("Parameters must be specified") + }) + }) + + context("when application has already been approved", () => { it("should revert", async () => { - const amount = initialStakerBalance - await tToken.connect(otherStaker).approve(tokenStaking.address, amount) await tokenStaking - .connect(otherStaker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) + .connect(deployer) + .approveApplication(application1Mock.address) await expect( - tokenStaking.stakeKeep(stakingProvider.address) - ).to.be.revertedWith("Provider is already in use") + tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address), + ).to.be.revertedWith("Can't approve application") }) }) - context( - "when specified address never was a staking provider in Keep", - () => { - it("should revert", async () => { - await expect( - tokenStaking.stakeKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to sync") - }) - } - ) + context("when application is disabled", () => { + it("should revert", async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + await expect( + tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address), + ).to.be.revertedWith("Can't approve application") + }) + }) - context("when staking provider exists in Keep staking contract", () => { + context("when approving new application", () => { let tx - context("when stake was canceled/withdrawn or not eligible", () => { - it("should revert", async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - 0 - ) - await expect( - tokenStaking.stakeKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to sync") - }) + beforeEach(async () => { + tx = await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) }) - context("when stake is eligible", () => { - const keepAmount = initialStakerBalance - const tAmount = convertToT(keepAmount, keepRatio).result - let blockTimestamp + it("should approve application", async () => { + expect( + await tokenStaking.applicationInfo(application1Mock.address), + ).to.deep.equal([ApplicationStatus.APPROVED, AddressZero]) + }) - beforeEach(async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - tx = await tokenStaking.stakeKeep(stakingProvider.address) - blockTimestamp = await lastBlockTime() - }) + it("should add application to the list of all applications", async () => { + expect(await tokenStaking.getApplicationsLength()).to.equal(1) + expect(await tokenStaking.applications(0)).to.equal( + application1Mock.address, + ) + }) - it("should set roles equal to the Keep values", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) + it("should emit ApplicationStatusChanged", async () => { + await expect(tx) + .to.emit(tokenStaking, "ApplicationStatusChanged") + .withArgs(application1Mock.address, ApplicationStatus.APPROVED) + }) + }) - it("should set value of stakes", async () => { - await assertStakes(stakingProvider.address, Zero, tAmount, Zero) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - 0 - ) - }) + context("when approving paused application", () => { + let tx - it("should start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + tx = await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + }) - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount) - }) + it("should enable application", async () => { + expect( + await tokenStaking.applicationInfo(application1Mock.address), + ).to.deep.equal([ApplicationStatus.APPROVED, panicButton.address]) + }) - it("should not increase 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( + 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.APPROVED) + }) + }) + }) + + describe("increaseAuthorization", () => { + context("when caller is not authorizer", () => { + it("should revert", async () => { + const amount = initialStakerBalance + await expect( + tokenStaking + .connect(staker) + .increaseAuthorization( stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) + application1Mock.address, + amount, + ), + ).to.be.revertedWith("Not authorizer") + }) + }) - it("should emit Staked event", async () => { - await expect(tx) - .to.emit(tokenStaking, "Staked") - .withArgs( - StakeTypes.KEEP, - staker.address, + 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, - tAmount + amount, ) }) - it("should create a new checkpoint for staked total supply", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount - ) - }) - it("shouldn't create a new checkpoint for stake owner", async () => { - expect(await tokenStaking.getVotes(staker.address)).to.equal(0) - }) - - context("after vote delegation", () => { - beforeEach(async () => { - tx = await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) + 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") }) + }) - it("should create a new checkpoint for staker's delegatee", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - tAmount - ) + context("when application was approved", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) }) - it("checkpoint for staked total supply should remain constant", async () => { - const lastBlock = await mineBlocks(1) - expect( - await tokenStaking.getPastTotalSupply(lastBlock - 1) - ).to.equal(tAmount) + 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") + }) }) - it("shouldn't create new checkpoint for any staker role", async () => { - expect( - await tokenStaking.getVotes(stakingProvider.address) - ).to.equal(0) - expect(await tokenStaking.getVotes(beneficiary.address)).to.equal(0) - expect(await tokenStaking.getVotes(authorizer.address)).to.equal(0) + 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") + }) }) - }) - }) - }) - }) - - describe("stakeNu", () => { - context("when caller did not provide staking provider", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(staker) - .stakeNu(AddressZero, beneficiary.address, authorizer.address) - ).to.be.revertedWith("Parameters must be specified") - }) - }) - - context("when caller did not provide beneficiary", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, AddressZero, authorizer.address) - ).to.be.revertedWith("Parameters must be specified") - }) - }) - context("when caller did not provide authorizer", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, beneficiary.address, AddressZero) - ).to.be.revertedWith("Parameters must be specified") - }) - }) - - context("when staking provider is in use", () => { - context( - "when other stake delegated to the specified staking provider", - () => { - it("should revert", async () => { - const amount = initialStakerBalance - await tToken - .connect(otherStaker) - .approve(tokenStaking.address, amount) - await tokenStaking - .connect(otherStaker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) - await expect( - tokenStaking - .connect(staker) - .stakeNu( + context("when already authorized maximum applications", () => { + it("should revert", async () => { + await tokenStaking.connect(deployer).setAuthorizationCeiling(1) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( stakingProvider.address, - beneficiary.address, - authorizer.address + application1Mock.address, + amount, ) - ).to.be.revertedWith("Provider is already in use") + 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 staking provider is in use in Keep staking contract", - () => { - it("should revert", async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - otherStaker.address, - AddressZero, - AddressZero, - createdAt, - 0, - 0 - ) - await expect( - tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - ).to.be.revertedWith("Provider is already in use") + 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 caller has no stake in NuCypher staking contract", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - ).to.be.revertedWith("Nothing to sync") - }) - }) - context("when caller has stake in NuCypher staking contract", () => { - const nuAmount = initialStakerBalance.add(1) - const conversion = convertToT(nuAmount, nuRatio) - const tAmount = conversion.result - let tx - let blockTimestamp + context("when authorize staked tokens in one tx", () => { + let tx + const authorizedAmount = amount.div(3) - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorizedAmount, + ) + }) - tx = await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - blockTimestamp = await lastBlockTime() - }) + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(authorizedAmount) + }) - it("should set roles equal to the provided values", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) + 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 set value of stakes", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, tAmount) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - nuAmount.sub(conversion.remainder) - ) - }) + 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 start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + authorizedAmount, + Zero, + ) + }) - it("should do callback to NuCypher staking contract", async () => { - await assertNuStakers(staker.address, nuAmount, stakingProvider.address) - }) + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + 0, + authorizedAmount, + ) + }) + }) - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount) - }) - - it("should emit Staked event", async () => { - await expect(tx) - .to.emit(tokenStaking, "Staked") - .withArgs( - StakeTypes.NU, - staker.address, - stakingProvider.address, - beneficiary.address, - authorizer.address, - tAmount - ) - }) - - it("should create a new checkpoint for staked total supply", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount - ) - }) - it("shouldn't create a new checkpoint for stake owner", async () => { - expect(await tokenStaking.getVotes(staker.address)).to.equal(0) - }) - - context("after vote delegation", () => { - beforeEach(async () => { - tx = await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - }) - - it("should create a new checkpoint for staker's delegatee", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - tAmount - ) - }) - - it("checkpoint for staked total supply should remain constant", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount - ) - }) - - it("shouldn't create new checkpoint for any staker role", async () => { - expect(await tokenStaking.getVotes(stakingProvider.address)).to.equal( - 0 + 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") + }) + }, ) - expect(await tokenStaking.getVotes(beneficiary.address)).to.equal(0) - expect(await tokenStaking.getVotes(authorizer.address)).to.equal(0) - }) - }) - }) - }) - - describe("approveApplication", () => { - context("when caller is not the governance", () => { - it("should revert", async () => { - await expect( - tokenStaking.connect(staker).approveApplication(AddressZero) - ).to.be.revertedWith("Caller is not the governance") - }) - }) - - context("when caller did not provide application", () => { - it("should revert", async () => { - await expect( - tokenStaking.connect(deployer).approveApplication(AddressZero) - ).to.be.revertedWith("Parameters must be specified") - }) - }) - - context("when application has already been approved", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await expect( - tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - ).to.be.revertedWith("Can't approve application") - }) - }) - - context("when application is disabled", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .disableApplication(application1Mock.address) - await expect( - tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - ).to.be.revertedWith("Can't approve application") - }) - }) - - context("when approving new application", () => { - let tx - - beforeEach(async () => { - tx = await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - }) - - it("should approve application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.APPROVED, AddressZero]) - }) - - it("should add application to the list of all applications", 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.APPROVED) - }) - }) - - context("when approving paused application", () => { - let tx - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - tx = await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - }) - - it("should enable application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.APPROVED, 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.APPROVED) - }) - }) - }) - - describe("increaseAuthorization", () => { - context("when caller is not authorizer", () => { - it("should revert", async () => { - const amount = initialStakerBalance - await expect( - tokenStaking - .connect(staker) - .increaseAuthorization( - 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 - ) - }) + context("when authorize staked tokens in several txs", () => { + let tx1 + let tx2 + const authorizedAmount1 = amount.sub(1) + const authorizedAmount2 = 1 - context("when application was not approved", () => { - it("should revert", async () => { - await expect( - tokenStaking + beforeEach(async () => { + tx1 = await tokenStaking .connect(authorizer) .increaseAuthorization( stakingProvider.address, application1Mock.address, - amount + authorizedAmount1, ) - ).to.be.revertedWith("Application is not approved") - }) - }) - - context("when application was approved", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - }) + tx2 = await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorizedAmount2, + ) + }) - 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( - 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( - 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( - 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) - - beforeEach(async () => { - tx = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount - ) - }) - - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(authorizedAmount) - }) - - it("should increase min staked amount in T", 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 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(amount.sub(authorizedAmount)) - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(amount) - }) - - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - authorizedAmount, - Zero - ) - }) - - it("should emit AuthorizationIncreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - 0, - authorizedAmount - ) - }) - }) - - context( - "when authorize 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 - - 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 - ) + application2Mock.address, + ), ).to.equal(amount) }) @@ -1251,20 +784,20 @@ describe("TokenStaking", () => { expect( await tokenStaking.getMinStaked( stakingProvider.address, - StakeTypes.T - ) + StakeTypes.T, + ), ).to.equal(amount) expect( await tokenStaking.getMinStaked( stakingProvider.address, - StakeTypes.NU - ) + StakeTypes.NU, + ), ).to.equal(0) expect( await tokenStaking.getMinStaked( stakingProvider.address, - StakeTypes.KEEP - ) + StakeTypes.KEEP, + ), ).to.equal(0) }) @@ -1273,7 +806,7 @@ describe("TokenStaking", () => { application1Mock, stakingProvider.address, amount, - Zero + Zero, ) }) @@ -1284,7 +817,7 @@ describe("TokenStaking", () => { stakingProvider.address, application1Mock.address, 0, - authorizedAmount1 + authorizedAmount1, ) await expect(tx2) .to.emit(tokenStaking, "AuthorizationIncreased") @@ -1292,7 +825,7 @@ describe("TokenStaking", () => { stakingProvider.address, application1Mock.address, authorizedAmount1, - authorizedAmount1.add(authorizedAmount2) + authorizedAmount1.add(authorizedAmount2), ) }) }) @@ -1304,1676 +837,310 @@ describe("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 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 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 - const tAmount = tStake.add(keepInTStake).add(nuInTStake) - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepStake - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - tx = await tokenStaking.stakeKeep(stakingProvider.address) - - await nucypherStakingMock.setStaker(staker.address, nuStake) - await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - - await tToken.connect(staker).approve(tokenStaking.address, tStake) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tStake) - }) - - context("when authorize more than staked amount", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - tAmount.add(1) - ) - ).to.be.revertedWith("Not enough stake to authorize") - }) - }) - - context("when authorize staked tokens in one tx", () => { - let tx - const notAuthorized = keepInTStake.sub(to1e18(1)) // tStake < keepInTStake < nuInTStake - const authorizedAmount = tAmount.sub(notAuthorized) - - beforeEach(async () => { - tx = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount - ) - }) - - it("should increase authorization", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(authorizedAmount) - }) - - it("should increase min staked amount in KEEP and NU", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(nuInTStake.sub(notAuthorized)) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(keepInTStake.sub(notAuthorized)) - }) - - 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(tAmount) - }) - - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - authorizedAmount, - Zero - ) - }) - - it("should emit AuthorizationIncreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - 0, - authorizedAmount - ) - }) - - context("when authorize to the second application", () => { - let tx2 - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - - tx2 = await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - tAmount - ) - }) - - 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(tAmount) - }) - - it("should set min staked amount equal to T/NU/KEEP stake", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).to.equal(tStake) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(nuInTStake) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(keepInTStake) - }) - - it("should decrease available amount to authorize for the second application", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(notAuthorized) - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(0) - }) - - it("should inform second application", async () => { - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - tAmount, - Zero - ) - }) - - it("should emit AuthorizationIncreased", async () => { - await expect(tx2) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application2Mock.address, - 0, - tAmount - ) - }) - }) - }) - - context("when authorize more than staked amount in several txs", () => { - it("should revert", async () => { - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - tAmount.sub(1) - ) - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - 2 - ) - ).to.be.revertedWith("Not enough stake to authorize") - }) - }) - } - ) - }) - - 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( - 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", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - 0 - ) - ).to.be.revertedWith("Parameters must be specified") - }) - }) - - context("when amount to decrease is more than authorized", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - amount.add(1) - ) - ).to.be.revertedWith("Amount exceeds authorized") - }) - }) - - context("when amount to decrease is less than authorized", () => { - const amountToDecrease = amount.div(3) - const expectedFromAmount = amount - const expectedToAmount = amount.sub(amountToDecrease) - let tx - - beforeEach(async () => { - tx = await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - amountToDecrease - ) - }) - - it("should keep authorized amount unchanged", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(amount) - }) - - it("should send request to application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - amount, - expectedToAmount - ) - }) - - it("should emit AuthorizationDecreaseRequested", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationDecreaseRequested") - .withArgs( - stakingProvider.address, - application1Mock.address, - expectedFromAmount, - expectedToAmount - ) - }) - }) - - context( - "when request to decrease all authorized amount for several applications", - () => { - let tx - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - amount - ) - tx = await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"]( - stakingProvider.address - ) - }) - - it("should keep authorized amount unchanged", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(amount) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(amount) - }) - - it("should send request to application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - amount, - Zero - ) - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - amount, - Zero - ) - }) - - it("should emit AuthorizationDecreaseRequested", async () => { - await expect(tx) - .to.emit(tokenStaking, "AuthorizationDecreaseRequested") - .withArgs( - stakingProvider.address, - application1Mock.address, - amount, - Zero - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationDecreaseRequested") - .withArgs( - stakingProvider.address, - application2Mock.address, - amount, - Zero - ) - }) - } - ) - - context("when decrease requested twice", () => { - const expectedFromAmount = amount - const amountToDecrease1 = amount.div(3) - const expectedToAmount1 = amount.sub(amountToDecrease1) - const amountToDecrease2 = amount.div(5) - const expectedToAmount2 = amount.sub(amountToDecrease2) - let tx1 - let tx2 - - beforeEach(async () => { - tx1 = await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - amountToDecrease1 - ) - tx2 = await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - amountToDecrease2 - ) - }) - - it("should keep authorized amount unchanged", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(amount) - }) - - it("should send request to application with last amount", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - amount, - expectedToAmount2 - ) - }) - - it("should emit AuthorizationDecreaseRequested twice", async () => { - await expect(tx1) - .to.emit(tokenStaking, "AuthorizationDecreaseRequested") - .withArgs( - stakingProvider.address, - application1Mock.address, - expectedFromAmount, - expectedToAmount1 - ) - await expect(tx2) - .to.emit(tokenStaking, "AuthorizationDecreaseRequested") - .withArgs( - stakingProvider.address, - application1Mock.address, - expectedFromAmount, - expectedToAmount2 - ) - }) - }) - } - ) - }) - - describe("approveAuthorizationDecrease", () => { - 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( - 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(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 - ) - }) - - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedAmount) - }) - - it("should not increase min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) - }) - - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) - }) - - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) - }) - }) - - context("when staking provider 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 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) - }) - - 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 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)) - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - await tToken.connect(deployer).transfer(authorizer.address, topUpAmount) - await tToken - .connect(authorizer) - .approve(tokenStaking.address, topUpAmount) - tx = await tokenStaking - .connect(authorizer) - .topUp(stakingProvider.address, topUpAmount) - }) + 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 update only T staked amount", async () => { - await assertStakes( - stakingProvider.address, - topUpAmount, - keepInTAmount, - Zero - ) - }) + 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 not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) + 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 not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address, + ) + await tokenStaking.addLegacyStake( stakingProvider.address, - application1Mock.address + keepInTStake, + nuInTStake, ) - ).to.equal(expectedAmount) - }) - it("should transfer tokens to the staking contract", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - topUpAmount - ) - }) + await tToken.connect(staker).approve(tokenStaking.address, tStake) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, tStake) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) - }) + 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 the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) - }) + context("when authorize staked tokens in one tx", () => { + let tx + const notAuthorized = tStake.sub(to1e18(1)) + const authorizedAmount = tStake.sub(notAuthorized) - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) - }) - }) + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorizedAmount, + ) + }) - 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 + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(authorizedAmount) + }) - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() + 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 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 decrease available amount to authorize for one application", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(notAuthorized) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address, + ), + ).to.equal(tStake) + }) - it("should update only T staked amount", async () => { - await assertStakes( - stakingProvider.address, - topUpAmount, - Zero, - nuInTAmount - ) - }) + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + authorizedAmount, + Zero, + ) + }) - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([staker.address, staker.address, staker.address]) - }) + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + 0, + authorizedAmount, + ) + }) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + context("when authorize to the second application", () => { + let tx2 - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedAmount) - }) + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + + tx2 = await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application2Mock.address, + tStake, + ) + }) + + it("should increase only one authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(authorizedAmount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address, + ), + ).to.equal(tStake) + }) + + it("should set min staked amount equal to T stake", async () => { + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T, + ), + ).to.equal(tStake) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP, + ), + ).to.equal(0) + }) - it("should transfer tokens to the staking contract", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - topUpAmount - ) - }) + it("should decrease available amount to authorize for the second application", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(notAuthorized) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address, + ), + ).to.equal(0) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, topUpAmount) - }) + it("should inform second application", async () => { + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + tStake, + Zero, + ) + }) - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount - ) - }) + it("should emit AuthorizationIncreased", async () => { + await expect(tx2) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + 0, + tStake, + ) + }) + }) + }) - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedAmount - ) - }) - }) + context("when authorize more than staked amount in several txs", () => { + it("should revert", async () => { + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + tStake.sub(1), + ) + await expect( + tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + 2, + ), + ).to.be.revertedWith("Not enough stake to authorize") + }) + }) + }, + ) }) - describe("topUpKeep", () => { - context("when staking provider has no delegated stake", () => { + describe("requestAuthorizationDecrease", () => { + context("when caller is not authorizer", () => { it("should revert", async () => { + const amount = initialStakerBalance await expect( tokenStaking - .connect(stakingProvider) - .topUpKeep(stakingProvider.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).topUpKeep(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") + .connect(staker) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount, + ), + ).to.be.revertedWith("Not authorizer") }) }) context( - "when specified address never was a staking provider in Keep", + "when caller is authorizer of staking provider with T stake", () => { - it("should revert", async () => { - const amount = initialStakerBalance + const amount = initialStakerBalance + + beforeEach(async () => { await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking .connect(staker) @@ -2981,810 +1148,641 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount + amount, + ) + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + amount, ) - await expect( - tokenStaking - .connect(stakingProvider) - .topUpKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to top-up") }) - } - ) - - context("when eligible stake is zero", () => { - it("should revert", async () => { - const amount = initialStakerBalance - await tToken.connect(staker).approve(tokenStaking.address, amount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - initialStakerBalance - ) - await expect( - tokenStaking.connect(staker).topUpKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to top-up") - }) - }) - - context("when eligible stake is less than cached", () => { - it("should revert", async () => { - const initialAmount = initialStakerBalance - const amount = initialAmount.div(2) - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - initialAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - await keepStakingMock.setAmount(stakingProvider.address, amount) - await expect( - tokenStaking - .connect(stakingProvider) - .topUpKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to top-up") - }) - }) - - context("when staking provider has Keep stake", () => { - const initialKeepAmount = initialStakerBalance - const initialKeepInTAmount = convertToT( - initialKeepAmount, - keepRatio - ).result - const newKeepAmount = initialStakerBalance.mul(2) - const newKeepInTAmount = convertToT(newKeepAmount, keepRatio).result - let tx - let blockTimestamp - - beforeEach(async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - initialKeepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - await keepStakingMock.setAmount(stakingProvider.address, newKeepAmount) - tx = await tokenStaking - .connect(stakingProvider) - .topUpKeep(stakingProvider.address) - }) - - it("should update only Keep staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - newKeepInTAmount, - Zero - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(newKeepInTAmount) - }) - - it("should not increase min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs( - stakingProvider.address, - newKeepInTAmount.sub(initialKeepInTAmount) - ) - }) + 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") + }) + }) - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - newKeepInTAmount - ) - }) + 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 the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - newKeepInTAmount - ) - }) - }) + context("when amount to decrease is zero", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + 0, + ), + ).to.be.revertedWith("Parameters must be specified") + }) + }) - context("when staking provider unstaked Keep previously", () => { - const keepAmount = initialStakerBalance - const keepInTAmount = convertToT(keepAmount, keepRatio).result - let tx - let blockTimestamp + context("when amount to decrease is more than authorized", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount.add(1), + ), + ).to.be.revertedWith("Amount exceeds authorized") + }) + }) - beforeEach(async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - blockTimestamp = await lastBlockTime() + context("when amount to decrease is less than authorized", () => { + const amountToDecrease = amount.div(3) + const expectedFromAmount = amount + const expectedToAmount = amount.sub(amountToDecrease) + let tx - await increaseTime(86400) // +24h + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amountToDecrease, + ) + }) - await tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - tx = await tokenStaking - .connect(staker) - .topUpKeep(stakingProvider.address) - }) + it("should keep authorized amount unchanged", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(amount) + }) - it("should update only Keep staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, keepInTAmount, Zero) - }) + it("should send request to application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + expectedToAmount, + ) + }) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + it("should emit AuthorizationDecreaseRequested", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationDecreaseRequested") + .withArgs( + stakingProvider.address, + application1Mock.address, + expectedFromAmount, + expectedToAmount, + ) + }) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, keepInTAmount) - }) - }) + context( + "when request to decrease all authorized amount for several applications", + () => { + let tx - context("when provider has T stake", () => { - const tAmount = initialStakerBalance.div(3) - const keepAmount = initialStakerBalance.mul(2) - const keepInTAmount = convertToT(keepAmount, keepRatio).result - let tx - let blockTimestamp + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application2Mock.address, + amount, + ) + tx = await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"]( + stakingProvider.address, + ) + }) - beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - staker.address, - staker.address, - tAmount - ) - blockTimestamp = await lastBlockTime() + it("should keep authorized amount unchanged", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(amount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address, + ), + ).to.equal(amount) + }) - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - tx = await tokenStaking - .connect(staker) - .topUpKeep(stakingProvider.address) - }) + it("should send request to application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + Zero, + ) + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + amount, + Zero, + ) + }) - it("should update only Keep staked amount", async () => { - await assertStakes( - stakingProvider.address, - tAmount, - keepInTAmount, - Zero + it("should emit AuthorizationDecreaseRequested", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationDecreaseRequested") + .withArgs( + stakingProvider.address, + application1Mock.address, + amount, + Zero, + ) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationDecreaseRequested") + .withArgs( + stakingProvider.address, + application2Mock.address, + amount, + 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) - }) + context("when decrease requested twice", () => { + const expectedFromAmount = amount + const amountToDecrease1 = amount.div(3) + const expectedToAmount1 = amount.sub(amountToDecrease1) + const amountToDecrease2 = amount.div(5) + const expectedToAmount2 = amount.sub(amountToDecrease2) + let tx1 + let tx2 - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount.add(keepInTAmount)) - }) + beforeEach(async () => { + tx1 = await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amountToDecrease1, + ) + tx2 = await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amountToDecrease2, + ) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, keepInTAmount) - }) - }) + it("should keep authorized amount unchanged", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(amount) + }) - context("when staking provider has NuCypher stake", () => { - const nuAmount = initialStakerBalance.div(3) - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const keepAmount = initialStakerBalance.mul(2) - const keepInTAmount = convertToT(keepAmount, keepRatio).result - let tx - let blockTimestamp + it("should send request to application with last amount", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + expectedToAmount2, + ) + }) - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() + it("should emit AuthorizationDecreaseRequested twice", async () => { + await expect(tx1) + .to.emit(tokenStaking, "AuthorizationDecreaseRequested") + .withArgs( + stakingProvider.address, + application1Mock.address, + expectedFromAmount, + expectedToAmount1, + ) + await expect(tx2) + .to.emit(tokenStaking, "AuthorizationDecreaseRequested") + .withArgs( + stakingProvider.address, + application1Mock.address, + expectedFromAmount, + expectedToAmount2, + ) + }) + }) + }, + ) + }) + + describe("approveAuthorizationDecrease", () => { + const amount = initialStakerBalance - const createdAt = 1 - await keepStakingMock.setOperator( + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( stakingProvider.address, - staker.address, beneficiary.address, authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true + amount, ) - tx = await tokenStaking - .connect(stakingProvider) - .topUpKeep(stakingProvider.address) - }) - - it("should update only Keep staked amount", async () => { - await assertStakes( + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( stakingProvider.address, - Zero, - keepInTAmount, - nuInTAmount + application1Mock.address, + amount, ) - }) - - 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(nuInTAmount.add(keepInTAmount)) - }) - - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, keepInTAmount) - }) }) - }) - describe("topUpNu", () => { - context("when staking provider has no delegated stake", () => { + context("when application was not approved", () => { it("should revert", async () => { await expect( - tokenStaking.connect(staker).topUpNu(stakingProvider.address) - ).to.be.revertedWith("Caller is not owner") + application2Mock.approveAuthorizationDecrease( + stakingProvider.address, + ), + ).to.be.revertedWith("Application is not approved") }) }) - context("when caller is not owner", () => { + context("when application was paused", () => { it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) await expect( - tokenStaking.connect(stakingProvider).topUpNu(stakingProvider.address) - ).to.be.revertedWith("Caller is not owner") + application1Mock.approveAuthorizationDecrease( + stakingProvider.address, + ), + ).to.be.revertedWith("Application is not approved") }) }) - context("when stake in NuCypher contract is zero", () => { + context("when application is disabled", () => { it("should revert", async () => { - const amount = initialStakerBalance - await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) + .connect(deployer) + .disableApplication(application1Mock.address) await expect( - tokenStaking.connect(staker).topUpNu(stakingProvider.address) - ).to.be.revertedWith("Nothing to top-up") + application1Mock.approveAuthorizationDecrease( + stakingProvider.address, + ), + ).to.be.revertedWith("Application is not approved") }) }) - context("when stake in NuCypher contract is less than cached", () => { + context("when approve without request", () => { it("should revert", async () => { - const initialAmount = initialStakerBalance - const amount = initialAmount.div(2) - await nucypherStakingMock.setStaker(staker.address, initialAmount) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - await nucypherStakingMock.setStaker(staker.address, amount) await expect( - tokenStaking.connect(staker).topUpNu(stakingProvider.address) - ).to.be.revertedWith("Nothing to top-up") + application1Mock.approveAuthorizationDecrease( + stakingProvider.address, + ), + ).to.be.revertedWith("No deauthorizing in process") }) }) - context("when staking provider has NuCypher stake", () => { - const initialNuAmount = initialStakerBalance - const initialNuInTAmount = convertToT(initialNuAmount, nuRatio).result - const newNuAmount = initialStakerBalance.mul(2) - const newNuInTAmount = convertToT(newNuAmount, nuRatio).result - let tx - let blockTimestamp - - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, initialNuAmount) + context("when approve twice", () => { + it("should revert", async () => { await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() - + .connect(deployer) + .approveApplication(application2Mock.address) await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - await nucypherStakingMock.setStaker(staker.address, newNuAmount) - tx = await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - }) - - it("should update only Nu staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, newNuInTAmount) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - newNuAmount - ) - }) - - 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(newNuInTAmount) - }) - - it("should not increase min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs( + .connect(authorizer) + .increaseAuthorization( stakingProvider.address, - newNuInTAmount.sub(initialNuInTAmount) + application2Mock.address, + amount, ) - }) - - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - newNuInTAmount - ) - }) - - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - newNuInTAmount - ) - }) - }) - - context("when staking provider unstaked Nu previously", () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - let tx - let blockTimestamp - - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() - - await increaseTime(86400) // +24h - await tokenStaking - .connect(staker) - .unstakeNu(stakingProvider.address, nuInTAmount) - tx = await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - }) - - it("should update only Nu staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, nuInTAmount) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - nuAmount - ) - }) - - 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, nuInTAmount) + .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 has T stake", () => { - const tAmount = initialStakerBalance.div(3) - const nuAmount = initialStakerBalance.mul(2) - const conversion = convertToT(nuAmount, nuRatio) - const nuInTAmount = conversion.result + 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 nucypherStakingMock.setStaker(staker.address, nuAmount) - await tToken.connect(staker).approve(tokenStaking.address, tAmount) await tokenStaking - .connect(staker) - .stake( + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( stakingProvider.address, - staker.address, - staker.address, - tAmount + application1Mock.address, + amountToDecrease, ) - blockTimestamp = await lastBlockTime() - - tx = await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - }) - - it("should update only Nu staked amount", async () => { - await assertStakes(stakingProvider.address, tAmount, Zero, nuInTAmount) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - nuAmount.sub(conversion.remainder) + tx = await application1Mock.approveAuthorizationDecrease( + stakingProvider.address, ) }) - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([staker.address, staker.address, staker.address]) + 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) }) - it("should not update start staking timestamp", async () => { + it("should decrease min staked amount in T", async () => { expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should increase available amount to authorize", async () => { + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T, + ), + ).to.equal(expectedToAmount) expect( - await tokenStaking.getAvailableToAuthorize( + await tokenStaking.getMinStaked( stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount.add(nuInTAmount)) - }) - - it("should do callback to NuCypher staking contract", async () => { - await assertNuStakers(staker.address, nuAmount, stakingProvider.address) + StakeTypes.NU, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP, + ), + ).to.equal(0) }) - it("should emit ToppedUp event", async () => { + it("should emit AuthorizationDecreaseApproved", async () => { await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, nuInTAmount) + .to.emit(tokenStaking, "AuthorizationDecreaseApproved") + .withArgs( + stakingProvider.address, + application1Mock.address, + expectedFromAmount, + expectedToAmount, + ) }) }) - context("when staking provider has Keep stake", () => { - const keepAmount = initialStakerBalance.div(2) - const keepInTAmount = convertToT(keepAmount, keepRatio).result - const nuAmount = initialStakerBalance.mul(3) - const conversion = convertToT(nuAmount, nuRatio) - const nuInTAmount = conversion.result - let tx - let blockTimestamp + context( + "when approve after request of full deauthorization for one app", + () => { + const otherAmount = amount.div(3) + let tx - beforeEach(async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - blockTimestamp = await lastBlockTime() + 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, + ) + }) - await nucypherStakingMock.setStaker(staker.address, nuAmount) - tx = await tokenStaking.connect(staker).topUpNu(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 update only Nu staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - keepInTAmount, - nuInTAmount - ) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - nuAmount.sub(conversion.remainder) - ) - }) + 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 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( + 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, - application1Mock.address ) - ).to.equal(nuInTAmount.add(keepInTAmount)) - }) + tx = await application2Mock.approveAuthorizationDecrease( + stakingProvider.address, + ) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, nuInTAmount) - }) - }) + 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("unstakeT", () => { - context("when staking provider has no stake", () => { + describe("forceDecreaseAuthorization", () => { + context("when application is not approved", () => { it("should revert", async () => { await expect( - tokenStaking.unstakeT(deployer.address, 0) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(auxiliaryAccount) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address, + ), + ).to.be.revertedWith("Application is not disabled") }) }) - context("when caller is not owner or staking provider", () => { + context("when application is approved", () => { it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) + .connect(deployer) + .approveApplication(application1Mock.address) await expect( - tokenStaking.connect(authorizer).unstakeT(stakingProvider.address, 0) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(deployer) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address, + ), + ).to.be.revertedWith("Application is not disabled") }) }) - context("when amount to unstake is zero", () => { + context("when application is paused", () => { it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) + .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).unstakeT(stakingProvider.address, 0) - ).to.be.revertedWith("Too much to unstake") - }) - }) - - context("when stake is only in Keep and Nu", () => { - it("should revert", async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - initialStakerBalance - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - - await nucypherStakingMock.setStaker( - staker.address, - initialStakerBalance - ) - await tokenStaking.connect(staker).topUpNu(stakingProvider.address) + tokenStaking + .connect(staker) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address, + ), + ).to.be.revertedWith("Application is not disabled") + }) + }) - const amountToUnstake = 1 + 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(stakingProvider) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") + .connect(deployer) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address, + ), + ).to.be.revertedWith("Application is not authorized") }) }) - context("when amount to unstake is more than not authorized", () => { - it("should revert", async () => { - const amount = initialStakerBalance + 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) @@ -3795,1206 +1793,936 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - amount + amount, ) - const authorized = amount.div(3) await tokenStaking .connect(authorizer) .increaseAuthorization( stakingProvider.address, application1Mock.address, - authorized + amount, ) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"](stakingProvider.address) - const amountToUnstake = amount.sub(authorized).add(1) - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") + await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + + tx = await tokenStaking + .connect(deployer) + .forceDecreaseAuthorization( + stakingProvider.address, + application1Mock.address, + ) }) - }) - context("when unstake before minimum staking time passes", () => { - const amount = initialStakerBalance - const minAmount = initialStakerBalance.div(3) + it("should set authorized amount to 0", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(0) + }) - beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, amount) + it("should allow to authorize more applications", async () => { await tokenStaking - .connect(staker) - .stake( + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( stakingProvider.address, - beneficiary.address, - authorizer.address, - amount + application2Mock.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") - }) + it("should emit AuthorizationDecreaseApproved", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationDecreaseApproved") + .withArgs( + stakingProvider.address, + application1Mock.address, + amount, + 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") - }) - }) + describe("pauseApplication", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, 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") - }) + 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 another stake type was topped-up", () => { - it("should revert", async () => { - const nuAmount = initialStakerBalance - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking.connect(staker).topUpNu(stakingProvider.address) + 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") + }) + }) - const amountToUnstake = amount - await expect( - tokenStaking - .connect(staker) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) + 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 unstake after minimum staking time passes", () => { - const amount = initialStakerBalance - const minAmount = initialStakerBalance.div(3) + context("when pause active application", () => { 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, - ]) + .connect(panicButton) + .pauseApplication(application1Mock.address) }) - it("should not update start staking timestamp", async () => { + it("should pause application", async () => { expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) + await tokenStaking.applicationInfo(application1Mock.address), + ).to.deep.equal([ApplicationStatus.PAUSED, panicButton.address]) }) - 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 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 Unstaked", async () => { + it("should emit ApplicationStatusChanged", async () => { await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, amount) + .to.emit(tokenStaking, "ApplicationStatusChanged") + .withArgs(application1Mock.address, ApplicationStatus.PAUSED) }) }) }) - 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") - }) + 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 owner or staking provider", () => { + 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(authorizer).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(panicButton) + .disableApplication(application1Mock.address), + ).to.be.revertedWith("Caller is not the governance") }) }) - context("when stake is only in T and Nu", () => { + context("when application is not approved", () => { 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 nucypherStakingMock.setStaker( - staker.address, - initialStakerBalance - ) - await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - await expect( tokenStaking - .connect(stakingProvider) - .unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to unstake") + .connect(deployer) + .disableApplication(application2Mock.address), + ).to.be.revertedWith("Can't disable application") }) }) - context("when authorized amount is more than non-Keep stake", () => { + context("when application is disabled", () => { it("should revert", async () => { - const tAmount = initialStakerBalance - const keepAmount = initialStakerBalance - await tokenStaking .connect(deployer) - .approveApplication(application1Mock.address) + .disableApplication(application1Mock.address) + await expect( + tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address), + ).to.be.revertedWith("Can't disable application") + }) + }) - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) + const contextDisable = (preparation) => { + let tx - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) + beforeEach(async () => { + await preparation() - const authorized = tAmount.add(1) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) + tx = await tokenStaking + .connect(deployer) + .disableApplication(application1Mock.address) + }) - await expect( - tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Keep stake still authorized") + it("should disable application", async () => { + expect( + await tokenStaking.applicationInfo(application1Mock.address), + ).to.deep.equal([ApplicationStatus.DISABLED, panicButton.address]) }) - }) - context("when unstake before minimum staking time passes", () => { - beforeEach(async () => { - const keepAmount = initialStakerBalance - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true + 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, ) - await tokenStaking.stakeKeep(stakingProvider.address) }) - context("when Keep was the only stake type", () => { - it("should revert", async () => { - await expect( - tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) + it("should emit ApplicationStatusChanged", async () => { + await expect(tx) + .to.emit(tokenStaking, "ApplicationStatusChanged") + .withArgs(application1Mock.address, ApplicationStatus.DISABLED) }) + } - context("when another stake type was topped-up", () => { - it("should revert", async () => { - const tAmount = initialStakerBalance - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) + context("when disable approved application", () => { + contextDisable(() => {}) + }) - await expect( - tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) + context("when disable paused application", () => { + contextDisable(async () => { + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) }) }) + }) - context( - "when authorized amount is less than non-Keep stake and enough time passed", - () => { - 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) - - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) + describe("setPanicButton", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + }) - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking + context("when caller is not the governance", () => { + it("should revert", async () => { + await expect( + 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) - .unstakeKeep(stakingProvider.address) - }) - - it("should set Keep staked amount to zero", async () => { - await assertStakes(stakingProvider.address, tAmount, Zero, Zero) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount.sub(authorized)) - }) - - it("should update min staked amount", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).to.equal(tAmount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, keepInTAmount) - }) - - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - tAmount - ) - }) - - it("should decrease the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount - ) - }) - } - ) - }) + .setPanicButton(application1Mock.address, panicButton.address), + ).to.be.revertedWith("Caller is not the governance") + }) + }) - describe("unstakeNu", () => { - context("when staking provider has no stake", () => { + context("when application was not approved", () => { it("should revert", async () => { await expect( - tokenStaking.unstakeNu(deployer.address, 0) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(deployer) + .setPanicButton(application2Mock.address, panicButton.address), + ).to.be.revertedWith("Application is not approved") }) }) - context("when caller is not owner or staking provider", () => { + context("when application is disabled", () => { it("should revert", async () => { - await nucypherStakingMock.setStaker( - staker.address, - initialStakerBalance - ) await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) + .connect(deployer) + .disableApplication(application1Mock.address) await expect( - tokenStaking.connect(authorizer).unstakeNu(stakingProvider.address, 0) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address), + ).to.be.revertedWith("Application is not approved") }) }) - context("when unstake before minimum staking time passes", () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const minTAmount = nuInTAmount.div(5) + context("when set panic button address for approved application", () => { + let tx beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - }) - - context("when the stake left would be above the minimum", () => { - it("should revert", async () => { - const amountToUnstake = nuInTAmount.sub(minTAmount).sub(1) - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) - }) - - context("when the stake left would be the minimum", () => { - it("should revert", async () => { - const amountToUnstake = nuInTAmount.sub(minTAmount) - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeNu(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 below the minimum", () => { - it("should revert", async () => { - const amountToUnstake = nuInTAmount.sub(minTAmount).add(1) - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeNu(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 another stake type was topped-up", () => { - it("should revert", async () => { - await tToken.connect(staker).approve(tokenStaking.address, minTAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, minTAmount) - - const amountToUnstake = nuInTAmount - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeNu(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 amount to unstake is zero", () => { + describe("setAuthorizationCeiling", () => { + context("when caller is not the governance", () => { it("should revert", async () => { - await nucypherStakingMock.setStaker( - staker.address, - initialStakerBalance - ) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) await expect( - tokenStaking.connect(staker).unstakeNu(stakingProvider.address, 0) - ).to.be.revertedWith("Too much to unstake") + tokenStaking.connect(staker).setAuthorizationCeiling(1), + ).to.be.revertedWith("Caller is not the governance") }) }) - context("when stake is only in Keep and T", () => { - it("should revert", async () => { - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - initialStakerBalance - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) + 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) - .topUp(stakingProvider.address, initialStakerBalance) - - const amountToUnstake = 1 + .stake( + stakingProvider.address, + staker.address, + staker.address, + initialStakerBalance, + ) await expect( tokenStaking .connect(stakingProvider) - .unstakeNu(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") + .topUp(stakingProvider.address, 0), + ).to.be.revertedWith("Parameters must be specified") }) }) - context("when amount to unstake is more than not authorized", () => { + context("when staking provider has no delegated stake", () => { it("should revert", async () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - - const authorized = nuInTAmount.div(3) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) - - const notAuthorizedInT = nuInTAmount.sub(authorized) - const notAuthorizedInNu = convertFromT(notAuthorizedInT, nuRatio).result - const amountToUnstake = convertToT( - notAuthorizedInNu.add(floatingPointDivisor), - nuRatio - ).result await expect( tokenStaking .connect(stakingProvider) - .unstakeNu(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") + .topUp(stakingProvider.address, initialStakerBalance), + ).to.be.revertedWith("Nothing to top-up") }) }) - context( - "when amount to unstake is less than not authorized and enough time passed", - () => { - const tAmount = initialStakerBalance - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const authorized = nuInTAmount.div(3).add(tAmount) - const amountToUnstake = nuInTAmount.div(4).add(1) - const expectedNuAmount = nuAmount.sub( - convertFromT(amountToUnstake, nuRatio).result - ) - const expectedNuInTAmount = convertToT(expectedNuAmount, nuRatio).result - const expectedUnstaked = nuInTAmount.sub(expectedNuInTAmount) - let tx - let blockTimestamp - - beforeEach(async () => { - await tokenStaking.connect(deployer).setMinimumStakeAmount(1) - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - 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, amountToUnstake) - }) + 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 - it("should update Nu staked amount", async () => { - await assertStakes( + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( stakingProvider.address, - tAmount, - Zero, - expectedNuInTAmount - ) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - expectedNuAmount - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedNuInTAmount.add(tAmount).sub(authorized)) - }) - - 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(authorized.sub(tAmount)) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, expectedUnstaked) - }) - - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedNuInTAmount.add(tAmount) + staker.address, + amount, ) - }) + blockTimestamp = await lastBlockTime() - it("should decrease the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedNuInTAmount.add(tAmount) - ) - }) - } - ) + 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) + }) - context( - "when amount to unstake is less than not authorized only after rounding and enough time passed", - () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const authorized = nuInTAmount.div(3) - const amountToUnstake = nuInTAmount.sub(authorized).add(1) - const expectedNuAmount = nuAmount.sub( - convertFromT(amountToUnstake, nuRatio).result - ) - const expectedNuInTAmount = convertToT(expectedNuAmount, nuRatio).result - const expectedUnstaked = nuInTAmount.sub(expectedNuInTAmount) - let tx + it("should update T staked amount", async () => { + await assertStakes(stakingProvider.address, expectedAmount, Zero, Zero) + }) - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) + it("should not update roles", async () => { + expect( + await tokenStaking.rolesOf(stakingProvider.address), + ).to.deep.equal([staker.address, staker.address, staker.address]) + }) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address), + ).to.equal(blockTimestamp) + }) - await increaseTime(86400) // +24h + it("should transfer tokens to the staking contract", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal( + expectedAmount, + ) + }) - tx = await tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address, amountToUnstake) - }) + it("should increase available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(expectedAmount) + }) - it("should update Nu staked amount", async () => { - await assertStakes( + it("should not increase min staked amount", async () => { + expect( + await tokenStaking.getMinStaked( stakingProvider.address, - Zero, - Zero, - expectedNuInTAmount - ) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - expectedNuAmount - ) - }) + StakeTypes.T, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP, + ), + ).to.equal(0) + }) - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, expectedUnstaked) - }) - } - ) - }) + it("should emit ToppedUp event", async () => { + await expect(tx) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, topUpAmount) + }) - describe("unstakeAll", () => { - context("when staking provider has no stake", () => { - it("should revert", async () => { - await expect( - tokenStaking.unstakeAll(deployer.address) - ).to.be.revertedWith("Not owner or provider") + it("should increase the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal( + expectedAmount, + ) + }) + + it("should increase the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + expectedAmount, + ) }) }) - context("when caller is not owner or staking provider", () => { - it("should revert", async () => { + context("when staking provider unstaked T previously", () => { + const amount = initialStakerBalance + let tx + let blockTimestamp + + beforeEach(async () => { await tToken .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) + .approve(tokenStaking.address, amount.mul(2)) await tokenStaking .connect(staker) .stake( stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance + staker.address, + staker.address, + amount, ) - await expect( - tokenStaking.connect(authorizer).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") + blockTimestamp = await lastBlockTime() + + 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) + }) + + 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 authorized amount is not zero", () => { - it("should revert", async () => { - const amount = initialStakerBalance + 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) - .approveApplication(application1Mock.address) - await tToken.connect(staker).approve(tokenStaking.address, amount) + .setMinimumStakeAmount(topUpAmount.add(1)) + + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address, + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTAmount, + 0, + ) + blockTimestamp = await lastBlockTime() + await tokenStaking .connect(staker) - .stake( + .delegateVoting(stakingProvider.address, delegatee.address) + await tToken.connect(deployer).transfer(authorizer.address, topUpAmount) + await tToken + .connect(authorizer) + .approve(tokenStaking.address, topUpAmount) + tx = await tokenStaking + .connect(authorizer) + .topUp(stakingProvider.address, topUpAmount) + }) + + it("should update only T staked amount", async () => { + await assertStakes( + stakingProvider.address, + topUpAmount, + keepInTAmount, + Zero, + ) + }) + + it("should not update roles", async () => { + expect( + await tokenStaking.rolesOf(stakingProvider.address), + ).to.deep.equal([ + staker.address, + beneficiary.address, + authorizer.address, + ]) + }) + + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address), + ).to.equal(blockTimestamp) + }) + + it("should increase available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) - const authorized = 1 + application1Mock.address, + ), + ).to.equal(topUpAmount) + }) + + it("should transfer tokens to the staking contract", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal( + topUpAmount, + ) + }) + + it("should emit ToppedUp event", async () => { + await expect(tx) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, topUpAmount) + }) + + it("should increase the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal( + expectedAmount, + ) + }) + + it("should increase the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + expectedAmount, + ) + }) + }) + + context("when staking provider has NuCypher stake", () => { + const nuAmount = initialStakerBalance + const nuInTAmount = convertToT(nuAmount, nuRatio).result + const topUpAmount = initialStakerBalance.mul(2) + const expectedAmount = nuInTAmount.add(topUpAmount) + let tx + let blockTimestamp + + beforeEach(async () => { + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + staker.address, + staker.address, + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + nuInTAmount, + ) + blockTimestamp = await lastBlockTime() + await tokenStaking - .connect(authorizer) - .increaseAuthorization( + .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, - authorized - ) + ), + ).to.equal(topUpAmount) + }) + + it("should transfer tokens to the staking contract", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal( + topUpAmount, + ) + }) + + it("should emit ToppedUp event", async () => { + await expect(tx) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, topUpAmount) + }) + + it("should increase the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal( + expectedAmount, + ) + }) + + it("should increase the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + expectedAmount, + ) + }) + }) + }) + describe("unstakeT", () => { + context("when staking provider has no stake", () => { + it("should revert", async () => { await expect( - tokenStaking - .connect(stakingProvider) - .unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Stake still authorized") + tokenStaking.unstakeT(deployer.address, 0), + ).to.be.revertedWith("Not owner or provider") }) }) - context("when unstake T before minimum staking time passes", () => { + context("when caller is not owner or staking provider", () => { it("should revert", async () => { - const amount = initialStakerBalance - const minAmount = 1 - await tToken.connect(staker).approve(tokenStaking.address, amount) + await tToken + .connect(staker) + .approve(tokenStaking.address, initialStakerBalance) await tokenStaking .connect(staker) .stake( stakingProvider.address, beneficiary.address, authorizer.address, - amount + initialStakerBalance, ) - await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") + tokenStaking.connect(authorizer).unstakeT(stakingProvider.address, 0), + ).to.be.revertedWith("Not owner or provider") }) }) - context("when unstake Nu before minimum staking time passes", () => { + context("when amount to unstake is zero", () => { it("should revert", async () => { - const nuAmount = initialStakerBalance - await nucypherStakingMock.setStaker(staker.address, nuAmount) + await tToken + .connect(staker) + .approve(tokenStaking.address, initialStakerBalance) await tokenStaking .connect(staker) - .stakeNu( + .stake( stakingProvider.address, beneficiary.address, - authorizer.address + authorizer.address, + initialStakerBalance, ) - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") + tokenStaking.connect(staker).unstakeT(stakingProvider.address, 0), + ).to.be.revertedWith("Too much to unstake") }) }) - context("when unstake Keep before minimum time passes", () => { + context("when stake is only in Keep and Nu", () => { it("should revert", async () => { - const keepAmount = initialStakerBalance - const createdAt = 1 - await keepStakingMock.setOperator( + await tokenStaking.setLegacyStakingProvider( stakingProvider.address, staker.address, beneficiary.address, authorizer.address, - createdAt, - 0, - keepAmount ) - await keepStakingMock.setEligibility( + await tokenStaking.addLegacyStake( stakingProvider.address, - tokenStaking.address, - true + initialStakerBalance, + initialStakerBalance, ) - await tokenStaking.stakeKeep(stakingProvider.address) + const amountToUnstake = 1 await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") + tokenStaking + .connect(stakingProvider) + .unstakeT(stakingProvider.address, amountToUnstake), + ).to.be.revertedWith("Too much to unstake") }) }) - const contextUnstakeAll = (preparation, tAmount, nuAmount, keepAmount) => { - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const keepInTAmount = convertToT(keepAmount, keepRatio).result - let tx - let blockTimestamp - - beforeEach(async () => { - blockTimestamp = await preparation() - - tx = await tokenStaking - .connect(stakingProvider) - .unstakeAll(stakingProvider.address) - }) - - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should transfer tokens to the staker address", async () => { - expect(await tToken.balanceOf(tokenStaking.address)).to.equal(0) - expect(await tToken.balanceOf(staker.address)).to.equal( - initialStakerBalance - ) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - }) - - it("should update min staked amount", async () => { - expect( - await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( + context("when amount to unstake is more than not authorized", () => { + 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, - StakeTypes.NU + beneficiary.address, + authorizer.address, + amount, ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( + const authorized = amount.div(3) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( stakingProvider.address, - StakeTypes.KEEP + application1Mock.address, + authorized, ) - ).to.equal(0) + + const amountToUnstake = amount.sub(authorized).add(1) + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeT(stakingProvider.address, amountToUnstake), + ).to.be.revertedWith("Too much to unstake") }) + }) - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs( + 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) + .stake( stakingProvider.address, - nuInTAmount.add(keepInTAmount).add(tAmount) + beneficiary.address, + authorizer.address, + amount, ) + await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) }) - } - - 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) - - 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() - - // - // top-up KEEP - // - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking - .connect(staker) - .topUpKeep(stakingProvider.address) - - // - // top-up NU - // - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - - await increaseTime(86400) // +24h - return blockTimestamp - }, - tAmount, - nuAmount, - keepAmount - ) - } - ) - - context( - "when unstake after minimum staking time passes for NU stake", - () => { - // subtracting arbitrary values just to keep them different - const tAmount = initialStakerBalance.sub(3) - const nuAmount = initialStakerBalance.sub(1) - const keepAmount = initialStakerBalance.sub(2) - - contextUnstakeAll( - async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking.connect(deployer).setMinimumStakeAmount(1) - // - // stake NU - // - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking + 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) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - const blockTimestamp = await lastBlockTime() + .unstakeT(stakingProvider.address, amountToUnstake), + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) - // - // top-up KEEP - // - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking + context("when the stake left would be the minimum", () => { + it("should revert", async () => { + const amountToUnstake = amount.sub(minAmount) + await expect( + tokenStaking .connect(staker) - .topUpKeep(stakingProvider.address) + .unstakeT(stakingProvider.address, amountToUnstake), + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) - // - // top-up T - // - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking + 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) - .topUp(stakingProvider.address, tAmount) - - await increaseTime(86400) // +24h - return blockTimestamp - }, - tAmount, - nuAmount, - keepAmount - ) - } - ) + .unstakeT(stakingProvider.address, amountToUnstake), + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) - context("when unstake after minimum time passes for KEEP stake", () => { - // subtracting arbitrary values just to keep them different - const tAmount = initialStakerBalance.sub(3) - const nuAmount = initialStakerBalance.sub(2) - const keepAmount = initialStakerBalance.sub(1) + context("when another stake type was topped-up", () => { + it("should revert", async () => { + const nuAmount = initialStakerBalance + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + nuAmount, + ) - contextUnstakeAll( - async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking.connect(deployer).setMinimumStakeAmount(1) + const amountToUnstake = amount + await expect( + tokenStaking + .connect(staker) + .unstakeT(stakingProvider.address, amountToUnstake), + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) + }) + + context("when unstake after minimum staking time passes", () => { + const amount = initialStakerBalance + const minAmount = initialStakerBalance.div(3) + let tx + let blockTimestamp - // - // stake KEEP - // - const createdAt = 1 - await keepStakingMock.setOperator( + beforeEach(async () => { + await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( stakingProvider.address, - staker.address, beneficiary.address, authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true + amount, ) - await tokenStaking.stakeKeep(stakingProvider.address) - const blockTimestamp = await lastBlockTime() + 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 - // - // top-up NU - // - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking.connect(staker).topUpNu(stakingProvider.address) + tx = await tokenStaking + .connect(stakingProvider) + .unstakeT(stakingProvider.address, amount) + }) - await increaseTime(86400) // +24h - return blockTimestamp - }, - tAmount, - nuAmount, - keepAmount - ) + 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) + }) + + it("should transfer tokens to the staker address", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal(0) + expect(await tToken.balanceOf(staker.address)).to.equal(amount) + }) + + it("should emit Unstaked", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, amount) + }) }) }) - describe("notifyKeepStakeDiscrepancy", () => { - context("when staking provider has no cached Keep stake", () => { + 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) @@ -5005,740 +2733,427 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - initialStakerBalance + initialStakerBalance, ) - - const createdAt = 1 - await keepStakingMock.setOperator( - stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - initialStakerBalance - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await expect( - tokenStaking.notifyKeepStakeDiscrepancy(stakingProvider.address) - ).to.be.revertedWith("Nothing to slash") + tokenStaking.connect(authorizer).unstakeKeep(stakingProvider.address), + ).to.be.revertedWith("Not owner or provider") }) }) - context("when no discrepancy between T and Keep staking contracts", () => { - const keepAmount = initialStakerBalance + context("when stake is only in T and Nu", () => { + it("should revert", async () => { + await tToken + .connect(staker) + .approve(tokenStaking.address, initialStakerBalance) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + initialStakerBalance, + ) - beforeEach(async () => { - const createdAt = 1 - await keepStakingMock.setOperator( + await tokenStaking.addLegacyStake( stakingProvider.address, - staker.address, - beneficiary.address, - authorizer.address, - createdAt, 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true + initialStakerBalance, ) - await tokenStaking.stakeKeep(stakingProvider.address) - }) - context("when stakes are equal in both contracts", () => { - it("should revert", async () => { - await expect( - tokenStaking.notifyKeepStakeDiscrepancy(stakingProvider.address) - ).to.be.revertedWith("There is no discrepancy") - }) + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeKeep(stakingProvider.address), + ).to.be.revertedWith("Nothing to unstake") }) - - context( - "when stake in Keep contract is greater than in T contract", - () => { - it("should revert", async () => { - await keepStakingMock.setAmount( - stakingProvider.address, - keepAmount.mul(2) - ) - await expect( - tokenStaking.notifyKeepStakeDiscrepancy(stakingProvider.address) - ).to.be.revertedWith("There is no discrepancy") - }) - } - ) }) - context("when discrepancy between Keep and T stakes", () => { - const keepAmount = initialStakerBalance - const keepInTAmount = convertToT(keepAmount, keepRatio).result - const newKeepAmount = keepAmount.div(3).add(1) - const newKeepInTAmount = convertToT(newKeepAmount, keepRatio).result - const createdAt = ethers.BigNumber.from(1) - const tPenalty = newKeepInTAmount.div(10).add(1) - const rewardMultiplier = 50 - let tx + context("when authorized amount is more than non-Keep stake", () => { + it("should revert", async () => { + const tAmount = initialStakerBalance + const keepAmount = initialStakerBalance - beforeEach(async () => { await tokenStaking .connect(deployer) .approveApplication(application1Mock.address) - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - - await keepStakingMock.setOperator( + await tokenStaking.setLegacyStakingProvider( stakingProvider.address, staker.address, - beneficiary.address, - authorizer.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(stakingProvider.address) - }) - - context("when penalty is not set (no apps)", () => { - beforeEach(async () => { - await keepStakingMock.setAmount( - stakingProvider.address, - newKeepAmount - ) - - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) - - it("should update staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - newKeepInTAmount, - Zero - ) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(newKeepInTAmount) - }) - - it("should not call seize in Keep contract", async () => { - await assertDelegationInfo( - stakingProvider.address, - newKeepAmount, - createdAt, - Zero - ) - - expect( - await keepStakingMock.tattletales(otherStaker.address) - ).to.equal(0) - }) - - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, 0, true) - }) - }) - - context("when penalty in Keep is zero (no apps)", () => { - beforeEach(async () => { - const tPenalty = 1 - await keepStakingMock.setAmount( - stakingProvider.address, - newKeepAmount - ) - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) - - it("should update staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - newKeepInTAmount, - Zero - ) - }) - - it("should not call seize in Keep contract", async () => { - await assertDelegationInfo( - stakingProvider.address, - newKeepAmount, - createdAt, - Zero - ) - expect( - await keepStakingMock.tattletales(otherStaker.address) - ).to.equal(0) - }) - - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, 0, true) - }) - }) - - context("when staker has no Keep stake anymore (1 app)", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await keepStakingMock.setAmount(stakingProvider.address, 0) - - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - keepInTAmount - ) - - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) - - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) - - 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) - }) + beneficiary.address, + authorizer.address, + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepAmount, + 0, + ) - it("should not call seize in Keep contract", async () => { - await assertDelegationInfo( - stakingProvider.address, - Zero, - createdAt, - Zero - ) - expect( - await keepStakingMock.tattletales(otherStaker.address) - ).to.equal(0) - }) + await tToken.connect(staker).approve(tokenStaking.address, tAmount) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, tAmount) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, + const authorized = tAmount.add(1) + await tokenStaking + .connect(authorizer) + .forceIncreaseAuthorization( stakingProvider.address, - Zero, - Zero + application1Mock.address, + authorized, ) - }) - it("should emit TokensSeized and AuthorizationInvoluntaryDecreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, 0, true) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - keepInTAmount, - Zero, - true - ) - }) + await expect( + tokenStaking.connect(staker).unstakeKeep(stakingProvider.address), + ).to.be.revertedWith("Keep stake still authorized") }) + }) - context( - "when penalty is less than Keep stake (2 apps, 2 involuntary calls)", - () => { - const authorizedAmount1 = keepInTAmount.sub(1) - const conversion = convertFromT(tPenalty, keepRatio) - const keepPenalty = conversion.result - const expectedKeepAmount = newKeepAmount.sub(keepPenalty) - const expectedKeepInTAmount = convertToT( - expectedKeepAmount, - keepRatio - ).result - const expectedReward = rewardFromPenalty( - keepPenalty, - rewardMultiplier - ) - const authorizedAmount2 = expectedKeepInTAmount.sub(1) - const expectedTPenalty = tPenalty.sub(conversion.remainder) - const expectedAuthorizedAmount1 = expectedKeepInTAmount - const expectedAuthorizedAmount2 = - authorizedAmount2.sub(expectedTPenalty) + 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) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await keepStakingMock.setAmount( - stakingProvider.address, - newKeepAmount - ) + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount1 - ) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - authorizedAmount2 - ) + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address, + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTAmount, + 0, + ) + blockTimestamp = await lastBlockTime() - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) + await tokenStaking + .connect(staker) + .delegateVoting(stakingProvider.address, delegatee.address) - it("should update staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - expectedKeepInTAmount, - Zero - ) - }) + await tToken.connect(staker).approve(tokenStaking.address, tAmount) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, tAmount) - it("should decrease 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(expectedKeepInTAmount.sub(expectedAuthorizedAmount2)) - }) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorized, + ) - it("should decrease authorized amount only for both applications", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedAuthorizedAmount1) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application2Mock.address - ) - ).to.equal(expectedAuthorizedAmount2) - }) + tx = await tokenStaking + .connect(stakingProvider) + .unstakeKeep(stakingProvider.address) + }) - 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(expectedKeepInTAmount) - }) + it("should set Keep staked amount to zero", async () => { + await assertStakes(stakingProvider.address, tAmount, Zero, Zero) + }) - it("should call seize in Keep contract", async () => { - await assertDelegationInfo( - stakingProvider.address, - expectedKeepAmount, - createdAt, - Zero - ) - expect( - await keepStakingMock.tattletales(otherStaker.address) - ).to.equal(expectedReward) - }) + it("should not update roles", async () => { + expect( + await tokenStaking.rolesOf(stakingProvider.address), + ).to.deep.equal([ + staker.address, + beneficiary.address, + authorizer.address, + ]) + }) - it("should inform only one application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - expectedAuthorizedAmount1, - Zero - ) - await assertApplicationStakingProviders( - application2Mock, - stakingProvider.address, - expectedAuthorizedAmount2, - Zero - ) - }) + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address), + ).to.equal(blockTimestamp) + }) - it("should emit TokensSeized and AuthorizationInvoluntaryDecreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs( - stakingProvider.address, - convertToT(keepPenalty, keepRatio).result, - true - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - authorizedAmount1, - expectedKeepInTAmount, - true - ) - }) - } - ) + it("should decrease available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(tAmount.sub(authorized)) + }) - context( - "when penalty is more than Keep stake (1 app with decreasing authorization)", - () => { - const keepPenalty = convertFromT(tPenalty, keepRatio) - const newKeepAmount = keepPenalty.result.sub(1) - const expectedKeepPenalty = convertToT(newKeepAmount, keepRatio) - const expectedKeepAmount = expectedKeepPenalty.remainder - const expectedReward = rewardFromPenalty( - newKeepAmount.sub(expectedKeepAmount), - rewardMultiplier - ) - const tStake = initialStakerBalance - const authorizedAmount = keepInTAmount.sub(1).add(tStake) - const authorizationDeacrease = keepInTAmount.div(2).add(tStake) + it("should update min staked amount", async () => { + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T, + ), + ).to.equal(tAmount) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP, + ), + ).to.equal(0) + }) - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await keepStakingMock.setAmount( - stakingProvider.address, - newKeepAmount - ) + it("should emit Unstaked", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, keepInTAmount) + }) - await tToken.connect(staker).approve(tokenStaking.address, tStake) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tStake) + it("should decrease the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) + }) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - application1Mock.address, - authorizationDeacrease - ) + it("should decrease the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + tAmount, + ) + }) + }) + }) + + describe("unstakeNu", () => { + context("when staking provider has no stake", () => { + it("should revert", async () => { + await expect( + tokenStaking.unstakeNu(deployer.address), + ).to.be.revertedWith("Not owner or provider") + }) + }) + + context("when caller is not owner or staking provider", () => { + it("should revert", async () => { + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address, + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + initialStakerBalance, + ) + await expect( + tokenStaking.connect(authorizer).unstakeNu(stakingProvider.address), + ).to.be.revertedWith("Not owner or provider") + }) + }) + + context("when stake is only in Keep and T", () => { + it("should revert", async () => { + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address, + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + initialStakerBalance, + 0, + ) - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) + await tToken + .connect(staker) + .approve(tokenStaking.address, initialStakerBalance) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, initialStakerBalance) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, tStake, Zero, Zero) - }) + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeNu(stakingProvider.address), + ).to.be.revertedWith("Nothing to unstake") + }) + }) - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - }) + context("when amount to unstake is more than not authorized", () => { + it("should revert", async () => { + const nuAmount = initialStakerBalance + const nuInTAmount = convertToT(nuAmount, nuRatio).result + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address, + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + nuInTAmount, + ) - it("should decrease authorized amount", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tStake) - }) + const authorized = nuInTAmount.div(3) + await tokenStaking + .connect(authorizer) + .forceIncreaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorized, + ) - it("should call seize in Keep contract", async () => { - await assertDelegationInfo( - stakingProvider.address, - expectedKeepAmount, - createdAt, - Zero - ) - expect( - await keepStakingMock.tattletales(otherStaker.address) - ).to.equal(expectedReward) - }) + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeNu(stakingProvider.address), + ).to.be.revertedWith("NU stake still authorized") + }) + }) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - tStake, - Zero - ) - await application1Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - Zero, - Zero - ) - }) + context("when amount to unstake is less than not authorized", () => { + const tAmount = initialStakerBalance + const nuAmount = initialStakerBalance + const nuInTAmount = convertToT(nuAmount, nuRatio).result + const authorized = tAmount + const expectedNuAmount = 0 + const expectedNuInTAmount = 0 + const expectedUnstaked = nuInTAmount + let tx + let blockTimestamp - it("should emit TokensSeized and AuthorizationInvoluntaryDecreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs( - stakingProvider.address, - expectedKeepPenalty.result, - true - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - authorizedAmount, - tStake, - true - ) - }) - } - ) + 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() - context( - "when started undelegating before unstake (2 broken apps)", - () => { - const authorizedAmount = keepInTAmount.sub(1) - const keepPenalty = convertFromT(tPenalty, keepRatio).result - const expectedKeepAmount = keepAmount.sub(keepPenalty) - const expectedReward = rewardFromPenalty( - keepPenalty, - rewardMultiplier - ) - const undelegatedAt = ethers.BigNumber.from(2) - let brokenApplicationMock - let expensiveApplicationMock + 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) - beforeEach(async () => { - const BrokenApplicationMock = await ethers.getContractFactory( - "BrokenApplicationMock" - ) - brokenApplicationMock = await BrokenApplicationMock.deploy( - tokenStaking.address - ) - await brokenApplicationMock.deployed() - const ExpensiveApplicationMock = await ethers.getContractFactory( - "ExpensiveApplicationMock" - ) - expensiveApplicationMock = await ExpensiveApplicationMock.deploy( - tokenStaking.address - ) - await expensiveApplicationMock.deployed() + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorized, + ) - await tokenStaking - .connect(deployer) - .approveApplication(brokenApplicationMock.address) - await tokenStaking - .connect(deployer) - .approveApplication(expensiveApplicationMock.address) + await increaseTime(86400) // +24h + tx = await tokenStaking + .connect(stakingProvider) + .unstakeNu(stakingProvider.address) + }) - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await keepStakingMock.setUndelegatedAt( - stakingProvider.address, - undelegatedAt - ) + it("should update Nu staked amount", async () => { + await assertStakes( + stakingProvider.address, + tAmount, + Zero, + expectedNuInTAmount, + ) + expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( + expectedNuAmount, + ) + }) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - brokenApplicationMock.address, - authorizedAmount - ) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - expensiveApplicationMock.address, - authorizedAmount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address,address,uint96)"]( - stakingProvider.address, - brokenApplicationMock.address, - authorizedAmount - ) + it("should not update roles", async () => { + expect( + await tokenStaking.rolesOf(stakingProvider.address), + ).to.deep.equal([ + staker.address, + beneficiary.address, + authorizer.address, + ]) + }) - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) + it("should start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address), + ).to.equal(blockTimestamp) + }) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) + it("should decrease available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(0) + }) - it("should decrease authorized amount for both applications", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - brokenApplicationMock.address - ) - ).to.equal(0) - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - expensiveApplicationMock.address - ) - ).to.equal(0) - }) + it("should update min staked amount", async () => { + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T, + ), + ).to.equal(tAmount) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP, + ), + ).to.equal(0) + }) - it("should call seize in Keep contract", async () => { - await assertDelegationInfo( - stakingProvider.address, - expectedKeepAmount, - createdAt, - undelegatedAt - ) - expect( - await keepStakingMock.tattletales(otherStaker.address) - ).to.equal(expectedReward) - }) + it("should emit Unstaked", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, expectedUnstaked) + }) - it("should catch exceptions during application calls", async () => { - await assertApplicationStakingProviders( - brokenApplicationMock, - stakingProvider.address, - authorizedAmount, - Zero - ) - await assertApplicationStakingProviders( - expensiveApplicationMock, - stakingProvider.address, - authorizedAmount, - Zero - ) - await expect( - brokenApplicationMock.approveAuthorizationDecrease( - stakingProvider.address - ) - ).to.be.revertedWith("No deauthorizing in process") - }) + it("should decrease the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) + }) - it("should emit TokensSeized and AuthorizationInvoluntaryDecreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs( - stakingProvider.address, - convertToT(keepPenalty, keepRatio).result, - true - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - brokenApplicationMock.address, - authorizedAmount, - Zero, - false - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - expensiveApplicationMock.address, - authorizedAmount, - Zero, - false - ) - }) - } - ) + it("should decrease the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + tAmount, + ) + }) }) }) - describe("notifyNuStakeDiscrepancy", () => { - const nuAmount = initialStakerBalance - - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) + describe("unstakeAll", () => { + context("when staking provider has no stake", () => { + it("should revert", async () => { + await expect( + tokenStaking.unstakeAll(deployer.address), + ).to.be.revertedWith("Not owner or provider") + }) }) - context("when staking provider has no cached Nu stake", () => { + context("when caller is not owner or staking provider", () => { it("should revert", async () => { await tToken .connect(staker) @@ -5749,376 +3164,283 @@ describe("TokenStaking", () => { stakingProvider.address, beneficiary.address, authorizer.address, - initialStakerBalance + initialStakerBalance, ) - await expect( - tokenStaking.notifyNuStakeDiscrepancy(stakingProvider.address) - ).to.be.revertedWith("Nothing to slash") + tokenStaking.connect(authorizer).unstakeAll(stakingProvider.address), + ).to.be.revertedWith("Not owner or provider") }) }) - context( - "when no discrepancy between T and NuCypher staking contracts", - () => { - beforeEach(async () => { - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - }) - - context("when stakes are equal in both contracts", () => { - it("should revert", async () => { - await expect( - tokenStaking.notifyNuStakeDiscrepancy(stakingProvider.address) - ).to.be.revertedWith("There is no discrepancy") - }) - }) - - context( - "when stake in NuCypher contract is greater than in T contract", - () => { - it("should revert", async () => { - await nucypherStakingMock.setStaker( - staker.address, - nuAmount.mul(2) - ) - await expect( - tokenStaking.notifyNuStakeDiscrepancy(stakingProvider.address) - ).to.be.revertedWith("There is no discrepancy") - }) - } - ) - } - ) - - context("when discrepancy between Nu and T stakes", () => { - const newNuAmount = nuAmount.div(3).add(1) - const newNuInTAmount = convertToT(newNuAmount, nuRatio).result - const tPenalty = newNuInTAmount.div(10).add(1) - const rewardMultiplier = 70 - let tx - - beforeEach(async () => { + context("when authorized amount is not zero", () => { + it("should revert", async () => { + const amount = initialStakerBalance + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tToken.connect(staker).approve(tokenStaking.address, amount) await tokenStaking .connect(staker) - .stakeNu( + .stake( stakingProvider.address, beneficiary.address, - authorizer.address + authorizer.address, + amount, ) - + const authorized = 1 await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - }) - - context("when penalty is not set (no apps)", () => { - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, newNuAmount) - - tx = await tokenStaking - .connect(otherStaker) - .notifyNuStakeDiscrepancy(stakingProvider.address) - }) - - it("should update staked amount", async () => { - await assertStakes( + .connect(authorizer) + .increaseAuthorization( stakingProvider.address, - Zero, - Zero, - newNuInTAmount - ) - }) - - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(newNuInTAmount) - }) - - it("should not call seize in NuCypher contract", async () => { - await assertNuStakers( - staker.address, - newNuAmount, - stakingProvider.address + application1Mock.address, + authorized, ) - expect( - await nucypherStakingMock.investigators(otherStaker.address) - ).to.equal(0) - }) - - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, 0, true) - }) + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeAll(stakingProvider.address), + ).to.be.revertedWith("Stake still authorized") }) + }) - context("when penalty in Nu is zero (no apps)", () => { - beforeEach(async () => { - const tPenalty = 1 - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - - await nucypherStakingMock.setStaker(staker.address, newNuAmount) - - tx = await tokenStaking - .connect(otherStaker) - .notifyNuStakeDiscrepancy(stakingProvider.address) - }) - - it("should update staked amount", async () => { - await assertStakes( + context("when unstake T before minimum staking time passes", () => { + it("should revert", async () => { + const amount = initialStakerBalance + const minAmount = 1 + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( stakingProvider.address, - Zero, - Zero, - newNuInTAmount - ) - }) - - it("should not call seize in NuCypher contract", async () => { - await assertNuStakers( - staker.address, - newNuAmount, - stakingProvider.address + beneficiary.address, + authorizer.address, + amount, ) - expect( - await nucypherStakingMock.investigators(otherStaker.address) - ).to.equal(0) - }) + await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, 0, true) - }) + await expect( + tokenStaking.connect(staker).unstakeAll(stakingProvider.address), + ).to.be.revertedWith("Can't unstake earlier than 24h") }) + }) - context("when staker has no Nu stake anymore (1 app)", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await nucypherStakingMock.setStaker(staker.address, 0) + 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 tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - newNuInTAmount - ) + await expect( + tokenStaking.connect(staker).unstakeAll(stakingProvider.address), + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) - tx = await tokenStaking - .connect(otherStaker) - .notifyNuStakeDiscrepancy(stakingProvider.address) - }) + 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, + ) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) + await expect( + tokenStaking.connect(staker).unstakeAll(stakingProvider.address), + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - }) + const contextUnstakeAll = (preparation, tAmount, nuAmount, keepAmount) => { + const nuInTAmount = convertToT(nuAmount, nuRatio).result + const keepInTAmount = convertToT(keepAmount, keepRatio).result + let tx + let blockTimestamp - 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) - }) + beforeEach(async () => { + blockTimestamp = await preparation() - it("should not call seize in NuCypher contract", async () => { - await assertNuStakers(staker.address, Zero, stakingProvider.address) - expect( - await nucypherStakingMock.investigators(otherStaker.address) - ).to.equal(0) - }) + tx = await tokenStaking + .connect(stakingProvider) + .unstakeAll(stakingProvider.address) + }) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - Zero, - Zero - ) - }) + it("should update staked amount", async () => { + await assertStakes(stakingProvider.address, Zero, Zero, Zero) + }) - it("should emit TokensSeized and AuthorizationInvoluntaryDecreased", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, 0, true) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - newNuInTAmount, - Zero, - true - ) - }) + it("should not update roles", async () => { + expect( + await tokenStaking.rolesOf(stakingProvider.address), + ).to.deep.equal([ + staker.address, + beneficiary.address, + authorizer.address, + ]) }) - context("when penalty is less than Nu stake (no apps)", () => { - const nuPenalty = convertFromT(tPenalty, nuRatio).result - const expectedNuAmount = newNuAmount.sub(nuPenalty) - const expectedNuInTAmount = convertToT(expectedNuAmount, nuRatio).result - const expectedReward = rewardFromPenalty(nuPenalty, rewardMultiplier) + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address), + ).to.equal(blockTimestamp) + }) - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await nucypherStakingMock.setStaker(staker.address, newNuAmount) + 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, + ) + }) - tx = await tokenStaking - .connect(otherStaker) - .notifyNuStakeDiscrepancy(stakingProvider.address) - }) + it("should decrease available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address, + ), + ).to.equal(0) + }) - it("should update staked amount", async () => { - await assertStakes( + it("should update min staked amount", async () => { + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( stakingProvider.address, - Zero, - Zero, - expectedNuInTAmount - ) - }) + StakeTypes.NU, + ), + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP, + ), + ).to.equal(0) + }) - it("should call seize in NuCypher contract", async () => { - await assertNuStakers( - staker.address, - expectedNuAmount, - stakingProvider.address + it("should emit Unstaked", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs( + stakingProvider.address, + nuInTAmount.add(keepInTAmount).add(tAmount), ) - expect( - await nucypherStakingMock.investigators(otherStaker.address) - ).to.equal(expectedReward) - }) - - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs( - stakingProvider.address, - convertToT(nuPenalty, nuRatio).result, - true - ) - }) }) + } - context("when penalty is more than Nu stake (no apps)", () => { - const nuPenalty = convertFromT(tPenalty, nuRatio) - const newNuAmount = nuPenalty.result.sub(1) - const expectedNuPenalty = convertToT(newNuAmount, nuRatio) - const expectedNuAmount = expectedNuPenalty.remainder - const expectedReward = rewardFromPenalty( - newNuAmount.sub(expectedNuAmount), - rewardMultiplier - ) - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await nucypherStakingMock.setStaker(staker.address, newNuAmount) + 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 - tx = await tokenStaking - .connect(otherStaker) - .notifyNuStakeDiscrepancy(stakingProvider.address) - }) + contextUnstakeAll( + async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking.connect(deployer).setMinimumStakeAmount(1) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) + // + // 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() - it("should call seize in NuCypher contract", async () => { - await assertNuStakers( - staker.address, - expectedNuAmount, - stakingProvider.address - ) - expect( - await nucypherStakingMock.investigators(otherStaker.address) - ).to.equal(expectedReward) - }) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTAmount, + nuInTAmount, + ) - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, expectedNuPenalty.result, true) - }) - }) - }) - }) + await increaseTime(86400) // +24h + return blockTimestamp + }, + tAmount, + nuAmount, + keepAmount, + ) + }, + ) - describe("setStakeDiscrepancyPenalty", () => { - const tPenalty = initialStakerBalance - const rewardMultiplier = 100 + 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 - context("when caller is not the governance", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(staker) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - ).to.be.revertedWith("Caller is not the governance") - }) - }) + contextUnstakeAll( + async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking.connect(deployer).setMinimumStakeAmount(1) - context("when caller is the governance", () => { - let tx + // + // 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() - beforeEach(async () => { - tx = await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - }) + // + // top-up T + // + await tToken.connect(staker).approve(tokenStaking.address, tAmount) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, tAmount) - it("should set values", async () => { - expect(await tokenStaking.stakeDiscrepancyPenalty()).to.equal(tPenalty) - expect(await tokenStaking.stakeDiscrepancyRewardMultiplier()).to.equal( - rewardMultiplier + await increaseTime(86400) // +24h + return blockTimestamp + }, + tAmount, + nuAmount, + keepAmount, ) - }) - - it("should emit StakeDiscrepancyPenaltySet event", async () => { - await expect(tx) - .to.emit(tokenStaking, "StakeDiscrepancyPenaltySet") - .withArgs(tPenalty, rewardMultiplier) - }) - }) + }, + ) }) describe("setNotificationReward", () => { @@ -6127,7 +3449,7 @@ describe("TokenStaking", () => { context("when caller is not the governance", () => { it("should revert", async () => { await expect( - tokenStaking.connect(staker).setNotificationReward(amount) + tokenStaking.connect(staker).setNotificationReward(amount), ).to.be.revertedWith("Caller is not the governance") }) }) @@ -6155,7 +3477,7 @@ describe("TokenStaking", () => { context("when reward is zero", () => { it("should revert", async () => { await expect(tokenStaking.pushNotificationReward(0)).to.be.revertedWith( - "Parameters must be specified" + "Parameters must be specified", ) }) }) @@ -6191,7 +3513,7 @@ describe("TokenStaking", () => { await expect( tokenStaking .connect(staker) - .withdrawNotificationReward(deployer.address, 1) + .withdrawNotificationReward(deployer.address, 1), ).to.be.revertedWith("Caller is not the governance") }) }) @@ -6201,7 +3523,7 @@ describe("TokenStaking", () => { await expect( tokenStaking .connect(deployer) - .withdrawNotificationReward(deployer.address, 1) + .withdrawNotificationReward(deployer.address, 1), ).to.be.revertedWith("Not enough tokens") }) }) @@ -6226,10 +3548,10 @@ describe("TokenStaking", () => { it("should transfer tokens to the recipient", async () => { expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedReward + expectedReward, ) expect(await tToken.balanceOf(auxiliaryAccount.address)).to.equal( - amount + amount, ) }) @@ -6245,7 +3567,7 @@ describe("TokenStaking", () => { context("when amount is zero", () => { it("should revert", async () => { await expect( - tokenStaking.slash(0, [stakingProvider.address]) + tokenStaking.slash(0, [stakingProvider.address]), ).to.be.revertedWith("Parameters must be specified") }) }) @@ -6253,7 +3575,7 @@ describe("TokenStaking", () => { context("when staking providers were not provided", () => { it("should revert", async () => { await expect( - tokenStaking.slash(initialStakerBalance, []) + tokenStaking.slash(initialStakerBalance, []), ).to.be.revertedWith("Parameters must be specified") }) }) @@ -6261,7 +3583,7 @@ describe("TokenStaking", () => { context("when application was not approved", () => { it("should revert", async () => { await expect( - tokenStaking.slash(initialStakerBalance, [stakingProvider.address]) + tokenStaking.slash(initialStakerBalance, [stakingProvider.address]), ).to.be.revertedWith("Application is not approved") }) }) @@ -6280,7 +3602,7 @@ describe("TokenStaking", () => { await expect( application1Mock.slash(initialStakerBalance, [ stakingProvider.address, - ]) + ]), ).to.be.revertedWith("Application is not approved") }) }) @@ -6296,7 +3618,7 @@ describe("TokenStaking", () => { await expect( application1Mock.slash(initialStakerBalance, [ stakingProvider.address, - ]) + ]), ).to.be.revertedWith("Application is not approved") }) }) @@ -6316,12 +3638,12 @@ describe("TokenStaking", () => { it("should skip this event", async () => { expect(await tokenStaking.getSlashingQueueLength()).to.equal(0) }) - } + }, ) context("when authorized amount is less than amount to slash", () => { - const amount = initialStakerBalance - const amountToSlash = convertToT(initialStakerBalance, nuRatio).result // amountToSlash > amount + const amount = initialStakerBalance.div(2) + const amountToSlash = initialStakerBalance // amountToSlash > amount beforeEach(async () => { await tokenStaking @@ -6335,33 +3657,33 @@ describe("TokenStaking", () => { stakingProvider.address, staker.address, staker.address, - amount + amount, ) await tokenStaking .connect(staker) .increaseAuthorization( stakingProvider.address, application1Mock.address, - amount + amount, ) - await nucypherStakingMock.setStaker( - otherStaker.address, - initialStakerBalance - ) + await tToken + .connect(otherStaker) + .approve(tokenStaking.address, amountToSlash) await tokenStaking .connect(otherStaker) - .stakeNu( + .stake( + otherStaker.address, otherStaker.address, otherStaker.address, - otherStaker.address + amountToSlash, ) await tokenStaking .connect(otherStaker) .increaseAuthorization( otherStaker.address, application1Mock.address, - amountToSlash + amountToSlash, ) await application1Mock.slash(amountToSlash, [ @@ -6396,14 +3718,14 @@ describe("TokenStaking", () => { stakingProvider.address, staker.address, staker.address, - amount + amount, ) await tokenStaking .connect(staker) .increaseAuthorization( stakingProvider.address, application1Mock.address, - authorized + authorized, ) await tokenStaking @@ -6412,14 +3734,14 @@ describe("TokenStaking", () => { otherStaker.address, otherStaker.address, otherStaker.address, - amount + amount, ) await tokenStaking .connect(otherStaker) .increaseAuthorization( otherStaker.address, application1Mock.address, - amount + amount, ) await application1Mock.slash(amountToSlash, [ @@ -6467,7 +3789,7 @@ describe("TokenStaking", () => { .increaseAuthorization( stakingProvider.address, application1Mock.address, - authorized + authorized, ) await tokenStaking @@ -6476,14 +3798,14 @@ describe("TokenStaking", () => { otherStaker.address, otherStaker.address, otherStaker.address, - amount + amount, ) await tokenStaking .connect(otherStaker) .increaseAuthorization( otherStaker.address, application1Mock.address, - amount + amount, ) }) @@ -6499,7 +3821,7 @@ describe("TokenStaking", () => { amountToSlash, rewardMultiplier, AddressZero, - [otherStaker.address, stakingProvider.address] + [otherStaker.address, stakingProvider.address], ) }) @@ -6517,7 +3839,7 @@ describe("TokenStaking", () => { expect(await tokenStaking.notifiersTreasury()).to.equal(reward) const expectedBalance = reward.add(amount.mul(2)) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(notifier.address)).to.equal(0) }) @@ -6532,7 +3854,7 @@ describe("TokenStaking", () => { amountToSlash, rewardMultiplier, notifier.address, - [stakingProvider.address] + [stakingProvider.address], ) }) @@ -6549,7 +3871,7 @@ describe("TokenStaking", () => { expect(await tokenStaking.notifiersTreasury()).to.equal(reward) const expectedBalance = reward.add(amount.mul(2)) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(notifier.address)).to.equal(0) }) @@ -6571,7 +3893,7 @@ describe("TokenStaking", () => { amountToSlash, rewardMultiplier, notifier.address, - [otherStaker.address] + [otherStaker.address], ) }) @@ -6587,7 +3909,7 @@ describe("TokenStaking", () => { expect(await tokenStaking.notifiersTreasury()).to.equal(0) const expectedBalance = amount.mul(2) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(notifier.address)).to.equal(0) }) @@ -6616,7 +3938,7 @@ describe("TokenStaking", () => { expect(await tokenStaking.notifiersTreasury()).to.equal(reward) const expectedBalance = reward.add(amount.mul(2)) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(notifier.address)).to.equal(0) }) @@ -6640,7 +3962,7 @@ describe("TokenStaking", () => { amountToSlash, rewardMultiplier, notifier.address, - [stakingProvider.address, otherStaker.address] + [stakingProvider.address, otherStaker.address], ) }) @@ -6648,7 +3970,7 @@ describe("TokenStaking", () => { expect(await tokenStaking.notifiersTreasury()).to.equal(0) const expectedBalance = amount.mul(2) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(notifier.address)).to.equal(reward) }) @@ -6678,21 +4000,21 @@ describe("TokenStaking", () => { amountToSlash, rewardMultiplier, notifier.address, - [stakingProvider.address, otherStaker.address, authorizer.address] + [stakingProvider.address, otherStaker.address, authorizer.address], ) }) it("should transfer all tokens", async () => { const expectedTreasuryBalance = reward.sub(expectedReward) expect(await tokenStaking.notifiersTreasury()).to.equal( - expectedTreasuryBalance + expectedTreasuryBalance, ) const expectedBalance = expectedTreasuryBalance.add(amount.mul(2)) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(notifier.address)).to.equal( - expectedReward + expectedReward, ) }) @@ -6708,18 +4030,14 @@ describe("TokenStaking", () => { context("when queue is empty", () => { it("should revert", async () => { await expect(tokenStaking.processSlashing(1)).to.be.revertedWith( - "Nothing to process" + "Nothing to process", ) }) }) context("when queue is not empty", () => { - const keepAmount = initialStakerBalance - const keepInTAmount = convertToT(keepAmount, keepRatio).result - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result const tAmount = initialStakerBalance.div(2) - const tStake2 = keepInTAmount.add(nuInTAmount).add(tAmount) + const tStake2 = tAmount.mul(2) const provider1Authorized1 = tAmount.div(2) const amountToSlash = provider1Authorized1.div(2) @@ -6728,9 +4046,8 @@ describe("TokenStaking", () => { const provider2Authorized2 = tAmount.div(100) const expectedTReward1 = rewardFromPenalty(amountToSlash, 100) - const expectedTReward2 = rewardFromPenalty(tAmount, 100) + const expectedTReward2 = rewardFromPenalty(tStake2, 100) - const createdAt = ethers.BigNumber.from(1) let tx beforeEach(async () => { @@ -6750,7 +4067,7 @@ describe("TokenStaking", () => { stakingProvider.address, staker.address, staker.address, - tAmount + tAmount, ) await tokenStaking .connect(staker) @@ -6760,53 +4077,40 @@ describe("TokenStaking", () => { .increaseAuthorization( stakingProvider.address, application1Mock.address, - provider1Authorized1 + provider1Authorized1, ) await tokenStaking .connect(staker) .increaseAuthorization( stakingProvider.address, application2Mock.address, - provider1Authorized2 + provider1Authorized2, ) - await keepStakingMock.setOperator( - otherStaker.address, - otherStaker.address, - otherStaker.address, - otherStaker.address, - createdAt, - 0, - keepAmount - ) - await keepStakingMock.setEligibility( - otherStaker.address, - tokenStaking.address, - true - ) - await tokenStaking.stakeKeep(otherStaker.address) - await nucypherStakingMock.setStaker(otherStaker.address, nuAmount) - await tokenStaking.connect(otherStaker).topUpNu(otherStaker.address) - - await tToken.connect(deployer).transfer(otherStaker.address, tAmount) - await tToken.connect(otherStaker).approve(tokenStaking.address, tAmount) + await tToken.connect(deployer).transfer(otherStaker.address, tStake2) + await tToken.connect(otherStaker).approve(tokenStaking.address, tStake2) await tokenStaking .connect(otherStaker) - .topUp(otherStaker.address, tAmount) + .stake( + otherStaker.address, + otherStaker.address, + otherStaker.address, + tStake2, + ) await tokenStaking .connect(otherStaker) .increaseAuthorization( otherStaker.address, application1Mock.address, - provider2Authorized1 + provider2Authorized1, ) await tokenStaking .connect(otherStaker) .increaseAuthorization( otherStaker.address, application2Mock.address, - provider2Authorized2 + provider2Authorized2, ) await application1Mock.slash(amountToSlash, [ @@ -6819,7 +4123,7 @@ describe("TokenStaking", () => { context("when provided number is zero", () => { it("should revert", async () => { await expect(tokenStaking.processSlashing(0)).to.be.revertedWith( - "Nothing to process" + "Nothing to process", ) }) }) @@ -6836,13 +4140,13 @@ describe("TokenStaking", () => { stakingProvider.address, expectedAmount, Zero, - Zero + Zero, ) }) it("should decrease the delegatee voting power", async () => { expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedAmount + expectedAmount, ) }) @@ -6852,71 +4156,46 @@ describe("TokenStaking", () => { }) it("should transfer reward to processor", async () => { - const expectedBalance = tAmount.mul(2).sub(expectedTReward1) + const expectedBalance = tAmount.add(tStake2).sub(expectedTReward1) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(auxiliaryAccount.address)).to.equal( - expectedTReward1 + expectedTReward1, ) }) it("should increase amount in notifiers treasury ", async () => { const expectedTreasuryBalance = amountToSlash.sub(expectedTReward1) expect(await tokenStaking.notifiersTreasury()).to.equal( - expectedTreasuryBalance - ) - }) - - it("should not call seize in Keep contract", async () => { - await assertDelegationInfo(stakingProvider.address, Zero, Zero, Zero) - await assertDelegationInfo( - otherStaker.address, - keepAmount, - createdAt, - Zero - ) - expect( - await keepStakingMock.tattletales(auxiliaryAccount.address) - ).to.equal(0) - }) - - it("should not call seize in NuCypher contract", async () => { - await assertNuStakers(staker.address, Zero, AddressZero) - await assertNuStakers( - otherStaker.address, - nuAmount, - otherStaker.address + expectedTreasuryBalance, ) - expect( - await nucypherStakingMock.investigators(auxiliaryAccount.address) - ).to.equal(0) }) it("should decrease authorized amounts only for one provider", async () => { expect( await tokenStaking.authorizedStake( stakingProvider.address, - application1Mock.address - ) + application1Mock.address, + ), ).to.equal(provider1Authorized1.sub(amountToSlash)) expect( await tokenStaking.authorizedStake( stakingProvider.address, - application2Mock.address - ) + application2Mock.address, + ), ).to.equal(provider1Authorized2.sub(amountToSlash)) expect( await tokenStaking.authorizedStake( otherStaker.address, - application1Mock.address - ) + application1Mock.address, + ), ).to.equal(provider2Authorized1) expect( await tokenStaking.authorizedStake( otherStaker.address, - application2Mock.address - ) + application2Mock.address, + ), ).to.equal(provider2Authorized2) }) @@ -6932,8 +4211,8 @@ describe("TokenStaking", () => { .increaseAuthorization( stakingProvider.address, auxiliaryAccount.address, - 1 - ) + 1, + ), ).to.be.revertedWith("Too many applications") }) @@ -6942,25 +4221,25 @@ describe("TokenStaking", () => { application1Mock, stakingProvider.address, provider1Authorized1.sub(amountToSlash), - Zero + Zero, ) await assertApplicationStakingProviders( application2Mock, stakingProvider.address, provider1Authorized2.sub(amountToSlash), - Zero + Zero, ) await assertApplicationStakingProviders( application1Mock, otherStaker.address, provider2Authorized1, - Zero + Zero, ) await assertApplicationStakingProviders( application2Mock, otherStaker.address, provider2Authorized2, - Zero + Zero, ) }) @@ -6979,10 +4258,6 @@ describe("TokenStaking", () => { beforeEach(async () => { await tokenStaking.connect(auxiliaryAccount).processSlashing(1) - await keepStakingMock.setAmount( - otherStaker.address, - keepAmount.div(2) - ) tx = await tokenStaking.connect(auxiliaryAccount).processSlashing(10) }) @@ -6996,64 +4271,48 @@ describe("TokenStaking", () => { }) it("should transfer reward to processor", async () => { - const expectedBalance = tAmount.mul(2).sub(expectedReward) + const expectedBalance = tAmount.add(tStake2).sub(expectedReward) expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(auxiliaryAccount.address)).to.equal( - expectedReward + expectedReward, ) }) it("should increase amount in notifiers treasury ", async () => { const expectedTreasuryBalance = amountToSlash - .add(tAmount) + .add(tStake2) .sub(expectedReward) expect(await tokenStaking.notifiersTreasury()).to.equal( - expectedTreasuryBalance + expectedTreasuryBalance, ) }) - it("should call seize in Keep contract", async () => { - const expectedKeepReward = rewardFromPenalty(keepAmount.div(2), 100) - await assertDelegationInfo(otherStaker.address, Zero, createdAt, Zero) - expect( - await keepStakingMock.tattletales(auxiliaryAccount.address) - ).to.equal(expectedKeepReward) - }) - - it("should call seize in NuCypher contract", async () => { - const expectedNuReward = rewardFromPenalty(nuAmount, 100) - await assertNuStakers(otherStaker.address, Zero, otherStaker.address) - expect( - await nucypherStakingMock.investigators(auxiliaryAccount.address) - ).to.equal(expectedNuReward) - }) - it("should decrease authorized amount and inform applications", async () => { expect( await tokenStaking.authorizedStake( otherStaker.address, - application1Mock.address - ) + application1Mock.address, + ), ).to.equal(0) expect( await tokenStaking.authorizedStake( otherStaker.address, - application2Mock.address - ) + application2Mock.address, + ), ).to.equal(0) await assertApplicationStakingProviders( application1Mock, otherStaker.address, Zero, - Zero + Zero, ) await assertApplicationStakingProviders( application2Mock, otherStaker.address, Zero, - Zero + Zero, ) }) @@ -7072,14 +4331,14 @@ describe("TokenStaking", () => { .increaseAuthorization( otherStaker.address, application1Mock.address, - tAmount + tAmount, ) await tokenStaking .connect(otherStaker) .increaseAuthorization( otherStaker.address, application2Mock.address, - tAmount + tAmount, ) }) @@ -7089,11 +4348,7 @@ describe("TokenStaking", () => { .withArgs(otherStaker.address, amountToSlash, false) await expect(tx) .to.emit(tokenStaking, "TokensSeized") - .withArgs( - otherStaker.address, - tStake2.sub(amountToSlash).sub(keepInTAmount.div(2)), - false - ) + .withArgs(otherStaker.address, tStake2.sub(amountToSlash), false) await expect(tx) .to.emit(tokenStaking, "SlashingProcessed") .withArgs(auxiliaryAccount.address, 2, expectedTReward2) @@ -7104,7 +4359,7 @@ describe("TokenStaking", () => { application1Mock.address, provider2Authorized1, provider2Authorized1.sub(amountToSlash), - true + true, ) await expect(tx) .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") @@ -7113,7 +4368,7 @@ describe("TokenStaking", () => { application1Mock.address, provider2Authorized1.sub(amountToSlash), Zero, - true + true, ) }) }) @@ -7124,10 +4379,10 @@ describe("TokenStaking", () => { .connect(staker) ["requestAuthorizationDecrease(address)"](stakingProvider.address) await application1Mock.approveAuthorizationDecrease( - stakingProvider.address + stakingProvider.address, ) await application2Mock.approveAuthorizationDecrease( - stakingProvider.address + stakingProvider.address, ) await increaseTime(86400) // +24h await tokenStaking @@ -7146,9 +4401,9 @@ describe("TokenStaking", () => { }) it("should not transfer reward to processor", async () => { - const expectedBalance = tAmount + const expectedBalance = tStake2 expect(await tToken.balanceOf(tokenStaking.address)).to.equal( - expectedBalance + expectedBalance, ) expect(await tToken.balanceOf(auxiliaryAccount.address)).to.equal(0) }) @@ -7190,21 +4445,21 @@ describe("TokenStaking", () => { stakingProvider.address, staker.address, staker.address, - tAmount + tAmount, ) await tokenStaking .connect(staker) .increaseAuthorization( stakingProvider.address, application2Mock.address, - authorized + authorized, ) await tokenStaking .connect(staker) .increaseAuthorization( stakingProvider.address, application1Mock.address, - authorized + authorized, ) await application1Mock.slash(amountToSlash, [stakingProvider.address]) @@ -7216,14 +4471,14 @@ describe("TokenStaking", () => { expect( await tokenStaking.authorizedStake( stakingProvider.address, - application1Mock.address - ) + application1Mock.address, + ), ).to.equal(0) expect( await tokenStaking.authorizedStake( stakingProvider.address, - application2Mock.address - ) + application2Mock.address, + ), ).to.equal(0) }) @@ -7233,7 +4488,7 @@ describe("TokenStaking", () => { .increaseAuthorization( stakingProvider.address, application1Mock.address, - authorized + authorized, ) await tokenStaking @@ -7241,7 +4496,7 @@ describe("TokenStaking", () => { .increaseAuthorization( stakingProvider.address, application2Mock.address, - authorized + authorized, ) await tokenStaking @@ -7253,8 +4508,8 @@ describe("TokenStaking", () => { .increaseAuthorization( stakingProvider.address, auxiliaryAccount.address, - authorized - ) + authorized, + ), ).to.be.revertedWith("Too many applications") }) }) @@ -7266,15 +4521,11 @@ describe("TokenStaking", () => { beforeEach(async () => { const ExtendedTokenStaking = await ethers.getContractFactory( - "ExtendedTokenStaking" + "ExtendedTokenStaking", ) extendedTokenStaking = await ExtendedTokenStaking.deploy( tToken.address, - keepStakingMock.address, - nucypherStakingMock.address, - keepVendingMachine.address, nucypherVendingMachine.address, - keepStake.address ) await extendedTokenStaking.deployed() }) @@ -7283,19 +4534,19 @@ describe("TokenStaking", () => { beforeEach(async () => { await extendedTokenStaking.setAuthorizedApplications( stakingProvider.address, - [application1Mock.address, application2Mock.address] + [application1Mock.address, application2Mock.address], ) await extendedTokenStaking.cleanAuthorizedApplications( stakingProvider.address, - 2 + 2, ) }) it("should remove all applications", async () => { expect( await extendedTokenStaking.getAuthorizedApplications( - stakingProvider.address - ) + stakingProvider.address, + ), ).to.deep.equal([]) }) }) @@ -7306,27 +4557,27 @@ describe("TokenStaking", () => { beforeEach(async () => { await extendedTokenStaking.setAuthorizedApplications( stakingProvider.address, - [application1Mock.address, application2Mock.address] + [application1Mock.address, application2Mock.address], ) await extendedTokenStaking.setAuthorization( stakingProvider.address, application2Mock.address, - amount + amount, ) await extendedTokenStaking.cleanAuthorizedApplications( stakingProvider.address, - 1 + 1, ) }) it("should remove only first application", async () => { expect( await extendedTokenStaking.getAuthorizedApplications( - stakingProvider.address - ) + stakingProvider.address, + ), ).to.deep.equal([application2Mock.address]) }) - } + }, ) context( @@ -7335,27 +4586,27 @@ describe("TokenStaking", () => { beforeEach(async () => { await extendedTokenStaking.setAuthorizedApplications( stakingProvider.address, - [application1Mock.address, application2Mock.address] + [application1Mock.address, application2Mock.address], ) await extendedTokenStaking.setAuthorization( stakingProvider.address, application1Mock.address, - amount + amount, ) await extendedTokenStaking.cleanAuthorizedApplications( stakingProvider.address, - 1 + 1, ) }) it("should remove only first application", async () => { expect( await extendedTokenStaking.getAuthorizedApplications( - stakingProvider.address - ) + stakingProvider.address, + ), ).to.deep.equal([application1Mock.address]) }) - } + }, ) context( @@ -7368,27 +4619,27 @@ describe("TokenStaking", () => { application1Mock.address, application2Mock.address, auxiliaryAccount.address, - ] + ], ) await extendedTokenStaking.setAuthorization( stakingProvider.address, application2Mock.address, - amount + amount, ) await extendedTokenStaking.cleanAuthorizedApplications( stakingProvider.address, - 2 + 2, ) }) it("should remove first and last applications", async () => { expect( await extendedTokenStaking.getAuthorizedApplications( - stakingProvider.address - ) + stakingProvider.address, + ), ).to.deep.equal([application2Mock.address]) }) - } + }, ) }) @@ -7396,35 +4647,35 @@ describe("TokenStaking", () => { address, expectedTStake, expectedKeepInTStake, - expectedNuInTStake + expectedNuInTStake, ) { expect( (await tokenStaking.stakes(address)).tStake, - "invalid tStake" + "invalid tStake", ).to.equal(expectedTStake) expect( (await tokenStaking.stakes(address)).keepInTStake, - "invalid keepInTStake" + "invalid keepInTStake", ).to.equal(expectedKeepInTStake) expect( (await tokenStaking.stakes(address)).nuInTStake, - "invalid nuInTStake" + "invalid nuInTStake", ).to.equal(expectedNuInTStake) } async function assertNuStakers( stakerAddress, expectedValue, - expectedStakingProvider + expectedStakingProvider, ) { expect( (await nucypherStakingMock.stakers(stakerAddress)).value, - "invalid value" + "invalid value", ).to.equal(expectedValue) expect( (await nucypherStakingMock.stakers(stakerAddress)).stakingProvider, - "invalid stakingProvider" + "invalid stakingProvider", ).to.equal(expectedStakingProvider) } @@ -7432,57 +4683,33 @@ describe("TokenStaking", () => { applicationMock, stakingProviderAddress, expectedAuthorized, - expectedDeauthorizingTo + expectedDeauthorizingTo, ) { expect( (await applicationMock.stakingProviders(stakingProviderAddress)) .authorized, - "invalid authorized" + "invalid authorized", ).to.equal(expectedAuthorized) expect( (await applicationMock.stakingProviders(stakingProviderAddress)) .deauthorizingTo, - "invalid deauthorizingTo" + "invalid deauthorizingTo", ).to.equal(expectedDeauthorizingTo) } - async function assertDelegationInfo( - stakingProviderAddress, - expectedAmount, - expectedCreatedAt, - expectedUndelegatedAt - ) { - expect( - (await keepStakingMock.getDelegationInfo(stakingProviderAddress)).amount, - "invalid amount" - ).to.equal(expectedAmount) - - expect( - (await keepStakingMock.getDelegationInfo(stakingProviderAddress)) - .createdAt, - "invalid createdAt" - ).to.equal(expectedCreatedAt) - - expect( - (await keepStakingMock.getDelegationInfo(stakingProviderAddress)) - .undelegatedAt, - "invalid undelegatedAt" - ).to.equal(expectedUndelegatedAt) - } - async function assertSlashingQueue( index, expectedStakingProviderAddress, - expectedAmount + expectedAmount, ) { expect( (await tokenStaking.slashingQueue(index)).stakingProvider, - "invalid stakingProvider" + "invalid stakingProvider", ).to.equal(expectedStakingProviderAddress) expect( (await tokenStaking.slashingQueue(index)).amount, - "invalid amount" + "invalid amount", ).to.equal(expectedAmount) } })