diff --git a/contracts/RewardsPool.sol b/contracts/RewardsPool.sol index 3c571442..15f710c0 100644 --- a/contracts/RewardsPool.sol +++ b/contracts/RewardsPool.sol @@ -76,6 +76,9 @@ contract RewardsPool is IRewardsPool, Initializable, OwnableUpgradeable, SQParam /// @notice the denominator of the alpha int32 public alphaDenominator; + /// @notice Adjustment for Era Rewards Pools: era => deployment => adjustment + mapping(uint256 => mapping(bytes32 => uint256)) public poolAdjustments; + /// @dev ### EVENTS /// @notice Emitted when update the alpha for cobb-douglas function event Alpha(int32 alphaNumerator, int32 alphaDenominator); @@ -291,7 +294,8 @@ contract RewardsPool is IRewardsPool, Initializable, OwnableUpgradeable, SQParam pool.totalReward, pool.labor[runner], pool.stake[runner], - pool.totalStake + pool.totalStake, + poolAdjustments[era][deploymentId] ); address rewardDistributer = settings.getContractAddress(SQContracts.RewardsDistributor); @@ -319,15 +323,20 @@ contract RewardsPool is IRewardsPool, Initializable, OwnableUpgradeable, SQParam delete pool.stake[runner]; if (pool.unclaimTotalLabor == 0) { - // burn the remained + // don't burn the remained, instead, move to latest era if (pool.unclaimReward > 0) { - address treasury = settings.getContractAddress(SQContracts.Treasury); - IERC20(settings.getContractAddress(SQContracts.SQToken)).safeTransfer( - treasury, - pool.unclaimReward - ); + uint256 latestEra = IEraManager( + ISettings(settings).getContractAddress(SQContracts.EraManager) + ).safeUpdateAndGetEra(); + require(latestEra > era, 'RP006'); + // EraPool storage latestEraPool = pools[latestEra]; + // Pool storage latestPool = latestEraPool.pools[deploymentId]; + // latestPool.totalReward += pool.unclaimReward; + pools[latestEra].pools[deploymentId].unclaimReward += pool.unclaimReward; + poolAdjustments[latestEra][deploymentId] += pool.unclaimReward; } + delete poolAdjustments[era][deploymentId]; delete eraPool.pools[deploymentId]; eraPool.totalUnclaimedDeployment -= 1; @@ -353,7 +362,8 @@ contract RewardsPool is IRewardsPool, Initializable, OwnableUpgradeable, SQParam uint256 reward, uint256 myLabor, uint256 myStake, - uint256 totalStake + uint256 totalStake, + uint256 adjustment ) private view returns (uint256) { if (myStake == totalStake) { return reward; @@ -382,6 +392,6 @@ contract RewardsPool is IRewardsPool, Initializable, OwnableUpgradeable, SQParam // depending on the choice we made earlier. n = feeRatio <= stakeRatio ? FixedMath.mul(stakeRatio, n) : FixedMath.div(stakeRatio, n); // Multiply the above with reward. - return FixedMath.uintMul(n, reward); + return FixedMath.uintMul(n, reward + adjustment); } } diff --git a/contracts/StateChannel.sol b/contracts/StateChannel.sol index 1e73f189..86c780d0 100644 --- a/contracts/StateChannel.sol +++ b/contracts/StateChannel.sol @@ -557,31 +557,32 @@ contract StateChannel is Initializable, OwnableUpgradeable, SQParameter { if (amount > 0) { address indexer = state.indexer; bytes32 deploymentId = state.deploymentId; - // rewards pool is deprecated - // address rewardPoolAddress = settings.getContractAddress(SQContracts.RewardsPool); - // IERC20(settings.getContractAddress(SQContracts.SQToken)).approve( - // rewardPoolAddress, - // amount - // ); - // IRewardsPool rewardsPool = IRewardsPool(rewardPoolAddress); - // rewardsPool.labor(deploymentId, indexer, amount); - - IRewardsDistributor rewardsDistributor = IRewardsDistributor( - ISettings(settings).getContractAddress(SQContracts.RewardsDistributor) - ); - IEraManager eraManager = IEraManager( - ISettings(settings).getContractAddress(SQContracts.EraManager) - ); + // rewards pool is reactivated + address rewardPoolAddress = settings.getContractAddress(SQContracts.RewardsPool); IERC20(settings.getContractAddress(SQContracts.SQToken)).safeIncreaseAllowance( - address(rewardsDistributor), + rewardPoolAddress, amount ); - rewardsDistributor.addInstantRewards( - indexer, - address(this), - amount, - eraManager.safeUpdateAndGetEra() - ); + IRewardsPool rewardsPool = IRewardsPool(rewardPoolAddress); + rewardsPool.labor(deploymentId, indexer, amount); + + // disable instant rewards + // IRewardsDistributor rewardsDistributor = IRewardsDistributor( + // ISettings(settings).getContractAddress(SQContracts.RewardsDistributor) + // ); + // IEraManager eraManager = IEraManager( + // ISettings(settings).getContractAddress(SQContracts.EraManager) + // ); + // IERC20(settings.getContractAddress(SQContracts.SQToken)).safeIncreaseAllowance( + // address(rewardsDistributor), + // amount + // ); + // rewardsDistributor.addInstantRewards( + // indexer, + // address(this), + // amount, + // eraManager.safeUpdateAndGetEra() + // ); emit ChannelLabor2(channelId, deploymentId, indexer, amount); } diff --git a/publish/ABI/RewardsPool.json b/publish/ABI/RewardsPool.json index 4509c7c0..48f24ff7 100644 --- a/publish/ABI/RewardsPool.json +++ b/publish/ABI/RewardsPool.json @@ -360,6 +360,30 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "poolAdjustments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "renounceOwnership", diff --git a/publish/testnet.json b/publish/testnet.json index dbed8dad..fc9246ef 100644 --- a/publish/testnet.json +++ b/publish/testnet.json @@ -129,10 +129,10 @@ "lastUpdate": "Thu, 01 Aug 2024 08:55:43 GMT" }, "RewardsDistributor": { - "innerAddress": "0xe42A85e38017cE848eEa220CF53f2D0fb772c6E0", + "innerAddress": "0x9eC4A8a31f6545002Ca1d5F7e4d7045aa36e6791", "address": "0x5c0d1F22C4D7aaF35Ade34CA7c7491dBB0A91Cb7", - "bytecodeHash": "d6772ec56699815859ee19fdd98fe548c8632b434b5ab933abb6305434fda96b", - "lastUpdate": "Wed, 11 Sep 2024 11:31:24 GMT" + "bytecodeHash": "381e3fab7024f94442d22d91cbb1a4ae4ecc0dbe39670ea54d1ca080217db295", + "lastUpdate": "Fri, 24 Jan 2025 06:59:04 GMT" }, "RewardsPool": { "innerAddress": "0x6C7F6Cd6A4295D74897ae19Ab01Ea04821979d80", @@ -153,10 +153,10 @@ "lastUpdate": "Fri, 09 Feb 2024 05:52:35 GMT" }, "StateChannel": { - "innerAddress": "0x48e657895d8DEc38558d095d9f5f7825AA7Da1E2", + "innerAddress": "0x8F40E8bC3C10DfAdbc2a3c4261aB01e6CABBd28E", "address": "0x8C3d312291CC666757daFbb6eD20874Ae573C895", - "bytecodeHash": "4bd7790e735b8f973d4c84bf46c7a6b5aea1352bb0271dfb75a46e630a63e8a8", - "lastUpdate": "Wed, 11 Sep 2024 11:32:13 GMT" + "bytecodeHash": "c0391673bb6d0d96beca30bc98e709aefa5edd9f60a95e6afaf0437fd90e64d2", + "lastUpdate": "Fri, 24 Jan 2025 06:59:37 GMT" }, "ConsumerHost": { "innerAddress": "0x07B1C6beeF0455135C37F8680f136bbE9aE8a9Ad", @@ -195,10 +195,10 @@ "lastUpdate": "Tue, 21 May 2024 22:04:33 GMT" }, "RewardsBooster": { - "innerAddress": "0x438bD56449Cd044F65C607EbdbB9a4Ede0c936b7", + "innerAddress": "0x2106495Cfe4c9E6857970CD458F6Ba3dF2a97E0e", "address": "0x4f6A1045A56EeD1D2795b5f6F6713972B67C09C2", - "bytecodeHash": "10ded472cbfa2afbcfd1ac3a8b44414e12339fdd8018fcfd4c47a5b338e08af9", - "lastUpdate": "Tue, 08 Oct 2024 23:50:39 GMT" + "bytecodeHash": "63f4764276f728e8a3afe784074d7468f5a0294fa56c36bf8bd802ba3d62162c", + "lastUpdate": "Fri, 24 Jan 2025 06:59:19 GMT" }, "StakingAllocation": { "innerAddress": "0xfbeB402CADFCDB28407E86f5F20Ee719f5F6F430", diff --git a/test/RewardsPool.test.ts b/test/RewardsPool.test.ts index d06cef08..eba6909c 100644 --- a/test/RewardsPool.test.ts +++ b/test/RewardsPool.test.ts @@ -9,7 +9,7 @@ import { etherParse, eventFrom, eventsFrom, registerRunner, startNewEra, time, t import { deployContracts } from './setup'; import { ethers, waffle } from 'hardhat'; -describe.skip('RewardsPool Contract', () => { +describe('RewardsPool Contract', () => { const deploymentId0 = deploymentIds[0]; const deploymentId1 = deploymentIds[1]; const deploymentId2 = deploymentIds[2]; @@ -67,6 +67,9 @@ describe.skip('RewardsPool Contract', () => { eraManager = deployment.eraManager; // Init indexer and delegator account. + // await token.connect(root).transfer(runner0.address, etherParse('10')); + // await token.connect(root).transfer(runner1.address, etherParse('10')); + // await token.connect(root).transfer(runner2.address, etherParse('10')); await token.connect(root).transfer(delegator0.address, etherParse('10')); await token.connect(root).transfer(delegator1.address, etherParse('10')); await token.connect(delegator0).increaseAllowance(staking.address, etherParse('10')); @@ -78,270 +81,274 @@ describe.skip('RewardsPool Contract', () => { await eraManager.connect(root).updateEraPeriod(time.duration.days(1).toString()); // Moved to era 2. - // await registerRunner(token, indexerRegistry, staking, root, root, etherParse('1000'), 1e5); - // await registerRunner(token, indexerRegistry, staking, root, runner0, etherParse('100000'), 1e5); - // await registerRunner(token, indexerRegistry, staking, root, runner1, etherParse('1000'), 1e5); - // await registerRunner(token, indexerRegistry, staking, root, runner2, etherParse('1000'), 1e5); + await registerRunner(token, indexerRegistry, staking, root, root, etherParse('1000'), 1e5); + await registerRunner(token, indexerRegistry, staking, root, runner0, etherParse('100000'), 1e5); + await registerRunner(token, indexerRegistry, staking, root, runner1, etherParse('1000'), 1e5); + await registerRunner(token, indexerRegistry, staking, root, runner2, etherParse('1000'), 1e5); }); - const metrix = { - alpha: [ - // [1, 10], - [1, 3], - [7, 10], - [8, 10], - [9, 10], - ], - alphaText: [ - // '1/10', - '0.33', - '0.7', - '0.8', - '0.9', - ], - operatorStakes: [ - // [etherParse('200000'), etherParse('200000')], // 1:1 - // [etherParse('200000'), etherParse('800000')], // 1:4 - // [etherParse('200000'), etherParse('2000000')], // 1:10 - // [etherParse('200000'), etherParse('20000000')], // 1:100 - // [etherParse('200000'), etherParse('200000000')], // 1:1000 - // 1:1:1:1:1:1:1:1:1:1 - [ - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), + it.skip('data metrics calcuation', function () { + const metrix = { + alpha: [ + // [1, 10], + [1, 3], + [7, 10], + [8, 10], + [9, 10], ], - // 1:2:3:4:5:6:7:8:9:10 - [ - etherParse('100000'), - etherParse('200000'), - etherParse('300000'), - etherParse('400000'), - etherParse('500000'), - etherParse('600000'), - etherParse('700000'), - etherParse('800000'), - etherParse('900000'), - etherParse('1000000'), + alphaText: [ + // '1/10', + '0.33', + '0.7', + '0.8', + '0.9', ], - // 1:1:1:1:1:1:1:1:1:10 - [ - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('2000000'), + operatorStakes: [ + // [etherParse('200000'), etherParse('200000')], // 1:1 + // [etherParse('200000'), etherParse('800000')], // 1:4 + // [etherParse('200000'), etherParse('2000000')], // 1:10 + // [etherParse('200000'), etherParse('20000000')], // 1:100 + // [etherParse('200000'), etherParse('200000000')], // 1:1000 + // 1:1:1:1:1:1:1:1:1:1 + [ + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + ], + // 1:2:3:4:5:6:7:8:9:10 + [ + etherParse('100000'), + etherParse('200000'), + etherParse('300000'), + etherParse('400000'), + etherParse('500000'), + etherParse('600000'), + etherParse('700000'), + etherParse('800000'), + etherParse('900000'), + etherParse('1000000'), + ], + // 1:1:1:1:1:1:1:1:1:10 + [ + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('2000000'), + ], + // 1:2:3:4:5:6:7:8:9:100 + [ + etherParse('100000'), + etherParse('200000'), + etherParse('300000'), + etherParse('400000'), + etherParse('500000'), + etherParse('600000'), + etherParse('700000'), + etherParse('800000'), + etherParse('900000'), + etherParse('10000000'), + ], + // 1:1:1:1:1:1:1:1:1:100 + [ + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('20000000'), + ], + // 1:2:3:4:5:6:7:8:100:1000 + [ + etherParse('100000'), + etherParse('200000'), + etherParse('300000'), + etherParse('400000'), + etherParse('500000'), + etherParse('600000'), + etherParse('700000'), + etherParse('800000'), + etherParse('10000000'), + etherParse('100000000'), + ], ], - // 1:2:3:4:5:6:7:8:9:100 - [ - etherParse('100000'), - etherParse('200000'), - etherParse('300000'), - etherParse('400000'), - etherParse('500000'), - etherParse('600000'), - etherParse('700000'), - etherParse('800000'), - etherParse('900000'), - etherParse('10000000'), + operatorStakesText: [ + '1:1:1:1:1:1:1:1:1:1', + '1:2:3:4:5:6:7:8:9:10', + '1:1:1:1:1:1:1:1:1:10', + '1:2:3:4:5:6:7:8:9:100', + '1:1:1:1:1:1:1:1:1:100', + '1:2:3:4:5:6:7:8:100:1000', ], - // 1:1:1:1:1:1:1:1:1:100 - [ - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('20000000'), + labors: [ + // [etherParse('2000'), etherParse('2000')], // 1:1 + // [etherParse('2000'), etherParse('6000')], // 1:3 + // [etherParse('1000'), etherParse('20000')], // 1:20 + // [etherParse('1000'), etherParse('1000000')], // 1:1000 + // 1:1:1:1:1:1:1:1:1:1 + [ + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + ], + // 1:2:3:4:5:6:7:8:9:10 + [ + etherParse('100000'), + etherParse('200000'), + etherParse('300000'), + etherParse('400000'), + etherParse('500000'), + etherParse('600000'), + etherParse('700000'), + etherParse('800000'), + etherParse('900000'), + etherParse('1000000'), + ], + // 1:1:1:1:1:1:1:1:1:10 + [ + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('2000000'), + ], + // 10:9:8:7:6:5:4:3:2:1 + [ + etherParse('1000000'), + etherParse('900000'), + etherParse('800000'), + etherParse('700000'), + etherParse('600000'), + etherParse('500000'), + etherParse('400000'), + etherParse('300000'), + etherParse('200000'), + etherParse('100000'), + ], + // 10:1:1:1:1:1:1:1:1:1 + [ + etherParse('2000000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + etherParse('200000'), + ], ], - // 1:2:3:4:5:6:7:8:100:1000 - [ - etherParse('100000'), - etherParse('200000'), - etherParse('300000'), - etherParse('400000'), - etherParse('500000'), - etherParse('600000'), - etherParse('700000'), - etherParse('800000'), - etherParse('10000000'), - etherParse('100000000'), + laborText: [ + '1:1:1:1:1:1:1:1:1:1', + '1:2:3:4:5:6:7:8:9:10', + '1:1:1:1:1:1:1:1:1:10', + '10:9:8:7:6:5:4:3:2:1', + '10:1:1:1:1:1:1:1:1:1', ], - ], - operatorStakesText: [ - '1:1:1:1:1:1:1:1:1:1', - '1:2:3:4:5:6:7:8:9:10', - '1:1:1:1:1:1:1:1:1:10', - '1:2:3:4:5:6:7:8:9:100', - '1:1:1:1:1:1:1:1:1:100', - '1:2:3:4:5:6:7:8:100:1000', - ], - labors: [ - // [etherParse('2000'), etherParse('2000')], // 1:1 - // [etherParse('2000'), etherParse('6000')], // 1:3 - // [etherParse('1000'), etherParse('20000')], // 1:20 - // [etherParse('1000'), etherParse('1000000')], // 1:1000 - // 1:1:1:1:1:1:1:1:1:1 - [ - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - ], - // 1:2:3:4:5:6:7:8:9:10 - [ - etherParse('100000'), - etherParse('200000'), - etherParse('300000'), - etherParse('400000'), - etherParse('500000'), - etherParse('600000'), - etherParse('700000'), - etherParse('800000'), - etherParse('900000'), - etherParse('1000000'), - ], - // 1:1:1:1:1:1:1:1:1:10 - [ - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('2000000'), - ], - // 10:9:8:7:6:5:4:3:2:1 - [ - etherParse('1000000'), - etherParse('900000'), - etherParse('800000'), - etherParse('700000'), - etherParse('600000'), - etherParse('500000'), - etherParse('400000'), - etherParse('300000'), - etherParse('200000'), - etherParse('100000'), - ], - // 10:1:1:1:1:1:1:1:1:1 - [ - etherParse('2000000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - etherParse('200000'), - ], - ], - laborText: [ - '1:1:1:1:1:1:1:1:1:1', - '1:2:3:4:5:6:7:8:9:10', - '1:1:1:1:1:1:1:1:1:10', - '10:9:8:7:6:5:4:3:2:1', - '10:1:1:1:1:1:1:1:1:1', - ], - }; - // alpha,total stake ratio,labor(unadjusted reward) ratio,rewards adjustment - A,rewards adjustment - B,total rewards loss - const results = []; - - for (const [alphaIdx, alpha] of metrix.alpha.entries()) { - for (const [stakeIdx, runnerStakes] of metrix.operatorStakes.entries()) { - for (const [laborIdx, labors] of metrix.labors.entries()) { - it(`Reward pool adjustment loss with alphaId ${alphaIdx} and operatorStakesId ${stakeIdx} and laborsId ${laborIdx}`, async () => { - await rewardsPool.setAlpha(alpha[0], alpha[1]); - for (const [runnerIdx, runnerStake] of runnerStakes.entries()) { - await registerRunner( - token, - indexerRegistry, - staking, - root, - runners[runnerIdx], - runnerStake, - 1e5 - ); - } - await startNewEra(eraManager); - for (const [runnerIdx, runnerLabor] of labors.entries()) { - await rewardsDistributor.collectAndDistributeRewards(runners[runnerIdx].address); - } - - const era = await eraManager.eraNumber(); - - for (const [runnerIdx, labor] of labors.entries()) { - await rewardsPool.connect(root).labor(deploymentId0, runners[runnerIdx].address, labor); - } - - // Check the status. - const [, totalLabor] = await rewardsPool.getReward(deploymentId0, era, runner0.address); - - await timeTravel(time.duration.days(1).toNumber()); - - let tx; - const runnerRewards = []; - for (const [runnerIdx, runnerLabor] of labors.entries()) { - tx = await rewardsPool.collect(deploymentId0, runners[runnerIdx].address); - const { amount } = await eventFrom(tx, rewardsPool, 'Collect(bytes32,address,uint256,uint256)'); - runnerRewards[runnerIdx] = amount; - } - const totalReward = runnerRewards.reduce((acc, cur) => acc.add(cur), BigNumber.from(0)); - const totalRewardDiff = totalReward.sub(totalLabor); - - console.log(`totalLabor: ${utils.formatEther(totalLabor)}`); - console.log(`totalRewardDiff: ${utils.formatEther(totalRewardDiff)}`); - // console.log(`runner0RewardDiff: ${utils.formatEther(runner0RewardDiff)}`); - // console.log(`runner1RewardDiff: ${utils.formatEther(runner1RewardDiff)}`); - results.push([ - metrix.alphaText[alphaIdx], - metrix.operatorStakesText[stakeIdx], - metrix.laborText[laborIdx], - runnerRewards[0].sub(labors[0]).mul(1000).div(labors[0]).toNumber() / 1000, - runnerRewards[1].sub(labors[1]).mul(1000).div(labors[1]).toNumber() / 1000, - runnerRewards[2].sub(labors[2]).mul(1000).div(labors[2]).toNumber() / 1000, - runnerRewards[3].sub(labors[3]).mul(1000).div(labors[3]).toNumber() / 1000, - runnerRewards[4].sub(labors[4]).mul(1000).div(labors[4]).toNumber() / 1000, - runnerRewards[5].sub(labors[5]).mul(1000).div(labors[5]).toNumber() / 1000, - runnerRewards[6].sub(labors[6]).mul(1000).div(labors[6]).toNumber() / 1000, - runnerRewards[7].sub(labors[7]).mul(1000).div(labors[7]).toNumber() / 1000, - runnerRewards[8].sub(labors[8]).mul(1000).div(labors[8]).toNumber() / 1000, - runnerRewards[9].sub(labors[9]).mul(1000).div(labors[9]).toNumber() / 1000, - totalRewardDiff.mul(1000).div(totalLabor).toNumber() / 1000, - ]); - }); + }; + // alpha,total stake ratio,labor(unadjusted reward) ratio,rewards adjustment - A,rewards adjustment - B,total rewards loss + const results = []; + + for (const [alphaIdx, alpha] of metrix.alpha.entries()) { + for (const [stakeIdx, runnerStakes] of metrix.operatorStakes.entries()) { + for (const [laborIdx, labors] of metrix.labors.entries()) { + it(`Reward pool adjustment loss with alphaId ${alphaIdx} and operatorStakesId ${stakeIdx} and laborsId ${laborIdx}`, async () => { + await rewardsPool.setAlpha(alpha[0], alpha[1]); + for (const [runnerIdx, runnerStake] of runnerStakes.entries()) { + await registerRunner( + token, + indexerRegistry, + staking, + root, + runners[runnerIdx], + runnerStake, + 1e5 + ); + } + await startNewEra(eraManager); + for (const [runnerIdx, runnerLabor] of labors.entries()) { + await rewardsDistributor.collectAndDistributeRewards(runners[runnerIdx].address); + } + + const era = await eraManager.eraNumber(); + + for (const [runnerIdx, labor] of labors.entries()) { + await rewardsPool.connect(root).labor(deploymentId0, runners[runnerIdx].address, labor); + } + + // Check the status. + const [, totalLabor] = await rewardsPool.getReward(deploymentId0, era, runner0.address); + + await timeTravel(time.duration.days(1).toNumber()); + + let tx; + const runnerRewards = []; + for (const [runnerIdx, runnerLabor] of labors.entries()) { + tx = await rewardsPool.collect(deploymentId0, runners[runnerIdx].address); + const { amount } = await eventFrom( + tx, + rewardsPool, + 'Collect(bytes32,address,uint256,uint256)' + ); + runnerRewards[runnerIdx] = amount; + } + const totalReward = runnerRewards.reduce((acc, cur) => acc.add(cur), BigNumber.from(0)); + const totalRewardDiff = totalReward.sub(totalLabor); + + console.log(`totalLabor: ${utils.formatEther(totalLabor)}`); + console.log(`totalRewardDiff: ${utils.formatEther(totalRewardDiff)}`); + // console.log(`runner0RewardDiff: ${utils.formatEther(runner0RewardDiff)}`); + // console.log(`runner1RewardDiff: ${utils.formatEther(runner1RewardDiff)}`); + results.push([ + metrix.alphaText[alphaIdx], + metrix.operatorStakesText[stakeIdx], + metrix.laborText[laborIdx], + runnerRewards[0].sub(labors[0]).mul(1000).div(labors[0]).toNumber() / 1000, + runnerRewards[1].sub(labors[1]).mul(1000).div(labors[1]).toNumber() / 1000, + runnerRewards[2].sub(labors[2]).mul(1000).div(labors[2]).toNumber() / 1000, + runnerRewards[3].sub(labors[3]).mul(1000).div(labors[3]).toNumber() / 1000, + runnerRewards[4].sub(labors[4]).mul(1000).div(labors[4]).toNumber() / 1000, + runnerRewards[5].sub(labors[5]).mul(1000).div(labors[5]).toNumber() / 1000, + runnerRewards[6].sub(labors[6]).mul(1000).div(labors[6]).toNumber() / 1000, + runnerRewards[7].sub(labors[7]).mul(1000).div(labors[7]).toNumber() / 1000, + runnerRewards[8].sub(labors[8]).mul(1000).div(labors[8]).toNumber() / 1000, + runnerRewards[9].sub(labors[9]).mul(1000).div(labors[9]).toNumber() / 1000, + totalRewardDiff.mul(1000).div(totalLabor).toNumber() / 1000, + ]); + }); + } } } - } - it('output result', function () { results.forEach((result) => { console.log(result.join(',')); }); @@ -413,16 +420,14 @@ describe.skip('RewardsPool Contract', () => { await timeTravel(time.duration.days(1).toNumber()); // Auto collect - await rewardsDistributor.collectAndDistributeRewards(runner0.address); - - const tx2 = await rewardsDistributor.collectAndDistributeRewards(runner1.address); - const evts2 = await eventsFrom( - tx2, - rewardsDistributor, - 'DistributeRewards(address,uint256,uint256,uint256)' - ); const tx = await rewardsDistributor.collectAndDistributeRewards(runner0.address); const evts = await eventsFrom(tx, rewardsDistributor, 'DistributeRewards(address,uint256,uint256,uint256)'); + // const tx2 = await rewardsDistributor.collectAndDistributeRewards(runner1.address); + // const evts2 = await eventsFrom( + // tx2, + // rewardsDistributor, + // 'DistributeRewards(address,uint256,uint256,uint256)' + // ); const rewards2 = await rewardsPool.getReward(deploymentId0, era, runner0.address); expect(rewards2[0]).to.be.eq(etherParse('0')); // already collected const isClaimed1 = await rewardsPool.isClaimed(era, runner0.address); diff --git a/test/StateChannel.test.ts b/test/StateChannel.test.ts index d8cb0a5b..3bcc375f 100644 --- a/test/StateChannel.test.ts +++ b/test/StateChannel.test.ts @@ -281,10 +281,10 @@ describe('StateChannel Contract', () => { // check rewards const evt = await eventFrom(tx, stateChannel, 'ChannelLabor2(uint256,bytes32,address,uint256)'); expect(evt.amount).to.be.eq(etherParse('0.1')); - // const currentEra = (await eraManager.eraNumber()).toNumber(); - // const infos = await rewardsPool.getReward(deploymentId, currentEra, runner.address); - // expect(infos[0]).to.be.eq(etherParse('0.1')); // labor - // expect(infos[1]).to.be.eq(etherParse('0.1')); // reward + const currentEra = (await eraManager.eraNumber()).toNumber(); + const infos = await rewardsPool.getReward(deploymentId, currentEra, runner.address); + expect(infos[0]).to.be.eq(etherParse('0.1')); // labor + expect(infos[1]).to.be.eq(etherParse('0.1')); // reward const query2 = await buildQueryState(channelId, runner, consumer, etherParse('0.2'), false); await stateChannel.checkpoint(query2); @@ -687,11 +687,11 @@ describe('StateChannel Contract', () => { // start new era so we can try collect the channel reward const era = await startNewEra(eraManager); - // const unclaimed = await rewardsPool.getUnclaimDeployments(era.toNumber() - 1, runner.address); - // expect(unclaimed).to.be.empty; - // const reward = await rewardsPool.getReward(deploymentId, era.toNumber() - 1, runner.address); - // expect(reward[0]).to.be.eq(0); - // expect(reward[1]).to.be.eq(etherParse('0.4')); + const unclaimed = await rewardsPool.getUnclaimDeployments(era.toNumber() - 1, runner.address); + expect(unclaimed).to.be.empty; + const reward = await rewardsPool.getReward(deploymentId, era.toNumber() - 1, runner.address); + expect(reward[0]).to.be.eq(0); // labor + expect(reward[1]).to.be.eq(etherParse('0.4')); // total reward // reward distribution should be skipped due to total stake is 0 tx = await rewardsHelper.connect(runner).indexerCatchup(runner.address); @@ -700,7 +700,8 @@ describe('StateChannel Contract', () => { }); /** - * when indexer also has delegator, after indexer unregistered, delegator can claim the rest reward + * when indexer also has delegator, after indexer unregistered, rewards go to + * pool, delegator's rewards should be 0 */ it('terminate State Channel after indexer unregistration #2', async () => { await token.connect(delegator).increaseAllowance(staking.address, etherParse('1')); @@ -746,11 +747,11 @@ describe('StateChannel Contract', () => { const evts = await eventsFrom(tx, rewardsDistributor, 'DistributeRewards(address,uint256,uint256,uint256)'); expect(evts.length).to.eq(2); expect(evts[0].rewards).to.eq(0); - expect(evts[1].rewards).to.eq(etherParse('0.4')); + expect(evts[1].rewards).to.eq(etherParse('0')); // delegator const delegatorRewards = await rewardsDistributor.userRewards(runner.address, delegator.address); - expect(delegatorRewards).to.eq(etherParse('0.4')); + expect(delegatorRewards).to.eq(etherParse('0')); }); /** diff --git a/test/StateChannelFlow.test.ts b/test/StateChannelFlow.test.ts index d4f4e911..384636e1 100644 --- a/test/StateChannelFlow.test.ts +++ b/test/StateChannelFlow.test.ts @@ -11,13 +11,14 @@ import { Staking, StateChannel, StakingManager, + RewardsHelper, } from '../src'; import { registerRunner, startNewEra, time, etherParse } from './helper'; import { Wallet, BigNumber } from 'ethers'; describe('StateChannel Workflow Tests', () => { let wallet_0, runner, runner2, consumer, consumer2; - let channelId, channelId2, channelId3, channelId4; + let channelId, channelId2, channelId3, channelId4, channelId5, channelId7; let token: ERC20; let staking: Staking; @@ -25,6 +26,7 @@ describe('StateChannel Workflow Tests', () => { let eraManager: EraManager; let rewardsDistributor: RewardsDistributor; let rewardsPool: RewardsPool; + let rewardsHelper: RewardsHelper; let stateChannel: StateChannel; let stakingManager: StakingManager; @@ -107,6 +109,7 @@ describe('StateChannel Workflow Tests', () => { token = deployment.token; rewardsDistributor = deployment.rewardsDistributor; rewardsPool = deployment.rewardsPool; + rewardsHelper = deployment.rewardsHelper; eraManager = deployment.eraManager; stateChannel = deployment.stateChannel; stakingManager = deployment.stakingManager; @@ -128,6 +131,8 @@ describe('StateChannel Workflow Tests', () => { channelId2 = ethers.utils.randomBytes(32); channelId3 = ethers.utils.randomBytes(32); channelId4 = ethers.utils.randomBytes(32); + channelId5 = ethers.utils.randomBytes(32); + channelId7 = ethers.utils.randomBytes(32); await openChannel( channelId, runner, @@ -164,6 +169,24 @@ describe('StateChannel Workflow Tests', () => { 3000000000000000, deploymentIds[1] ); + await openChannel( + channelId5, + runner, + consumer, + etherParse('10'), + etherParse('1'), + 3000000000000000, + deploymentIds[0] + ); + await openChannel( + channelId7, + runner2, + consumer, + etherParse('10'), + etherParse('1'), + 3000000000000000, + deploymentIds[0] + ); }); it('check balance when one indexer with no staking', async () => { @@ -174,14 +197,14 @@ describe('StateChannel Workflow Tests', () => { await operateChannel(channelId, runner, consumer, etherParse('3'), false, 0); expect((await stateChannel.channel(channelId)).spent).to.equal(etherParse('3')); - expect(await token.balanceOf(stateChannel.address)).to.equal(etherParse('37')); - expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('3')); + expect(await token.balanceOf(stateChannel.address)).to.equal(etherParse('57')); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('3')); await startNewEra(eraManager); //batchCollect at rewardpool await rewardsPool.batchCollect(runner.address); - // expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('0')); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('0')); expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('3')); expect( await rewardsDistributor.getRewardAddTable(runner.address, (await eraManager.eraNumber()).sub(1)) @@ -209,22 +232,69 @@ describe('StateChannel Workflow Tests', () => { await operateChannel(channelId3, runner2, consumer, etherParse('4'), false, 0); expect((await stateChannel.channel(channelId3)).spent).to.equal(etherParse('4')); - expect(await token.balanceOf(stateChannel.address)).to.equal(etherParse('35')); - expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('5')); + expect(await token.balanceOf(stateChannel.address)).to.equal(etherParse('55')); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('5')); + + // era 3 -> era 4 + await startNewEra(eraManager); + + //batchCollect at rewardpool + await rewardsPool.batchCollect(runner.address); + await rewardsPool.batchCollect(runner2.address); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('0.233966512466940628')); + expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('4.766033487533059372')); + expect( + await rewardsDistributor.getRewardAddTable(runner.address, (await eraManager.eraNumber()).sub(1)) + ).to.equal(etherParse('1.842015749320193294')); + expect( + await rewardsDistributor.getRewardAddTable(runner2.address, (await eraManager.eraNumber()).sub(1)) + ).to.equal(etherParse('2.924017738212866078')); + + await rewardsDistributor.collectAndDistributeRewards(runner.address); + await rewardsDistributor.collectAndDistributeRewards(runner2.address); + + await rewardsDistributor.claimFrom(runner.address, runner.address); + await rewardsDistributor.claimFrom(runner2.address, runner2.address); + + // precision adjustment + expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('0.000000001533059372')); + //checkpoint channel + await operateChannel(channelId5, runner, consumer, etherParse('1'), false, 0); + expect((await stateChannel.channel(channelId5)).spent).to.equal(etherParse('1')); + + await operateChannel(channelId7, runner2, consumer, etherParse('4'), false, 0); + expect((await stateChannel.channel(channelId7)).spent).to.equal(etherParse('4')); + + expect(await token.balanceOf(stateChannel.address)).to.equal(etherParse('50')); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('5.233966512466940628')); + + // multi era + await startNewEra(eraManager); await startNewEra(eraManager); + await startNewEra(eraManager); + + // catchup before batchCollect + await rewardsHelper.indexerCatchup(runner.address); + await rewardsHelper.indexerCatchup(runner2.address); //batchCollect at rewardpool await rewardsPool.batchCollect(runner.address); await rewardsPool.batchCollect(runner2.address); - // expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('0')); - expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('5')); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('0.244537549581081481')); + expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('4.989428964418918519')); expect( await rewardsDistributor.getRewardAddTable(runner.address, (await eraManager.eraNumber()).sub(1)) - ).to.equal(etherParse('1')); + ).to.equal(etherParse('0')); expect( await rewardsDistributor.getRewardAddTable(runner2.address, (await eraManager.eraNumber()).sub(1)) - ).to.equal(etherParse('4')); + ).to.equal(etherParse('0')); + + await rewardsDistributor.claimFrom(runner.address, runner.address); + await rewardsDistributor.claimFrom(runner2.address, runner2.address); + + // precision adjustment + expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('0.005549218418918519')); }); it('check balance when two indexer with staking and deploymentIds', async () => { @@ -243,21 +313,21 @@ describe('StateChannel Workflow Tests', () => { await operateChannel(channelId4, runner2, consumer2, etherParse('3'), false, 0); expect((await stateChannel.channel(channelId4)).spent).to.equal(etherParse('3')); - expect(await token.balanceOf(stateChannel.address)).to.equal(etherParse('30')); - expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('10')); + expect(await token.balanceOf(stateChannel.address)).to.equal(etherParse('50')); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('10')); await startNewEra(eraManager); //batchCollect at rewardpool await rewardsPool.batchCollect(runner.address); await rewardsPool.batchCollect(runner2.address); - // expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('0')); - expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('10')); + expect(await token.balanceOf(rewardsPool.address)).to.equal(etherParse('0.256525672704023517')); + expect(await token.balanceOf(rewardsDistributor.address)).to.equal(etherParse('9.743474327295976483')); expect( await rewardsDistributor.getRewardAddTable(runner.address, (await eraManager.eraNumber()).sub(1)) - ).to.equal(etherParse('3')); + ).to.equal(etherParse('4.162810166126582740')); expect( await rewardsDistributor.getRewardAddTable(runner2.address, (await eraManager.eraNumber()).sub(1)) - ).to.equal(etherParse('7')); + ).to.equal(etherParse('5.580664161169393743')); }); });