import { ContractManager , ConstantsHolder , BountyV2 , NodesMock , DelegationController , SkaleToken , ValidatorService } from "../typechain" ; import { deployContractManager } from "./tools/deploy/contractManager" ; import { deployConstantsHolder } from "./tools/deploy/constantsHolder" ; import { deployBounty } from "./tools/deploy/bounty" ; import { currentTime , nextMonth , skipTime , skipTimeToDate } from "./tools/time" ; import chaiAsPromised from "chai-as-promised" ; import chaiAlmost from "chai-almost" ; import * as chai from "chai" ; import { deployNodesMock } from "./tools/deploy/test/nodesMock" ; import { deploySkaleToken } from "./tools/deploy/skaleToken" ; import { deployDelegationController } from "./tools/deploy/delegation/delegationController" ; import { deployValidatorService } from "./tools/deploy/delegation/validatorService" ; import { deployDelegationPeriodManager } from "./tools/deploy/delegation/delegationPeriodManager" ; import { deploySkaleManagerMock } from "./tools/deploy/test/skaleManagerMock" ; import { ethers } from "hardhat" ; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address" ; import { BigNumber , Event } from "ethers" ; import { deployPunisher } from "./tools/deploy/delegation/punisher" ; import { fastBeforeEach } from "./tools/mocha" ; chai . should ( ) ; chai . use ( chaiAsPromised ) ; function getBountyForEpoch ( epoch ) { const bountyForFirst6Years = [ 385000000 , 346500000 , 308000000 , 269500000 , 231000000 , 192500000 ] ; const year = Math . floor ( epoch / 12 ) ; if ( year < 6 ) { return bountyForFirst6Years [ year ] / 12 ; } else { return bountyForFirst6Years [ 5 ] / 2 ** ( Math . floor ( ( year - 6 ) / 3 ) + 1 ) ; } } describe ( "Bounty" , ( ) => { let owner ; let admin ; let hacker ; let validator ; let validator2 ; let contractManager ; let constantsHolder ; let bountyContract ; let nodes ; const ten18 = BigNumber . from ( 10 ) . pow ( 18 ) ; const day = 60 * 60 * 24 ; const month = 31 * day ; fastBeforeEach ( async ( ) => { chai . use ( chaiAlmost ( 2 ) ) ; [ owner , admin , hacker , validator , validator2 ] = await ethers . getSigners ( ) ; contractManager = await deployContractManager ( ) ; constantsHolder = await deployConstantsHolder ( contractManager ) ; bountyContract = await deployBounty ( contractManager ) ; nodes = await deployNodesMock ( contractManager ) ; await contractManager . setContractsAddress ( "Nodes" , nodes . address ) ; const skaleManagerMock = await deploySkaleManagerMock ( contractManager ) ; await contractManager . setContractsAddress ( "SkaleManager" , skaleManagerMock . address ) ; const BOUNTY_REDUCTION_MANAGER_ROLE = await bountyContract . BOUNTY_REDUCTION_MANAGER_ROLE ( ) ; await bountyContract . grantRole ( BOUNTY_REDUCTION_MANAGER_ROLE , owner . address ) ; const CONSTANTS_HOLDER_MANAGER_ROLE = await constantsHolder . CONSTANTS_HOLDER_MANAGER_ROLE ( ) ; await constantsHolder . grantRole ( CONSTANTS_HOLDER_MANAGER_ROLE , owner . address ) ; } ) ; it ( "should allow only owner to call enableBountyReduction" , async ( ) => { await bountyContract . connect ( hacker ) . enableBountyReduction ( ) . should . be . eventually . rejectedWith ( "BOUNTY_REDUCTION_MANAGER_ROLE is required" ) ; await bountyContract . connect ( admin ) . enableBountyReduction ( ) . should . be . eventually . rejectedWith ( "BOUNTY_REDUCTION_MANAGER_ROLE is required" ) ; await bountyContract . enableBountyReduction ( ) ; } ) ; it ( "should allow only owner to call disableBountyReduction" , async ( ) => { await bountyContract . connect ( hacker ) . disableBountyReduction ( ) . should . be . eventually . rejectedWith ( "BOUNTY_REDUCTION_MANAGER_ROLE is required" ) ; await bountyContract . connect ( admin ) . disableBountyReduction ( ) . should . be . eventually . rejectedWith ( "BOUNTY_REDUCTION_MANAGER_ROLE is required" ) ; await bountyContract . disableBountyReduction ( ) ; } ) ; describe ( "when validator is registered and has active delegations" , async ( ) => { let skaleToken ; let delegationController ; let validatorService ; const validatorId = 1 ; const validatorAmount = 1e6 ; fastBeforeEach ( async ( ) => { skaleToken = await deploySkaleToken ( contractManager ) ; delegationController = await deployDelegationController ( contractManager ) ; validatorService = await deployValidatorService ( contractManager ) ; const VALIDATOR_MANAGER_ROLE = await validatorService . VALIDATOR_MANAGER_ROLE ( ) ; await validatorService . grantRole ( VALIDATOR_MANAGER_ROLE , owner . address ) ; await skaleToken . mint ( validator . address , ten18 . mul ( validatorAmount ) . toString ( ) , "0x" , "0x" ) ; await validatorService . connect ( validator ) . registerValidator ( "Validator" , "" , 150 , 1e6 + 1 ) ; await validatorService . enableValidator ( validatorId ) ; await delegationController . connect ( validator ) . delegate ( validatorId , ten18 . mul ( validatorAmount ) . toString ( ) , 2 , "" ) ; await delegationController . connect ( validator ) . acceptPendingDelegation ( 0 ) ; await nextMonth ( contractManager ) ; } ) ; async function calculateBounty ( nodeId ) { const estimate = ( await bountyContract . estimateBounty ( nodeId ) ) . div ( ten18 ) . toNumber ( ) ; const bounty = ( await bountyContract . callStatic . calculateBounty ( nodeId ) ) . div ( ten18 ) . toNumber ( ) ; bounty . should . be . almost ( estimate ) ; await bountyContract . calculateBounty ( nodeId ) ; await nodes . changeNodeLastRewardDate ( nodeId ) ; return bounty ; } describe ( "when second validator is registered and has active delegations" , async ( ) => { const validator2Id = 2 ; const validator2Amount = 0.5e6 ; fastBeforeEach ( async ( ) => { const delegationPeriodManager = await deployDelegationPeriodManager ( contractManager ) ; const DELEGATION_PERIOD_SETTER_ROLE = await delegationPeriodManager . DELEGATION_PERIOD_SETTER_ROLE ( ) ; await delegationPeriodManager . grantRole ( DELEGATION_PERIOD_SETTER_ROLE , owner . address ) ; await skaleToken . mint ( validator2 . address , ten18 . mul ( validator2Amount ) . toString ( ) , "0x" , "0x" ) ; await validatorService . connect ( validator2 ) . registerValidator ( "Validator" , "" , 150 , 1e6 + 1 ) ; await validatorService . enableValidator ( validator2Id ) ; await delegationPeriodManager . setDelegationPeriod ( 12 , 200 ) ; await delegationController . connect ( validator2 ) . delegate ( validator2Id , ten18 . mul ( validator2Amount ) . toString ( ) , 12 , "" ) ; await delegationController . connect ( validator2 ) . acceptPendingDelegation ( 1 ) ; await nextMonth ( contractManager ) ; await skipTimeToDate ( 1 , 0 ) ; await constantsHolder . setLaunchTimestamp ( await currentTime ( ) ) ; await constantsHolder . setMSR ( ten18 . mul ( validator2Amount ) . toString ( ) ) ; } ) ; it ( "should pay bounty proportionally to effective validator's stake" , async ( ) => { await nodes . registerNodes ( 2 , validatorId ) ; await nodes . registerNodes ( 1 , validator2Id ) ; await skipTime ( 29 * day ) ; const bounty0 = await calculateBounty ( 0 ) + await calculateBounty ( 1 ) ; const bounty1 = await calculateBounty ( 2 ) ; bounty0 . should . be . equal ( bounty1 ) ; bounty0 . should . be . almost ( getBountyForEpoch ( 0 ) / 2 ) ; } ) ; it ( "should process nodes adding and removing, delegation and undelegation and slashing" , async ( ) => { await skaleToken . mint ( validator . address , ten18 . mul ( 10e6 ) . toString ( ) , "0x" , "0x" ) ; await skaleToken . mint ( validator2 . address , ten18 . mul ( 10e6 ) . toString ( ) , "0x" , "0x" ) ; const punisher = await deployPunisher ( contractManager ) ; await contractManager . setContractsAddress ( "SkaleDKG" , contractManager . address ) ; const million = ten18 . mul ( 1e6 ) . toString ( ) ; await delegationController . connect ( validator ) . requestUndelegation ( 0 ) ; await skipTimeToDate ( 15 , 0 ) ; await delegationController . connect ( validator2 ) . delegate ( 2 , million , 2 , "" ) ; await delegationController . connect ( validator2 ) . acceptPendingDelegation ( 2 ) ; await skipTimeToDate ( 30 , 0 ) ; await punisher . slash ( 1 , million ) ; await skipTimeToDate ( 1 , 1 ) ; await nodes . registerNodes ( 1 , validator2Id ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 15 , 1 ) ; await delegationController . connect ( validator ) . delegate ( validatorId , million , 12 , "" ) ; await delegationController . connect ( validator ) . acceptPendingDelegation ( 3 ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 27 , 1 ) ; await delegationController . connect ( validator ) . delegate ( validatorId , million , 2 , "" ) ; await delegationController . connect ( validator ) . acceptPendingDelegation ( 4 ) ; await constantsHolder . setMSR ( ten18 . mul ( 1.5e6 ) . toString ( ) ) ; let bounty = await calculateBounty ( 0 ) ; bounty . should . be . almost ( getBountyForEpoch ( 0 ) + getBountyForEpoch ( 1 ) ) ; await skipTimeToDate ( 1 , 2 ) ; await delegationController . connect ( validator ) . requestUndelegation ( 3 ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 15 , 2 ) ; await punisher . slash ( validatorId , million ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 29 , 2 ) ; await delegationController . connect ( validator ) . delegate ( validatorId , million , 2 , "" ) ; await delegationController . connect ( validator ) . acceptPendingDelegation ( 5 ) ; let effectiveDelegated1 = 0.5e6 * 100 + 0.5e6 * 200 ; let effectiveDelegated2 = 1e6 * 100 + 0.5e6 * 200 ; let totalBounty = 0 ; let bountyLeft = getBountyForEpoch ( 2 ) ; bounty = await calculateBounty ( 0 ) ; bounty . should . be . almost ( bountyLeft * effectiveDelegated2 / ( effectiveDelegated1 + effectiveDelegated2 ) ) ; totalBounty += bounty ; await skipTimeToDate ( 1 , 3 ) ; await nodes . registerNodes ( 1 , validatorId ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await bountyContract . calculateBounty ( 1 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 15 , 3 ) ; await nodes . registerNodes ( 1 , validatorId ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await bountyContract . calculateBounty ( 1 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await bountyContract . calculateBounty ( 2 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 28 , 3 ) ; await delegationController . connect ( validator2 ) . delegate ( validator2Id , million , 2 , "" ) ; await delegationController . connect ( validator2 ) . acceptPendingDelegation ( 6 ) ; effectiveDelegated1 = 1.5e6 * 100 + 0.5e6 * 200 ; bountyLeft += getBountyForEpoch ( 3 ) - totalBounty ; totalBounty = 0 ; bounty = await calculateBounty ( 0 ) ; bounty . should . be . almost ( bountyLeft * effectiveDelegated2 / ( effectiveDelegated1 + effectiveDelegated2 ) ) ; totalBounty += bounty ; bounty = await calculateBounty ( 1 ) ; bounty . should . be . almost ( bountyLeft * effectiveDelegated1 / ( effectiveDelegated1 + effectiveDelegated2 ) ) ; totalBounty += bounty ; await bountyContract . calculateBounty ( 2 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 1 , 4 ) ; await nodes . registerNodes ( 1 , validator2Id ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await bountyContract . calculateBounty ( 1 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await bountyContract . calculateBounty ( 2 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await bountyContract . calculateBounty ( 3 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 15 , 4 ) ; await delegationController . connect ( validator2 ) . requestUndelegation ( 1 ) ; effectiveDelegated2 = 2e6 * 100 + 0.5e6 * 200 ; bountyLeft += getBountyForEpoch ( 4 ) - totalBounty ; totalBounty = 0 ; effectiveDelegated1 . should . be . almost ( ( await delegationController . getEffectiveDelegatedValuesByValidator ( validatorId ) ) [ 0 ] . div ( ten18 ) . toNumber ( ) ) ; effectiveDelegated2 . should . be . almost ( ( await delegationController . getEffectiveDelegatedValuesByValidator ( validator2Id ) ) [ 1 ] . div ( ten18 ) . toNumber ( ) ) ; await bountyContract . calculateBounty ( 0 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await bountyContract . calculateBounty ( 1 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; bounty = await calculateBounty ( 2 ) ; bounty . should . be . almost ( bountyLeft * ( effectiveDelegated1 ) / ( effectiveDelegated1 + effectiveDelegated2 ) ) ; totalBounty += bounty ; await bountyContract . calculateBounty ( 3 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 29 , 4 ) ; await punisher . slash ( validator2Id , ten18 . mul ( 1.25e6 ) . toString ( ) ) ; effectiveDelegated2 = 1e6 * 100 + 0.25e6 * 200 ; effectiveDelegated1 . should . be . almost ( ( await delegationController . getEffectiveDelegatedValuesByValidator ( validatorId ) ) [ 0 ] . div ( ten18 ) . toNumber ( ) ) ; effectiveDelegated2 . should . be . almost ( ( await delegationController . getEffectiveDelegatedValuesByValidator ( validator2Id ) ) [ 0 ] . div ( ten18 ) . toNumber ( ) ) ; bounty = await calculateBounty ( 0 ) ; bounty . should . be . almost ( 0 ) ; totalBounty += bounty ; bounty = await calculateBounty ( 1 ) ; totalBounty += bounty ; await bountyContract . calculateBounty ( 2 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; bounty = await calculateBounty ( 3 ) ; bounty . should . be . almost ( 0 ) ; totalBounty += bounty ; totalBounty . should . be . lessThan ( getBountyForEpoch ( 4 ) ) ; await skipTimeToDate ( 16 , 5 ) ; bountyLeft += getBountyForEpoch ( 5 ) - totalBounty ; totalBounty = 0 ; await nodes . removeNode ( 0 ) ; await bountyContract . calculateBounty ( 1 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; bounty = await calculateBounty ( 2 ) ; bounty . should . be . almost ( bountyLeft * effectiveDelegated1 / ( effectiveDelegated1 + effectiveDelegated2 ) ) ; totalBounty += bounty ; await bountyContract . calculateBounty ( 3 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; await skipTimeToDate ( 27 , 5 ) ; await delegationController . connect ( validator2 ) . requestUndelegation ( 6 ) ; await skipTimeToDate ( 28 , 5 ) ; bounty = await calculateBounty ( 1 ) ; bounty . should . be . almost ( 0 ) ; totalBounty += bounty ; await bountyContract . calculateBounty ( 2 ) . should . be . eventually . rejectedWith ( "Transaction is sent too early" ) ; bounty = await calculateBounty ( 3 ) ; bounty . should . be . almost ( 0 ) ; totalBounty += bounty ; await skipTimeToDate ( 29 , 6 ) ; effectiveDelegated2 = 0.5e6 * 100 + 0.25e6 * 200 ; bountyLeft += getBountyForEpoch ( 5 ) - totalBounty ; totalBounty = 0 ; bounty = await calculateBounty ( 1 ) ; bounty . should . be . almost ( bountyLeft * effectiveDelegated1 / ( effectiveDelegated1 + effectiveDelegated2 ) , 3 ) ; totalBounty += bounty ; bounty = await calculateBounty ( 2 ) ; bounty . should . be . almost ( 0 ) ; totalBounty += bounty ; bounty = await calculateBounty ( 3 ) ; bounty . should . be . almost ( 0 ) ; totalBounty += bounty ; } ) ; } ) ; } ) ; } ) ;