diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index dc83896a..61bdeb4c 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -75,34 +75,6 @@ jobs: if: github.ref != 'refs/heads/dapp-development' run: yarn test - contracts-system-tests: - needs: contracts-detect-changes - if: | - needs.contracts-detect-changes.outputs.system-tests == 'true' - && github.ref != 'refs/heads/dapp-development' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-node@v3 - with: - # Using fixed version, because 18.16 was sometimes causing issues with - # artifacts generation during `hardhat compile` - see - # https://github.com/NomicFoundation/hardhat/issues/3877 - node-version: "18.15.0" - cache: "yarn" - - - name: Install dependencies - run: yarn install - - - name: Build contracts - run: yarn build - - - name: Run system tests - env: - FORKING_URL: ${{ secrets.MAINNET_ETH_HOSTNAME_HTTP }} - run: yarn test:system - contracts-deployment-dry-run: runs-on: ubuntu-latest steps: diff --git a/contracts/governance/BaseTokenholderGovernor.sol b/contracts/governance/BaseTokenholderGovernor.sol index 7aefb8ab..4da8e704 100644 --- a/contracts/governance/BaseTokenholderGovernor.sol +++ b/contracts/governance/BaseTokenholderGovernor.sol @@ -122,6 +122,16 @@ contract BaseTokenholderGovernor is return super.supportsInterface(interfaceId); } + function proposalDeadline(uint256 proposalId) + public + view + virtual + override(IGovernor, Governor, GovernorPreventLateQuorum) + returns (uint256) + { + return super.proposalDeadline(proposalId); + } + function _execute( uint256 proposalId, address[] memory targets, @@ -141,25 +151,6 @@ contract BaseTokenholderGovernor is return super._cancel(targets, values, calldatas, descriptionHash); } - function _executor() - internal - view - override(Governor, GovernorTimelockControl) - returns (address) - { - return super._executor(); - } - - function proposalDeadline(uint256 proposalId) - public - view - virtual - override(IGovernor, Governor, GovernorPreventLateQuorum) - returns (uint256) - { - return super.proposalDeadline(proposalId); - } - function _castVote( uint256 proposalId, address account, @@ -173,4 +164,13 @@ contract BaseTokenholderGovernor is { return super._castVote(proposalId, account, support, reason); } + + function _executor() + internal + view + override(Governor, GovernorTimelockControl) + returns (address) + { + return super._executor(); + } } 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..1d7019b8 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 @@ -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 to 0 the amount of T that is cached from the legacy + /// NU staking contract. Reverts if there is at least one + /// authorization higher than the sum of remaining legacy NU stake + /// and native T stake for that staking provider or if the unstaked + /// amount is higher than the cached legacy stake amount. If succeeded, + /// the legacy NU stake can be partially or fully undelegated on + /// the legacy NU staking contract. This function allows to unstake + /// from NU staking contract while still being able to operate in + /// T network and earning rewards based on the native T staked. + /// Can be called only by the stake owner or the staking provider. + function unstakeNu(address stakingProvider) external; /// @notice Sets cached legacy stake amount to 0, sets the liquid T stake /// amount to 0 and withdraws all liquid T from the stake to the @@ -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; @@ -360,20 +303,20 @@ interface IStaking { /// @notice Returns minimum possible stake for T, KEEP or NU in T /// denomination. - /// @dev For example, suppose the given staking provider has 10 T, 20 T - /// worth of KEEP, and 30 T worth of NU all staked, and the maximum + /// @dev For example, suppose the given staking provider has 10 T, 20 T worth + /// of KEEP, and 30 T worth of NU all staked, and the maximum /// application authorization is 40 T, then `getMinStaked` for /// that staking provider returns: /// * 0 T if KEEP stake type specified i.e. - /// min = 40 T max - (10 T + 30 T worth of NU) = 0 T + /// min = 40 T max - (10 T) = 30 T /// * 10 T if NU stake type specified i.e. - /// min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T + /// min = 40 T max - (10 T) = 30 T /// * 0 T if T stake type specified i.e. - /// min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T + /// min = 40 T max = 40 T /// In other words, the minimum stake amount for the specified /// stake type is the minimum amount of stake of the given type - /// needed to satisfy the maximum application authorization given the - /// staked amounts of the other stake types for that staking provider. + /// needed to satisfy the maximum application authorization given + /// the staked amounts of the T stake types for that staking provider. function getMinStaked(address stakingProvider, StakeType stakeTypes) external view 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..f8f17110 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"; @@ -86,23 +84,17 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable T internal immutable token; - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - 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; + // slither-disable-next-line constable-states + uint96 private legacyStakeDiscrepancyPenalty; + // slither-disable-next-line constable-states + uint256 private legacyStakeDiscrepancyRewardMultiplier; uint256 public notifiersTreasury; uint256 public notificationReward; @@ -164,7 +156,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 +165,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 +210,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 +250,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 +279,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 @@ -722,55 +592,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) @@ -837,35 +658,27 @@ 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 to 0 the amount of T that is cached from the legacy + /// NU staking contract. Reverts if there is at least one + /// authorization higher than the sum of remaining legacy NU stake + /// and native T stake for that staking provider or if the unstaked + /// amount is higher than the cached legacy stake amount. If succeeded, + /// the legacy NU stake can be partially or fully undelegated on + /// the legacy NU staking contract. This function allows to unstake + /// from NU staking contract while still being able to operate in + /// T network and earning rewards based on the native T staked. + /// Can be called only by the stake owner or the staking provider. /// @dev This function (or `unstakeAll`) must be called before `withdraw` /// in NuCypher staking contract. Otherwise NU tokens can't be /// unlocked. /// @param stakingProvider Staking provider address - /// @param amount Amount of NU to unstake in T denomination - function unstakeNu(address stakingProvider, uint96 amount) + function unstakeNu(address stakingProvider) external override onlyOwnerOrStakingProvider(stakingProvider) @@ -873,26 +686,16 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { 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 @@ -935,126 +738,21 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } } + /// @notice Involuntary decrease authorization for all application up to T + /// stake amount for all staking providers in the list. + /// Sets cached legacy stake amount to 0. Can be called by anyone + function forceUnstakeLegacy(address[] memory _stakingProviders) external { + for (uint256 i = 0; i < _stakingProviders.length; i++) { + forceUnstakeLegacy(_stakingProviders[i]); + } + } + // // // Keeping information in sync // // - /// @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) @@ -1315,22 +1013,87 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { ); } + /// @notice Involuntary decrease authorization for all application up to T + /// stake amount. Sets cached legacy stake amount to 0. + /// Can be called by anyone + function forceUnstakeLegacy(address stakingProvider) public { + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + uint96 legacyStake = stakingProviderStruct.keepInTStake + + stakingProviderStruct.nuInTStake; + require(legacyStake > 0, "No legacy stake"); + + // similar to authorizationDecrease method + uint256 applicationsToDelete = 0; + for ( + uint256 i = 0; + i < stakingProviderStruct.authorizedApplications.length; + i++ + ) { + address authorizedApplication = stakingProviderStruct + .authorizedApplications[i]; + AppAuthorization storage authorization = stakingProviderStruct + .authorizations[authorizedApplication]; + uint96 fromAmount = authorization.authorized; + + if (fromAmount <= stakingProviderStruct.tStake) { + continue; + } + authorization.authorized = stakingProviderStruct.tStake; + + bool successful = true; + //slither-disable-next-line calls-loop + try + IApplication(authorizedApplication) + .involuntaryAuthorizationDecrease{ + gas: GAS_LIMIT_AUTHORIZATION_DECREASE + }(stakingProvider, fromAmount, authorization.authorized) + {} catch { + successful = false; + } + if (authorization.deauthorizing > authorization.authorized) { + authorization.deauthorizing = authorization.authorized; + } + emit AuthorizationInvoluntaryDecreased( + stakingProvider, + authorizedApplication, + fromAmount, + authorization.authorized, + successful + ); + if (authorization.authorized == 0) { + applicationsToDelete++; + } + } + if (applicationsToDelete > 0) { + cleanAuthorizedApplications( + stakingProviderStruct, + applicationsToDelete + ); + } + + emit Unstaked(stakingProvider, legacyStake); + stakingProviderStruct.keepInTStake = 0; + stakingProviderStruct.nuInTStake = 0; + decreaseStakeCheckpoint(stakingProvider, legacyStake); + } + /// @notice Returns minimum possible stake for T, KEEP or NU in T denomination /// @dev For example, suppose the given staking provider has 10 T, 20 T worth /// of KEEP, and 30 T worth of NU all staked, and the maximum /// application authorization is 40 T, then `getMinStaked` for /// that staking provider returns: /// * 0 T if KEEP stake type specified i.e. - /// min = 40 T max - (10 T + 30 T worth of NU) = 0 T + /// min = 40 T max - (10 T) = 30 T /// * 10 T if NU stake type specified i.e. - /// min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T + /// min = 40 T max - (10 T) = 30 T /// * 0 T if T stake type specified i.e. - /// min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T + /// min = 40 T max = 40 T /// In other words, the minimum stake amount for the specified /// stake type is the minimum amount of stake of the given type /// needed to satisfy the maximum application authorization given - /// the staked amounts of the other stake types for that staking - /// provider. + /// the staked amounts of the T stake types for that staking provider. function getMinStaked(address stakingProvider, StakeType stakeTypes) public view @@ -1364,18 +1127,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 +1139,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 @@ -1491,40 +1244,11 @@ 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 - ); - } + tAmountToBurn = MathUpgradeable + .min(tAmountToSlash, stakingProviderStruct.tStake) + .toUint96(); + stakingProviderStruct.tStake -= tAmountToBurn; + tAmountToSlash -= tAmountToBurn; uint96 slashedAmount = slashing.amount - tAmountToSlash; emit TokensSeized(slashing.stakingProvider, slashedAmount, false); @@ -1600,90 +1324,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( @@ -1769,56 +1409,12 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { 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. 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/SimplePREApplicationStub.sol b/contracts/test/SimplePREApplicationStub.sol index daffd62a..c5123092 100644 --- a/contracts/test/SimplePREApplicationStub.sol +++ b/contracts/test/SimplePREApplicationStub.sol @@ -3,6 +3,14 @@ pragma solidity ^0.8.0; contract SimplePREApplicationStub { + struct StakingProviderInfo { + address operator; + bool operatorConfirmed; + uint256 operatorStartTimestamp; + } + + mapping(address => StakingProviderInfo) public stakingProviderInfo; + event OperatorBonded( address indexed stakingProvider, address indexed operator, @@ -14,14 +22,6 @@ contract SimplePREApplicationStub { address indexed operator ); - struct StakingProviderInfo { - address operator; - bool operatorConfirmed; - uint256 operatorStartTimestamp; - } - - mapping(address => StakingProviderInfo) public stakingProviderInfo; - function bondOperator(address _stakingProvider, address _operator) external { diff --git a/contracts/test/TokenStakingTestSet.sol b/contracts/test/TokenStakingTestSet.sol index cd34030a..abdadc2c 100644 --- a/contracts/test/TokenStakingTestSet.sol +++ b/contracts/test/TokenStakingTestSet.sol @@ -2,196 +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; @@ -263,6 +76,14 @@ contract ApplicationMock is IApplication { ); } + function availableRewards(address) external pure returns (uint96) { + return 0; + } + + function minimumAuthorization() external pure returns (uint96) { + return 0; + } + function involuntaryAuthorizationDecrease( address stakingProvider, uint96, @@ -283,14 +104,6 @@ contract ApplicationMock is IApplication { } stakingProviderStruct.authorized = toAmount; } - - function availableRewards(address) external pure returns (uint96) { - return 0; - } - - function minimumAuthorization() external pure returns (uint96) { - return 0; - } } contract BrokenApplicationMock is ApplicationMock { @@ -336,22 +149,8 @@ contract ManagedGrantMock { } contract ExtendedTokenStaking is TokenStaking { - constructor( - T _token, - IKeepTokenStaking _keepStakingContract, - INuCypherStakingEscrow _nucypherStakingContract, - VendingMachine _keepVendingMachine, - VendingMachine _nucypherVendingMachine, - KeepStake _keepStake - ) - TokenStaking( - _token, - _keepStakingContract, - _nucypherStakingContract, - _keepVendingMachine, - _nucypherVendingMachine, - _keepStake - ) + constructor(T _token, VendingMachine _nucypherVendingMachine) + TokenStaking(_token, _nucypherVendingMachine) {} function cleanAuthorizedApplications( @@ -393,3 +192,72 @@ contract ExtendedTokenStaking is TokenStaking { return stakingProviders[stakingProvider].authorizedApplications; } } + +contract LegacyTokenStaking is TokenStaking { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(T _token, VendingMachine _nucypherVendingMachine) + TokenStaking(_token, _nucypherVendingMachine) + {} + + function setLegacyStakingProviderDefault(address stakingProvider) external { + setLegacyStakingProvider( + stakingProvider, + stakingProvider, + payable(stakingProvider), + stakingProvider + ); + } + + function addLegacyStake( + address stakingProvider, + uint96 keepInTStake, + uint96 nuInTStake + ) external { + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + stakingProviderStruct.keepInTStake += keepInTStake; + stakingProviderStruct.nuInTStake += nuInTStake; + if (stakingProviderStruct.startStakingTimestamp == 0) { + /* solhint-disable-next-line not-rely-on-time */ + stakingProviderStruct.startStakingTimestamp = block.timestamp; + } + increaseStakeCheckpoint(stakingProvider, keepInTStake + nuInTStake); + } + + function forceIncreaseAuthorization( + address stakingProvider, + address application, + uint96 amount + ) external { + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + AppAuthorization storage authorization = stakingProviderStruct + .authorizations[application]; + uint96 fromAmount = authorization.authorized; + if (fromAmount == 0) { + stakingProviderStruct.authorizedApplications.push(application); + } + authorization.authorized += amount; + IApplication(application).authorizationIncreased( + stakingProvider, + fromAmount, + authorization.authorized + ); + } + + function setLegacyStakingProvider( + address stakingProvider, + address owner, + address payable beneficiary, + address authorizer + ) public { + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + stakingProviderStruct.owner = owner; + stakingProviderStruct.authorizer = authorizer; + stakingProviderStruct.beneficiary = beneficiary; + } +} diff --git a/deploy/00_resolve_keep_registry.ts b/deploy/00_resolve_keep_registry.ts deleted file mode 100644 index 62aa3551..00000000 --- a/deploy/00_resolve_keep_registry.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HardhatRuntimeEnvironment, HardhatNetworkConfig } from "hardhat/types" -import { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, deployments, helpers } = hre - const { log } = deployments - const { deployer } = await getNamedAccounts() - - const KeepRegistry = await deployments.getOrNull("KeepRegistry") - - if (KeepRegistry && helpers.address.isValid(KeepRegistry.address)) { - log(`using existing KeepRegistry contract at ${KeepRegistry.address}`) - } else if ( - !hre.network.tags.allowStubs || - (hre.network.config as HardhatNetworkConfig)?.forking?.enabled - ) { - throw new Error("deployed KeepRegistry contract not found") - } else { - log(`deploying KeepRegistry stub`) - - await deployments.deploy("KeepRegistry", { - contract: "KeepRegistryStub", - from: deployer, - log: true, - }) - } -} - -export default func - -func.tags = ["KeepRegistry"] diff --git a/deploy/00_resolve_keep_token.ts b/deploy/00_resolve_keep_token.ts deleted file mode 100644 index 4bbd5134..00000000 --- a/deploy/00_resolve_keep_token.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { HardhatRuntimeEnvironment, HardhatNetworkConfig } from "hardhat/types" -import { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, deployments, helpers } = hre - const { log } = deployments - const { deployer } = await getNamedAccounts() - const { execute, read } = deployments - const { to1e18, from1e18 } = helpers.number - - const KeepToken = await deployments.getOrNull("KeepToken") - - if (KeepToken && helpers.address.isValid(KeepToken.address)) { - log(`using existing KeepToken at ${KeepToken.address}`) - - // Save deployment artifact of external contract to include it in the package. - await deployments.save("KeepToken", KeepToken) - } else if ( - !hre.network.tags.allowStubs || - (hre.network.config as HardhatNetworkConfig)?.forking?.enabled - ) { - throw new Error("deployed KeepToken contract not found") - } else { - log(`deploying KeepToken stub`) - - // For deployments on hardhat network we don't have the KeepToken deployed, - // so we're deploying a stub contact and minting the KEEP in the amount - // close to the KEEP supply on the production environment (~1B KEEP). - const KEEP_SUPPLY = to1e18("1000000000") // 1B KEEP - - await deployments.deploy("KeepToken", { - contract: "TestToken", - from: deployer, - log: true, - }) - - await execute( - "KeepToken", - { from: deployer }, - "mint", - deployer, - KEEP_SUPPLY - ) - - log(`minted ${from1e18(await read("KeepToken", "totalSupply"))} KEEP`) - } -} - -export default func - -func.tags = ["KeepToken"] diff --git a/deploy/00_resolve_keep_token_staking.ts b/deploy/00_resolve_keep_token_staking.ts deleted file mode 100644 index 809d9c42..00000000 --- a/deploy/00_resolve_keep_token_staking.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HardhatRuntimeEnvironment, HardhatNetworkConfig } from "hardhat/types" -import { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, deployments, helpers } = hre - const { log } = deployments - const { deployer } = await getNamedAccounts() - - const KeepTokenStaking = await deployments.getOrNull("KeepTokenStaking") - - if (KeepTokenStaking && helpers.address.isValid(KeepTokenStaking.address)) { - log(`using existing KeepTokenStaking at ${KeepTokenStaking.address}`) - } else if ( - !hre.network.tags.allowStubs || - (hre.network.config as HardhatNetworkConfig)?.forking?.enabled - ) { - throw new Error("deployed KeepTokenStaking contract not found") - } else { - log(`deploying KeepTokenStaking stub`) - - await deployments.deploy("KeepTokenStaking", { - contract: "KeepTokenStakingMock", - from: deployer, - log: true, - }) - } -} - -export default func - -func.tags = ["KeepTokenStaking"] diff --git a/deploy/00_resolve_nucypher_staking_escrow.ts b/deploy/00_resolve_nucypher_staking_escrow.ts deleted file mode 100644 index 509c90d8..00000000 --- a/deploy/00_resolve_nucypher_staking_escrow.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { - HardhatNetworkConfig, - HardhatRuntimeEnvironment, -} from "hardhat/types" -import type { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, deployments, helpers } = hre - const { log } = deployments - const { deployer } = await getNamedAccounts() - - const NuCypherStakingEscrow = await deployments.getOrNull( - "NuCypherStakingEscrow" - ) - - if ( - NuCypherStakingEscrow && - helpers.address.isValid(NuCypherStakingEscrow.address) - ) { - log( - `using existing NuCypherStakingEscrow at ${NuCypherStakingEscrow.address}` - ) - - // Save deployment artifact of external contract to include it in the package. - await deployments.save("NuCypherStakingEscrow", NuCypherStakingEscrow) - } else if ( - // TODO: For testnets currently we deploy a stub contract. We should consider - // switching to an actual contract. - hre.network.name !== "sepolia" && - hre.network.name !== "goerli" && - (!hre.network.tags.allowStubs || - (hre.network.config as HardhatNetworkConfig)?.forking?.enabled) - ) { - throw new Error("deployed NuCypherStakingEscrow contract not found") - } else { - log(`deploying NuCypherStakingEscrow stub`) - - await deployments.deploy("NuCypherStakingEscrow", { - contract: "NuCypherTokenStakingMock", - from: deployer, - log: true, - }) - } -} - -export default func - -func.tags = ["NuCypherStakingEscrow"] diff --git a/deploy/03_deploy_vending_machine_keep.ts b/deploy/03_deploy_vending_machine_keep.ts deleted file mode 100644 index 1fea3b26..00000000 --- a/deploy/03_deploy_vending_machine_keep.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types" -import { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, deployments, helpers } = hre - const { deployer } = await getNamedAccounts() - const { to1e18 } = helpers.number - - const KeepToken = await deployments.get("KeepToken") - const T = await deployments.get("T") - - const KEEP_TOKEN_ALLOCATION = "940795010800000000000000000" - - // We're wrapping 100% of the minted KEEP and will be allocating 45% of the - // minted T tokens. The remaining T tokens will be in the future distributed - // between another instance of the VendingMachine (which will be wrapping NU - // token) and a DAO treasury. - const T_ALLOCATION_KEEP = to1e18("4500000000") - - const vendingMachine = await deployments.deploy("VendingMachineKeep", { - contract: "VendingMachine", - from: deployer, - args: [ - KeepToken.address, - T.address, - KEEP_TOKEN_ALLOCATION, - T_ALLOCATION_KEEP, - ], - log: true, - }) - - if (hre.network.tags.tenderly) { - await hre.tenderly.verify({ - name: "VendingMachineKeep", - address: vendingMachine.address, - }) - } -} - -export default func - -func.tags = ["VendingMachineKeep"] -func.dependencies = ["T", "KeepToken"] diff --git a/deploy/05_transfer_t.ts b/deploy/05_transfer_t.ts index ec68947a..ccd95224 100644 --- a/deploy/05_transfer_t.ts +++ b/deploy/05_transfer_t.ts @@ -7,11 +7,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { execute } = deployments const { to1e18, from1e18 } = helpers.number - const VendingMachineKeep = await deployments.get("VendingMachineKeep") const VendingMachineNuCypher = await deployments.get("VendingMachineNuCypher") const vendingMachines = [ - { tokenSymbol: "KEEP", vendingMachineAddress: VendingMachineKeep.address }, { tokenSymbol: "NU", vendingMachineAddress: VendingMachineNuCypher.address, @@ -43,4 +41,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func func.tags = ["TransferT"] -func.dependencies = ["MintT", "VendingMachineKeep", "VendingMachineNuCypher"] +func.dependencies = ["MintT", "VendingMachineNuCypher"] diff --git a/deploy/06_deploy_keep_stake.ts b/deploy/06_deploy_keep_stake.ts deleted file mode 100644 index 83599291..00000000 --- a/deploy/06_deploy_keep_stake.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types" -import { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, deployments } = hre - const { deployer } = await getNamedAccounts() - - const KeepTokenStaking = await deployments.get("KeepTokenStaking") - - const KeepStake = await deployments.deploy("KeepStake", { - from: deployer, - args: [KeepTokenStaking.address], - log: true, - }) - - if (hre.network.tags.tenderly) { - await hre.tenderly.verify({ - name: "KeepStake", - address: KeepStake.address, - }) - } -} - -export default func - -func.tags = ["KeepStake"] -func.dependencies = ["KeepTokenStaking"] diff --git a/deploy/07_deploy_token_staking.ts b/deploy/07_deploy_token_staking.ts index f8db19a9..2820f162 100644 --- a/deploy/07_deploy_token_staking.ts +++ b/deploy/07_deploy_token_staking.ts @@ -9,19 +9,11 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { deployer } = await getNamedAccounts() const T = await deployments.get("T") - const KeepTokenStaking = await deployments.get("KeepTokenStaking") - const NuCypherStakingEscrow = await deployments.get("NuCypherStakingEscrow") - const VendingMachineKeep = await deployments.get("VendingMachineKeep") const VendingMachineNuCypher = await deployments.get("VendingMachineNuCypher") - const KeepStake = await deployments.get("KeepStake") const tokenStakingConstructorArgs = [ T.address, - KeepTokenStaking.address, - NuCypherStakingEscrow.address, - VendingMachineKeep.address, VendingMachineNuCypher.address, - KeepStake.address, ] const tokenStakingInitializerArgs = [] @@ -82,12 +74,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func func.tags = ["TokenStaking"] -func.dependencies = [ - "T", - "KeepTokenStaking", - "NuCypherStakingEscrow", - "VendingMachineKeep", - "VendingMachineNuCypher", - "KeepStake", - "MintT", -] +func.dependencies = ["T", "VendingMachineNuCypher", "MintT"] diff --git a/deploy/08_configure_keep_registry.ts b/deploy/08_configure_keep_registry.ts deleted file mode 100644 index c9a493ab..00000000 --- a/deploy/08_configure_keep_registry.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types" -import { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, deployments } = hre - const { keepRegistryKeeper, deployer } = await getNamedAccounts() - const { execute, log } = deployments - - const TokenStaking = await deployments.get("TokenStaking") - - await execute( - "KeepRegistry", - { from: keepRegistryKeeper || deployer }, - "approveOperatorContract", - TokenStaking.address - ) - - log( - `Approved T TokenStaking operator contract [${TokenStaking.address}] in KeepRegistry` - ) -} - -export default func - -func.tags = ["ConfigureKeepRegistry"] -func.dependencies = ["TokenStaking", "KeepRegistry"] -func.skip = async function (hre: HardhatRuntimeEnvironment): Promise { - return hre.network.name === "mainnet" -} diff --git a/deploy/54_transfer_ownership_keep_stake.ts b/deploy/54_transfer_ownership_keep_stake.ts deleted file mode 100644 index 9d15809c..00000000 --- a/deploy/54_transfer_ownership_keep_stake.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types" -import { DeployFunction } from "hardhat-deploy/types" - -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, helpers } = hre - const { deployer, thresholdCouncil } = await getNamedAccounts() - - await helpers.ownable.transferOwnership( - "KeepStake", - thresholdCouncil, - deployer - ) -} - -export default func - -func.tags = ["TransferOwnershipKeepStake"] -func.dependencies = ["KeepStake"] -func.runAtTheEnd = true -func.skip = async function (hre: HardhatRuntimeEnvironment): Promise { - return hre.network.name !== "mainnet" -} diff --git a/deployments/mainnet/KeepStake.json b/deployments/mainnet/KeepStake.json deleted file mode 100644 index 5f505920..00000000 --- a/deployments/mainnet/KeepStake.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "address": "0x10DE37cF84202A20cae61069C617B3Aa874aF8b4", - "abi": [ - { - "inputs": [ - { - "internalType": "contract IKeepTokenStaking", - "name": "_keepTokenStaking", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [], - "name": "keepTokenStaking", - "outputs": [ - { - "internalType": "contract IKeepTokenStaking", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "operatorToGrantee", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "operatorToManagedGrant", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "resolveOwner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "address", - "name": "grantee", - "type": "address" - } - ], - "name": "setGrantee", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "address", - "name": "managedGrant", - "type": "address" - } - ], - "name": "setManagedGrant", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "transactionHash": "0xab80a05ee089aa5df29de6c6a5ec00e66c00f7d242623b0650c9ad2619cf6336", - "receipt": { - "to": null, - "from": "0xFfFd7092685bDeeBD121D1A0FEA3c349114Cce50", - "contractAddress": "0x10DE37cF84202A20cae61069C617B3Aa874aF8b4", - "transactionIndex": 164, - "gasUsed": "1355541", - "logsBloom": "0x00000000000000000000000000004000000000000000000000801000000000000000000000000000000000000000000000000000000000000000000000100000000000002000000000000000000000000001000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000020000000000000000000000000800000000000000000000000000000000000000000", - "blockHash": "0xea85285f049f20d12c2ffe1ab62046c1e1eb11110562e040c5d6d8855219016d", - "transactionHash": "0xab80a05ee089aa5df29de6c6a5ec00e66c00f7d242623b0650c9ad2619cf6336", - "logs": [ - { - "transactionIndex": 164, - "blockNumber": 14113763, - "transactionHash": "0xab80a05ee089aa5df29de6c6a5ec00e66c00f7d242623b0650c9ad2619cf6336", - "address": "0x10DE37cF84202A20cae61069C617B3Aa874aF8b4", - "topics": [ - "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x000000000000000000000000fffd7092685bdeebd121d1a0fea3c349114cce50" - ], - "data": "0x", - "logIndex": 179, - "blockHash": "0xea85285f049f20d12c2ffe1ab62046c1e1eb11110562e040c5d6d8855219016d" - } - ], - "blockNumber": 14113763, - "cumulativeGasUsed": "11865435", - "status": 1, - "byzantium": true - }, - "args": [ - "0x1293a54e160D1cd7075487898d65266081A15458" - ], - "numDeployments": 1, - "solcInputHash": "ecfd0a8dc1d11e9f659c5fce34a455a2", - "metadata": "{\"compiler\":{\"version\":\"0.8.9+commit.e5eed63a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract IKeepTokenStaking\",\"name\":\"_keepTokenStaking\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"keepTokenStaking\",\"outputs\":[{\"internalType\":\"contract IKeepTokenStaking\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"operatorToGrantee\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"operatorToManagedGrant\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"resolveOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"grantee\",\"type\":\"address\"}],\"name\":\"setGrantee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"managedGrant\",\"type\":\"address\"}],\"name\":\"setManagedGrant\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"title\":\"KEEP stake owner resolver\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"resolveOwner(address)\":{\"notice\":\"Resolves KEEP stake owner for the provided operator address. Reverts if could not resolve the owner.\"},\"setGrantee(address,address)\":{\"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.\"},\"setManagedGrant(address,address)\":{\"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.\"}},\"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 staking contract in T staking contract, it fitst 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.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/staking/KeepStake.sol\":\"KeepStake\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0x24e0364e503a9bbde94c715d26573a76f14cd2a202d45f96f52134ab806b67b9\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"contracts/staking/ILegacyTokenStaking.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-or-later\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.9;\\n\\n/// @title IKeepTokenStaking\\n/// @notice Interface for Keep TokenStaking contract\\ninterface IKeepTokenStaking {\\n /// @notice Seize provided token amount from every member in the misbehaved\\n /// operators array. The tattletale is rewarded with 5% of the total seized\\n /// amount scaled by the reward adjustment parameter and the rest 95% is burned.\\n /// @param amountToSeize Token amount to seize from every misbehaved operator.\\n /// @param rewardMultiplier Reward adjustment in percentage. Min 1% and 100% max.\\n /// @param tattletale Address to receive the 5% reward.\\n /// @param misbehavedOperators Array of addresses to seize the tokens from.\\n function seize(\\n uint256 amountToSeize,\\n uint256 rewardMultiplier,\\n address tattletale,\\n address[] memory misbehavedOperators\\n ) external;\\n\\n /// @notice Gets stake delegation info for the given operator.\\n /// @param operator Operator address.\\n /// @return amount The amount of tokens the given operator delegated.\\n /// @return createdAt The time when the stake has been delegated.\\n /// @return undelegatedAt The time when undelegation has been requested.\\n /// If undelegation has not been requested, 0 is returned.\\n function getDelegationInfo(address operator)\\n external\\n view\\n returns (\\n uint256 amount,\\n uint256 createdAt,\\n uint256 undelegatedAt\\n );\\n\\n /// @notice Gets the stake owner for the specified operator address.\\n /// @return Stake owner address.\\n function ownerOf(address operator) external view returns (address);\\n\\n /// @notice Gets the beneficiary for the specified operator address.\\n /// @return Beneficiary address.\\n function beneficiaryOf(address operator)\\n external\\n view\\n returns (address payable);\\n\\n /// @notice Gets the authorizer for the specified operator address.\\n /// @return Authorizer address.\\n function authorizerOf(address operator) external view returns (address);\\n\\n /// @notice Gets the eligible stake balance of the specified address.\\n /// An eligible stake is a stake that passed the initialization period\\n /// and is not currently undelegating. Also, the operator had to approve\\n /// the specified operator contract.\\n ///\\n /// Operator with a minimum required amount of eligible stake can join the\\n /// network and participate in new work selection.\\n ///\\n /// @param operator address of stake operator.\\n /// @param operatorContract address of operator contract.\\n /// @return balance an uint256 representing the eligible stake balance.\\n function eligibleStake(address operator, address operatorContract)\\n external\\n view\\n returns (uint256 balance);\\n}\\n\\n/// @title INuCypherStakingEscrow\\n/// @notice Interface for NuCypher StakingEscrow contract\\ninterface INuCypherStakingEscrow {\\n /// @notice Slash the staker's stake and reward the investigator\\n /// @param staker Staker's address\\n /// @param penalty Penalty\\n /// @param investigator Investigator\\n /// @param reward Reward for the investigator\\n function slashStaker(\\n address staker,\\n uint256 penalty,\\n address investigator,\\n uint256 reward\\n ) external;\\n\\n /// @notice Request merge between NuCypher staking contract and T staking contract.\\n /// Returns amount of staked tokens\\n function requestMerge(address staker, address stakingProvider)\\n external\\n returns (uint256);\\n\\n /// @notice Get all tokens belonging to the staker\\n function getAllTokens(address staker) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x807d42d248e94c1f2269c7aa27031302a1225525e1405010424dd91adcc092ab\",\"license\":\"GPL-3.0-or-later\"},\"contracts/staking/KeepStake.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-or-later\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.9;\\n\\nimport \\\"./ILegacyTokenStaking.sol\\\";\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\n/// @title KEEP ManagedGrant contract interface\\ninterface IManagedGrant {\\n function grantee() external view returns (address);\\n}\\n\\n/// @title KEEP stake owner resolver\\n/// @notice T network staking contract supports existing KEEP stakes by allowing\\n/// KEEP stakers to use their stakes in T network and weights them based\\n/// on KEEP<>T token ratio. KEEP stake owner is cached in T staking\\n/// contract and used to restrict access to all functions only owner or\\n/// operator should call. To cache KEEP staking contract in T staking\\n/// contract, it fitst needs to resolve the owner. Resolving liquid\\n/// KEEP stake owner is easy. Resolving token grant stake owner is\\n/// complicated and not possible to do on-chain from a contract external\\n/// to KEEP TokenStaking contract. Keep TokenStaking knows the grant ID\\n/// but does not expose it externally.\\n///\\n/// KeepStake contract addresses this problem by exposing\\n/// operator-owner mappings snapshotted off-chain based on events and\\n/// information publicly available from KEEP TokenStaking contract and\\n/// KEEP TokenGrant contract. Additionally, it gives the Governance\\n/// ability to add new mappings in case they are ever needed; in\\n/// practice, this will be needed only if someone decides to stake their\\n/// KEEP token grant in KEEP network after 2021-11-11 when the snapshot\\n/// was taken.\\n///\\n/// Operator-owner pairs were snapshotted 2021-11-11 in the following\\n/// way:\\n/// 1. Fetch all TokenStaking events from KEEP staking contract.\\n/// 2. Filter out undelegated operators.\\n/// 3. Filter out canceled delegations.\\n/// 4. Fetch grant stake information from KEEP TokenGrant for that\\n/// operator to determine if we are dealing with grant delegation.\\n/// 5. Fetch grantee address from KEEP TokenGrant contract.\\n/// 6. Check if we are dealing with ManagedGrant by looking for all\\n/// created ManagedGrants and comparing their address against grantee\\n/// address fetched from TokenGrant contract.\\ncontract KeepStake is Ownable {\\n IKeepTokenStaking public immutable keepTokenStaking;\\n\\n mapping(address => address) public operatorToManagedGrant;\\n mapping(address => address) public operatorToGrantee;\\n\\n constructor(IKeepTokenStaking _keepTokenStaking) {\\n keepTokenStaking = _keepTokenStaking;\\n }\\n\\n /// @notice Allows the Governance to set new operator-managed grant pair.\\n /// This function should only be called for managed grants if\\n /// the snapshot does include this pair.\\n function setManagedGrant(address operator, address managedGrant)\\n external\\n onlyOwner\\n {\\n operatorToManagedGrant[operator] = managedGrant;\\n }\\n\\n /// @notice Allows the Governance to set new operator-grantee pair.\\n /// This function should only be called for non-managed grants if\\n /// the snapshot does include this pair.\\n function setGrantee(address operator, address grantee) external onlyOwner {\\n operatorToGrantee[operator] = grantee;\\n }\\n\\n /// @notice Resolves KEEP stake owner for the provided operator address.\\n /// Reverts if could not resolve the owner.\\n function resolveOwner(address operator) external view returns (address) {\\n address owner = operatorToManagedGrant[operator];\\n if (owner != address(0)) {\\n return IManagedGrant(owner).grantee();\\n }\\n\\n owner = operatorToGrantee[operator];\\n if (owner != address(0)) {\\n return owner;\\n }\\n\\n owner = resolveSnapshottedManagedGrantees(operator);\\n if (owner != address(0)) {\\n return owner;\\n }\\n\\n owner = resolveSnapshottedGrantees(operator);\\n if (owner != address(0)) {\\n return owner;\\n }\\n\\n owner = keepTokenStaking.ownerOf(operator);\\n require(owner != address(0), \\\"Could not resolve the owner\\\");\\n\\n return owner;\\n }\\n\\n function resolveSnapshottedManagedGrantees(address operator)\\n internal\\n view\\n returns (address)\\n {\\n if (operator == 0x855A951162B1B93D70724484d5bdc9D00B56236B) {\\n return\\n IManagedGrant(0xFADbF758307A054C57B365Db1De90acA71feaFE5)\\n .grantee();\\n }\\n if (operator == 0xF1De9490Bf7298b5F350cE74332Ad7cf8d5cB181) {\\n return\\n IManagedGrant(0xAEd493Aaf3E76E83b29E151848b71eF4544f92f1)\\n .grantee();\\n }\\n if (operator == 0x39d2aCBCD80d80080541C6eed7e9feBb8127B2Ab) {\\n return\\n IManagedGrant(0xA2fa09D6f8C251422F5fde29a0BAd1C53dEfAe66)\\n .grantee();\\n }\\n if (operator == 0xd66cAE89FfBc6E50e6b019e45c1aEc93Dec54781) {\\n return\\n IManagedGrant(0x306309f9d105F34132db0bFB3Ce3f5B0245Cd386)\\n .grantee();\\n }\\n if (operator == 0x2eBE08379f4fD866E871A9b9E1d5C695154C6A9F) {\\n return\\n IManagedGrant(0xd00c0d43b747C33726B3f0ff4BDA4b72dc53c6E9)\\n .grantee();\\n }\\n if (operator == 0xA97c34278162b556A527CFc01B53eb4DDeDFD223) {\\n return\\n IManagedGrant(0xB3E967355c456B1Bd43cB0188A321592D410D096)\\n .grantee();\\n }\\n if (operator == 0x6C76d49322C9f8761A1623CEd89A31490cdB649d) {\\n return\\n IManagedGrant(0xB3E967355c456B1Bd43cB0188A321592D410D096)\\n .grantee();\\n }\\n if (operator == 0x4a41c7a884d119eaaefE471D0B3a638226408382) {\\n return\\n IManagedGrant(0xcdf3d216d82a463Ce82971F2F5DA3d8f9C5f093A)\\n .grantee();\\n }\\n if (operator == 0x9c06Feb7Ebc8065ee11Cd5E8EEdaAFb2909A7087) {\\n return\\n IManagedGrant(0x45119cd98d145283762BA9eBCAea75F72D188733)\\n .grantee();\\n }\\n if (operator == 0x9bD818Ab6ACC974f2Cf2BD2EBA7a250126Accb9F) {\\n return\\n IManagedGrant(0x6E535043377067621954ee84065b0bd7357e7aBa)\\n .grantee();\\n }\\n if (operator == 0x1d803c89760F8B4057DB15BCb3B8929E0498D310) {\\n return\\n IManagedGrant(0xB3E967355c456B1Bd43cB0188A321592D410D096)\\n .grantee();\\n }\\n if (operator == 0x3101927DEeC27A2bfA6c4a6316e3A221f631dB91) {\\n return\\n IManagedGrant(0x178Bf1946feD0e2362fdF8bcD3f91F0701a012C6)\\n .grantee();\\n }\\n if (operator == 0x9d9b187E478bC62694A7bED216Fc365de87F280C) {\\n return\\n IManagedGrant(0xFBad17CFad6cb00D726c65501D69FdC13Ca5477c)\\n .grantee();\\n }\\n if (operator == 0xd977144724Bc77FaeFAe219F958AE3947205d0b5) {\\n return\\n IManagedGrant(0x087B442BFd4E42675cf2df5fa566F87d7A96Fb12)\\n .grantee();\\n }\\n if (operator == 0x045E511f53DeBF55c9C0B4522f14F602f7C7cA81) {\\n return\\n IManagedGrant(0xFcfe8C036C414a15cF871071c483687095caF7D6)\\n .grantee();\\n }\\n if (operator == 0x3Dd301b3c96A282d8092E1e6f6846f24172D45C1) {\\n return\\n IManagedGrant(0xb5Bdd2D9B3541fc8f581Af37430D26527e59aeF8)\\n .grantee();\\n }\\n if (operator == 0x5d84DEB482E770479154028788Df79aA7C563aA4) {\\n return\\n IManagedGrant(0x9D1a179c469a8BdD0b683A9f9250246cc47e8fBE)\\n .grantee();\\n }\\n if (operator == 0x1dF927B69A97E8140315536163C029d188e8573b) {\\n return\\n IManagedGrant(0xb5Bdd2D9B3541fc8f581Af37430D26527e59aeF8)\\n .grantee();\\n }\\n if (operator == 0x617daCE069Fbd41993491de211b4DfccdAcbd348) {\\n return\\n IManagedGrant(0xb5Bdd2D9B3541fc8f581Af37430D26527e59aeF8)\\n .grantee();\\n }\\n if (operator == 0x650A9eD18Df873cad98C88dcaC8170531cAD2399) {\\n return\\n IManagedGrant(0x1Df7324A3aD20526DFa02Cc803eD2D97Cac81F3b)\\n .grantee();\\n }\\n if (operator == 0x07C9a8f8264221906b7b8958951Ce4753D39628B) {\\n return\\n IManagedGrant(0x305D12b4d70529Cd618dA7399F5520701E510041)\\n .grantee();\\n }\\n if (operator == 0x63eB4c3DD0751F9BE7070A01156513C227fa1eF6) {\\n return\\n IManagedGrant(0x306309f9d105F34132db0bFB3Ce3f5B0245Cd386)\\n .grantee();\\n }\\n if (operator == 0xc6349eEC31048787676b6297ba71721376A8DdcF) {\\n return\\n IManagedGrant(0xac1a985E75C6a0b475b9c807Ad0705a988Be2D99)\\n .grantee();\\n }\\n if (operator == 0x3B945f9C0C8737e44f8e887d4F04B5B3A491Ac4d) {\\n return\\n IManagedGrant(0x82e17477726E8D9D2C237745cA9989631582eE98)\\n .grantee();\\n }\\n if (operator == 0xF35343299a4f80Dd5D917bbe5ddd54eBB820eBd4) {\\n return\\n IManagedGrant(0xCC88c15506251B62ccCeebA193e100d6bBC9a30D)\\n .grantee();\\n }\\n if (operator == 0x3B9e5ae72d068448bB96786989c0d86FBC0551D1) {\\n return\\n IManagedGrant(0x306309f9d105F34132db0bFB3Ce3f5B0245Cd386)\\n .grantee();\\n }\\n if (operator == 0xB2D53Be158Cb8451dFc818bD969877038c1BdeA1) {\\n return\\n IManagedGrant(0xaE55e3800f0A3feaFdcE535A8C0fab0fFdB90DEe)\\n .grantee();\\n }\\n if (operator == 0xF6dbF7AFe05b8Bb6f198eC7e69333c98D3C4608C) {\\n return\\n IManagedGrant(0xbb8D24a20c20625f86739824014C3cBAAAb26700)\\n .grantee();\\n }\\n if (operator == 0xB62Fc1ADfFb2ab832041528C8178358338d85f76) {\\n return\\n IManagedGrant(0x9ED98fD1C29018B9342CB8F57A3073B9695f0c02)\\n .grantee();\\n }\\n if (operator == 0x9bC8d30d971C9e74298112803036C05db07D73e3) {\\n return\\n IManagedGrant(0x66beda757939f8e505b5Eb883cd02C8d4a11Bca2)\\n .grantee();\\n }\\n\\n return address(0);\\n }\\n\\n function resolveSnapshottedGrantees(address operator)\\n internal\\n pure\\n returns (address)\\n {\\n if (operator == 0x1147ccFB4AEFc6e587a23b78724Ef20Ec6e474D4) {\\n return 0x3FB49dA4375Ef9019f17990D04c6d5daD482D80a;\\n }\\n if (operator == 0x4c21541f95a00C03C75F38C71DC220bd27cbbEd9) {\\n return 0xC897cfeE43a8d827F76D4226994D5CE5EBBe2571;\\n }\\n if (operator == 0x7E6332d18719a5463d3867a1a892359509589a3d) {\\n return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da;\\n }\\n if (operator == 0x8Bd660A764Ca14155F3411a4526a028b6316CB3E) {\\n return 0xf6f372DfAeCC1431186598c304e91B79Ce115766;\\n }\\n if (operator == 0x4F4f0D0dfd93513B3f4Cb116Fe9d0A005466F725) {\\n return 0x8b055ac1c4dd287E2a46D4a52d61FE76FB551bD0;\\n }\\n if (operator == 0x1DF0250027fEC876d8876d1ac7A392c9098F1a1e) {\\n return 0xE408fFa969707Ce5d7aA3e5F8d44674Fa4b26219;\\n }\\n if (operator == 0x860EF3f83B6adFEF757F98345c3B8DdcFCA9d152) {\\n return 0x08a3633AAb8f3E436DEA204288Ee26Fe094406b0;\\n }\\n if (operator == 0xe3a2d16dA142E6B190A5d9F7e0C07cc460B58A5F) {\\n return 0x875f8fFCDDeD63B5d8Cf54be4E4b82FE6c6E249C;\\n }\\n if (operator == 0xBDE07f1cA107Ef319b0Bb26eBF1d0a5b4c97ffc1) {\\n return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da;\\n }\\n if (operator == 0xE86181D6b672d78D33e83029fF3D0ef4A601B4C4) {\\n return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da;\\n }\\n if (operator == 0xb7c561e2069aCaE2c4480111B1606790BB4E13fE) {\\n return 0x1578eD833D986c1188D1a998aA5FEcD418beF5da;\\n }\\n if (operator == 0x526c013f8382B050d32d86e7090Ac84De22EdA4D) {\\n return 0x61C6E5DDacded540CD08066C08cbc096d22D91f4;\\n }\\n\\n return address(0);\\n }\\n}\\n\",\"keccak256\":\"0x4ad3a18ebfb9d1b5e189ed4a827e7af96cf14f2b4386c96ca08e940418391a0b\",\"license\":\"GPL-3.0-or-later\"}},\"version\":1}", - "bytecode": "0x60a060405234801561001057600080fd5b506040516117fa3803806117fa83398101604081905261002f91610099565b61003833610049565b6001600160a01b03166080526100c9565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000602082840312156100ab57600080fd5b81516001600160a01b03811681146100c257600080fd5b9392505050565b6080516117106100ea6000396000818160b6015261028f01526117106000f3fe608060405234801561001057600080fd5b50600436106100835760003560e01c806301f326961461008857806303055815146100b157806310b648cb146100d8578063715018a6146100ed57806384f82c53146100f55780638da5cb5b14610108578063edc6640014610110578063f2fde38b14610139578063f371b2161461014c575b600080fd5b61009b61009636600461161e565b610175565b6040516100a8919061163b565b60405180910390f35b61009b7f000000000000000000000000000000000000000000000000000000000000000081565b6100eb6100e636600461164f565b610375565b005b6100eb6103d2565b6100eb61010336600461164f565b61040d565b61009b61046a565b61009b61011e36600461161e565b6002602052600090815260409020546001600160a01b031681565b6100eb61014736600461161e565b610479565b61009b61015a36600461161e565b6001602052600090815260409020546001600160a01b031681565b6001600160a01b03808216600090815260016020526040812054909116801561021057806001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b1580156101d157600080fd5b505afa1580156101e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102099190611688565b9392505050565b506001600160a01b038083166000908152600260205260409020541680156102385792915050565b61024183610519565b90506001600160a01b038116156102585792915050565b610261836112b7565b90506001600160a01b038116156102785792915050565b604051630a57ebcf60e11b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906314afd79e906102c490869060040161163b565b60206040518083038186803b1580156102dc57600080fd5b505afa1580156102f0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103149190611688565b90506001600160a01b03811661036f5760405162461bcd60e51b815260206004820152601b60248201527a21b7bab632103737ba103932b9b7b63b32903a34329037bbb732b960291b60448201526064015b60405180910390fd5b92915050565b3361037e61046a565b6001600160a01b0316146103a45760405162461bcd60e51b8152600401610366906116a5565b6001600160a01b03918216600090815260016020526040902080546001600160a01b03191691909216179055565b336103db61046a565b6001600160a01b0316146104015760405162461bcd60e51b8152600401610366906116a5565b61040b60006115b9565b565b3361041661046a565b6001600160a01b03161461043c5760405162461bcd60e51b8152600401610366906116a5565b6001600160a01b03918216600090815260026020526040902080546001600160a01b03191691909216179055565b6000546001600160a01b031690565b3361048261046a565b6001600160a01b0316146104a85760405162461bcd60e51b8152600401610366906116a5565b6001600160a01b03811661050d5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610366565b610516816115b9565b50565b600073855a951162b1b93d70724484d5bdc9d00b56236b6001600160a01b03831614156105c55773fadbf758307a054c57b365db1de90aca71feafe56001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b505afa1580156105a1573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061036f9190611688565b73f1de9490bf7298b5f350ce74332ad7cf8d5cb1816001600160a01b03831614156106375773aed493aaf3e76e83b29e151848b71ef4544f92f16001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b7339d2acbcd80d80080541c6eed7e9febb8127b2ab6001600160a01b03831614156106a95773a2fa09d6f8c251422f5fde29a0bad1c53defae666001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73d66cae89ffbc6e50e6b019e45c1aec93dec547816001600160a01b038316141561071b5773306309f9d105f34132db0bfb3ce3f5b0245cd3866001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b732ebe08379f4fd866e871a9b9e1d5c695154c6a9f6001600160a01b038316141561078d5773d00c0d43b747c33726b3f0ff4bda4b72dc53c6e96001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73a97c34278162b556a527cfc01b53eb4ddedfd2236001600160a01b03831614156107ff5773b3e967355c456b1bd43cb0188a321592d410d0966001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b736c76d49322c9f8761a1623ced89a31490cdb649d6001600160a01b03831614156108715773b3e967355c456b1bd43cb0188a321592d410d0966001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b734a41c7a884d119eaaefe471d0b3a6382264083826001600160a01b03831614156108e35773cdf3d216d82a463ce82971f2f5da3d8f9c5f093a6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739c06feb7ebc8065ee11cd5e8eedaafb2909a70876001600160a01b0383161415610955577345119cd98d145283762ba9ebcaea75f72d1887336001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739bd818ab6acc974f2cf2bd2eba7a250126accb9f6001600160a01b03831614156109c757736e535043377067621954ee84065b0bd7357e7aba6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b731d803c89760f8b4057db15bcb3b8929e0498d3106001600160a01b0383161415610a395773b3e967355c456b1bd43cb0188a321592d410d0966001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733101927deec27a2bfa6c4a6316e3a221f631db916001600160a01b0383161415610aab5773178bf1946fed0e2362fdf8bcd3f91f0701a012c66001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739d9b187e478bc62694a7bed216fc365de87f280c6001600160a01b0383161415610b1d5773fbad17cfad6cb00d726c65501d69fdc13ca5477c6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73d977144724bc77faefae219f958ae3947205d0b56001600160a01b0383161415610b8f5773087b442bfd4e42675cf2df5fa566f87d7a96fb126001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73045e511f53debf55c9c0b4522f14f602f7c7ca816001600160a01b0383161415610c015773fcfe8c036c414a15cf871071c483687095caf7d66001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733dd301b3c96a282d8092e1e6f6846f24172d45c16001600160a01b0383161415610c735773b5bdd2d9b3541fc8f581af37430d26527e59aef86001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b735d84deb482e770479154028788df79aa7c563aa46001600160a01b0383161415610ce557739d1a179c469a8bdd0b683a9f9250246cc47e8fbe6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b731df927b69a97e8140315536163c029d188e8573b6001600160a01b0383161415610d575773b5bdd2d9b3541fc8f581af37430d26527e59aef86001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73617dace069fbd41993491de211b4dfccdacbd3486001600160a01b0383161415610dc95773b5bdd2d9b3541fc8f581af37430d26527e59aef86001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73650a9ed18df873cad98c88dcac8170531cad23996001600160a01b0383161415610e3b57731df7324a3ad20526dfa02cc803ed2d97cac81f3b6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b7307c9a8f8264221906b7b8958951ce4753d39628b6001600160a01b0383161415610ead5773305d12b4d70529cd618da7399f5520701e5100416001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b7363eb4c3dd0751f9be7070a01156513c227fa1ef66001600160a01b0383161415610f1f5773306309f9d105f34132db0bfb3ce3f5b0245cd3866001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73c6349eec31048787676b6297ba71721376a8ddcf6001600160a01b0383161415610f915773ac1a985e75c6a0b475b9c807ad0705a988be2d996001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733b945f9c0c8737e44f8e887d4f04b5b3a491ac4d6001600160a01b0383161415611003577382e17477726e8d9d2c237745ca9989631582ee986001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73f35343299a4f80dd5d917bbe5ddd54ebb820ebd46001600160a01b03831614156110755773cc88c15506251b62ccceeba193e100d6bbc9a30d6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733b9e5ae72d068448bb96786989c0d86fbc0551d16001600160a01b03831614156110e75773306309f9d105f34132db0bfb3ce3f5b0245cd3866001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73b2d53be158cb8451dfc818bd969877038c1bdea16001600160a01b03831614156111595773ae55e3800f0a3feafdce535a8c0fab0ffdb90dee6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73f6dbf7afe05b8bb6f198ec7e69333c98d3c4608c6001600160a01b03831614156111cb5773bb8d24a20c20625f86739824014c3cbaaab267006001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73b62fc1adffb2ab832041528c8178358338d85f766001600160a01b038316141561123d57739ed98fd1c29018b9342cb8f57a3073b9695f0c026001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739bc8d30d971c9e74298112803036c05db07d73e36001600160a01b03831614156112af577366beda757939f8e505b5eb883cd02c8d4a11bca26001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b506000919050565b6000731147ccfb4aefc6e587a23b78724ef20ec6e474d46001600160a01b03831614156112f95750733fb49da4375ef9019f17990d04c6d5dad482d80a919050565b734c21541f95a00c03c75f38c71dc220bd27cbbed96001600160a01b0383161415611339575073c897cfee43a8d827f76d4226994d5ce5ebbe2571919050565b737e6332d18719a5463d3867a1a892359509589a3d6001600160a01b03831614156113795750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b738bd660a764ca14155f3411a4526a028b6316cb3e6001600160a01b03831614156113b9575073f6f372dfaecc1431186598c304e91b79ce115766919050565b734f4f0d0dfd93513b3f4cb116fe9d0a005466f7256001600160a01b03831614156113f95750738b055ac1c4dd287e2a46d4a52d61fe76fb551bd0919050565b731df0250027fec876d8876d1ac7a392c9098f1a1e6001600160a01b0383161415611439575073e408ffa969707ce5d7aa3e5f8d44674fa4b26219919050565b73860ef3f83b6adfef757f98345c3b8ddcfca9d1526001600160a01b038316141561147957507308a3633aab8f3e436dea204288ee26fe094406b0919050565b73e3a2d16da142e6b190a5d9f7e0c07cc460b58a5f6001600160a01b03831614156114b9575073875f8ffcdded63b5d8cf54be4e4b82fe6c6e249c919050565b73bde07f1ca107ef319b0bb26ebf1d0a5b4c97ffc16001600160a01b03831614156114f95750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b73e86181d6b672d78d33e83029ff3d0ef4a601b4c46001600160a01b03831614156115395750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b73b7c561e2069acae2c4480111b1606790bb4e13fe6001600160a01b03831614156115795750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b73526c013f8382b050d32d86e7090ac84de22eda4d6001600160a01b03831614156112af57507361c6e5ddacded540cd08066c08cbc096d22d91f4919050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811461051657600080fd5b60006020828403121561163057600080fd5b813561020981611609565b6001600160a01b0391909116815260200190565b6000806040838503121561166257600080fd5b823561166d81611609565b9150602083013561167d81611609565b809150509250929050565b60006020828403121561169a57600080fd5b815161020981611609565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260408201526060019056fea2646970667358221220c0ac96c7f48a68918e7d689e73ff0792db6f94354c2b8641f039b4f5ed22036c64736f6c63430008090033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100835760003560e01c806301f326961461008857806303055815146100b157806310b648cb146100d8578063715018a6146100ed57806384f82c53146100f55780638da5cb5b14610108578063edc6640014610110578063f2fde38b14610139578063f371b2161461014c575b600080fd5b61009b61009636600461161e565b610175565b6040516100a8919061163b565b60405180910390f35b61009b7f000000000000000000000000000000000000000000000000000000000000000081565b6100eb6100e636600461164f565b610375565b005b6100eb6103d2565b6100eb61010336600461164f565b61040d565b61009b61046a565b61009b61011e36600461161e565b6002602052600090815260409020546001600160a01b031681565b6100eb61014736600461161e565b610479565b61009b61015a36600461161e565b6001602052600090815260409020546001600160a01b031681565b6001600160a01b03808216600090815260016020526040812054909116801561021057806001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b1580156101d157600080fd5b505afa1580156101e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102099190611688565b9392505050565b506001600160a01b038083166000908152600260205260409020541680156102385792915050565b61024183610519565b90506001600160a01b038116156102585792915050565b610261836112b7565b90506001600160a01b038116156102785792915050565b604051630a57ebcf60e11b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906314afd79e906102c490869060040161163b565b60206040518083038186803b1580156102dc57600080fd5b505afa1580156102f0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103149190611688565b90506001600160a01b03811661036f5760405162461bcd60e51b815260206004820152601b60248201527a21b7bab632103737ba103932b9b7b63b32903a34329037bbb732b960291b60448201526064015b60405180910390fd5b92915050565b3361037e61046a565b6001600160a01b0316146103a45760405162461bcd60e51b8152600401610366906116a5565b6001600160a01b03918216600090815260016020526040902080546001600160a01b03191691909216179055565b336103db61046a565b6001600160a01b0316146104015760405162461bcd60e51b8152600401610366906116a5565b61040b60006115b9565b565b3361041661046a565b6001600160a01b03161461043c5760405162461bcd60e51b8152600401610366906116a5565b6001600160a01b03918216600090815260026020526040902080546001600160a01b03191691909216179055565b6000546001600160a01b031690565b3361048261046a565b6001600160a01b0316146104a85760405162461bcd60e51b8152600401610366906116a5565b6001600160a01b03811661050d5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610366565b610516816115b9565b50565b600073855a951162b1b93d70724484d5bdc9d00b56236b6001600160a01b03831614156105c55773fadbf758307a054c57b365db1de90aca71feafe56001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b505afa1580156105a1573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061036f9190611688565b73f1de9490bf7298b5f350ce74332ad7cf8d5cb1816001600160a01b03831614156106375773aed493aaf3e76e83b29e151848b71ef4544f92f16001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b7339d2acbcd80d80080541c6eed7e9febb8127b2ab6001600160a01b03831614156106a95773a2fa09d6f8c251422f5fde29a0bad1c53defae666001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73d66cae89ffbc6e50e6b019e45c1aec93dec547816001600160a01b038316141561071b5773306309f9d105f34132db0bfb3ce3f5b0245cd3866001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b732ebe08379f4fd866e871a9b9e1d5c695154c6a9f6001600160a01b038316141561078d5773d00c0d43b747c33726b3f0ff4bda4b72dc53c6e96001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73a97c34278162b556a527cfc01b53eb4ddedfd2236001600160a01b03831614156107ff5773b3e967355c456b1bd43cb0188a321592d410d0966001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b736c76d49322c9f8761a1623ced89a31490cdb649d6001600160a01b03831614156108715773b3e967355c456b1bd43cb0188a321592d410d0966001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b734a41c7a884d119eaaefe471d0b3a6382264083826001600160a01b03831614156108e35773cdf3d216d82a463ce82971f2f5da3d8f9c5f093a6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739c06feb7ebc8065ee11cd5e8eedaafb2909a70876001600160a01b0383161415610955577345119cd98d145283762ba9ebcaea75f72d1887336001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739bd818ab6acc974f2cf2bd2eba7a250126accb9f6001600160a01b03831614156109c757736e535043377067621954ee84065b0bd7357e7aba6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b731d803c89760f8b4057db15bcb3b8929e0498d3106001600160a01b0383161415610a395773b3e967355c456b1bd43cb0188a321592d410d0966001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733101927deec27a2bfa6c4a6316e3a221f631db916001600160a01b0383161415610aab5773178bf1946fed0e2362fdf8bcd3f91f0701a012c66001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739d9b187e478bc62694a7bed216fc365de87f280c6001600160a01b0383161415610b1d5773fbad17cfad6cb00d726c65501d69fdc13ca5477c6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73d977144724bc77faefae219f958ae3947205d0b56001600160a01b0383161415610b8f5773087b442bfd4e42675cf2df5fa566f87d7a96fb126001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73045e511f53debf55c9c0b4522f14f602f7c7ca816001600160a01b0383161415610c015773fcfe8c036c414a15cf871071c483687095caf7d66001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733dd301b3c96a282d8092e1e6f6846f24172d45c16001600160a01b0383161415610c735773b5bdd2d9b3541fc8f581af37430d26527e59aef86001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b735d84deb482e770479154028788df79aa7c563aa46001600160a01b0383161415610ce557739d1a179c469a8bdd0b683a9f9250246cc47e8fbe6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b731df927b69a97e8140315536163c029d188e8573b6001600160a01b0383161415610d575773b5bdd2d9b3541fc8f581af37430d26527e59aef86001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73617dace069fbd41993491de211b4dfccdacbd3486001600160a01b0383161415610dc95773b5bdd2d9b3541fc8f581af37430d26527e59aef86001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73650a9ed18df873cad98c88dcac8170531cad23996001600160a01b0383161415610e3b57731df7324a3ad20526dfa02cc803ed2d97cac81f3b6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b7307c9a8f8264221906b7b8958951ce4753d39628b6001600160a01b0383161415610ead5773305d12b4d70529cd618da7399f5520701e5100416001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b7363eb4c3dd0751f9be7070a01156513c227fa1ef66001600160a01b0383161415610f1f5773306309f9d105f34132db0bfb3ce3f5b0245cd3866001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73c6349eec31048787676b6297ba71721376a8ddcf6001600160a01b0383161415610f915773ac1a985e75c6a0b475b9c807ad0705a988be2d996001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733b945f9c0c8737e44f8e887d4f04b5b3a491ac4d6001600160a01b0383161415611003577382e17477726e8d9d2c237745ca9989631582ee986001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73f35343299a4f80dd5d917bbe5ddd54ebb820ebd46001600160a01b03831614156110755773cc88c15506251b62ccceeba193e100d6bbc9a30d6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b733b9e5ae72d068448bb96786989c0d86fbc0551d16001600160a01b03831614156110e75773306309f9d105f34132db0bfb3ce3f5b0245cd3866001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73b2d53be158cb8451dfc818bd969877038c1bdea16001600160a01b03831614156111595773ae55e3800f0a3feafdce535a8c0fab0ffdb90dee6001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73f6dbf7afe05b8bb6f198ec7e69333c98d3c4608c6001600160a01b03831614156111cb5773bb8d24a20c20625f86739824014c3cbaaab267006001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b73b62fc1adffb2ab832041528c8178358338d85f766001600160a01b038316141561123d57739ed98fd1c29018b9342cb8f57a3073b9695f0c026001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b739bc8d30d971c9e74298112803036c05db07d73e36001600160a01b03831614156112af577366beda757939f8e505b5eb883cd02c8d4a11bca26001600160a01b031663d5f520766040518163ffffffff1660e01b815260040160206040518083038186803b15801561058d57600080fd5b506000919050565b6000731147ccfb4aefc6e587a23b78724ef20ec6e474d46001600160a01b03831614156112f95750733fb49da4375ef9019f17990d04c6d5dad482d80a919050565b734c21541f95a00c03c75f38c71dc220bd27cbbed96001600160a01b0383161415611339575073c897cfee43a8d827f76d4226994d5ce5ebbe2571919050565b737e6332d18719a5463d3867a1a892359509589a3d6001600160a01b03831614156113795750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b738bd660a764ca14155f3411a4526a028b6316cb3e6001600160a01b03831614156113b9575073f6f372dfaecc1431186598c304e91b79ce115766919050565b734f4f0d0dfd93513b3f4cb116fe9d0a005466f7256001600160a01b03831614156113f95750738b055ac1c4dd287e2a46d4a52d61fe76fb551bd0919050565b731df0250027fec876d8876d1ac7a392c9098f1a1e6001600160a01b0383161415611439575073e408ffa969707ce5d7aa3e5f8d44674fa4b26219919050565b73860ef3f83b6adfef757f98345c3b8ddcfca9d1526001600160a01b038316141561147957507308a3633aab8f3e436dea204288ee26fe094406b0919050565b73e3a2d16da142e6b190a5d9f7e0c07cc460b58a5f6001600160a01b03831614156114b9575073875f8ffcdded63b5d8cf54be4e4b82fe6c6e249c919050565b73bde07f1ca107ef319b0bb26ebf1d0a5b4c97ffc16001600160a01b03831614156114f95750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b73e86181d6b672d78d33e83029ff3d0ef4a601b4c46001600160a01b03831614156115395750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b73b7c561e2069acae2c4480111b1606790bb4e13fe6001600160a01b03831614156115795750731578ed833d986c1188d1a998aa5fecd418bef5da919050565b73526c013f8382b050d32d86e7090ac84de22eda4d6001600160a01b03831614156112af57507361c6e5ddacded540cd08066c08cbc096d22d91f4919050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b038116811461051657600080fd5b60006020828403121561163057600080fd5b813561020981611609565b6001600160a01b0391909116815260200190565b6000806040838503121561166257600080fd5b823561166d81611609565b9150602083013561167d81611609565b809150509250929050565b60006020828403121561169a57600080fd5b815161020981611609565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260408201526060019056fea2646970667358221220c0ac96c7f48a68918e7d689e73ff0792db6f94354c2b8641f039b4f5ed22036c64736f6c63430008090033", - "devdoc": { - "kind": "dev", - "methods": { - "owner()": { - "details": "Returns the address of the current owner." - }, - "renounceOwnership()": { - "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." - }, - "transferOwnership(address)": { - "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." - } - }, - "title": "KEEP stake owner resolver", - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": { - "resolveOwner(address)": { - "notice": "Resolves KEEP stake owner for the provided operator address. Reverts if could not resolve the owner." - }, - "setGrantee(address,address)": { - "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." - }, - "setManagedGrant(address,address)": { - "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." - } - }, - "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 staking contract in T staking contract, it fitst 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.", - "version": 1 - }, - "storageLayout": { - "storage": [ - { - "astId": 1491, - "contract": "contracts/staking/KeepStake.sol:KeepStake", - "label": "_owner", - "offset": 0, - "slot": "0", - "type": "t_address" - }, - { - "astId": 11750, - "contract": "contracts/staking/KeepStake.sol:KeepStake", - "label": "operatorToManagedGrant", - "offset": 0, - "slot": "1", - "type": "t_mapping(t_address,t_address)" - }, - { - "astId": 11754, - "contract": "contracts/staking/KeepStake.sol:KeepStake", - "label": "operatorToGrantee", - "offset": 0, - "slot": "2", - "type": "t_mapping(t_address,t_address)" - } - ], - "types": { - "t_address": { - "encoding": "inplace", - "label": "address", - "numberOfBytes": "20" - }, - "t_mapping(t_address,t_address)": { - "encoding": "mapping", - "key": "t_address", - "label": "mapping(address => address)", - "numberOfBytes": "32", - "value": "t_address" - } - } - } -} \ No newline at end of file diff --git a/deployments/mainnet/KeepToken.json b/deployments/mainnet/KeepToken.json deleted file mode 100644 index 506d8200..00000000 --- a/deployments/mainnet/KeepToken.json +++ /dev/null @@ -1,430 +0,0 @@ -{ - "address": "0x85Eee30c52B0b379b046Fb0F85F4f3Dc3009aFEC", - "abi": [ - { - "inputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "constant": true, - "inputs": [], - "name": "DECIMALS", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "INITIAL_SUPPLY", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "NAME", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "SYMBOL", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_extraData", - "type": "bytes" - } - ], - "name": "approveAndCall", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "burnFrom", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } - ], - "numDeployments": 2 -} \ No newline at end of file diff --git a/deployments/mainnet/KeepTokenStaking.json b/deployments/mainnet/KeepTokenStaking.json deleted file mode 100644 index 82ffbbe2..00000000 --- a/deployments/mainnet/KeepTokenStaking.json +++ /dev/null @@ -1,915 +0,0 @@ -{ - "address": "0x1293a54e160D1cd7075487898d65266081A15458", - "abi": [ - { - "inputs": [ - { - "internalType": "contract ERC20Burnable", - "name": "_token", - "type": "address" - }, - { - "internalType": "contract TokenGrant", - "name": "_tokenGrant", - "type": "address" - }, - { - "internalType": "contract TokenStakingEscrow", - "name": "_escrow", - "type": "address" - }, - { - "internalType": "contract KeepRegistry", - "name": "_registry", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_initializationPeriod", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "lockCreator", - "type": "address" - } - ], - "name": "ExpiredLockReleased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "lockCreator", - "type": "address" - } - ], - "name": "LockReleased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "beneficiary", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "authorizer", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "OperatorStaked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "RecoveredStake", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "StakeDelegated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "lockCreator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "until", - "type": "uint256" - } - ], - "name": "StakeLocked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "StakeOwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "TokensSeized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "TokensSlashed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newAmount", - "type": "uint256" - } - ], - "name": "TopUpCompleted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "topUp", - "type": "uint256" - } - ], - "name": "TopUpInitiated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "undelegatedAt", - "type": "uint256" - } - ], - "name": "Undelegated", - "type": "event" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "address", - "name": "_operatorContract", - "type": "address" - } - ], - "name": "activeStake", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "address", - "name": "_operatorContract", - "type": "address" - } - ], - "name": "authorizeOperatorContract", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "authorizerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_address", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "beneficiaryOf", - "outputs": [ - { - "internalType": "address payable", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "cancelStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "delegatedAuthoritySource", - "type": "address" - } - ], - "name": "claimDelegatedAuthority", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "commitTopUp", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "deployedAt", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "address", - "name": "_operatorContract", - "type": "address" - } - ], - "name": "eligibleStake", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "operatorContract", - "type": "address" - } - ], - "name": "getAuthoritySource", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "getDelegationInfo", - "outputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "createdAt", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "undelegatedAt", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "getLocks", - "outputs": [ - { - "internalType": "address[]", - "name": "creators", - "type": "address[]" - }, - { - "internalType": "uint256[]", - "name": "expirations", - "type": "uint256[]" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "internalType": "address", - "name": "operatorContract", - "type": "address" - } - ], - "name": "hasMinimumStake", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "initializationPeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operatorContract", - "type": "address" - } - ], - "name": "isApprovedOperatorContract", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "address", - "name": "_operatorContract", - "type": "address" - } - ], - "name": "isAuthorizedForOperator", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isStakeLocked", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "uint256", - "name": "duration", - "type": "uint256" - } - ], - "name": "lockStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "minimumStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "bytes", - "name": "_extraData", - "type": "bytes" - } - ], - "name": "receiveApproval", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "recoverStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "address", - "name": "operatorContract", - "type": "address" - } - ], - "name": "releaseExpiredLock", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "amountToSeize", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "rewardMultiplier", - "type": "uint256" - }, - { - "internalType": "address", - "name": "tattletale", - "type": "address" - }, - { - "internalType": "address[]", - "name": "misbehavedOperators", - "type": "address[]" - } - ], - "name": "seize", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "amountToSlash", - "type": "uint256" - }, - { - "internalType": "address[]", - "name": "misbehavedOperators", - "type": "address[]" - } - ], - "name": "slash", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferStakeOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "undelegate", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_undelegationTimestamp", - "type": "uint256" - } - ], - "name": "undelegateAt", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "undelegationPeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "unlockStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } - ] -} diff --git a/deployments/mainnet/NuCypherStakingEscrow.json b/deployments/mainnet/NuCypherStakingEscrow.json deleted file mode 100644 index eeb4298a..00000000 --- a/deployments/mainnet/NuCypherStakingEscrow.json +++ /dev/null @@ -1,1897 +0,0 @@ -{ - "address": "0xbbD3C0C794F40c4f993B03F65343aCC6fcfCb2e2", - "abi": [ - { - "inputs": [ - { - "internalType": "contract NuCypherToken", - "name": "_token", - "type": "address" - }, - { - "internalType": "contract PolicyManagerInterface", - "name": "_policyManager", - "type": "address" - }, - { - "internalType": "contract AdjudicatorInterface", - "name": "_adjudicator", - "type": "address" - }, - { - "internalType": "contract WorkLockInterface", - "name": "_workLock", - "type": "address" - }, - { - "internalType": "uint32", - "name": "_genesisHoursPerPeriod", - "type": "uint32" - }, - { - "internalType": "uint32", - "name": "_hoursPerPeriod", - "type": "uint32" - }, - { - "internalType": "uint256", - "name": "_issuanceDecayCoefficient", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_lockDurationCoefficient1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_lockDurationCoefficient2", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_maximumRewardedPeriods", - "type": "uint16" - }, - { - "internalType": "uint256", - "name": "_firstPhaseTotalSupply", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_firstPhaseMaxIssuance", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_minLockedPeriods", - "type": "uint16" - }, - { - "internalType": "uint256", - "name": "_minAllowableLockedTokens", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxAllowableLockedTokens", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_minWorkerPeriods", - "type": "uint16" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint16", - "name": "period", - "type": "uint16" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "CommitmentMade", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "periods", - "type": "uint16" - } - ], - "name": "Deposited", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "oldValue", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "lastPeriod", - "type": "uint16" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newValue", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "periods", - "type": "uint16" - } - ], - "name": "Divided", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Donated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "reservedReward", - "type": "uint256" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "firstPeriod", - "type": "uint16" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "periods", - "type": "uint16" - } - ], - "name": "Locked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value1", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value2", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "lastPeriod", - "type": "uint16" - } - ], - "name": "Merged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint16", - "name": "period", - "type": "uint16" - } - ], - "name": "Migrated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint16", - "name": "period", - "type": "uint16" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Minted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "lastPeriod", - "type": "uint16" - }, - { - "indexed": false, - "internalType": "uint16", - "name": "periods", - "type": "uint16" - } - ], - "name": "Prolonged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "reStake", - "type": "bool" - } - ], - "name": "ReStakeSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "penalty", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "investigator", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "reward", - "type": "uint256" - } - ], - "name": "Slashed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "snapshotsEnabled", - "type": "bool" - } - ], - "name": "SnapshotSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "testTarget", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "StateVerified", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "UpgradeFinished", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "windDown", - "type": "bool" - } - ], - "name": "WindDownSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Withdrawn", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "measureWork", - "type": "bool" - } - ], - "name": "WorkMeasurementSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "worker", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint16", - "name": "startPeriod", - "type": "uint16" - } - ], - "name": "WorkerBonded", - "type": "event" - }, - { - "inputs": [], - "name": "MAX_SUB_STAKES", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "adjudicator", - "outputs": [ - { - "internalType": "contract AdjudicatorInterface", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "balanceHistory", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_worker", - "type": "address" - } - ], - "name": "bondWorker", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "commitToNextPeriod", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "currentMintingPeriod", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "currentPeriodSupply", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_unlockingDuration", - "type": "uint16" - } - ], - "name": "deposit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "depositAndIncrease", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_unlockingDuration", - "type": "uint16" - } - ], - "name": "depositFromWorkLock", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_newValue", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_additionalDuration", - "type": "uint16" - } - ], - "name": "divideStake", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "donate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint16", - "name": "_period", - "type": "uint16" - } - ], - "name": "findIndexOfPastDowntime", - "outputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_target", - "type": "address" - } - ], - "name": "finishUpgrade", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "firstPhaseMaxIssuance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "firstPhaseTotalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "genesisSecondsPerPeriod", - "outputs": [ - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "_offsetPeriods", - "type": "uint16" - }, - { - "internalType": "uint256", - "name": "_startIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_maxStakers", - "type": "uint256" - } - ], - "name": "getActiveStakers", - "outputs": [ - { - "internalType": "uint256", - "name": "allLockedTokens", - "type": "uint256" - }, - { - "internalType": "uint256[2][]", - "name": "activeStakers", - "type": "uint256[2][]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "getAllTokens", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "getCompletedWork", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentPeriod", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "getFlags", - "outputs": [ - { - "internalType": "bool", - "name": "windDown", - "type": "bool" - }, - { - "internalType": "bool", - "name": "reStake", - "type": "bool" - }, - { - "internalType": "bool", - "name": "measureWork", - "type": "bool" - }, - { - "internalType": "bool", - "name": "snapshots", - "type": "bool" - }, - { - "internalType": "bool", - "name": "migrated", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "getLastCommittedPeriod", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "getLastPeriodOfSubStake", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint16", - "name": "_offsetPeriods", - "type": "uint16" - } - ], - "name": "getLockedTokens", - "outputs": [ - { - "internalType": "uint256", - "name": "lockedValue", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "getPastDowntime", - "outputs": [ - { - "internalType": "uint16", - "name": "startPeriod", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "endPeriod", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "getPastDowntimeLength", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getReservedReward", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getStakersLength", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - } - ], - "name": "getSubStakeInfo", - "outputs": [ - { - "internalType": "uint16", - "name": "firstPeriod", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "lastPeriod", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "unlockingDuration", - "type": "uint16" - }, - { - "internalType": "uint128", - "name": "lockedValue", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "getSubStakesLength", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "getWorkerFromStaker", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_reservedReward", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_sourceOfFunds", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "isOwner", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "isUpgrade", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_unlockingDuration", - "type": "uint16" - } - ], - "name": "lockAndCreate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "lockAndIncrease", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "lockDurationCoefficient1", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lockDurationCoefficient2", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "_period", - "type": "uint16" - } - ], - "name": "lockedPerPeriod", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maxAllowableLockedTokens", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "maximumRewardedPeriods", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_index2", - "type": "uint256" - } - ], - "name": "mergeStake", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - } - ], - "name": "migrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "minAllowableLockedTokens", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minLockedPeriods", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minWorkerPeriods", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "mintingCoefficient", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "policyManager", - "outputs": [ - { - "internalType": "contract PolicyManagerInterface", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "previousPeriodSupply", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "previousTarget", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_index", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "_additionalDuration", - "type": "uint16" - } - ], - "name": "prolongStake", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_tokenContract", - "type": "address" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "receiveApproval", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "_index", - "type": "uint16" - } - ], - "name": "removeUnusedSubStake", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "secondsPerPeriod", - "outputs": [ - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "_reStake", - "type": "bool" - } - ], - "name": "setReStake", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "_enableSnapshots", - "type": "bool" - } - ], - "name": "setSnapshots", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "_windDown", - "type": "bool" - } - ], - "name": "setWindDown", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "bool", - "name": "_measureWork", - "type": "bool" - } - ], - "name": "setWorkMeasurement", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_staker", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_penalty", - "type": "uint256" - }, - { - "internalType": "address", - "name": "_investigator", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_reward", - "type": "uint256" - } - ], - "name": "slashStaker", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "stakerFromWorker", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "stakerInfo", - "outputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "currentCommittedPeriod", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "nextCommittedPeriod", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "lastCommittedPeriod", - "type": "uint16" - }, - { - "internalType": "uint16", - "name": "stub1", - "type": "uint16" - }, - { - "internalType": "uint256", - "name": "completedWork", - "type": "uint256" - }, - { - "internalType": "uint16", - "name": "workerStartPeriod", - "type": "uint16" - }, - { - "internalType": "address", - "name": "worker", - "type": "address" - }, - { - "internalType": "uint256", - "name": "flags", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "reservedSlot1", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "reservedSlot2", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "reservedSlot3", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "reservedSlot4", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "reservedSlot5", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "stakers", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "supportsHistory", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "target", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "token", - "outputs": [ - { - "internalType": "contract NuCypherToken", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_blockNumber", - "type": "uint256" - } - ], - "name": "totalStakedAt", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_blockNumber", - "type": "uint256" - } - ], - "name": "totalStakedForAt", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_testTarget", - "type": "address" - } - ], - "name": "verifyState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_value", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "workLock", - "outputs": [ - { - "internalType": "contract WorkLockInterface", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "numDeployments": 2 -} \ No newline at end of file diff --git a/deployments/mainnet/VendingMachineKeep.json b/deployments/mainnet/VendingMachineKeep.json deleted file mode 100644 index c9eed6d4..00000000 --- a/deployments/mainnet/VendingMachineKeep.json +++ /dev/null @@ -1,400 +0,0 @@ -{ - "address": "0xE47c80e8c23f6B4A1aE41c34837a0599D5D16bb0", - "abi": [ - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "_wrappedToken", - "type": "address" - }, - { - "internalType": "contract T", - "name": "_tToken", - "type": "address" - }, - { - "internalType": "uint96", - "name": "_wrappedTokenAllocation", - "type": "uint96" - }, - { - "internalType": "uint96", - "name": "_tTokenAllocation", - "type": "uint96" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "tTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "wrappedTokenAmount", - "type": "uint256" - } - ], - "name": "Unwrapped", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "wrappedTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "tTokenAmount", - "type": "uint256" - } - ], - "name": "Wrapped", - "type": "event" - }, - { - "inputs": [], - "name": "FLOATING_POINT_DIVISOR", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "WRAPPED_TOKEN_CONVERSION_PRECISION", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "conversionFromT", - "outputs": [ - { - "internalType": "uint256", - "name": "wrappedAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "tRemainder", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "conversionToT", - "outputs": [ - { - "internalType": "uint256", - "name": "tAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "wrappedRemainder", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ratio", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "receiveApproval", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "tToken", - "outputs": [ - { - "internalType": "contract T", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "unwrap", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "wrap", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "wrappedBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "wrappedToken", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "transactionHash": "0x53e880961e210ed0f124005cb314e624b5a951c599db9c153aeba2123dce94f5", - "receipt": { - "to": null, - "from": "0x123694886DBf5Ac94DDA07135349534536D14cAf", - "contractAddress": "0xE47c80e8c23f6B4A1aE41c34837a0599D5D16bb0", - "transactionIndex": 45, - "gasUsed": "805864", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x78101893fcad87bd948c9f6687bc9da5899360f4573075693567121ec749eb18", - "transactionHash": "0x53e880961e210ed0f124005cb314e624b5a951c599db9c153aeba2123dce94f5", - "logs": [], - "blockNumber": 13912439, - "cumulativeGasUsed": "2337985", - "status": 1, - "byzantium": true - }, - "args": [ - "0x85Eee30c52B0b379b046Fb0F85F4f3Dc3009aFEC", - "0xCdF7028ceAB81fA0C6971208e83fa7872994beE5", - "940795010800000000000000000", - "4500000000000000000000000000" - ], - "solcInputHash": "43eb8b17fe7c2ce0e21167459a2d9d30", - "metadata": "{\"compiler\":{\"version\":\"0.8.9+commit.e5eed63a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"_wrappedToken\",\"type\":\"address\"},{\"internalType\":\"contract T\",\"name\":\"_tToken\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"_wrappedTokenAllocation\",\"type\":\"uint96\"},{\"internalType\":\"uint96\",\"name\":\"_tTokenAllocation\",\"type\":\"uint96\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"tTokenAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wrappedTokenAmount\",\"type\":\"uint256\"}],\"name\":\"Unwrapped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wrappedTokenAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"tTokenAmount\",\"type\":\"uint256\"}],\"name\":\"Wrapped\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"FLOATING_POINT_DIVISOR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"WRAPPED_TOKEN_CONVERSION_PRECISION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"conversionFromT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"wrappedAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"tRemainder\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"conversionToT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"tAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"wrappedRemainder\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ratio\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"receiveApproval\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tToken\",\"outputs\":[{\"internalType\":\"contract T\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unwrap\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"wrap\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"wrappedBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wrappedToken\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"constructor\":{\"details\":\"Multiplications in this contract can't overflow uint256 as we restrict `_wrappedTokenAllocation` and `_tTokenAllocation` to 96 bits and FLOATING_POINT_DIVISOR fits in less than 60 bits.\",\"params\":{\"_tToken\":\"Address of T token\",\"_tTokenAllocation\":\"The allocation of T this instance of Vending Machine will receive\",\"_wrappedToken\":\"Address to ERC20 token that will be wrapped to T\",\"_wrappedTokenAllocation\":\"The total supply of the token that will be wrapped to T\"}},\"receiveApproval(address,uint256,address,bytes)\":{\"params\":{\"amount\":\"The amount of KEEP/NU to be wrapped\",\"from\":\"Caller's address, must be the same as `wrappedToken` field\",\"token\":\"Token's address, must be the same as `wrappedToken` field\"}},\"unwrap(uint256)\":{\"params\":{\"amount\":\"The amount of T to unwrap back to the collateral (KEEP/NU)\"}},\"wrap(uint256)\":{\"params\":{\"amount\":\"The amount of KEEP/NU to be wrapped\"}}},\"title\":\"T token vending machine\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"FLOATING_POINT_DIVISOR()\":{\"notice\":\"Divisor for precision purposes, used to represent fractions.\"},\"WRAPPED_TOKEN_CONVERSION_PRECISION()\":{\"notice\":\"Number of decimal places of precision in conversion to/from wrapped tokens (assuming typical ERC20 token with 18 decimals). This implies that amounts of wrapped tokens below this precision won't take part in the conversion. E.g., for a value of 3, then for a conversion of 1.123456789 wrapped tokens, only 1.123 is convertible (i.e., 3 decimal places), and 0.000456789 is left.\"},\"constructor\":{\"notice\":\"Sets the reference to `wrappedToken` and `tToken`. Initializes conversion `ratio` between wrapped token and T based on the provided `_tTokenAllocation` and `_wrappedTokenAllocation`.\"},\"conversionFromT(uint256)\":{\"notice\":\"The amount of wrapped tokens (KEEP/NU) that's obtained from `amount` T tokens, and the remainder that can't be downgraded.\"},\"conversionToT(uint256)\":{\"notice\":\"Returns the T token amount that's obtained from `amount` wrapped tokens (KEEP/NU), and the remainder that can't be upgraded.\"},\"ratio()\":{\"notice\":\"The ratio with which T token is converted based on the provided token being wrapped (KEEP/NU), expressed in 1e18 precision. When wrapping: x [T] = amount [KEEP/NU] * ratio / FLOATING_POINT_DIVISOR When unwrapping: x [KEEP/NU] = amount [T] * FLOATING_POINT_DIVISOR / ratio\"},\"receiveApproval(address,uint256,address,bytes)\":{\"notice\":\"Wraps up to the given amount of the token (KEEP/NU) and releases T token proportionally to the amount being wrapped with respect to the wrap ratio. This is a shortcut to `wrap` function that avoids a separate approval transaction. Only KEEP/NU token is allowed as a caller, so please call this function via token's `approveAndCall`.\"},\"tToken()\":{\"notice\":\"T token contract.\"},\"unwrap(uint256)\":{\"notice\":\"Unwraps up to the given `amount` of T back to the legacy token (KEEP/NU) according to the wrap ratio. It can only be called by a token holder who previously wrapped their tokens in this vending machine contract. The token holder can't unwrap more tokens than they originally wrapped. The token holder needs to have at least the given amount of T tokens approved to transfer to the Vending Machine before calling this function.\"},\"wrap(uint256)\":{\"notice\":\"Wraps up to the the given `amount` of the token (KEEP/NU) and releases T token proportionally to the amount being wrapped with respect to the wrap ratio. The token holder needs to have at least the given amount of the wrapped token (KEEP/NU) approved to transfer to the Vending Machine before calling this function.\"},\"wrappedBalance(address)\":{\"notice\":\"The total balance of wrapped tokens for the given holder account. Only holders that have previously wrapped KEEP/NU to T can unwrap, up to the amount previously wrapped.\"},\"wrappedToken()\":{\"notice\":\"The token being wrapped to T (KEEP/NU).\"}},\"notice\":\"Contract implements a special update protocol to enable KEEP/NU token holders to wrap their tokens and obtain T tokens according to a fixed ratio. This will go on indefinitely and enable NU and KEEP token holders to join T network without needing to buy or sell any assets. Logistically, anyone holding NU or KEEP can wrap those assets in order to upgrade to T. They can also unwrap T in order to downgrade back to the underlying asset. There is a separate instance of this contract deployed for KEEP holders and a separate instance of this contract deployed for NU holders.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/vending/VendingMachine.sol\":\"VendingMachine\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":100},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xa1b27b3f44ff825974e5268e8f63ad3b03add5b464880d860fbb8cae043e17f7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0xc1452b054778f1926419196ef12ae200758a4ee728df69ae1cd13e5c16ca7df7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x842c66d5965ed0bf77f274732c2a93a7e2223d53171ec9cccc473bde75104ead\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0x671741933530f343f023a40e58e61bc09d62494b96c6f3e39e647f315facd519\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (token/ERC721/IERC721.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n}\\n\",\"keccak256\":\"0x872ba21af7c1f0ae04a715beca31e8fcac764d6c8762940b0fe1bfb6ed8e86f4\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (utils/Address.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9944d1038f27dcebff810d7ba16b3b8058b967173d76874fb72dd7cd84129656\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0x7736c187e6f1358c1ea9350a2a21aa8528dec1c2f43b374a9067465a3a51f5d3\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x5fa25f305839292fab713256214f2868e0257d29826b14282bbd7f1e34f5af38\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (utils/cryptography/ECDSA.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Strings.sol\\\";\\n\\n/**\\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\\n *\\n * These functions can be used to verify that a message was signed by the holder\\n * of the private keys of a given address.\\n */\\nlibrary ECDSA {\\n enum RecoverError {\\n NoError,\\n InvalidSignature,\\n InvalidSignatureLength,\\n InvalidSignatureS,\\n InvalidSignatureV\\n }\\n\\n function _throwError(RecoverError error) private pure {\\n if (error == RecoverError.NoError) {\\n return; // no error: do nothing\\n } else if (error == RecoverError.InvalidSignature) {\\n revert(\\\"ECDSA: invalid signature\\\");\\n } else if (error == RecoverError.InvalidSignatureLength) {\\n revert(\\\"ECDSA: invalid signature length\\\");\\n } else if (error == RecoverError.InvalidSignatureS) {\\n revert(\\\"ECDSA: invalid signature 's' value\\\");\\n } else if (error == RecoverError.InvalidSignatureV) {\\n revert(\\\"ECDSA: invalid signature 'v' value\\\");\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature` or error string. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n *\\n * Documentation for signature generation:\\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\\n // Check the signature length\\n // - case 65: r,s,v signature (standard)\\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\\n if (signature.length == 65) {\\n bytes32 r;\\n bytes32 s;\\n uint8 v;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n s := mload(add(signature, 0x40))\\n v := byte(0, mload(add(signature, 0x60)))\\n }\\n return tryRecover(hash, v, r, s);\\n } else if (signature.length == 64) {\\n bytes32 r;\\n bytes32 vs;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n vs := mload(add(signature, 0x40))\\n }\\n return tryRecover(hash, r, vs);\\n } else {\\n return (address(0), RecoverError.InvalidSignatureLength);\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature`. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n */\\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, signature);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\\n *\\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address, RecoverError) {\\n bytes32 s;\\n uint8 v;\\n assembly {\\n s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)\\n v := add(shr(255, vs), 27)\\n }\\n return tryRecover(hash, v, r, s);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\\n *\\n * _Available since v4.2._\\n */\\n function recover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address, RecoverError) {\\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\\n // the valid range for s in (301): 0 < s < secp256k1n \\u00f7 2 + 1, and for v in (302): v \\u2208 {27, 28}. Most\\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\\n //\\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\\n // these malleable signatures as well.\\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\\n return (address(0), RecoverError.InvalidSignatureS);\\n }\\n if (v != 27 && v != 28) {\\n return (address(0), RecoverError.InvalidSignatureV);\\n }\\n\\n // If the signature is valid (and not malleable), return the signer address\\n address signer = ecrecover(hash, v, r, s);\\n if (signer == address(0)) {\\n return (address(0), RecoverError.InvalidSignature);\\n }\\n\\n return (signer, RecoverError.NoError);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n */\\n function recover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", hash));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from `s`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n\\\", Strings.toString(s.length), s));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Typed Data, created from a\\n * `domainSeparator` and a `structHash`. This produces hash corresponding\\n * to the one signed with the\\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\\n * JSON-RPC method as part of EIP-712.\\n *\\n * See {recover}.\\n */\\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19\\\\x01\\\", domainSeparator, structHash));\\n }\\n}\\n\",\"keccak256\":\"0x594efd2fa154f4fbe0fa92c2356cb2a9531ef3902e35784c2bc69764d0d8886a\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x6aa521718bf139b44ce56f194f6aea1d590cacef995b5a84703fb1579fa49be9\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/Math.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (utils/math/Math.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Standard math utilities missing in the Solidity language.\\n */\\nlibrary Math {\\n /**\\n * @dev Returns the largest of two numbers.\\n */\\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a >= b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the smallest of two numbers.\\n */\\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a < b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the average of two numbers. The result is rounded towards\\n * zero.\\n */\\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\\n // (a + b) / 2 can overflow.\\n return (a & b) + (a ^ b) / 2;\\n }\\n\\n /**\\n * @dev Returns the ceiling of the division of two numbers.\\n *\\n * This differs from standard division with `/` in that it rounds up instead\\n * of rounding down.\\n */\\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\\n // (a + b - 1) / b can overflow on addition, so we distribute.\\n return a / b + (a % b == 0 ? 0 : 1);\\n }\\n}\\n\",\"keccak256\":\"0xe936fc79332de2ca7b1c06a70f81345aa2466958aab00f463e312ca0585e85cf\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SafeCast.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.0 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCast {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0x47c0131bd8a972c31596958aa86752ea18d60e33f1cd94d412b9e29fd6ab25a6\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IERC20WithPermit.sol\\\";\\nimport \\\"./IReceiveApproval.sol\\\";\\n\\n/// @title ERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ncontract ERC20WithPermit is IERC20WithPermit, Ownable {\\n /// @notice The amount of tokens owned by the given account.\\n mapping(address => uint256) public override balanceOf;\\n\\n /// @notice The remaining number of tokens that spender will be\\n /// allowed to spend on behalf of owner through `transferFrom` and\\n /// `burnFrom`. This is zero by default.\\n mapping(address => mapping(address => uint256)) public override allowance;\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n mapping(address => uint256) public override nonce;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n bytes32 public constant override PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n /// @notice The amount of tokens in existence.\\n uint256 public override totalSupply;\\n\\n /// @notice The name of the token.\\n string public override name;\\n\\n /// @notice The symbol of the token.\\n string public override symbol;\\n\\n /// @notice The decimals places of the token.\\n uint8 public constant override decimals = 18;\\n\\n constructor(string memory _name, string memory _symbol) {\\n name = _name;\\n symbol = _symbol;\\n\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Moves `amount` tokens from the caller's account to `recipient`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n function transfer(address recipient, uint256 amount)\\n external\\n override\\n returns (bool)\\n {\\n _transfer(msg.sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice Moves `amount` tokens from `spender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `spender` and `recipient` cannot be the zero address,\\n /// - `spender` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `spender`'s tokens of at least\\n /// `amount`.\\n function transferFrom(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) external override returns (bool) {\\n uint256 currentAllowance = allowance[spender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n _approve(spender, msg.sender, currentAllowance - amount);\\n }\\n _transfer(spender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferFrom` and `burnFrom` will\\n /// not reduce an allowance.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external override {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonce[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approve(owner, spender, amount);\\n }\\n\\n /// @notice Creates `amount` tokens and assigns them to `account`,\\n /// increasing the total supply.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address.\\n function mint(address recipient, uint256 amount) external onlyOwner {\\n require(recipient != address(0), \\\"Mint to the zero address\\\");\\n\\n beforeTokenTransfer(address(0), recipient, amount);\\n\\n totalSupply += amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(address(0), recipient, amount);\\n }\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n /// @dev Requirements:\\n /// - the caller must have a balance of at least `amount`.\\n function burn(uint256 amount) external override {\\n _burn(msg.sender, amount);\\n }\\n\\n /// @notice Destroys `amount` of tokens from `account` using the allowance\\n /// mechanism. `amount` is then deducted from the caller's allowance\\n /// unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `account` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `account`'s tokens of at least\\n /// `amount`.\\n function burnFrom(address account, uint256 amount) external override {\\n uint256 currentAllowance = allowance[account][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Burn amount exceeds allowance\\\"\\n );\\n _approve(account, msg.sender, currentAllowance - amount);\\n }\\n _burn(account, amount);\\n }\\n\\n /// @notice Calls `receiveApproval` function on spender previously approving\\n /// the spender to withdraw from the caller multiple times, up to\\n /// the `amount` amount. If this function is called again, it\\n /// overwrites the current allowance with `amount`. Reverts if the\\n /// approval reverted or if `receiveApproval` call on the spender\\n /// reverted.\\n /// @return True if both approval and `receiveApproval` calls succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external override returns (bool) {\\n if (approve(spender, amount)) {\\n IReceiveApproval(spender).receiveApproval(\\n msg.sender,\\n amount,\\n address(this),\\n extraData\\n );\\n return true;\\n }\\n return false;\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// tokens.\\n /// @return True if the operation succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n /// Beware that changing an allowance with this method brings the risk\\n /// that someone may use both the old and the new allowance by\\n /// unfortunate transaction ordering. One possible solution to mitigate\\n /// this race condition is to first reduce the spender's allowance to 0\\n /// and set the desired value afterwards:\\n /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n function approve(address spender, uint256 amount)\\n public\\n override\\n returns (bool)\\n {\\n _approve(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view override returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n /// @dev Hook that is called before any transfer of tokens. This includes\\n /// minting and burning.\\n ///\\n /// Calling conditions:\\n /// - when `from` and `to` are both non-zero, `amount` of `from`'s tokens\\n /// will be to transferred to `to`.\\n /// - when `from` is zero, `amount` tokens will be minted for `to`.\\n /// - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n /// - `from` and `to` are never both zero.\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal virtual {}\\n\\n function _burn(address account, uint256 amount) internal {\\n uint256 currentBalance = balanceOf[account];\\n require(currentBalance >= amount, \\\"Burn amount exceeds balance\\\");\\n\\n beforeTokenTransfer(account, address(0), amount);\\n\\n balanceOf[account] = currentBalance - amount;\\n totalSupply -= amount;\\n emit Transfer(account, address(0), amount);\\n }\\n\\n function _transfer(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(spender != address(0), \\\"Transfer from the zero address\\\");\\n require(recipient != address(0), \\\"Transfer to the zero address\\\");\\n require(recipient != address(this), \\\"Transfer to the token address\\\");\\n\\n beforeTokenTransfer(spender, recipient, amount);\\n\\n uint256 spenderBalance = balanceOf[spender];\\n require(spenderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n balanceOf[spender] = spenderBalance - amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(spender, recipient, amount);\\n }\\n\\n function _approve(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(owner != address(0), \\\"Approve from the zero address\\\");\\n require(spender != address(0), \\\"Approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(name)),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x1e1bf4ec5c9d6fe70f6f834316482aeff3f122ff4ffaa7178099e7ae71a0b16d\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by tokens supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IApproveAndCall {\\n /// @notice Executes `receiveApproval` function on spender as specified in\\n /// `IReceiveApproval` interface. Approves spender to withdraw from\\n /// the caller multiple times, up to the `amount`. If this\\n /// function is called again, it overwrites the current allowance\\n /// with `amount`. Reverts if the approval reverted or if\\n /// `receiveApproval` call on the spender reverted.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x393d18ef81a57dcc96fff4c340cc2945deaebb37b9796c322cf2bc96872c3df8\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\nimport \\\"./IApproveAndCall.sol\\\";\\n\\n/// @title IERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ninterface IERC20WithPermit is IERC20, IERC20Metadata, IApproveAndCall {\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n function burn(uint256 amount) external;\\n\\n /// @notice Destroys `amount` of tokens from `account`, deducting the amount\\n /// from caller's allowance.\\n function burnFrom(address account, uint256 amount) external;\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n function nonce(address owner) external view returns (uint256);\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function PERMIT_TYPEHASH() external pure returns (bytes32);\\n}\\n\",\"keccak256\":\"0xdac9a5086c19a7128b505a7be1ab0ac1aa314f6989cb88d2417e9d7383f89fa9\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by contracts supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IReceiveApproval {\\n /// @notice Receives approval to spend tokens. Called as a result of\\n /// `approveAndCall` call on the token.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x6a30d83ad230548b1e7839737affc8489a035314209de14b89dbef7fb0f66395\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC721/IERC721.sol\\\";\\n\\n/// @title MisfundRecovery\\n/// @notice Allows the owner of the token contract extending MisfundRecovery\\n/// to recover any ERC20 and ERC721 sent mistakenly to the token\\n/// contract address.\\ncontract MisfundRecovery is Ownable {\\n using SafeERC20 for IERC20;\\n\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n}\\n\",\"keccak256\":\"0xbbfea02bf20e2a6df5a497bbc05c7540a3b7c7dfb8b1feeaffef7f6b8ba65d65\",\"license\":\"MIT\"},\"contracts/governance/Checkpoints.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-or-later\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.9;\\n\\nimport \\\"./IVotesHistory.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/math/Math.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\n\\n/// @title Checkpoints\\n/// @dev Abstract contract to support checkpoints for Compound-like voting and\\n/// delegation. This implementation supports token supply up to 2^96 - 1.\\n/// This contract keeps a history (checkpoints) of each account's vote\\n/// power. Vote power can be delegated either by calling the {delegate}\\n/// function directly, or by providing a signature to be used with\\n/// {delegateBySig}. Voting power can be publicly queried through\\n/// {getVotes} and {getPastVotes}.\\n/// NOTE: Extracted from OpenZeppelin ERCVotes.sol.\\nabstract contract Checkpoints is IVotesHistory {\\n struct Checkpoint {\\n uint32 fromBlock;\\n uint96 votes;\\n }\\n\\n // slither-disable-next-line uninitialized-state\\n mapping(address => address) internal _delegates;\\n mapping(address => uint128[]) internal _checkpoints;\\n uint128[] internal _totalSupplyCheckpoints;\\n\\n /// @notice Emitted when an account changes their delegate.\\n event DelegateChanged(\\n address indexed delegator,\\n address indexed fromDelegate,\\n address indexed toDelegate\\n );\\n\\n /// @notice Emitted when a balance or delegate change results in changes\\n /// to an account's voting power.\\n event DelegateVotesChanged(\\n address indexed delegate,\\n uint256 previousBalance,\\n uint256 newBalance\\n );\\n\\n function checkpoints(address account, uint32 pos)\\n public\\n view\\n virtual\\n returns (Checkpoint memory checkpoint)\\n {\\n (uint32 fromBlock, uint96 votes) = decodeCheckpoint(\\n _checkpoints[account][pos]\\n );\\n checkpoint = Checkpoint(fromBlock, votes);\\n }\\n\\n /// @notice Get number of checkpoints for `account`.\\n function numCheckpoints(address account)\\n public\\n view\\n virtual\\n returns (uint32)\\n {\\n return SafeCast.toUint32(_checkpoints[account].length);\\n }\\n\\n /// @notice Get the address `account` is currently delegating to.\\n function delegates(address account) public view virtual returns (address) {\\n return _delegates[account];\\n }\\n\\n /// @notice Gets the current votes balance for `account`.\\n /// @param account The address to get votes balance\\n /// @return The number of current votes for `account`\\n function getVotes(address account) public view returns (uint96) {\\n uint256 pos = _checkpoints[account].length;\\n return pos == 0 ? 0 : decodeValue(_checkpoints[account][pos - 1]);\\n }\\n\\n /// @notice Determine the prior number of votes for an account as of\\n /// a block number.\\n /// @dev Block number must be a finalized block or else this function will\\n /// revert to prevent misinformation.\\n /// @param account The address of the account to check\\n /// @param blockNumber The block number to get the vote balance at\\n /// @return The number of votes the account had as of the given block\\n function getPastVotes(address account, uint256 blockNumber)\\n public\\n view\\n returns (uint96)\\n {\\n return lookupCheckpoint(_checkpoints[account], blockNumber);\\n }\\n\\n /// @notice Retrieve the `totalSupply` at the end of `blockNumber`.\\n /// Note, this value is the sum of all balances, but it is NOT the\\n /// sum of all the delegated votes!\\n /// @param blockNumber The block number to get the total supply at\\n /// @dev `blockNumber` must have been already mined\\n function getPastTotalSupply(uint256 blockNumber)\\n public\\n view\\n returns (uint96)\\n {\\n return lookupCheckpoint(_totalSupplyCheckpoints, blockNumber);\\n }\\n\\n /// @notice Change delegation for `delegator` to `delegatee`.\\n // slither-disable-next-line dead-code\\n function delegate(address delegator, address delegatee) internal virtual;\\n\\n /// @notice Moves voting power from one delegate to another\\n /// @param src Address of old delegate\\n /// @param dst Address of new delegate\\n /// @param amount Voting power amount to transfer between delegates\\n function moveVotingPower(\\n address src,\\n address dst,\\n uint256 amount\\n ) internal {\\n if (src != dst && amount > 0) {\\n if (src != address(0)) {\\n // https://github.com/crytic/slither/issues/960\\n // slither-disable-next-line variable-scope\\n (uint256 oldWeight, uint256 newWeight) = writeCheckpoint(\\n _checkpoints[src],\\n subtract,\\n amount\\n );\\n emit DelegateVotesChanged(src, oldWeight, newWeight);\\n }\\n\\n if (dst != address(0)) {\\n // https://github.com/crytic/slither/issues/959\\n // slither-disable-next-line uninitialized-local\\n (uint256 oldWeight, uint256 newWeight) = writeCheckpoint(\\n _checkpoints[dst],\\n add,\\n amount\\n );\\n emit DelegateVotesChanged(dst, oldWeight, newWeight);\\n }\\n }\\n }\\n\\n /// @notice Writes a new checkpoint based on operating last stored value\\n /// with a `delta`. Usually, said operation is the `add` or\\n /// `subtract` functions from this contract, but more complex\\n /// functions can be passed as parameters.\\n /// @param ckpts The checkpoints array to use\\n /// @param op The function to apply over the last value and the `delta`\\n /// @param delta Variation with respect to last stored value to be used\\n /// for new checkpoint\\n function writeCheckpoint(\\n uint128[] storage ckpts,\\n function(uint256, uint256) view returns (uint256) op,\\n uint256 delta\\n ) internal returns (uint256 oldWeight, uint256 newWeight) {\\n uint256 pos = ckpts.length;\\n oldWeight = pos == 0 ? 0 : decodeValue(ckpts[pos - 1]);\\n newWeight = op(oldWeight, delta);\\n\\n if (pos > 0) {\\n uint32 fromBlock = decodeBlockNumber(ckpts[pos - 1]);\\n // slither-disable-next-line incorrect-equality\\n if (fromBlock == block.number) {\\n ckpts[pos - 1] = encodeCheckpoint(\\n fromBlock,\\n SafeCast.toUint96(newWeight)\\n );\\n return (oldWeight, newWeight);\\n }\\n }\\n\\n ckpts.push(\\n encodeCheckpoint(\\n SafeCast.toUint32(block.number),\\n SafeCast.toUint96(newWeight)\\n )\\n );\\n }\\n\\n /// @notice Lookup a value in a list of (sorted) checkpoints.\\n /// @param ckpts The checkpoints array to use\\n /// @param blockNumber Block number when we want to get the checkpoint at\\n function lookupCheckpoint(uint128[] storage ckpts, uint256 blockNumber)\\n internal\\n view\\n returns (uint96)\\n {\\n // We run a binary search to look for the earliest checkpoint taken\\n // after `blockNumber`. During the loop, the index of the wanted\\n // checkpoint remains in the range [low-1, high). With each iteration,\\n // either `low` or `high` is moved towards the middle of the range to\\n // maintain the invariant.\\n // - If the middle checkpoint is after `blockNumber`,\\n // we look in [low, mid)\\n // - If the middle checkpoint is before or equal to `blockNumber`,\\n // we look in [mid+1, high)\\n // Once we reach a single value (when low == high), we've found the\\n // right checkpoint at the index high-1, if not out of bounds (in that\\n // case we're looking too far in the past and the result is 0).\\n // Note that if the latest checkpoint available is exactly for\\n // `blockNumber`, we end up with an index that is past the end of the\\n // array, so we technically don't find a checkpoint after\\n // `blockNumber`, but it works out the same.\\n require(blockNumber < block.number, \\\"Block not yet determined\\\");\\n\\n uint256 high = ckpts.length;\\n uint256 low = 0;\\n while (low < high) {\\n uint256 mid = Math.average(low, high);\\n uint32 midBlock = decodeBlockNumber(ckpts[mid]);\\n if (midBlock > blockNumber) {\\n high = mid;\\n } else {\\n low = mid + 1;\\n }\\n }\\n\\n return high == 0 ? 0 : decodeValue(ckpts[high - 1]);\\n }\\n\\n /// @notice Maximum token supply. Defaults to `type(uint96).max` (2^96 - 1)\\n // slither-disable-next-line dead-code\\n function maxSupply() internal view virtual returns (uint96) {\\n return type(uint96).max;\\n }\\n\\n /// @notice Encodes a `blockNumber` and `value` into a single `uint128`\\n /// checkpoint.\\n /// @dev `blockNumber` is stored in the first 32 bits, while `value` in the\\n /// remaining 96 bits.\\n function encodeCheckpoint(uint32 blockNumber, uint96 value)\\n internal\\n pure\\n returns (uint128)\\n {\\n return (uint128(blockNumber) << 96) | uint128(value);\\n }\\n\\n /// @notice Decodes a block number from a `uint128` `checkpoint`.\\n function decodeBlockNumber(uint128 checkpoint)\\n internal\\n pure\\n returns (uint32)\\n {\\n return uint32(bytes4(bytes16(checkpoint)));\\n }\\n\\n /// @notice Decodes a voting value from a `uint128` `checkpoint`.\\n function decodeValue(uint128 checkpoint) internal pure returns (uint96) {\\n return uint96(checkpoint);\\n }\\n\\n /// @notice Decodes a block number and voting value from a `uint128`\\n /// `checkpoint`.\\n function decodeCheckpoint(uint128 checkpoint)\\n internal\\n pure\\n returns (uint32 blockNumber, uint96 value)\\n {\\n blockNumber = decodeBlockNumber(checkpoint);\\n value = decodeValue(checkpoint);\\n }\\n\\n // slither-disable-next-line dead-code\\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a + b;\\n }\\n\\n // slither-disable-next-line dead-code\\n function subtract(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a - b;\\n }\\n}\\n\",\"keccak256\":\"0x25f420d34548648aa59703bccdad450815da5c9e18adf575845a659f0945d131\",\"license\":\"GPL-3.0-or-later\"},\"contracts/governance/IVotesHistory.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-or-later\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.9;\\n\\ninterface IVotesHistory {\\n function getPastVotes(address account, uint256 blockNumber)\\n external\\n view\\n returns (uint96);\\n\\n function getPastTotalSupply(uint256 blockNumber)\\n external\\n view\\n returns (uint96);\\n}\\n\",\"keccak256\":\"0x535e87cf4c2e9a9439d99cf0918f013965fa6c4ddfbab07ff6ca4b195c8edc9f\",\"license\":\"GPL-3.0-or-later\"},\"contracts/token/T.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-or-later\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.9;\\n\\nimport \\\"../governance/Checkpoints.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/math/SafeCast.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\\\";\\n\\n/// @title T token\\n/// @notice Threshold Network T token\\n/// @dev By default, token balance does not account for voting power.\\n/// This makes transfers cheaper. The downside is that it requires users\\n/// to delegate to themselves to activate checkpoints and have their\\n/// voting power tracked.\\ncontract T is ERC20WithPermit, MisfundRecovery, Checkpoints {\\n /// @notice The EIP-712 typehash for the delegation struct used by\\n /// `delegateBySig`.\\n bytes32 public constant DELEGATION_TYPEHASH =\\n keccak256(\\n \\\"Delegation(address delegatee,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n constructor() ERC20WithPermit(\\\"Threshold Network Token\\\", \\\"T\\\") {}\\n\\n /// @notice Delegates votes from signatory to `delegatee`\\n /// @param delegatee The address to delegate votes to\\n /// @param deadline The time at which to expire the signature\\n /// @param v The recovery byte of the signature\\n /// @param r Half of the ECDSA signature pair\\n /// @param s Half of the ECDSA signature pair\\n function delegateBySig(\\n address signatory,\\n address delegatee,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Delegation expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n DELEGATION_TYPEHASH,\\n delegatee,\\n nonce[signatory]++,\\n deadline\\n )\\n )\\n )\\n );\\n\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == signatory,\\n \\\"Invalid signature\\\"\\n );\\n\\n return delegate(signatory, delegatee);\\n }\\n\\n /// @notice Delegate votes from `msg.sender` to `delegatee`.\\n /// @param delegatee The address to delegate votes to\\n function delegate(address delegatee) public virtual {\\n return delegate(msg.sender, delegatee);\\n }\\n\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal override {\\n uint96 safeAmount = SafeCast.toUint96(amount);\\n\\n // When minting:\\n if (from == address(0)) {\\n // Does not allow to mint more than uint96 can fit. Otherwise, the\\n // Checkpoint might not fit the balance.\\n require(\\n totalSupply + amount <= maxSupply(),\\n \\\"Maximum total supply exceeded\\\"\\n );\\n writeCheckpoint(_totalSupplyCheckpoints, add, safeAmount);\\n }\\n\\n // When burning:\\n if (to == address(0)) {\\n writeCheckpoint(_totalSupplyCheckpoints, subtract, safeAmount);\\n }\\n\\n moveVotingPower(delegates(from), delegates(to), safeAmount);\\n }\\n\\n function delegate(address delegator, address delegatee)\\n internal\\n virtual\\n override\\n {\\n address currentDelegate = delegates(delegator);\\n uint96 delegatorBalance = SafeCast.toUint96(balanceOf[delegator]);\\n _delegates[delegator] = delegatee;\\n\\n emit DelegateChanged(delegator, currentDelegate, delegatee);\\n\\n moveVotingPower(currentDelegate, delegatee, delegatorBalance);\\n }\\n}\\n\",\"keccak256\":\"0x6265416225fd15b1108fce13d570b53a862a5d256ba2e6329bccf658eccac430\",\"license\":\"GPL-3.0-or-later\"},\"contracts/vending/VendingMachine.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-or-later\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.9;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\\\";\\nimport \\\"../token/T.sol\\\";\\n\\n/// @title T token vending machine\\n/// @notice Contract implements a special update protocol to enable KEEP/NU\\n/// token holders to wrap their tokens and obtain T tokens according\\n/// to a fixed ratio. This will go on indefinitely and enable NU and\\n/// KEEP token holders to join T network without needing to buy or\\n/// sell any assets. Logistically, anyone holding NU or KEEP can wrap\\n/// those assets in order to upgrade to T. They can also unwrap T in\\n/// order to downgrade back to the underlying asset. There is a separate\\n/// instance of this contract deployed for KEEP holders and a separate\\n/// instance of this contract deployed for NU holders.\\ncontract VendingMachine is IReceiveApproval {\\n using SafeERC20 for IERC20;\\n using SafeERC20 for T;\\n\\n /// @notice Number of decimal places of precision in conversion to/from\\n /// wrapped tokens (assuming typical ERC20 token with 18 decimals).\\n /// This implies that amounts of wrapped tokens below this precision\\n /// won't take part in the conversion. E.g., for a value of 3, then\\n /// for a conversion of 1.123456789 wrapped tokens, only 1.123 is\\n /// convertible (i.e., 3 decimal places), and 0.000456789 is left.\\n uint256 public constant WRAPPED_TOKEN_CONVERSION_PRECISION = 3;\\n\\n /// @notice Divisor for precision purposes, used to represent fractions.\\n uint256 public constant FLOATING_POINT_DIVISOR =\\n 10**(18 - WRAPPED_TOKEN_CONVERSION_PRECISION);\\n\\n /// @notice The token being wrapped to T (KEEP/NU).\\n IERC20 public immutable wrappedToken;\\n\\n /// @notice T token contract.\\n T public immutable tToken;\\n\\n /// @notice The ratio with which T token is converted based on the provided\\n /// token being wrapped (KEEP/NU), expressed in 1e18 precision.\\n ///\\n /// When wrapping:\\n /// x [T] = amount [KEEP/NU] * ratio / FLOATING_POINT_DIVISOR\\n ///\\n /// When unwrapping:\\n /// x [KEEP/NU] = amount [T] * FLOATING_POINT_DIVISOR / ratio\\n uint256 public immutable ratio;\\n\\n /// @notice The total balance of wrapped tokens for the given holder\\n /// account. Only holders that have previously wrapped KEEP/NU to T\\n /// can unwrap, up to the amount previously wrapped.\\n mapping(address => uint256) public wrappedBalance;\\n\\n event Wrapped(\\n address indexed recipient,\\n uint256 wrappedTokenAmount,\\n uint256 tTokenAmount\\n );\\n event Unwrapped(\\n address indexed recipient,\\n uint256 tTokenAmount,\\n uint256 wrappedTokenAmount\\n );\\n\\n /// @notice Sets the reference to `wrappedToken` and `tToken`. Initializes\\n /// conversion `ratio` between wrapped token and T based on the\\n /// provided `_tTokenAllocation` and `_wrappedTokenAllocation`.\\n /// @param _wrappedToken Address to ERC20 token that will be wrapped to T\\n /// @param _tToken Address of T token\\n /// @param _wrappedTokenAllocation The total supply of the token that will be\\n /// wrapped to T\\n /// @param _tTokenAllocation The allocation of T this instance of Vending\\n /// Machine will receive\\n /// @dev Multiplications in this contract can't overflow uint256 as we\\n /// restrict `_wrappedTokenAllocation` and `_tTokenAllocation` to\\n /// 96 bits and FLOATING_POINT_DIVISOR fits in less than 60 bits.\\n constructor(\\n IERC20 _wrappedToken,\\n T _tToken,\\n uint96 _wrappedTokenAllocation,\\n uint96 _tTokenAllocation\\n ) {\\n wrappedToken = _wrappedToken;\\n tToken = _tToken;\\n ratio =\\n (FLOATING_POINT_DIVISOR * _tTokenAllocation) /\\n _wrappedTokenAllocation;\\n }\\n\\n /// @notice Wraps up to the the given `amount` of the token (KEEP/NU) and\\n /// releases T token proportionally to the amount being wrapped with\\n /// respect to the wrap ratio. The token holder needs to have at\\n /// least the given amount of the wrapped token (KEEP/NU) approved\\n /// to transfer to the Vending Machine before calling this function.\\n /// @param amount The amount of KEEP/NU to be wrapped\\n function wrap(uint256 amount) external {\\n _wrap(msg.sender, amount);\\n }\\n\\n /// @notice Wraps up to the given amount of the token (KEEP/NU) and releases\\n /// T token proportionally to the amount being wrapped with respect\\n /// to the wrap ratio. This is a shortcut to `wrap` function that\\n /// avoids a separate approval transaction. Only KEEP/NU token\\n /// is allowed as a caller, so please call this function via\\n /// token's `approveAndCall`.\\n /// @param from Caller's address, must be the same as `wrappedToken` field\\n /// @param amount The amount of KEEP/NU to be wrapped\\n /// @param token Token's address, must be the same as `wrappedToken` field\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata\\n ) external override {\\n require(\\n token == address(wrappedToken),\\n \\\"Token is not the wrapped token\\\"\\n );\\n require(\\n msg.sender == address(wrappedToken),\\n \\\"Only wrapped token caller allowed\\\"\\n );\\n _wrap(from, amount);\\n }\\n\\n /// @notice Unwraps up to the given `amount` of T back to the legacy token\\n /// (KEEP/NU) according to the wrap ratio. It can only be called by\\n /// a token holder who previously wrapped their tokens in this\\n /// vending machine contract. The token holder can't unwrap more\\n /// tokens than they originally wrapped. The token holder needs to\\n /// have at least the given amount of T tokens approved to transfer\\n /// to the Vending Machine before calling this function.\\n /// @param amount The amount of T to unwrap back to the collateral (KEEP/NU)\\n function unwrap(uint256 amount) external {\\n _unwrap(msg.sender, amount);\\n }\\n\\n /// @notice Returns the T token amount that's obtained from `amount` wrapped\\n /// tokens (KEEP/NU), and the remainder that can't be upgraded.\\n function conversionToT(uint256 amount)\\n public\\n view\\n returns (uint256 tAmount, uint256 wrappedRemainder)\\n {\\n wrappedRemainder = amount % FLOATING_POINT_DIVISOR;\\n uint256 convertibleAmount = amount - wrappedRemainder;\\n tAmount = (convertibleAmount * ratio) / FLOATING_POINT_DIVISOR;\\n }\\n\\n /// @notice The amount of wrapped tokens (KEEP/NU) that's obtained from\\n /// `amount` T tokens, and the remainder that can't be downgraded.\\n function conversionFromT(uint256 amount)\\n public\\n view\\n returns (uint256 wrappedAmount, uint256 tRemainder)\\n {\\n tRemainder = amount % ratio;\\n uint256 convertibleAmount = amount - tRemainder;\\n wrappedAmount = (convertibleAmount * FLOATING_POINT_DIVISOR) / ratio;\\n }\\n\\n function _wrap(address tokenHolder, uint256 wrappedTokenAmount) internal {\\n (uint256 tTokenAmount, uint256 remainder) = conversionToT(\\n wrappedTokenAmount\\n );\\n wrappedTokenAmount -= remainder;\\n require(wrappedTokenAmount > 0, \\\"Disallow conversions of zero value\\\");\\n emit Wrapped(tokenHolder, wrappedTokenAmount, tTokenAmount);\\n\\n wrappedBalance[tokenHolder] += wrappedTokenAmount;\\n wrappedToken.safeTransferFrom(\\n tokenHolder,\\n address(this),\\n wrappedTokenAmount\\n );\\n tToken.safeTransfer(tokenHolder, tTokenAmount);\\n }\\n\\n function _unwrap(address tokenHolder, uint256 tTokenAmount) internal {\\n (uint256 wrappedTokenAmount, uint256 remainder) = conversionFromT(\\n tTokenAmount\\n );\\n tTokenAmount -= remainder;\\n require(tTokenAmount > 0, \\\"Disallow conversions of zero value\\\");\\n require(\\n wrappedBalance[tokenHolder] >= wrappedTokenAmount,\\n \\\"Can not unwrap more than previously wrapped\\\"\\n );\\n\\n emit Unwrapped(tokenHolder, tTokenAmount, wrappedTokenAmount);\\n wrappedBalance[tokenHolder] -= wrappedTokenAmount;\\n tToken.safeTransferFrom(tokenHolder, address(this), tTokenAmount);\\n wrappedToken.safeTransfer(tokenHolder, wrappedTokenAmount);\\n }\\n}\\n\",\"keccak256\":\"0xa4e7de5a9063db09b32ca97fa0fb5d8d1cc7b539387d1b44d74d15d3c2ed4134\",\"license\":\"GPL-3.0-or-later\"}},\"version\":1}", - "bytecode": "0x60e06040523480156200001157600080fd5b5060405162001097380380620010978339810160408190526200003491620000cc565b6001600160a01b03808516608052831660a0526001600160601b03808316908216620000636003601262000144565b6200007090600a6200025d565b6200007c919062000272565b62000088919062000294565b60c05250620002b792505050565b6001600160a01b0381168114620000ac57600080fd5b50565b80516001600160601b0381168114620000c757600080fd5b919050565b60008060008060808587031215620000e357600080fd5b8451620000f08162000096565b6020860151909450620001038162000096565b92506200011360408601620000af565b91506200012360608601620000af565b905092959194509250565b634e487b7160e01b600052601160045260246000fd5b6000828210156200015957620001596200012e565b500390565b600181815b808511156200019f5781600019048211156200018357620001836200012e565b808516156200019157918102915b93841c939080029062000163565b509250929050565b600082620001b85750600162000257565b81620001c75750600062000257565b8160018114620001e05760028114620001eb576200020b565b600191505062000257565b60ff841115620001ff57620001ff6200012e565b50506001821b62000257565b5060208310610133831016604e8410600b841016171562000230575081810a62000257565b6200023c83836200015e565b80600019048211156200025357620002536200012e565b0290505b92915050565b60006200026b8383620001a7565b9392505050565b60008160001904831182151516156200028f576200028f6200012e565b500290565b600082620002b257634e487b7160e01b600052601260045260246000fd5b500490565b60805160a05160c051610d72620003256000396000818160f601528181610244015281816103b601526103ed0152600081816101910152818161053101526106960152600081816101520152818161029701528181610326015281816104fc01526106cb0152610d726000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063996c6cc311610071578063996c6cc31461014d578063c35d64ea1461018c578063cff0eac5146101b3578063de0e9a3e146101c6578063ea598cb0146101d9578063f4fc2f47146101ec57600080fd5b80631c060cbe146100ae57806351b83ebc146100db57806371ca337d146100f15780637a93f256146101185780638f4ffcb114610138575b600080fd5b6100c16100bc3660046109de565b6101f4565b604080519283526020830191909152015b60405180910390f35b6100e361027b565b6040519081526020016100d2565b6100e37f000000000000000000000000000000000000000000000000000000000000000081565b6100e3610126366004610a13565b60006020819052908152604090205481565b61014b610146366004610a2e565b610295565b005b6101747f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100d2565b6101747f000000000000000000000000000000000000000000000000000000000000000081565b6100c16101c13660046109de565b6103ae565b61014b6101d43660046109de565b61042d565b61014b6101e73660046109de565b61043a565b6100e3600381565b60008061020360036012610adf565b61020e90600a610bdc565b6102189084610bfe565b905060006102268285610adf565b905061023460036012610adf565b61023f90600a610bdc565b6102697f000000000000000000000000000000000000000000000000000000000000000083610c12565b6102739190610c31565b925050915091565b61028760036012610adf565b61029290600a610bdc565b81565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b03161461031b5760405162461bcd60e51b815260206004820152601e60248201527f546f6b656e206973206e6f7420746865207772617070656420746f6b656e000060448201526064015b60405180910390fd5b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461039d5760405162461bcd60e51b815260206004820152602160248201527f4f6e6c79207772617070656420746f6b656e2063616c6c657220616c6c6f77656044820152601960fa1b6064820152608401610312565b6103a78585610440565b5050505050565b6000806103db7f000000000000000000000000000000000000000000000000000000000000000084610bfe565b905060006103e98285610adf565b90507f000000000000000000000000000000000000000000000000000000000000000061041860036012610adf565b61042390600a610bdc565b6102699083610c12565b610437338261055e565b50565b61043733825b60008061044c836101f4565b909250905061045b8184610adf565b92506000831161047d5760405162461bcd60e51b815260040161031290610c45565b60408051848152602081018490526001600160a01b038616917f727200b48f3c812bfb404b578574e1c03694edb122d80fa6dcb352a9e4f8a938910160405180910390a26001600160a01b038416600090815260208190526040812080548592906104e9908490610c87565b9091555061052490506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168530866106f2565b6105586001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016858461075d565b50505050565b60008061056a836103ae565b90925090506105798184610adf565b92506000831161059b5760405162461bcd60e51b815260040161031290610c45565b6001600160a01b0384166000908152602081905260409020548211156106175760405162461bcd60e51b815260206004820152602b60248201527f43616e206e6f7420756e77726170206d6f7265207468616e2070726576696f7560448201526a1cdb1e481ddc985c1c195960aa1b6064820152608401610312565b60408051848152602081018490526001600160a01b038616917ff64ae1cc3e0e07da9c895b3225439175cab5838aca24c4e74852704858c96a7b910160405180910390a26001600160a01b03841660009081526020819052604081208054849290610683908490610adf565b909155506106be90506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168530866106f2565b6105586001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016858461075d565b6040516001600160a01b03808516602483015283166044820152606481018290526105589085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610792565b6040516001600160a01b03831660248201526044810182905261078d90849063a9059cbb60e01b90606401610726565b505050565b60006107e7826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166108649092919063ffffffff16565b80519091501561078d57808060200190518101906108059190610c9f565b61078d5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610312565b6060610873848460008561087d565b90505b9392505050565b6060824710156108de5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610312565b843b61092c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610312565b600080866001600160a01b031685876040516109489190610ced565b60006040518083038185875af1925050503d8060008114610985576040519150601f19603f3d011682016040523d82523d6000602084013e61098a565b606091505b509150915061099a8282866109a5565b979650505050505050565b606083156109b4575081610876565b8251156109c45782518084602001fd5b8160405162461bcd60e51b81526004016103129190610d09565b6000602082840312156109f057600080fd5b5035919050565b80356001600160a01b0381168114610a0e57600080fd5b919050565b600060208284031215610a2557600080fd5b610876826109f7565b600080600080600060808688031215610a4657600080fd5b610a4f866109f7565b945060208601359350610a64604087016109f7565b9250606086013567ffffffffffffffff80821115610a8157600080fd5b818801915088601f830112610a9557600080fd5b813581811115610aa457600080fd5b896020828501011115610ab657600080fd5b9699959850939650602001949392505050565b634e487b7160e01b600052601160045260246000fd5b600082821015610af157610af1610ac9565b500390565b600181815b80851115610b31578160001904821115610b1757610b17610ac9565b80851615610b2457918102915b93841c9390800290610afb565b509250929050565b600082610b4857506001610bd6565b81610b5557506000610bd6565b8160018114610b6b5760028114610b7557610b91565b6001915050610bd6565b60ff841115610b8657610b86610ac9565b50506001821b610bd6565b5060208310610133831016604e8410600b8410161715610bb4575081810a610bd6565b610bbe8383610af6565b8060001904821115610bd257610bd2610ac9565b0290505b92915050565b60006108768383610b39565b634e487b7160e01b600052601260045260246000fd5b600082610c0d57610c0d610be8565b500690565b6000816000190483118215151615610c2c57610c2c610ac9565b500290565b600082610c4057610c40610be8565b500490565b60208082526022908201527f446973616c6c6f7720636f6e76657273696f6e73206f66207a65726f2076616c604082015261756560f01b606082015260800190565b60008219821115610c9a57610c9a610ac9565b500190565b600060208284031215610cb157600080fd5b8151801515811461087657600080fd5b60005b83811015610cdc578181015183820152602001610cc4565b838111156105585750506000910152565b60008251610cff818460208701610cc1565b9190910192915050565b6020815260008251806020840152610d28816040850160208701610cc1565b601f01601f1916919091016040019291505056fea264697066735822122067d5c8bd0f1249a48948d1f378dbf5ab4ff1b77bc5d7fc1dca574055bd5d487264736f6c63430008090033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c8063996c6cc311610071578063996c6cc31461014d578063c35d64ea1461018c578063cff0eac5146101b3578063de0e9a3e146101c6578063ea598cb0146101d9578063f4fc2f47146101ec57600080fd5b80631c060cbe146100ae57806351b83ebc146100db57806371ca337d146100f15780637a93f256146101185780638f4ffcb114610138575b600080fd5b6100c16100bc3660046109de565b6101f4565b604080519283526020830191909152015b60405180910390f35b6100e361027b565b6040519081526020016100d2565b6100e37f000000000000000000000000000000000000000000000000000000000000000081565b6100e3610126366004610a13565b60006020819052908152604090205481565b61014b610146366004610a2e565b610295565b005b6101747f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100d2565b6101747f000000000000000000000000000000000000000000000000000000000000000081565b6100c16101c13660046109de565b6103ae565b61014b6101d43660046109de565b61042d565b61014b6101e73660046109de565b61043a565b6100e3600381565b60008061020360036012610adf565b61020e90600a610bdc565b6102189084610bfe565b905060006102268285610adf565b905061023460036012610adf565b61023f90600a610bdc565b6102697f000000000000000000000000000000000000000000000000000000000000000083610c12565b6102739190610c31565b925050915091565b61028760036012610adf565b61029290600a610bdc565b81565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b03161461031b5760405162461bcd60e51b815260206004820152601e60248201527f546f6b656e206973206e6f7420746865207772617070656420746f6b656e000060448201526064015b60405180910390fd5b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461039d5760405162461bcd60e51b815260206004820152602160248201527f4f6e6c79207772617070656420746f6b656e2063616c6c657220616c6c6f77656044820152601960fa1b6064820152608401610312565b6103a78585610440565b5050505050565b6000806103db7f000000000000000000000000000000000000000000000000000000000000000084610bfe565b905060006103e98285610adf565b90507f000000000000000000000000000000000000000000000000000000000000000061041860036012610adf565b61042390600a610bdc565b6102699083610c12565b610437338261055e565b50565b61043733825b60008061044c836101f4565b909250905061045b8184610adf565b92506000831161047d5760405162461bcd60e51b815260040161031290610c45565b60408051848152602081018490526001600160a01b038616917f727200b48f3c812bfb404b578574e1c03694edb122d80fa6dcb352a9e4f8a938910160405180910390a26001600160a01b038416600090815260208190526040812080548592906104e9908490610c87565b9091555061052490506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168530866106f2565b6105586001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016858461075d565b50505050565b60008061056a836103ae565b90925090506105798184610adf565b92506000831161059b5760405162461bcd60e51b815260040161031290610c45565b6001600160a01b0384166000908152602081905260409020548211156106175760405162461bcd60e51b815260206004820152602b60248201527f43616e206e6f7420756e77726170206d6f7265207468616e2070726576696f7560448201526a1cdb1e481ddc985c1c195960aa1b6064820152608401610312565b60408051848152602081018490526001600160a01b038616917ff64ae1cc3e0e07da9c895b3225439175cab5838aca24c4e74852704858c96a7b910160405180910390a26001600160a01b03841660009081526020819052604081208054849290610683908490610adf565b909155506106be90506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168530866106f2565b6105586001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016858461075d565b6040516001600160a01b03808516602483015283166044820152606481018290526105589085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610792565b6040516001600160a01b03831660248201526044810182905261078d90849063a9059cbb60e01b90606401610726565b505050565b60006107e7826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166108649092919063ffffffff16565b80519091501561078d57808060200190518101906108059190610c9f565b61078d5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610312565b6060610873848460008561087d565b90505b9392505050565b6060824710156108de5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610312565b843b61092c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610312565b600080866001600160a01b031685876040516109489190610ced565b60006040518083038185875af1925050503d8060008114610985576040519150601f19603f3d011682016040523d82523d6000602084013e61098a565b606091505b509150915061099a8282866109a5565b979650505050505050565b606083156109b4575081610876565b8251156109c45782518084602001fd5b8160405162461bcd60e51b81526004016103129190610d09565b6000602082840312156109f057600080fd5b5035919050565b80356001600160a01b0381168114610a0e57600080fd5b919050565b600060208284031215610a2557600080fd5b610876826109f7565b600080600080600060808688031215610a4657600080fd5b610a4f866109f7565b945060208601359350610a64604087016109f7565b9250606086013567ffffffffffffffff80821115610a8157600080fd5b818801915088601f830112610a9557600080fd5b813581811115610aa457600080fd5b896020828501011115610ab657600080fd5b9699959850939650602001949392505050565b634e487b7160e01b600052601160045260246000fd5b600082821015610af157610af1610ac9565b500390565b600181815b80851115610b31578160001904821115610b1757610b17610ac9565b80851615610b2457918102915b93841c9390800290610afb565b509250929050565b600082610b4857506001610bd6565b81610b5557506000610bd6565b8160018114610b6b5760028114610b7557610b91565b6001915050610bd6565b60ff841115610b8657610b86610ac9565b50506001821b610bd6565b5060208310610133831016604e8410600b8410161715610bb4575081810a610bd6565b610bbe8383610af6565b8060001904821115610bd257610bd2610ac9565b0290505b92915050565b60006108768383610b39565b634e487b7160e01b600052601260045260246000fd5b600082610c0d57610c0d610be8565b500690565b6000816000190483118215151615610c2c57610c2c610ac9565b500290565b600082610c4057610c40610be8565b500490565b60208082526022908201527f446973616c6c6f7720636f6e76657273696f6e73206f66207a65726f2076616c604082015261756560f01b606082015260800190565b60008219821115610c9a57610c9a610ac9565b500190565b600060208284031215610cb157600080fd5b8151801515811461087657600080fd5b60005b83811015610cdc578181015183820152602001610cc4565b838111156105585750506000910152565b60008251610cff818460208701610cc1565b9190910192915050565b6020815260008251806020840152610d28816040850160208701610cc1565b601f01601f1916919091016040019291505056fea264697066735822122067d5c8bd0f1249a48948d1f378dbf5ab4ff1b77bc5d7fc1dca574055bd5d487264736f6c63430008090033", - "devdoc": { - "kind": "dev", - "methods": { - "constructor": { - "details": "Multiplications in this contract can't overflow uint256 as we restrict `_wrappedTokenAllocation` and `_tTokenAllocation` to 96 bits and FLOATING_POINT_DIVISOR fits in less than 60 bits.", - "params": { - "_tToken": "Address of T token", - "_tTokenAllocation": "The allocation of T this instance of Vending Machine will receive", - "_wrappedToken": "Address to ERC20 token that will be wrapped to T", - "_wrappedTokenAllocation": "The total supply of the token that will be wrapped to T" - } - }, - "receiveApproval(address,uint256,address,bytes)": { - "params": { - "amount": "The amount of KEEP/NU to be wrapped", - "from": "Caller's address, must be the same as `wrappedToken` field", - "token": "Token's address, must be the same as `wrappedToken` field" - } - }, - "unwrap(uint256)": { - "params": { - "amount": "The amount of T to unwrap back to the collateral (KEEP/NU)" - } - }, - "wrap(uint256)": { - "params": { - "amount": "The amount of KEEP/NU to be wrapped" - } - } - }, - "title": "T token vending machine", - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": { - "FLOATING_POINT_DIVISOR()": { - "notice": "Divisor for precision purposes, used to represent fractions." - }, - "WRAPPED_TOKEN_CONVERSION_PRECISION()": { - "notice": "Number of decimal places of precision in conversion to/from wrapped tokens (assuming typical ERC20 token with 18 decimals). This implies that amounts of wrapped tokens below this precision won't take part in the conversion. E.g., for a value of 3, then for a conversion of 1.123456789 wrapped tokens, only 1.123 is convertible (i.e., 3 decimal places), and 0.000456789 is left." - }, - "constructor": { - "notice": "Sets the reference to `wrappedToken` and `tToken`. Initializes conversion `ratio` between wrapped token and T based on the provided `_tTokenAllocation` and `_wrappedTokenAllocation`." - }, - "conversionFromT(uint256)": { - "notice": "The amount of wrapped tokens (KEEP/NU) that's obtained from `amount` T tokens, and the remainder that can't be downgraded." - }, - "conversionToT(uint256)": { - "notice": "Returns the T token amount that's obtained from `amount` wrapped tokens (KEEP/NU), and the remainder that can't be upgraded." - }, - "ratio()": { - "notice": "The ratio with which T token is converted based on the provided token being wrapped (KEEP/NU), expressed in 1e18 precision. When wrapping: x [T] = amount [KEEP/NU] * ratio / FLOATING_POINT_DIVISOR When unwrapping: x [KEEP/NU] = amount [T] * FLOATING_POINT_DIVISOR / ratio" - }, - "receiveApproval(address,uint256,address,bytes)": { - "notice": "Wraps up to the given amount of the token (KEEP/NU) and releases T token proportionally to the amount being wrapped with respect to the wrap ratio. This is a shortcut to `wrap` function that avoids a separate approval transaction. Only KEEP/NU token is allowed as a caller, so please call this function via token's `approveAndCall`." - }, - "tToken()": { - "notice": "T token contract." - }, - "unwrap(uint256)": { - "notice": "Unwraps up to the given `amount` of T back to the legacy token (KEEP/NU) according to the wrap ratio. It can only be called by a token holder who previously wrapped their tokens in this vending machine contract. The token holder can't unwrap more tokens than they originally wrapped. The token holder needs to have at least the given amount of T tokens approved to transfer to the Vending Machine before calling this function." - }, - "wrap(uint256)": { - "notice": "Wraps up to the the given `amount` of the token (KEEP/NU) and releases T token proportionally to the amount being wrapped with respect to the wrap ratio. The token holder needs to have at least the given amount of the wrapped token (KEEP/NU) approved to transfer to the Vending Machine before calling this function." - }, - "wrappedBalance(address)": { - "notice": "The total balance of wrapped tokens for the given holder account. Only holders that have previously wrapped KEEP/NU to T can unwrap, up to the amount previously wrapped." - }, - "wrappedToken()": { - "notice": "The token being wrapped to T (KEEP/NU)." - } - }, - "notice": "Contract implements a special update protocol to enable KEEP/NU token holders to wrap their tokens and obtain T tokens according to a fixed ratio. This will go on indefinitely and enable NU and KEEP token holders to join T network without needing to buy or sell any assets. Logistically, anyone holding NU or KEEP can wrap those assets in order to upgrade to T. They can also unwrap T in order to downgrade back to the underlying asset. There is a separate instance of this contract deployed for KEEP holders and a separate instance of this contract deployed for NU holders.", - "version": 1 - }, - "storageLayout": { - "storage": [ - { - "astId": 15943, - "contract": "contracts/vending/VendingMachine.sol:VendingMachine", - "label": "wrappedBalance", - "offset": 0, - "slot": "0", - "type": "t_mapping(t_address,t_uint256)" - } - ], - "types": { - "t_address": { - "encoding": "inplace", - "label": "address", - "numberOfBytes": "20" - }, - "t_mapping(t_address,t_uint256)": { - "encoding": "mapping", - "key": "t_address", - "label": "mapping(address => uint256)", - "numberOfBytes": "32", - "value": "t_uint256" - }, - "t_uint256": { - "encoding": "inplace", - "label": "uint256", - "numberOfBytes": "32" - } - } - } -} \ No newline at end of file diff --git a/docs/rfc-1-staking-contract.adoc b/docs/rfc-1-staking-contract.adoc index 3091984d..d3f24dea 100644 --- a/docs/rfc-1-staking-contract.adoc +++ b/docs/rfc-1-staking-contract.adoc @@ -18,18 +18,13 @@ not require any owner’s keys at all. The stake delegation should also optimize network throughput without compromising the security of the owners’ stake. This proposal aims at implementing a minimum viable staking contract version -allowing to support legacy KEEP and NU stakes, as well as native T delegations -in all applications developed against this staking contract version. -It means that all stakers, no matter of the type of their stake (legacy KEEP, -legacy NU, liquid T) will be able to participate in all applications developed -against this staking contract version on equal rules. -The functionality of the staking contract can be further extended by the -upgradeability of the contract code. +allowing to support native T delegations in all applications developed against +this staking contract version. The functionality of the staking contract can be +further extended by the upgradeability of the contract code. === Terminology -Owner:: An address owning T tokens and tokens that could be converted to -T: KEEP, NU, including grants. An owner is the ultimate holder of the tokens. +Owner:: An address owning T tokens. An owner is the ultimate holder of the tokens. Before stake delegation, the owner has full control over the tokens, and the tokens are returned to the owner after stake delegation has finished. Owner’s participation is not required in the day-to-day operations on the @@ -86,16 +81,6 @@ particular application contract utilizing the staking contract. All off-chain client software should be able to run without exposing operator's or staking provider’s private key. -Existing KEEP and NU stake owners also need to perform a delegation so that T -staking contract can record their staking provider, beneficiary, and authorizer. -Since these values are read-only, they should be copied from KEEP staking contract -to T staking contract and NU stakers should set them during the delegation. For -existing stakes, the entire amount staked in the legacy contract is cached in T -staking contract. Caching amount staked in the legacy staking contract helps to -identify discrepancy between staking contracts. It is much easier to just compare -two values instead of checking authorization of every application (see Keeping -information in sync section). - ==== Authorizing an application Before the staking provider is eligible to participate in the given application, @@ -144,29 +129,11 @@ increase the authorization for applications. This increases the probability of b chosen for work in the application but is only effective for future checks of the authorized amount. -Top-ups can be executed for native T stakes and for legacy KEEP/NU stakes. - -Native T stakers can only top-up their stakes with a liquid T. - -Existing KEEP and NU stakers wanting to execute a top-up have two options. One -option is to wrap their KEEP/NU to T and then, execute a top-up in T staking -contract. The second option is to execute a top-up in their legacy staking -contracts and notify T staking contract about the fact their legacy stake -increased. +Stakes can only be topped-up with liquid T. -Effectively, it means that existing KEEP stakers can mix their legacy KEEP -stakes with liquid T stakes. Similarly, existing NU stakers can mix their legacy -NU stakes with liquid T stakes. This functionality adds some complexity to the -staking contract but it puts existing KEEP/NU stakers in the same spot as new T -stakers. Without it, existing stakers would not be able to top-up their stakes -with T earned from operating in the network, so they would be in a worse spot -than new T stakers allowed to top-up their stakes using earned T and this way -increasing their rewards. - -The owner or staking provider can execute a stake top-up for a staking provider -using a liquid T. Stake top-up does not automatically increase authorization -levels for applications. Stake top-up is a one-step process and does not require -any delay. +Anyone can execute a stake top-up for a staking provider using liquid T. +Stake top-up does not automatically increase authorization levels for applications. +Stake top-up is a one-step process and does not require any delay. ==== Undelegating a stake (unstaking) @@ -177,66 +144,10 @@ unstaked, relationship between owner, staking provider, beneficiary, and authorizer is retained in the staking contract in case some applications still have some rewards waiting for withdrawal. -It is possible to change the composition of the staked amount by unstaking -legacy tokens or by unstaking liquid T tokens. This allows existing KEEP/NU -stakers to unstake their legacy stakes one day while still being able to operate -in T network and earning rewards. - If the owner or staking provider attempts to unstake tokens before 24 hours passed since the delegation so that the amount left in the contract would be below the minimum stake, the transaction reverts. -It is expected that full unstaking is first completed on T staking contract before -full unstaking on a legacy staking contract for the given staking provider gets -initiated. - -==== Keeping information in sync - -To avoid expensive calls to legacy staking contract, it is assumed that cached -information in T staking contract about the amount staked in the legacy contract -is always up-to-date. - -T staking contract should expose a function allowing to seize some amount of T -from the staking provider in case that staking provider has a lower active stake -(eligible for work selection) in the old staking contract than the amount cached -in T staking contract. 5% of the amount seized is given to the person who -notified about the discrepancy and the rest is burned. The amount is a -governable parameter and can be updated at any time by the governance, with no -governance delay. The transaction notifying about stake amounts not being in sync -needs to update authorizations of all affected applications and execute an -involuntary authorization decrease on each affected application. - -For legacy stakers, staked amount can become out-of-sync in three cases: - -* stake undelegated on the legacy contract, -* stake topped-up on the legacy contract, -* stake slashed on the legacy contract. - -It is expected that stake undelegation will be first performed on T staking -contract and then on the legacy staking contract. - -It is expected that a top-up will be first performed on the legacy staking -contract, and then propagated to the new staking contract, in the same -transaction. Even if it does not happen in the same transaction, this kind of -discrepancy is not slashable given that the stake amount on the legacy contract -is higher than the stake amount on T staking contract. - -In case the stake has been slashed on the legacy contract, the staking provider is -required to update their information on T staking contract as soon as possible. -In practice, for Keep, with the random beacon disabled, and tBTC v1 slashing the -stake only in case of a proven fraud that had to be committed by all staking -providers of ECDSA keep, this approach is acceptable. - -An integral part of the staking contract should be a bot or process inside an -off-chain client monitoring stakes and notifying about discrepancies. This is -especially important given that the bot may need to voluntarily inform about -discrepancies for staking providers that have been already slashed to zero. - -Owner or staking provider can decrease the legacy contract active stake cached -amount on T staking contract if no application has authorization higher than the -liquid T stake. It allows to undelegate from the legacy staking contract while -still being able to operate in T network and earning rewards. - ==== Slashing a stake Authorized applications can slash or seize a stake. Slash operation decreases @@ -270,9 +181,6 @@ never retried so it is in the application's best interest to ensure it can alway execute the callback. The same happens if the slash operation fails because the given staking provider has insufficient stake to slash. -In the case of legacy stakers, their liquid T is slashed first before a call to -the legacy contract is executed. - It is important to note slashing executed in the context of one application may lead to involuntarily decreasing the authorization for other applications in case the amount of stake available after the slashing is lower than these @@ -294,19 +202,6 @@ beneficiary, and authorizer. Transfers the given amount of T to the staking contract. The owner of the delegation needs to have the amount approved to transfer to the staking contract. -==== `stakeKeep(address stakingProvider) external` - -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. - -==== `stakeNu(address stakingProvider, address payable beneficiary, address authorizer) external` - -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. - ==== `setMinimumStakeAmount(uint96 amount) external onlyGovernance` Allows the governance to set the minimum required stake amount. This amount is @@ -391,16 +286,6 @@ Increases the amount of the stake for the given staking provider. The sender of transaction needs to have the amount approved to transfer to the staking contract. -==== `topUpKeep(address stakingProvider) external onlyOwnerOrStakingProvider(stakingProvider)` - -Propagates information about stake top-up from the legacy KEEP staking contract -to T staking contract. Can be called only by the owner or staking provider. - -==== `topUpNu(address stakingProvider) external onlyOwnerOrStakingProvider(stakingProvider)` - -Propagates information about stake top-up from the legacy NU staking contract -to T staking contract. Can be called only by the owner or staking provider. - === Undelegating a stake (unstaking) ==== `unstakeT(address stakingProvider, uint96 amount) external onlyOwnerOrStakingProvider(stakingProvider)` @@ -421,14 +306,14 @@ delegation owner or the staking provider. ==== `unstakeNu(address stakingProvider, uint96 amount) external onlyOwnerOrStakingProvider(stakingProvider)` -Reduces cached legacy NU stake amount by `amount`. Reverts if there is at least -one authorization higher than the sum of remaining legacy NU stake and liquid T -stake for that provider or if 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 sill being able to operate in T network and earning rewards based -on the liquid T staked. Can be called only by the delegation owner or the -staking provider. +Sets to 0 the amount of T that is cached from the legacy NU staking contract. +Reverts if there is at least one authorization higher than the sum of remaining +legacy NU stake and native T stake for that staking provider or if the unstaked +amount is higher than the cached legacy stake amount. If succeeded, the legacy +NU stake can be partially or fully undelegated on the legacy NU staking contract. +This function allows to unstake from NU staking contract while still being able +to operate in T network and earning rewards based on the native T staked. +Can be called only by the stake owner or the staking provider. ==== `unstakeAll(address stakingProvider) external onlyOwnerOrStakingProvider(stakingProvider)` @@ -439,37 +324,6 @@ staking provider. === Keeping information in sync -==== `notifyKeepStakeDiscrepancy(address stakingProvider)` - -Notifies about the discrepancy between legacy KEEP active stake and 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. - -Optionally: reward withdrawal can be split into a separate function to protect -against MEV frontrunners. - -==== `notifyNuStakeDiscrepancy(address stakingProvider)` - -Notifies about the discrepancy between legacy NU active stake and 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. - -Optionally: reward withdrawal can be split into a separate function to protect -against MEV frontrunners. - -==== `setStakeDiscrepancyPenalty(uint96 penalty, unit256 rewardMultiplier) external onlyGovernance` - -Sets the penalty amount for stake discrepancy and reward multiplier for -reporting it. The penalty is seized from the staking provider account, 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. - ==== `setNotificationReward(uint96 reward) external onlyGovernance` Sets reward in T tokens for notification of misbehaviour of one staking provider. @@ -544,14 +398,14 @@ For example, suppose the given staking provider has 10 T, 20 T worth of KEEP, and 30 T worth of NU all staked, and the maximum application authorization is 40 T, then `getMinStaked` for that staking provider returns: -* 0 T if KEEP stake type specified i.e. min = 40 T max - (10 T + 30 T worth of NU) = 0 T -* 10 T if NU stake type specified i.e. min = 40 T max - (10 T + 20 T worth of KEEP) = 10 T -* 0 T if T stake type specified i.e. min = 40 T max - (20 T worth of KEEP + 30 T worth of NU) < 0 T +* 0 T if KEEP stake type specified i.e. min = 40 T max - (10 T) = 30 T +* 10 T if NU stake type specified i.e. min = 40 T max - (10 T) = 30 T +* 0 T if T stake type specified i.e. min = 40 T max = 40 T In other words, the minimum stake amount for the specified stake type is the minimum amount of stake of the given type needed to satisfy the maximum application authorization given the staked amounts of the -other stake types for that staking provider. +T stake type for that staking provider. ==== `getAvailableToAuthorize(address stakingProvider, address application) external view returns (uint96)` 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..4c4d2ac9 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,45 +89,19 @@ 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 ) 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() @@ -258,35 +218,6 @@ describe("TokenStaking", () => { }) } ) - - 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 - ) - ).to.be.revertedWith("Provider is already in use") - }) - } - ) }) context("when staker delegates too small amount", () => { @@ -447,3738 +378,1231 @@ describe("TokenStaking", () => { ) }) - 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 + application1Mock.address, + amount ) - ).to.equal(0) - }) + ).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) - }) - - it("should create a new checkpoint for staker's delegatee", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - tAmount - ) + 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("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 approved", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) }) - 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 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") + }) }) - }) - }) - }) - }) - - 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( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) - ).to.be.revertedWith("Provider is already in use") + 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 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( + 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 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 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 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 do callback to NuCypher staking contract", async () => { - await assertNuStakers(staker.address, nuAmount, stakingProvider.address) - }) - - 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 - ) - 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 application was not approved", () => { - it("should revert", async () => { - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - ).to.be.revertedWith("Application is not approved") - }) - }) - - context("when application was approved", () => { - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - }) - - context("when application was paused", () => { - it("should revert", async () => { - await tokenStaking - .connect(deployer) - .setPanicButton(application1Mock.address, panicButton.address) - await tokenStaking - .connect(panicButton) - .pauseApplication(application1Mock.address) - await expect( - tokenStaking - .connect(authorizer) - .increaseAuthorization( - 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 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 - ) - ).to.equal(amount) - }) - - it("should increase min staked amount in T", async () => { - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.T - ) - ).to.equal(amount) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.NU - ) - ).to.equal(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(0) - }) - - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - amount, - Zero - ) - }) - - it("should emit two AuthorizationIncreased", async () => { - await expect(tx1) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - 0, - authorizedAmount1 - ) - await expect(tx2) - .to.emit(tokenStaking, "AuthorizationIncreased") - .withArgs( - stakingProvider.address, - application1Mock.address, - authorizedAmount1, - authorizedAmount1.add(authorizedAmount2) - ) - }) - }) - - context("when authorize after full deauthorization", () => { - beforeEach(async () => { - await tokenStaking.connect(deployer).setAuthorizationCeiling(1) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - amount - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"]( - stakingProvider.address - ) - await application1Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - await tokenStaking - .connect(deployer) - .approveApplication(application2Mock.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - amount - ) - }) - - it("should 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) - }) - - it("should update only T staked amount", async () => { - await assertStakes( - stakingProvider.address, - topUpAmount, - keepInTAmount, - Zero - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedAmount) - }) - - 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 nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() - - await tokenStaking - .connect(staker) - .delegateVoting(stakingProvider.address, delegatee.address) - await tToken - .connect(deployer) - .transfer(stakingProvider.address, topUpAmount) - await tToken - .connect(stakingProvider) - .approve(tokenStaking.address, topUpAmount) - tx = await tokenStaking - .connect(stakingProvider) - .topUp(stakingProvider.address, topUpAmount) - }) - - it("should update only T staked amount", async () => { - await assertStakes( - stakingProvider.address, - topUpAmount, - Zero, - nuInTAmount - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([staker.address, staker.address, staker.address]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(expectedAmount) - }) - - 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("topUpKeep", () => { - context("when staking provider has no delegated stake", () => { - it("should revert", async () => { - 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") - }) - }) - - context( - "when specified address never was a staking provider in Keep", - () => { - 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 - ) - 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) - ) - }) - - it("should increase the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - newKeepInTAmount - ) - }) - - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - newKeepInTAmount - ) - }) - }) - - context("when staking provider unstaked Keep previously", () => { - const keepAmount = initialStakerBalance - const keepInTAmount = convertToT(keepAmount, keepRatio).result - let tx - let blockTimestamp - - 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() - - await increaseTime(86400) // +24h - - await tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - tx = await tokenStaking - .connect(staker) - .topUpKeep(stakingProvider.address) - }) - - it("should update only Keep staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, keepInTAmount, 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, keepInTAmount) - }) - }) - - 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 tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - staker.address, - staker.address, - tAmount - ) - blockTimestamp = await lastBlockTime() - - 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 update only Keep staked amount", async () => { - await assertStakes( - stakingProvider.address, - tAmount, - keepInTAmount, - 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 increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount.add(keepInTAmount)) - }) - - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, keepInTAmount) - }) - }) - - 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 - - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() - - 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(stakingProvider) - .topUpKeep(stakingProvider.address) - }) - - it("should update only Keep staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - keepInTAmount, - nuInTAmount - ) - }) - - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([staker.address, staker.address, staker.address]) - }) - - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) - - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(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", () => { - it("should revert", async () => { - await expect( - tokenStaking.connect(staker).topUpNu(stakingProvider.address) - ).to.be.revertedWith("Caller is not owner") - }) - }) - - context("when caller is not owner", () => { - 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(stakingProvider).topUpNu(stakingProvider.address) - ).to.be.revertedWith("Caller is not owner") - }) - }) - - context("when stake in NuCypher contract 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 - ) - await expect( - tokenStaking.connect(staker).topUpNu(stakingProvider.address) - ).to.be.revertedWith("Nothing to top-up") - }) - }) - - context("when stake in NuCypher contract is less than cached", () => { - 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") - }) - }) - - 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) - await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() - - 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 inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + authorizedAmount, + Zero + ) + }) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + 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 + 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") + }) + } ) - ).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) - }) + context("when authorize staked tokens in several txs", () => { + let tx1 + let tx2 + const authorizedAmount1 = amount.sub(1) + const authorizedAmount2 = 1 - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs( - stakingProvider.address, - newNuInTAmount.sub(initialNuInTAmount) - ) - }) + 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 the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - newNuInTAmount - ) - }) + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount) + }) - it("should increase the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - newNuInTAmount - ) - }) - }) + it("should decrease available amount to authorize for one application", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount) + }) - context("when staking provider unstaked Nu previously", () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - let tx - let blockTimestamp + it("should increase min staked amount in T", async () => { + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T + ) + ).to.equal(amount) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU + ) + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP + ) + ).to.equal(0) + }) - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu(stakingProvider.address, staker.address, staker.address) - blockTimestamp = await lastBlockTime() + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + Zero + ) + }) - await increaseTime(86400) // +24h - await tokenStaking - .connect(staker) - .unstakeNu(stakingProvider.address, nuInTAmount) - tx = await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - }) + it("should emit two AuthorizationIncreased", async () => { + await expect(tx1) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + 0, + authorizedAmount1 + ) + await expect(tx2) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + authorizedAmount1, + authorizedAmount1.add(authorizedAmount2) + ) + }) + }) - it("should update only Nu staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, nuInTAmount) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - nuAmount - ) - }) + context("when authorize after full deauthorization", () => { + beforeEach(async () => { + await tokenStaking.connect(deployer).setAuthorizationCeiling(1) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + amount + ) + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"]( + stakingProvider.address + ) + await application1Mock.approveAuthorizationDecrease( + stakingProvider.address + ) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application2Mock.address, + amount + ) + }) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + 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 emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, nuInTAmount) - }) - }) + 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 - 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 - let tx - let blockTimestamp + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) - beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .stake( + await tokenStaking.setLegacyStakingProvider( stakingProvider.address, staker.address, - staker.address, - tAmount + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTStake, + nuInTStake ) - blockTimestamp = await lastBlockTime() - tx = await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - }) + await tToken.connect(staker).approve(tokenStaking.address, tStake) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, tStake) + }) - 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) - ) - }) + 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 not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([staker.address, staker.address, staker.address]) - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + context("when authorize staked tokens in one tx", () => { + let tx + const notAuthorized = tStake.sub(to1e18(1)) + const authorizedAmount = tStake.sub(notAuthorized) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorizedAmount + ) + }) - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tAmount.add(nuInTAmount)) - }) + it("should increase authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(authorizedAmount) + }) - it("should do callback to NuCypher staking contract", async () => { - await assertNuStakers(staker.address, nuAmount, stakingProvider.address) - }) + it("should increase min staked amount in T only", async () => { + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T + ) + ).to.equal(authorizedAmount) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU + ) + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP + ) + ).to.equal(0) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, nuInTAmount) - }) - }) + it("should decrease available amount to authorize for one application", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(notAuthorized) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(tStake) + }) - context("when 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 + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + authorizedAmount, + Zero + ) + }) - 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() + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + 0, + authorizedAmount + ) + }) - await nucypherStakingMock.setStaker(staker.address, nuAmount) - tx = await tokenStaking.connect(staker).topUpNu(stakingProvider.address) - }) + context("when authorize to the second application", () => { + let tx2 - 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) - ) - }) + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) - it("should not update roles", async () => { - expect( - await tokenStaking.rolesOf(stakingProvider.address) - ).to.deep.equal([ - staker.address, - beneficiary.address, - authorizer.address, - ]) - }) + tx2 = await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application2Mock.address, + tStake + ) + }) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + it("should increase only one authorization", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(authorizedAmount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(tStake) + }) + + it("should set min staked amount equal to T stake", async () => { + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.T + ) + ).to.equal(tStake) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU + ) + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP + ) + ).to.equal(0) + }) + + it("should decrease available amount to authorize for the second application", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(notAuthorized) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(0) + }) - it("should increase available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(nuInTAmount.add(keepInTAmount)) - }) + it("should inform second application", async () => { + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + tStake, + Zero + ) + }) - it("should emit ToppedUp event", async () => { - await expect(tx) - .to.emit(tokenStaking, "ToppedUp") - .withArgs(stakingProvider.address, nuInTAmount) - }) - }) + it("should emit AuthorizationIncreased", async () => { + await expect(tx2) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + 0, + tStake + ) + }) + }) + }) + + context("when authorize more than staked amount in several txs", () => { + it("should revert", async () => { + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + tStake.sub(1) + ) + await expect( + tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + 2 + ) + ).to.be.revertedWith("Not enough stake to authorize") + }) + }) + } + ) }) - describe("unstakeT", () => { - context("when staking provider has no stake", () => { + describe("requestAuthorizationDecrease", () => { + context("when caller is not authorizer", () => { it("should revert", async () => { + const amount = initialStakerBalance await expect( - tokenStaking.unstakeT(deployer.address, 0) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(staker) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amount + ) + ).to.be.revertedWith("Not authorizer") }) }) - 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).unstakeT(stakingProvider.address, 0) - ).to.be.revertedWith("Not owner or provider") - }) - }) + context( + "when caller is authorizer of staking provider with T stake", + () => { + const amount = initialStakerBalance - context("when amount to unstake is zero", () => { - it("should revert", async () => { - await tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - initialStakerBalance - ) - await expect( - tokenStaking.connect(staker).unstakeT(stakingProvider.address, 0) - ).to.be.revertedWith("Too much to unstake") - }) - }) + 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 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) + 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") + }) + }) - await nucypherStakingMock.setStaker( - staker.address, - initialStakerBalance - ) - 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(authorizer) + ["requestAuthorizationDecrease(address)"]( + stakingProvider.address + ) + ).to.be.revertedWith("Application is not approved") + }) + }) - const amountToUnstake = 1 - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") - }) - }) + 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 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, - beneficiary.address, - authorizer.address, - amount - ) - const authorized = amount.div(3) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) + 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") + }) + }) - const amountToUnstake = amount.sub(authorized).add(1) - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") - }) - }) + context("when amount to decrease is less than authorized", () => { + const amountToDecrease = amount.div(3) + const expectedFromAmount = amount + const expectedToAmount = amount.sub(amountToDecrease) + let tx - context("when unstake before minimum staking time passes", () => { - const amount = initialStakerBalance - const minAmount = initialStakerBalance.div(3) + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amountToDecrease + ) + }) - beforeEach(async () => { - await tToken.connect(staker).approve(tokenStaking.address, amount) - await tokenStaking - .connect(staker) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - amount - ) - await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) - }) + it("should keep authorized amount unchanged", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(amount) + }) - 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 send request to application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + expectedToAmount + ) + }) - context("when the stake left would be the minimum", () => { - it("should revert", async () => { - const amountToUnstake = amount.sub(minAmount) - await expect( - tokenStaking - .connect(staker) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") + it("should emit AuthorizationDecreaseRequested", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationDecreaseRequested") + .withArgs( + stakingProvider.address, + application1Mock.address, + expectedFromAmount, + expectedToAmount + ) + }) }) - }) - 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 request to decrease all authorized amount for several applications", + () => { + let tx - 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) + 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 + ) + }) - const amountToUnstake = amount - await expect( - tokenStaking - .connect(staker) - .unstakeT(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Can't unstake earlier than 24h") - }) - }) - }) + 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) + }) - context("when unstake after minimum staking time passes", () => { - const amount = initialStakerBalance - const minAmount = initialStakerBalance.div(3) - let tx - let blockTimestamp + it("should send request to application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + Zero + ) + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + amount, + Zero + ) + }) - 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() + 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 + ) + }) + } + ) - await increaseTime(86400) // +24h + 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 - tx = await tokenStaking - .connect(stakingProvider) - .unstakeT(stakingProvider.address, amount) - }) + 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 update T staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) + it("should keep authorized amount unchanged", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.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, - ]) - }) + it("should send request to application with last amount", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + amount, + expectedToAmount2 + ) + }) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + 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 + ) + }) + }) + } + ) + }) - 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) - }) + describe("approveAuthorizationDecrease", () => { + const amount = initialStakerBalance - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, amount) - }) + 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 + ) }) - }) - describe("unstakeKeep", () => { - context("when staking provider has no stake", () => { + context("when application was not approved", () => { it("should revert", async () => { await expect( - tokenStaking.unstakeKeep(deployer.address) - ).to.be.revertedWith("Not owner or provider") + application2Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("Application is not approved") }) }) - context("when caller is not owner or staking provider", () => { + 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(authorizer).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("Application is not approved") }) }) - context("when stake is only in T and Nu", () => { + context("when application is disabled", () => { 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) - + .connect(deployer) + .disableApplication(application1Mock.address) await expect( - tokenStaking - .connect(stakingProvider) - .unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Nothing to unstake") + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("Application is not approved") }) }) - context("when authorized amount is more than non-Keep stake", () => { + context("when approve without request", () => { it("should revert", async () => { - const tAmount = initialStakerBalance - const keepAmount = initialStakerBalance + 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(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) - - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) - - const authorized = tAmount.add(1) + .approveApplication(application2Mock.address) await tokenStaking .connect(authorizer) .increaseAuthorization( stakingProvider.address, - application1Mock.address, - authorized + application2Mock.address, + amount ) - + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address)"](stakingProvider.address) + application1Mock.approveAuthorizationDecrease(stakingProvider.address) await expect( - tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - ).to.be.revertedWith("Keep stake still authorized") + application1Mock.approveAuthorizationDecrease(stakingProvider.address) + ).to.be.revertedWith("No deauthorizing in process") }) }) - context("when unstake before minimum staking time passes", () => { + 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 () => { - 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 + await tokenStaking + .connect(authorizer) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + amountToDecrease + ) + tx = await application1Mock.approveAuthorizationDecrease( + stakingProvider.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 decrease authorized amount", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(expectedToAmount) }) - 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) + 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) + }) - await expect( - tokenStaking.connect(staker).unstakeKeep(stakingProvider.address) - ).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, + expectedFromAmount, + expectedToAmount + ) }) }) context( - "when authorized amount is less than non-Keep stake and enough time passed", + "when approve after request of full deauthorization for one app", () => { - const tAmount = initialStakerBalance - const keepAmount = initialStakerBalance - const keepInTAmount = convertToT(keepAmount, keepRatio).result - const authorized = tAmount + const otherAmount = amount.div(3) 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) - - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tAmount) - + .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, - authorized + amount ) - - 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) + tx = await application1Mock.approveAuthorizationDecrease( + stakingProvider.address + ) }) - it("should decrease available amount to authorize", async () => { + it("should decrease authorized amount", async () => { expect( - await tokenStaking.getAvailableToAuthorize( + await tokenStaking.authorizedStake( stakingProvider.address, application1Mock.address ) - ).to.equal(tAmount.sub(authorized)) + ).to.equal(0) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(otherAmount) }) - it("should update min staked amount", async () => { + it("should decrease min staked amount in T", async () => { expect( await tokenStaking.getMinStaked( stakingProvider.address, StakeTypes.T ) - ).to.equal(tAmount) + ).to.equal(otherAmount) expect( await tokenStaking.getMinStaked( stakingProvider.address, @@ -4193,420 +1617,481 @@ describe("TokenStaking", () => { ).to.equal(0) }) - it("should emit Unstaked", async () => { + it("should emit AuthorizationDecreaseApproved", async () => { await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, keepInTAmount) + .to.emit(tokenStaking, "AuthorizationDecreaseApproved") + .withArgs( + stakingProvider.address, + application1Mock.address, + amount, + Zero + ) }) + } + ) - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - tAmount + 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 the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - tAmount - ) + 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("unstakeNu", () => { - context("when staking provider has no stake", () => { + describe("forceDecreaseAuthorization", () => { + context("when application is not approved", () => { it("should revert", async () => { await expect( - tokenStaking.unstakeNu(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 nucypherStakingMock.setStaker( - staker.address, - initialStakerBalance - ) + 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) - .stakeNu( + .stake( stakingProvider.address, beneficiary.address, - authorizer.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(authorizer).unstakeNu(stakingProvider.address, 0) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + ).to.be.revertedWith("Can't pause application") }) }) - context("when unstake before minimum staking time passes", () => { - const nuAmount = initialStakerBalance - const nuInTAmount = convertToT(nuAmount, nuRatio).result - const minTAmount = nuInTAmount.div(5) + context("when pause active application", () => { + let tx beforeEach(async () => { - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) + tx = await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.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") - }) + it("should pause application", async () => { + expect( + await tokenStaking.applicationInfo(application1Mock.address) + ).to.deep.equal([ApplicationStatus.PAUSED, panicButton.address]) }) - 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") - }) + 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 + ) }) - 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 emit ApplicationStatusChanged", async () => { + await expect(tx) + .to.emit(tokenStaking, "ApplicationStatusChanged") + .withArgs(application1Mock.address, ApplicationStatus.PAUSED) }) + }) + }) - 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") - }) - }) + describe("disableApplication", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) }) - context("when amount to unstake is zero", () => { + 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(panicButton) + .disableApplication(application1Mock.address) + ).to.be.revertedWith("Caller is not the governance") }) }) - context("when stake is only in Keep and T", () => { + context("when application is not approved", () => { 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 tToken - .connect(staker) - .approve(tokenStaking.address, initialStakerBalance) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, initialStakerBalance) - - const amountToUnstake = 1 await expect( tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") + .connect(deployer) + .disableApplication(application2Mock.address) + ).to.be.revertedWith("Can't disable application") }) }) - context("when amount to unstake is more than not authorized", () => { + context("when application is disabled", () => { 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 + .disableApplication(application1Mock.address) await expect( tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address, amountToUnstake) - ).to.be.revertedWith("Too much to unstake") + .connect(deployer) + .disableApplication(application1Mock.address) + ).to.be.revertedWith("Can't disable application") }) }) - 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) - }) + const contextDisable = (preparation) => { + let tx - it("should update Nu staked amount", async () => { - await assertStakes( - stakingProvider.address, - tAmount, - Zero, - expectedNuInTAmount - ) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - expectedNuAmount - ) - }) + beforeEach(async () => { + await preparation() - 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(deployer) + .disableApplication(application1Mock.address) + }) - it("should start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) - }) + it("should disable application", async () => { + expect( + await tokenStaking.applicationInfo(application1Mock.address) + ).to.deep.equal([ApplicationStatus.DISABLED, panicButton.address]) + }) - 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 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 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 ApplicationStatusChanged", async () => { + await expect(tx) + .to.emit(tokenStaking, "ApplicationStatusChanged") + .withArgs(application1Mock.address, ApplicationStatus.DISABLED) + }) + } - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, expectedUnstaked) - }) + context("when disable approved application", () => { + contextDisable(() => {}) + }) - it("should decrease the delegatee voting power", async () => { - expect(await tokenStaking.getVotes(delegatee.address)).to.equal( - expectedNuInTAmount.add(tAmount) - ) - }) + context("when disable paused application", () => { + contextDisable(async () => { + await tokenStaking + .connect(panicButton) + .pauseApplication(application1Mock.address) + }) + }) + }) - it("should decrease the total voting power", async () => { - const lastBlock = await mineBlocks(1) - expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( - expectedNuInTAmount.add(tAmount) - ) - }) - } - ) + describe("setPanicButton", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + }) - 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 + 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") + }) + }) - beforeEach(async () => { - await tokenStaking + context("when application was not approved", () => { + it("should revert", async () => { + await expect( + tokenStaking .connect(deployer) - .approveApplication(application1Mock.address) - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking - .connect(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) + .setPanicButton(application2Mock.address, panicButton.address) + ).to.be.revertedWith("Application is not approved") + }) + }) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorized - ) + 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") + }) + }) - await increaseTime(86400) // +24h + context("when set panic button address for approved application", () => { + let tx - tx = await tokenStaking - .connect(stakingProvider) - .unstakeNu(stakingProvider.address, amountToUnstake) - }) + beforeEach(async () => { + tx = await tokenStaking + .connect(deployer) + .setPanicButton(application1Mock.address, panicButton.address) + }) - it("should update Nu staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - Zero, - expectedNuInTAmount - ) - expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( - expectedNuAmount - ) - }) + it("should set address of panic button", async () => { + expect( + await tokenStaking.applicationInfo(application1Mock.address) + ).to.deep.equal([ApplicationStatus.APPROVED, panicButton.address]) + }) - it("should emit Unstaked", async () => { - await expect(tx) - .to.emit(tokenStaking, "Unstaked") - .withArgs(stakingProvider.address, expectedUnstaked) - }) - } - ) + it("should emit PanicButtonSet", async () => { + await expect(tx) + .to.emit(tokenStaking, "PanicButtonSet") + .withArgs(application1Mock.address, panicButton.address) + }) + }) }) - describe("unstakeAll", () => { - context("when staking provider has no stake", () => { + describe("setAuthorizationCeiling", () => { + context("when caller is not the governance", () => { it("should revert", async () => { await expect( - tokenStaking.unstakeAll(deployer.address) - ).to.be.revertedWith("Not owner or provider") + tokenStaking.connect(staker).setAuthorizationCeiling(1) + ).to.be.revertedWith("Caller is not the governance") }) }) - context("when caller is not owner or staking provider", () => { + 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) @@ -4615,386 +2100,606 @@ describe("TokenStaking", () => { .connect(staker) .stake( stakingProvider.address, - beneficiary.address, - authorizer.address, + staker.address, + staker.address, initialStakerBalance ) await expect( - tokenStaking.connect(authorizer).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Not owner or provider") + tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, 0) + ).to.be.revertedWith("Parameters must be specified") }) }) - context("when authorized amount is not zero", () => { + context("when staking provider has no delegated stake", () => { it("should revert", async () => { - const amount = initialStakerBalance - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) + 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, - beneficiary.address, - authorizer.address, + staker.address, + staker.address, amount ) - const authorized = 1 + 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 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, - authorized + application1Mock.address ) + ).to.equal(expectedAmount) + }) - await expect( - tokenStaking - .connect(stakingProvider) - .unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Stake still authorized") + 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 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) + 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, - beneficiary.address, - authorizer.address, + staker.address, + staker.address, amount ) - await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) + blockTimestamp = await lastBlockTime() - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") + 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 unstake Nu before minimum staking time passes", () => { - it("should revert", async () => { - const nuAmount = initialStakerBalance - await nucypherStakingMock.setStaker(staker.address, nuAmount) + 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(staker) - .stakeNu( - stakingProvider.address, - beneficiary.address, - authorizer.address - ) + .connect(deployer) + .setMinimumStakeAmount(topUpAmount.add(1)) - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTAmount, + 0 + ) + blockTimestamp = await lastBlockTime() + + await tokenStaking + .connect(staker) + .delegateVoting(stakingProvider.address, delegatee.address) + await tToken.connect(deployer).transfer(authorizer.address, topUpAmount) + await tToken + .connect(authorizer) + .approve(tokenStaking.address, topUpAmount) + tx = await tokenStaking + .connect(authorizer) + .topUp(stakingProvider.address, topUpAmount) }) - }) - context("when unstake Keep before minimum time passes", () => { - it("should revert", async () => { - const keepAmount = initialStakerBalance - const createdAt = 1 - await keepStakingMock.setOperator( + 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, - createdAt, - 0, - keepAmount + ]) + }) + + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address) + ).to.equal(blockTimestamp) + }) + + it("should increase available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(topUpAmount) + }) + + it("should transfer tokens to the staking contract", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal( + topUpAmount ) - await keepStakingMock.setEligibility( - stakingProvider.address, - tokenStaking.address, - true + }) + + 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 ) - await tokenStaking.stakeKeep(stakingProvider.address) + }) - await expect( - tokenStaking.connect(staker).unstakeAll(stakingProvider.address) - ).to.be.revertedWith("Can't unstake earlier than 24h") + it("should increase the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + expectedAmount + ) }) }) - const contextUnstakeAll = (preparation, tAmount, nuAmount, keepAmount) => { + context("when staking provider has NuCypher stake", () => { + const nuAmount = initialStakerBalance const nuInTAmount = convertToT(nuAmount, nuRatio).result - const keepInTAmount = convertToT(keepAmount, keepRatio).result + const topUpAmount = initialStakerBalance.mul(2) + const expectedAmount = nuInTAmount.add(topUpAmount) let tx let blockTimestamp beforeEach(async () => { - blockTimestamp = await preparation() + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + staker.address, + staker.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + nuInTAmount + ) + blockTimestamp = await lastBlockTime() + await tokenStaking + .connect(staker) + .delegateVoting(stakingProvider.address, delegatee.address) + await tToken + .connect(deployer) + .transfer(stakingProvider.address, topUpAmount) + await tToken + .connect(stakingProvider) + .approve(tokenStaking.address, topUpAmount) tx = await tokenStaking .connect(stakingProvider) - .unstakeAll(stakingProvider.address) + .topUp(stakingProvider.address, topUpAmount) }) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) + 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, - beneficiary.address, - authorizer.address, - ]) + ).to.deep.equal([staker.address, staker.address, staker.address]) + }) + + it("should not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address) + ).to.equal(blockTimestamp) + }) + + it("should increase available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(topUpAmount) + }) + + it("should transfer tokens to the staking contract", async () => { + expect(await tToken.balanceOf(tokenStaking.address)).to.equal( + topUpAmount + ) + }) + + it("should emit ToppedUp event", async () => { + await expect(tx) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, topUpAmount) + }) + + it("should increase the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal( + expectedAmount + ) + }) + + it("should increase the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + expectedAmount + ) + }) + }) + }) + + describe("unstakeT", () => { + context("when staking provider has no stake", () => { + it("should revert", async () => { + await expect( + tokenStaking.unstakeT(deployer.address, 0) + ).to.be.revertedWith("Not owner or provider") + }) + }) + + 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).unstakeT(stakingProvider.address, 0) + ).to.be.revertedWith("Not owner or provider") }) + }) - it("should not update start staking timestamp", async () => { - expect( - await tokenStaking.getStartStakingTimestamp(stakingProvider.address) - ).to.equal(blockTimestamp) + context("when amount to unstake is zero", () => { + it("should revert", async () => { + await tToken + .connect(staker) + .approve(tokenStaking.address, initialStakerBalance) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + initialStakerBalance + ) + await expect( + tokenStaking.connect(staker).unstakeT(stakingProvider.address, 0) + ).to.be.revertedWith("Too much to unstake") }) + }) - 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( + context("when stake is only in Keep and Nu", () => { + it("should revert", async () => { + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + initialStakerBalance, initialStakerBalance ) - }) - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) + const amountToUnstake = 1 + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeT(stakingProvider.address, amountToUnstake) + ).to.be.revertedWith("Too much to unstake") }) + }) - 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 + 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) - .stake( - stakingProvider.address, - beneficiary.address, - authorizer.address, - tAmount - ) - 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) - - // - // 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) + .unstakeT(stakingProvider.address, amountToUnstake) + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) - // - // stake NU - // - await nucypherStakingMock.setStaker(staker.address, nuAmount) - 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) - .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 - .connect(staker) - .topUpKeep(stakingProvider.address) + context("when another stake type was topped-up", () => { + it("should revert", async () => { + const nuAmount = initialStakerBalance + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + nuAmount + ) - // - // top-up T - // - await tToken.connect(staker).approve(tokenStaking.address, tAmount) - await tokenStaking + const amountToUnstake = amount + await expect( + tokenStaking .connect(staker) - .topUp(stakingProvider.address, tAmount) - - await increaseTime(86400) // +24h - return blockTimestamp - }, - tAmount, - nuAmount, - keepAmount - ) - } - ) - - 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) + .unstakeT(stakingProvider.address, amountToUnstake) + ).to.be.revertedWith("Can't unstake earlier than 24h") + }) + }) + }) - contextUnstakeAll( - async () => { - await tokenStaking - .connect(deployer) - .approveApplication(application1Mock.address) - await tokenStaking.connect(deployer).setMinimumStakeAmount(1) + 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 + + tx = await tokenStaking + .connect(stakingProvider) + .unstakeT(stakingProvider.address, amount) + }) - // - // top-up NU - // - await nucypherStakingMock.setStaker(staker.address, nuAmount) - await tokenStaking.connect(staker).topUpNu(stakingProvider.address) + it("should update T staked amount", async () => { + await assertStakes(stakingProvider.address, Zero, Zero, Zero) + }) - await increaseTime(86400) // +24h - return blockTimestamp - }, - tAmount, - nuAmount, - keepAmount - ) + 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) @@ -5007,738 +2712,419 @@ describe("TokenStaking", () => { authorizer.address, 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 + authorizer.address ) - await keepStakingMock.setEligibility( + await tokenStaking.addLegacyStake( stakingProvider.address, - tokenStaking.address, - true + keepAmount, + 0 ) - 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) - }) - 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) - - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await keepStakingMock.setAmount( - stakingProvider.address, - newKeepAmount - ) + 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 - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - authorizedAmount1 - ) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application2Mock.address, - authorizedAmount2 - ) + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTAmount, + 0 + ) + blockTimestamp = await lastBlockTime() - it("should update staked amount", async () => { - await assertStakes( - stakingProvider.address, - Zero, - expectedKeepInTAmount, - Zero - ) - }) + await tokenStaking + .connect(staker) + .delegateVoting(stakingProvider.address, delegatee.address) - 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 tToken.connect(staker).approve(tokenStaking.address, tAmount) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, tAmount) - 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) - }) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + 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(0) - expect( - await tokenStaking.getMinStaked( - stakingProvider.address, - StakeTypes.KEEP - ) - ).to.equal(expectedKeepInTAmount) - }) + tx = await tokenStaking + .connect(stakingProvider) + .unstakeKeep(stakingProvider.address) + }) - 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 set Keep staked amount to zero", async () => { + await assertStakes(stakingProvider.address, tAmount, Zero, Zero) + }) - 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 roles", async () => { + expect( + await tokenStaking.rolesOf(stakingProvider.address) + ).to.deep.equal([ + staker.address, + beneficiary.address, + authorizer.address, + ]) + }) - 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 not update start staking timestamp", async () => { + expect( + await tokenStaking.getStartStakingTimestamp(stakingProvider.address) + ).to.equal(blockTimestamp) + }) - 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 + it("should decrease available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address ) - const tStake = initialStakerBalance - const authorizedAmount = keepInTAmount.sub(1).add(tStake) - const authorizationDeacrease = keepInTAmount.div(2).add(tStake) + ).to.equal(tAmount.sub(authorized)) + }) - beforeEach(async () => { - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await keepStakingMock.setAmount( - stakingProvider.address, - newKeepAmount - ) + 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) + }) - await tToken.connect(staker).approve(tokenStaking.address, tStake) - await tokenStaking - .connect(staker) - .topUp(stakingProvider.address, tStake) + it("should emit Unstaked", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, keepInTAmount) + }) - 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 delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) + }) - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) + it("should decrease the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + tAmount + ) + }) + }) + }) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, tStake, Zero, Zero) - }) + 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") + }) + }) - it("should decrease available amount to authorize", async () => { - expect( - await tokenStaking.getAvailableToAuthorize( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(0) - }) + context("when caller is not owner or staking provider", () => { + it("should revert", async () => { + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + initialStakerBalance + ) + await expect( + tokenStaking.connect(authorizer).unstakeNu(stakingProvider.address) + ).to.be.revertedWith("Not owner or provider") + }) + }) - it("should decrease authorized amount", async () => { - expect( - await tokenStaking.authorizedStake( - stakingProvider.address, - application1Mock.address - ) - ).to.equal(tStake) - }) + 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 + ) - 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 tToken + .connect(staker) + .approve(tokenStaking.address, initialStakerBalance) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, initialStakerBalance) - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - tStake, - Zero - ) - await application1Mock.approveAuthorizationDecrease( - stakingProvider.address - ) - await assertApplicationStakingProviders( - application1Mock, - stakingProvider.address, - Zero, - Zero - ) - }) + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeNu(stakingProvider.address) + ).to.be.revertedWith("Nothing to unstake") + }) + }) - 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 - ) - }) - } - ) + 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 + ) - 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 authorized = nuInTAmount.div(3) + await tokenStaking + .connect(authorizer) + .forceIncreaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorized ) - const undelegatedAt = ethers.BigNumber.from(2) - let brokenApplicationMock - let expensiveApplicationMock - 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 expect( + tokenStaking + .connect(stakingProvider) + .unstakeNu(stakingProvider.address) + ).to.be.revertedWith("NU stake still authorized") + }) + }) - await tokenStaking - .connect(deployer) - .approveApplication(brokenApplicationMock.address) - await tokenStaking - .connect(deployer) - .approveApplication(expensiveApplicationMock.address) + 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 - await tokenStaking - .connect(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - await keepStakingMock.setUndelegatedAt( - stakingProvider.address, - undelegatedAt - ) + beforeEach(async () => { + await tokenStaking.connect(deployer).setMinimumStakeAmount(1) + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + 0, + nuInTAmount + ) + blockTimestamp = await lastBlockTime() - await tokenStaking - .connect(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 - ) + 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) - tx = await tokenStaking - .connect(otherStaker) - .notifyKeepStakeDiscrepancy(stakingProvider.address) - }) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorized + ) - it("should update staked amount", async () => { - await assertStakes(stakingProvider.address, Zero, Zero, Zero) - }) + await increaseTime(86400) // +24h + tx = await tokenStaking + .connect(stakingProvider) + .unstakeNu(stakingProvider.address) + }) - 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 Nu staked amount", async () => { + await assertStakes( + stakingProvider.address, + tAmount, + Zero, + expectedNuInTAmount + ) + expect(await tokenStaking.stakedNu(stakingProvider.address)).to.equal( + expectedNuAmount + ) + }) - 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 not update roles", async () => { + expect( + await tokenStaking.rolesOf(stakingProvider.address) + ).to.deep.equal([ + staker.address, + beneficiary.address, + authorizer.address, + ]) + }) - 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 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, - brokenApplicationMock.address, - authorizedAmount, - Zero, - false - ) - await expect(tx) - .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") - .withArgs( - stakingProvider.address, - expensiveApplicationMock.address, - authorizedAmount, - Zero, - false - ) - }) - } - ) + it("should decrease available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + }) + + it("should update min staked amount", async () => { + expect( + await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) + ).to.equal(tAmount) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU + ) + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP + ) + ).to.equal(0) + }) + + it("should emit Unstaked", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, expectedUnstaked) + }) + + it("should decrease the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) + }) + + it("should decrease the total voting power", async () => { + const lastBlock = await mineBlocks(1) + expect(await tokenStaking.getPastTotalSupply(lastBlock - 1)).to.equal( + tAmount + ) + }) }) }) - describe("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) @@ -5751,374 +3137,278 @@ describe("TokenStaking", () => { authorizer.address, 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( - 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 - ) - - 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) - }) - }) - - 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( - stakingProvider.address, - Zero, - Zero, - newNuInTAmount - ) - }) - - it("should not call seize in NuCypher contract", async () => { - await assertNuStakers( - staker.address, - newNuAmount, - stakingProvider.address - ) - 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) - }) - }) - - 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) - - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - stakingProvider.address, - application1Mock.address, - newNuInTAmount - ) - - tx = await tokenStaking - .connect(otherStaker) - .notifyNuStakeDiscrepancy(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) - }) - - 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) - }) - - it("should inform application", async () => { - await assertApplicationStakingProviders( - application1Mock, + .connect(authorizer) + .increaseAuthorization( 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, - newNuInTAmount, - Zero, - true - ) - }) + await expect( + tokenStaking + .connect(stakingProvider) + .unstakeAll(stakingProvider.address) + ).to.be.revertedWith("Stake still authorized") }) + }) - 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) - - beforeEach(async () => { - 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, - expectedNuInTAmount - ) - }) - - it("should call seize in NuCypher contract", async () => { - await assertNuStakers( - staker.address, - expectedNuAmount, - stakingProvider.address + beneficiary.address, + authorizer.address, + amount ) - expect( - await nucypherStakingMock.investigators(otherStaker.address) - ).to.equal(expectedReward) - }) + await tokenStaking.connect(deployer).setMinimumStakeAmount(minAmount) - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs( - stakingProvider.address, - convertToT(nuPenalty, nuRatio).result, - true - ) - }) + await expect( + tokenStaking.connect(staker).unstakeAll(stakingProvider.address) + ).to.be.revertedWith("Can't unstake earlier than 24h") }) + }) - 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 + 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) - beforeEach(async () => { - 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(stakingProvider.address, Zero, Zero, Zero) - }) - - it("should call seize in NuCypher contract", async () => { - await assertNuStakers( - staker.address, - expectedNuAmount, - stakingProvider.address - ) - expect( - await nucypherStakingMock.investigators(otherStaker.address) - ).to.equal(expectedReward) - }) - - it("should emit TokensSeized", async () => { - await expect(tx) - .to.emit(tokenStaking, "TokensSeized") - .withArgs(stakingProvider.address, expectedNuPenalty.result, true) - }) + await expect( + tokenStaking.connect(staker).unstakeAll(stakingProvider.address) + ).to.be.revertedWith("Can't unstake earlier than 24h") }) }) - }) - - describe("setStakeDiscrepancyPenalty", () => { - const tPenalty = initialStakerBalance - const rewardMultiplier = 100 - context("when caller is not the governance", () => { + context("when unstake Keep before minimum time passes", () => { it("should revert", async () => { + const keepAmount = initialStakerBalance + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepAmount, + 0 + ) + await expect( - tokenStaking - .connect(staker) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) - ).to.be.revertedWith("Caller is not the governance") + tokenStaking.connect(staker).unstakeAll(stakingProvider.address) + ).to.be.revertedWith("Can't unstake earlier than 24h") }) }) - context("when caller is the governance", () => { + 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(deployer) - .setStakeDiscrepancyPenalty(tPenalty, rewardMultiplier) + .connect(stakingProvider) + .unstakeAll(stakingProvider.address) }) - it("should set values", async () => { - expect(await tokenStaking.stakeDiscrepancyPenalty()).to.equal(tPenalty) - expect(await tokenStaking.stakeDiscrepancyRewardMultiplier()).to.equal( - rewardMultiplier + 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 emit StakeDiscrepancyPenaltySet event", async () => { + it("should decrease available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + }) + + it("should update min staked amount", async () => { + expect( + await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU + ) + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP + ) + ).to.equal(0) + }) + + it("should emit Unstaked", async () => { await expect(tx) - .to.emit(tokenStaking, "StakeDiscrepancyPenaltySet") - .withArgs(tPenalty, rewardMultiplier) + .to.emit(tokenStaking, "Unstaked") + .withArgs( + stakingProvider.address, + nuInTAmount.add(keepInTAmount).add(tAmount) + ) }) - }) + } + + context( + "when unstake after minimum staking time passes for T stake", + () => { + // subtracting arbitrary values just to keep them different + const tAmount = initialStakerBalance.sub(1) + const nuAmount = initialStakerBalance.sub(2) + const keepAmount = initialStakerBalance.sub(3) + const nuInTAmount = convertToT(nuAmount, nuRatio).result + const keepInTAmount = convertToT(keepAmount, keepRatio).result + + contextUnstakeAll( + async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking.connect(deployer).setMinimumStakeAmount(1) + + // + // stake T + // + await tToken.connect(staker).approve(tokenStaking.address, tAmount) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + tAmount + ) + const blockTimestamp = await lastBlockTime() + + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTAmount, + nuInTAmount + ) + + await increaseTime(86400) // +24h + return blockTimestamp + }, + tAmount, + nuAmount, + keepAmount + ) + } + ) + + context( + "when unstake after minimum staking time passes for NU and KEEP stake", + () => { + // subtracting arbitrary values just to keep them different + const tAmount = initialStakerBalance.sub(3) + const nuAmount = initialStakerBalance.sub(1) + const keepAmount = initialStakerBalance.sub(2) + const nuInTAmount = convertToT(nuAmount, nuRatio).result + const keepInTAmount = convertToT(keepAmount, keepRatio).result + + contextUnstakeAll( + async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking.connect(deployer).setMinimumStakeAmount(1) + + // + // legacy stake NU and KEEP + // + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTAmount, + nuInTAmount + ) + const blockTimestamp = await lastBlockTime() + + // + // top-up T + // + await tToken.connect(staker).approve(tokenStaking.address, tAmount) + await tokenStaking + .connect(staker) + .topUp(stakingProvider.address, tAmount) + + await increaseTime(86400) // +24h + return blockTimestamp + }, + tAmount, + nuAmount, + keepAmount + ) + } + ) }) describe("setNotificationReward", () => { @@ -6320,8 +3610,8 @@ describe("TokenStaking", () => { ) 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 @@ -6345,16 +3635,16 @@ describe("TokenStaking", () => { 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) @@ -6714,12 +4004,8 @@ describe("TokenStaking", () => { }) 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 +4014,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 () => { @@ -6770,29 +4055,16 @@ describe("TokenStaking", () => { 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) @@ -6852,7 +4124,7 @@ 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 ) @@ -6861,36 +4133,11 @@ describe("TokenStaking", () => { ) }) - 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 + it("should increase amount in notifiers treasury ", async () => { + const expectedTreasuryBalance = amountToSlash.sub(expectedTReward1) + expect(await tokenStaking.notifiersTreasury()).to.equal( + expectedTreasuryBalance ) - expect( - await nucypherStakingMock.investigators(auxiliaryAccount.address) - ).to.equal(0) }) it("should decrease authorized amounts only for one provider", async () => { @@ -6979,10 +4226,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,7 +4239,7 @@ 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 ) @@ -7007,29 +4250,13 @@ describe("TokenStaking", () => { it("should increase amount in notifiers treasury ", async () => { const expectedTreasuryBalance = amountToSlash - .add(tAmount) + .add(tStake2) .sub(expectedReward) expect(await tokenStaking.notifiersTreasury()).to.equal( 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( @@ -7089,11 +4316,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) @@ -7146,7 +4369,7 @@ 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 ) @@ -7270,11 +4493,7 @@ describe("TokenStaking", () => { ) extendedTokenStaking = await ExtendedTokenStaking.deploy( tToken.address, - keepStakingMock.address, - nucypherStakingMock.address, - keepVendingMachine.address, - nucypherVendingMachine.address, - keepStake.address + nucypherVendingMachine.address ) await extendedTokenStaking.deployed() }) @@ -7392,6 +4611,306 @@ describe("TokenStaking", () => { ) }) + describe("forceUnstakeLegacy", () => { + const tAmount = initialStakerBalance + const keepInTStake = convertToT(initialStakerBalance, keepRatio).result + const nuInTStake = convertToT(initialStakerBalance, nuRatio).result + + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tToken + .connect(staker) + .approve(tokenStaking.address, initialStakerBalance) + await tokenStaking + .connect(staker) + .stake(stakingProvider.address, staker.address, staker.address, tAmount) + }) + + context("when no legacy stake", () => { + it("should revert", async () => { + await tokenStaking + .connect(staker) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + tAmount + ) + await expect( + tokenStaking["forceUnstakeLegacy(address)"](stakingProvider.address) + ).to.be.revertedWith("No legacy stake") + }) + }) + + context("when authorized only T stake", () => { + let tx + + beforeEach(async () => { + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTStake, + nuInTStake + ) + await tokenStaking + .connect(staker) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + tAmount + ) + await tokenStaking + .connect(staker) + .delegateVoting(stakingProvider.address, delegatee.address) + + tx = await tokenStaking["forceUnstakeLegacy(address)"]( + stakingProvider.address + ) + }) + + it("should update staked amount", async () => { + await assertStakes(stakingProvider.address, tAmount, Zero, Zero) + }) + + it("should decrease the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) + }) + + it("should not decrease authorized amounts", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(tAmount) + }) + + it("should not inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + tAmount, + Zero + ) + }) + + it("should emit Unstaked event", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, nuInTStake.add(keepInTStake)) + }) + }) + + context("when authorized T, KEEP and NU stakes", () => { + const authorized1 = tAmount.add(keepInTStake).add(nuInTStake) + const authorized2 = nuInTStake.add(keepInTStake) + const deauth = authorized1.sub(tAmount).sub(1) + let tx + + beforeEach(async () => { + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTStake, + nuInTStake + ) + await tokenStaking + .connect(staker) + .forceIncreaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorized1 + ) + await tokenStaking + .connect(staker) + .forceIncreaseAuthorization( + stakingProvider.address, + application2Mock.address, + authorized2 + ) + await tokenStaking + .connect(staker) + ["requestAuthorizationDecrease(address,address,uint96)"]( + stakingProvider.address, + application1Mock.address, + deauth + ) + await tokenStaking + .connect(staker) + .delegateVoting(stakingProvider.address, delegatee.address) + + tx = await tokenStaking["forceUnstakeLegacy(address)"]( + stakingProvider.address + ) + }) + + it("should update staked amount", async () => { + await assertStakes(stakingProvider.address, tAmount, Zero, Zero) + }) + + it("should decrease the delegatee voting power", async () => { + expect(await tokenStaking.getVotes(delegatee.address)).to.equal(tAmount) + }) + + it("should decrease authorized amounts", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(tAmount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(tAmount) + }) + + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + tAmount, + Zero + ) + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + tAmount, + Zero + ) + }) + + it("should emit Unstaked and AuthorizationInvoluntaryDecreased event", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, nuInTStake.add(keepInTStake)) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + authorized1, + tAmount, + true + ) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + authorized2, + tAmount, + true + ) + }) + }) + + context("when unstake multiple legacy stakes", () => { + let tx + + beforeEach(async () => { + await tokenStaking.addLegacyStake( + stakingProvider.address, + keepInTStake, + 0 + ) + await tokenStaking.setLegacyStakingProvider( + stakingProvider.address, + staker.address, + beneficiary.address, + authorizer.address + ) + await tokenStaking.addLegacyStake(otherStaker.address, 0, nuInTStake) + + await tokenStaking + .connect(staker) + .forceIncreaseAuthorization( + stakingProvider.address, + application1Mock.address, + tAmount.add(keepInTStake) + ) + await tokenStaking + .connect(staker) + .forceIncreaseAuthorization( + otherStaker.address, + application1Mock.address, + nuInTStake + ) + + tx = await tokenStaking["forceUnstakeLegacy(address[])"]([ + stakingProvider.address, + otherStaker.address, + ]) + }) + + it("should update staked amount", async () => { + await assertStakes(stakingProvider.address, tAmount, Zero, Zero) + await assertStakes(otherStaker.address, Zero, Zero, Zero) + }) + + it("should decrease authorized amounts", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(tAmount) + expect( + await tokenStaking.authorizedStake( + otherStaker.address, + application1Mock.address + ) + ).to.equal(Zero) + }) + + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + tAmount, + Zero + ) + await assertApplicationStakingProviders( + application1Mock, + otherStaker.address, + Zero, + Zero + ) + }) + + it("should emit Unstaked and AuthorizationInvoluntaryDecreased event", async () => { + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(stakingProvider.address, keepInTStake) + await expect(tx) + .to.emit(tokenStaking, "Unstaked") + .withArgs(otherStaker.address, nuInTStake) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + tAmount.add(keepInTStake), + tAmount, + true + ) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationInvoluntaryDecreased") + .withArgs( + otherStaker.address, + application1Mock.address, + nuInTStake, + Zero, + true + ) + }) + }) + }) + async function assertStakes( address, expectedTStake, @@ -7412,22 +4931,6 @@ describe("TokenStaking", () => { ).to.equal(expectedNuInTStake) } - async function assertNuStakers( - stakerAddress, - expectedValue, - expectedStakingProvider - ) { - expect( - (await nucypherStakingMock.stakers(stakerAddress)).value, - "invalid value" - ).to.equal(expectedValue) - - expect( - (await nucypherStakingMock.stakers(stakerAddress)).stakingProvider, - "invalid stakingProvider" - ).to.equal(expectedStakingProvider) - } - async function assertApplicationStakingProviders( applicationMock, stakingProviderAddress, @@ -7447,30 +4950,6 @@ describe("TokenStaking", () => { ).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, diff --git a/test/system/constants.js b/test/system/constants.js deleted file mode 100644 index 107c25e1..00000000 --- a/test/system/constants.js +++ /dev/null @@ -1,43 +0,0 @@ -const { helpers } = require("hardhat") -const { to1e18 } = helpers.number - -module.exports.keepTokenAddress = "0x85Eee30c52B0b379b046Fb0F85F4f3Dc3009aFEC" -module.exports.keepTokenGrantAddress = - "0x175989c71fd023d580c65f5dc214002687ff88b7" -module.exports.nuCypherTokenAddress = - "0x4fE83213D56308330EC302a8BD641f1d0113A4Cc" -module.exports.keepTokenStakingAddress = - "0x1293a54e160D1cd7075487898d65266081A15458" -module.exports.nuCypherStakingEscrowAddress = - "0xbbD3C0C794F40c4f993B03F65343aCC6fcfCb2e2" -module.exports.keepRegistryAddress = - "0x1a9589F56c969d6b0D3787ea02322476eAd3fB05" - -// liquid token owner with 3,000,000 staked -module.exports.keepLiquidTokenStake = { - owner: "0x4A0A927043B01a7fB175BCa4F4837e3b817C5e6b", - operator: "0x64A8856cBD255765D16B901a0B899daefC78FB13", - authorizer: "0x5481188e698a5752c5cab6e2494fc2cfbb644f2d", - beneficiary: "0xa49f1b845a8086ac0b820ce6fa8ce92d223765d2", - keepStaked: to1e18("3000000"), -} - -// managed grantee with 1,196,000 KEEP staked -module.exports.keepManagedGrantStake = { - grantee: "0x011074cA9EEff0836a68b170E46c4d20F8CAc727", - operator: "0xc6349eEC31048787676b6297ba71721376A8DdcF", - authorizer: "0x011074cA9EEff0836a68b170E46c4d20F8CAc727", - beneficiary: "0x011074cA9EEff0836a68b170E46c4d20F8CAc727", - managedGrant: "0xac1a985E75C6a0b475b9c807Ad0705a988Be2D99", - keepStaked: to1e18("1196000"), -} - -// standard grant delegation with 832,533 staked -module.exports.keepGrantStake = { - grantee: "0xf6f372DfAeCC1431186598c304e91B79Ce115766", - operator: "0x8Bd660A764Ca14155F3411a4526a028b6316CB3E", - authorizer: "0x826b18a8c61e976156a962613e2c189b3ee5f2cb", - beneficiary: "0xf6f372DfAeCC1431186598c304e91B79Ce115766", - keepStaked: to1e18("832533"), - grantID: 37, -} diff --git a/test/system/init-contracts.js b/test/system/init-contracts.js deleted file mode 100644 index 0e7c5f4e..00000000 --- a/test/system/init-contracts.js +++ /dev/null @@ -1,180 +0,0 @@ -const { helpers, upgrades } = require("hardhat") -const { impersonateAccount } = helpers.account -const { to1e18 } = helpers.number - -const { - keepTokenAddress, - nuCypherTokenAddress, - keepTokenStakingAddress, - nuCypherStakingEscrowAddress, - keepRegistryAddress, - keepTokenGrantAddress, -} = require("./constants.js") - -async function initContracts() { - const deployer = await ethers.getSigner(0) - - const keepToken = await resolveKeepToken() - const nuCypherToken = await resolveNuCypherToken() - const keepTokenStaking = await resolveKeepTokenStaking() - const nuCypherStakingEscrow = await resolveNuCypherStakingEscrow() - const keepRegistry = await resolveKeepRegistry() - const keepTokenGrant = await resolveKeepTokenGrant() - - // 10 billion T minted - const tToken = await deployTToken(to1e18(10000000000)) - - const keepVendingMachine = await deployVendingMachine( - keepToken, - tToken, - // 999,848,780 KEEP is the current KEEP total supply - to1e18(999848780), - // 45% of T supply goes to KEEP vending machine - to1e18(4500000000) - ) - - const nuCypherVendingMachine = await deployVendingMachine( - nuCypherToken, - tToken, - // 1,350,000,000 is the NU total supply after pausing inflation - // (this number will be different for mainnet deployment but close to this one) - to1e18(1350000000), - // 45% of T supply goes to KEEP vending machine - to1e18(4500000000) - ) - - const keepStake = await deployKeepStake(keepTokenStaking) - - const tokenStaking = await deployTokenStaking( - tToken, - keepTokenStaking, - nuCypherStakingEscrow, - keepVendingMachine, - nuCypherVendingMachine, - keepStake - ) - - // Token staking must be approved in the Keep registry in order - // to work with Keep authorizations. - await keepRegistry - .connect( - await impersonateAccount(await keepRegistry.registryKeeper(), { - from: deployer, - value: "5", - }) - ) - .approveOperatorContract(tokenStaking.address) - - return { - keepToken: keepToken, - keepTokenGrant: keepTokenGrant, - keepTokenStaking: keepTokenStaking, - tokenStaking: tokenStaking, - keepVendingMachine: keepVendingMachine, - nuVendingMaching: nuCypherVendingMachine, - } -} - -async function resolveKeepToken() { - return await ethers.getContractAt("IKeepToken", keepTokenAddress) -} - -async function resolveNuCypherToken() { - return await ethers.getContractAt("IERC20", nuCypherTokenAddress) -} - -async function resolveKeepTokenStaking() { - return await ethers.getContractAt( - "ITestKeepTokenStaking", - keepTokenStakingAddress - ) -} - -async function resolveNuCypherStakingEscrow() { - return await ethers.getContractAt( - "INuCypherStakingEscrow", - nuCypherStakingEscrowAddress - ) -} - -async function resolveKeepRegistry() { - return await ethers.getContractAt("IKeepRegistry", keepRegistryAddress) -} - -async function resolveKeepTokenGrant() { - return await ethers.getContractAt("IKeepTokenGrant", keepTokenGrantAddress) -} - -async function deployTToken() { - const TToken = await ethers.getContractFactory("T") - const tToken = await TToken.deploy() - - await tToken.deployed() - - return tToken -} - -async function deployVendingMachine( - wrappedToken, - tToken, - wrappedTokenAllocation, - tTokenAllocation -) { - const deployer = await ethers.getSigner(0) - await tToken.mint(deployer.address, tTokenAllocation) - - const VendingMachine = await ethers.getContractFactory("VendingMachine") - const vendingMachine = await VendingMachine.deploy( - wrappedToken.address, - tToken.address, - wrappedTokenAllocation, - tTokenAllocation - ) - - await vendingMachine.deployed() - - await tToken.transfer(vendingMachine.address, tTokenAllocation) - - return vendingMachine -} - -async function deployKeepStake(keepTokenStaking) { - const KeepStake = await ethers.getContractFactory("KeepStake") - const keepStake = await KeepStake.deploy(keepTokenStaking.address) - - await keepStake.deployed() - - return keepStake -} - -async function deployTokenStaking( - tToken, - keepTokenStaking, - nuCypherStakingEscrow, - keepVendingMachine, - nuCypherVendingMachine, - keepStake -) { - const TokenStaking = await ethers.getContractFactory("TokenStaking") - const tokenStakingInitializerArgs = [] - const tokenStaking = await upgrades.deployProxy( - TokenStaking, - tokenStakingInitializerArgs, - { - constructorArgs: [ - tToken.address, - keepTokenStaking.address, - nuCypherStakingEscrow.address, - keepVendingMachine.address, - nuCypherVendingMachine.address, - keepStake.address, - ], - } - ) - - await tokenStaking.deployed() - - return tokenStaking -} - -module.exports.initContracts = initContracts diff --git a/test/system/staking.test.js b/test/system/staking.test.js deleted file mode 100644 index 0d57e848..00000000 --- a/test/system/staking.test.js +++ /dev/null @@ -1,534 +0,0 @@ -const { expect } = require("chai") - -const { helpers } = require("hardhat") -const { impersonateAccount } = helpers.account -const { increaseTime } = helpers.time -const { to1e18 } = helpers.number -const { resetFork } = helpers.forking - -const { initContracts } = require("./init-contracts") -const { - keepGrantStake, - keepLiquidTokenStake, - keepManagedGrantStake, - keepTokenGrantAddress, -} = require("./constants") - -const describeFn = - process.env.NODE_ENV === "system-test" ? describe : describe.skip - -describeFn("SystemTests: TokenStaking", () => { - const startingBlock = 13619810 - - let governance - let purse - - // Contracts - let keepToken - let keepTokenStaking - let keepVendingMachine - let tokenStaking - let mockApplication - - beforeEach(async () => { - await resetFork(startingBlock) - - governance = await ethers.getSigner(0) - purse = await ethers.getSigner(1) - - const contracts = await initContracts() - keepToken = contracts.keepToken - keepTokenStaking = contracts.keepTokenStaking - keepVendingMachine = contracts.keepVendingMachine - tokenStaking = contracts.tokenStaking - - const ApplicationMock = await ethers.getContractFactory("ApplicationMock") - mockApplication = await ApplicationMock.deploy(tokenStaking.address) - await mockApplication.deployed() - }) - - const describeStake = ( - ownerAddress, - operatorAddress, - authorizerAddress, - keepStake - ) => { - let owner - let operator - let authorizer - - beforeEach(async () => { - // impersonate and drop 1 ETH for each account - owner = await impersonateAccount(ownerAddress, { - from: purse, - amount: "1", - }) - operator = await impersonateAccount(operatorAddress, { - from: purse, - amount: "1", - }) - authorizer = await impersonateAccount(authorizerAddress, { - from: purse, - amount: "1", - }) - }) - - context("when I have authorized T staking contract", () => { - beforeEach(async () => { - await keepTokenStaking - .connect(authorizer) - .authorizeOperatorContract(operatorAddress, tokenStaking.address) - }) - - context("when I have not undelegated my legacy stake", () => { - describe("stakeKeep", () => { - beforeEach(async () => { - await tokenStaking.stakeKeep(operator.address) - }) - - it("should copy my stake to T staking contract", async () => { - const stakeInT = await keepVendingMachine.conversionToT(keepStake) - const stakes = await tokenStaking.stakes(operatorAddress) - expect(stakes[0]).to.be.equal(0) - expect(stakes[1]).to.be.equal(stakeInT.tAmount) - expect(stakes[2]).to.be.equal(0) - }) - }) - }) - - context("when I have undelegated my legacy stake", () => { - beforeEach(async () => { - await keepTokenStaking.connect(owner).undelegate(operator.address) - }) - - describe("stakeKeep", () => { - it("should revert", async () => { - await expect( - tokenStaking.stakeKeep(operator.address) - ).to.be.revertedWith("Nothing to sync") - }) - }) - }) - }) - } - - const describeTopUp = ( - ownerAddress, - operatorAddress, - authorizerAddress, - beneficiaryAddress, - topUpLegacyStakeFn, - keepStake - ) => { - let owner - let operator - let authorizer - let beneficiary - - beforeEach(async () => { - // impersonate and drop 1 ETH for each account - owner = await impersonateAccount(ownerAddress, { - from: purse, - amount: "1", - }) - operator = await impersonateAccount(operatorAddress, { - from: purse, - amount: "1", - }) - authorizer = await impersonateAccount(authorizerAddress, { - from: purse, - amount: "1", - }) - beneficiary = await impersonateAccount(beneficiaryAddress, { - from: purse, - amount: "1", - }) - }) - - context("when I copied my stake to T staking contract", () => { - beforeEach(async () => { - await keepTokenStaking - .connect(authorizer) - .authorizeOperatorContract(operatorAddress, tokenStaking.address) - await tokenStaking.stakeKeep(operator.address) - }) - - context("when I executed top-up of my legacy stake", () => { - beforeEach(async () => { - await topUpLegacyStakeFn(owner, operator, authorizer, beneficiary) - }) - - describe("topUpKeep", () => { - beforeEach(async () => { - await tokenStaking.connect(owner).topUpKeep(operatorAddress) - }) - - it("should top-up my stake in T staking contract", async () => { - const stakeInT = await keepVendingMachine.conversionToT(keepStake) - const stakes = await tokenStaking.stakes(operatorAddress) - expect(stakes[0]).to.be.equal(0) - expect(stakes[1]).to.be.equal(stakeInT.tAmount) - expect(stakes[2]).to.be.equal(0) - }) - }) - }) - }) - } - - const describeUndelegation = ( - ownerAddress, - operatorAddress, - authorizerAddress, - keepStake - ) => { - let owner - let operator - let authorizer - - beforeEach(async () => { - // impersonate and drop 1 ETH for each account - owner = await impersonateAccount(ownerAddress, { - from: purse, - amount: "1", - }) - operator = await impersonateAccount(operatorAddress, { - from: purse, - amount: "1", - }) - authorizer = await impersonateAccount(authorizerAddress, { - from: purse, - amount: "1", - }) - }) - - context("when I copied my stake to T staking contract", () => { - beforeEach(async () => { - await keepTokenStaking - .connect(authorizer) - .authorizeOperatorContract(operatorAddress, tokenStaking.address) - await tokenStaking.stakeKeep(operator.address) - await increaseTime(86400) // +24h - }) - - context("when I authorized and deauthorized application", () => { - beforeEach(async () => { - await tokenStaking - .connect(governance) - .approveApplication(mockApplication.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - operatorAddress, - mockApplication.address, - keepStake - ) - await tokenStaking - .connect(authorizer) - ["requestAuthorizationDecrease(address)"](operatorAddress) - await mockApplication.approveAuthorizationDecrease(operatorAddress) - }) - - describe("unstakeKeep", () => { - it("should release my KEEP stake", async () => { - await tokenStaking.connect(owner).unstakeKeep(operator.address) - const stakes = await tokenStaking.stakes(operatorAddress) - expect(stakes[0]).to.be.equal(0) - expect(stakes[1]).to.be.equal(0) - expect(stakes[2]).to.be.equal(0) - }) - - it("should let me undelegate my KEEP stake", async () => { - await keepTokenStaking.connect(owner).undelegate(operatorAddress) - // We can't test recover because KEEP stake is locked by tBTC - // deposits but we can ensure neither T staking contract nor the - // authorized application locked the stake. - const locks = await keepTokenStaking.getLocks(operatorAddress) - for (let i = 0; i < locks.creators.length; i++) { - expect(locks.creators[i]).not.to.be.equal(tokenStaking.address) - expect(locks.creators[i]).not.to.be.equal(mockApplication.address) - } - }) - }) - }) - }) - } - - const describeSlashing = (operatorAddress, authorizerAddress, keepStake) => { - let operator - let authorizer - - beforeEach(async () => { - // impersonate and drop 1 ETH for each account - operator = await impersonateAccount(operatorAddress, { - from: purse, - amount: "1", - }) - authorizer = await impersonateAccount(authorizerAddress, { - from: purse, - amount: "1", - }) - }) - - context("when I copied my stake to T staking contract", () => { - beforeEach(async () => { - await keepTokenStaking - .connect(authorizer) - .authorizeOperatorContract(operatorAddress, tokenStaking.address) - await tokenStaking.stakeKeep(operator.address) - }) - - context("when I authorized application", () => { - beforeEach(async () => { - await tokenStaking - .connect(governance) - .approveApplication(mockApplication.address) - await tokenStaking - .connect(authorizer) - .increaseAuthorization( - operatorAddress, - mockApplication.address, - keepStake - ) - }) - - context("when I got slashed by the application", () => { - const slashAmount = to1e18(9121) // amount in [T] - - beforeEach(async () => { - await mockApplication.slash(slashAmount, [operatorAddress]) - await tokenStaking.processSlashing(1) - }) - - describe("processSlashing", () => { - it("should slash my KEEP stake", async () => { - const slashAmountInKeep = - await keepVendingMachine.conversionFromT(slashAmount) - const expectedStakeInKeep = keepStake.sub( - slashAmountInKeep.wrappedAmount - ) - const actualStakeInKeep = await keepTokenStaking.eligibleStake( - operatorAddress, - tokenStaking.address - ) - - expect(actualStakeInKeep).to.equal(expectedStakeInKeep) - }) - }) - - describe("notifyKeepStakeDiscrepancy", () => { - it("should revert", async () => { - await expect( - tokenStaking.notifyKeepStakeDiscrepancy(operatorAddress) - ).to.be.revertedWith("There is no discrepancy") - }) - }) - }) - - context("when the application seized my stake", () => { - const seizeAmount = to1e18(4413) // amount in [T] - - beforeEach(async () => { - await mockApplication.seize(seizeAmount, 100, purse.address, [ - operatorAddress, - ]) - await tokenStaking.processSlashing(1) - }) - - describe("processSlashing", () => { - it("should seize my KEEP stake", async () => { - const seizeAmountInKeep = - await keepVendingMachine.conversionFromT(seizeAmount) - const expectedStakeInKeep = keepStake.sub( - seizeAmountInKeep.wrappedAmount - ) - const actualStakeInKeep = await keepTokenStaking.eligibleStake( - operatorAddress, - tokenStaking.address - ) - - expect(actualStakeInKeep).to.equal(expectedStakeInKeep) - }) - }) - - describe("notifyKeepStakeDiscrepancy", () => { - it("should revert", async () => { - await expect( - tokenStaking.notifyKeepStakeDiscrepancy(operatorAddress) - ).to.be.revertedWith("There is no discrepancy") - }) - }) - }) - }) - }) - } - - context("Given I am KEEP network liquid token staker", () => { - const ownerAddress = keepLiquidTokenStake.owner - const operatorAddress = keepLiquidTokenStake.operator - const authorizerAddress = keepLiquidTokenStake.authorizer - const beneficiaryAddress = keepLiquidTokenStake.beneficiary - const keepStaked = keepLiquidTokenStake.keepStaked - - describeStake(ownerAddress, operatorAddress, authorizerAddress, keepStaked) - - const topUpAmountKeep = to1e18("1000") - const keepStakeAfterTopUp = keepStaked.add(topUpAmountKeep) - const topUpLegacyStakeFn = async ( - owner, - operator, - authorizer, - beneficiary - ) => { - // Send KEEP from the beneficiary account since the owner has 0 KEEP. - await keepToken - .connect(beneficiary) - .transfer(owner.address, topUpAmountKeep) - - const data = ethers.utils.solidityPack( - ["address", "address", "address"], - [beneficiary.address, operator.address, authorizer.address] - ) - await keepToken - .connect(owner) - .approveAndCall(keepTokenStaking.address, topUpAmountKeep, data) - - // Jump beyond the stake initialization period. - // Required to commit the top-up. - await helpers.time.increaseTime(43200) - - await keepTokenStaking.connect(owner).commitTopUp(operator.address) - } - - describeTopUp( - ownerAddress, - operatorAddress, - authorizerAddress, - beneficiaryAddress, - topUpLegacyStakeFn, - keepStakeAfterTopUp - ) - - describeUndelegation( - ownerAddress, - operatorAddress, - authorizerAddress, - keepStaked - ) - - describeSlashing(operatorAddress, authorizerAddress, keepStaked) - }) - - context("Given I am KEEP network managed grant staker", () => { - const ownerAddress = keepManagedGrantStake.grantee - const operatorAddress = keepManagedGrantStake.operator - const authorizerAddress = keepManagedGrantStake.authorizer - const beneficiaryAddress = keepManagedGrantStake.beneficiary - const managedGrantAddress = keepManagedGrantStake.managedGrant - const keepStaked = keepManagedGrantStake.keepStaked - - describeStake(ownerAddress, operatorAddress, authorizerAddress, keepStaked) - - const topUpAmountKeep = to1e18("100") - const keepStakeAfterTopUp = keepStaked.add(topUpAmountKeep) - const topUpLegacyStakeFn = async ( - owner, - operator, - authorizer, - beneficiary - ) => { - const managedGrant = await ethers.getContractAt( - "IKeepManagedGrant", - managedGrantAddress - ) - - const data = ethers.utils.solidityPack( - ["address", "address", "address"], - [beneficiary.address, operator.address, authorizer.address] - ) - await managedGrant - .connect(owner) - .stake(keepTokenStaking.address, topUpAmountKeep, data) - - // Jump beyond the stake initialization period. - // Required to commit the top-up. - await helpers.time.increaseTime(43200) - - await keepTokenStaking.connect(owner).commitTopUp(operator.address) - } - - describeTopUp( - ownerAddress, - operatorAddress, - authorizerAddress, - beneficiaryAddress, - topUpLegacyStakeFn, - keepStakeAfterTopUp - ) - - describeUndelegation( - ownerAddress, - operatorAddress, - authorizerAddress, - keepStaked - ) - - describeSlashing(operatorAddress, authorizerAddress, keepStaked) - }) - - context("Given I am KEEP network non-managed grant staker", () => { - const ownerAddress = keepGrantStake.grantee - const operatorAddress = keepGrantStake.operator - const authorizerAddress = keepGrantStake.authorizer - const beneficiaryAddress = keepGrantStake.beneficiary - const keepStaked = keepGrantStake.keepStaked - const grantID = keepGrantStake.grantID - - describeStake(ownerAddress, operatorAddress, authorizerAddress, keepStaked) - - const topUpAmountKeep = "100000000000000000" // [KEEP] - const keepStakeAfterTopUp = keepStaked.add(topUpAmountKeep) - const topUpLegacyStakeFn = async ( - owner, - operator, - authorizer, - beneficiary - ) => { - const tokenGrant = await ethers.getContractAt( - "IKeepTokenGrant", - keepTokenGrantAddress - ) - - const data = ethers.utils.solidityPack( - ["address", "address", "address"], - [beneficiary.address, operator.address, authorizer.address] - ) - await tokenGrant - .connect(owner) - .stake(grantID, keepTokenStaking.address, topUpAmountKeep, data) - - // Jump beyond the stake initialization period. - // Required to commit the top-up. - await helpers.time.increaseTime(43200) - - await keepTokenStaking.connect(owner).commitTopUp(operator.address) - } - - describeTopUp( - ownerAddress, - operatorAddress, - authorizerAddress, - beneficiaryAddress, - topUpLegacyStakeFn, - keepStakeAfterTopUp - ) - - describeUndelegation( - ownerAddress, - operatorAddress, - authorizerAddress, - keepStaked - ) - - describeSlashing(operatorAddress, authorizerAddress, keepStaked) - }) -})