// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import { MockProposal } from "./MockProposal.sol"; import { Staker, ITornadoStakingRewards } from "@interfaces/ITornadoStakingRewards.sol"; import { Test } from "@forge-std/Test.sol"; import "@forge-std/console2.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract TestProposal is MockProposal { ITornadoStakingRewards staking = ITornadoStakingRewards(_stakingAddress); function testGovernanceBalance() public { IERC20 TORN = IERC20(_tokenAddress); uint256 governanceBalanceBeforeExecution = TORN.balanceOf(_governanceAddress); console2.log("Governance balance before proposal execution: %s TORN", governanceBalanceBeforeExecution / _tornDecimals); createAndExecuteProposal(); uint256 governanceBalanceAfterExecution = TORN.balanceOf(_governanceAddress); console2.log("Governance balance after proposal execution: %s TORN", governanceBalanceAfterExecution / _tornDecimals); uint256 governanceBalanceDifference = governanceBalanceBeforeExecution - governanceBalanceAfterExecution; console2.log("Governance balance difference: %s TORN, %s", governanceBalanceDifference / _tornDecimals, governanceBalanceDifference); require(governanceBalanceDifference == summaryRestoreAmount, "Incorrect Governance balance after execution"); } function testOtherUsersRewardUnchanged() public { uint256 rewardsBeforeProposal = staking.checkReward(TEST_REAL_ADDRESS_WITH_BALANCE); console2.log("Developer rewards before proposal execution: %s TORN", rewardsBeforeProposal / _tornDecimals); createAndExecuteProposal(); uint256 rewardsAfterProposal = staking.checkReward(TEST_REAL_ADDRESS_WITH_BALANCE); console2.log("Developer rewards after proposal execution: %s TORN", rewardsBeforeProposal / _tornDecimals); require(rewardsAfterProposal == rewardsBeforeProposal, "Other stakers rewards changed"); } function testCannotCallSetRewardsNotFromGovernance() public executeCurrentProposalBefore { // Trying to set rewards to myself, expect error revert call vm.startPrank(TEST_ADDRESS_ONE); vm.expectRevert(bytes("only governance")); staking.setReward(TEST_ADDRESS_ONE, 100_000 ether); vm.stopPrank(); require(staking.checkReward(TEST_ADDRESS_ONE) == 0, "Rewards accrued without permissions (not from Governance call)"); vm.startPrank(_governanceAddress); staking.setReward(TEST_ADDRESS_ONE, 100_000 ether); vm.stopPrank(); require(staking.checkReward(TEST_ADDRESS_ONE) == 100_000 ether, "Rewards not accrued by Governance"); } function getStakersRewardsSum(Staker[cheatingStakersCount] memory stakers) internal returns (uint256) { uint256 rewardsSum = 0; for (uint16 i = 0; i < stakers.length; i++) { rewardsSum += staking.accumulatedRewards(stakers[i].addr); } return rewardsSum; } function testVerifyStakersAccrual() internal { Staker[cheatingStakersCount] memory stakers = getOldStakers(); uint256[cheatingStakersCount] memory rewardsBefore; uint256[cheatingStakersCount] memory rewardsAfter; for (uint16 i = 0; i < stakers.length; i++) { rewardsBefore[i] = staking.accumulatedRewards(stakers[i].addr); } createAndExecuteProposal(); for (uint16 i = 0; i < stakers.length; i++) { rewardsAfter[i] = staking.accumulatedRewards(stakers[i].addr); } for (uint16 i = 0; i < stakers.length; i++) { console2.log("\nStaker address: %s", stakers[i].addr); console2.log("Rewards before: %s", rewardsBefore[i]); console2.log("Rewards after: %s", rewardsAfter[i]); console2.log("Real difference: %s", (rewardsAfter[i] - rewardsBefore[i])); console2.log("Expected difference: %s", stakers[i].oldRewards); require(rewardsAfter[i] - rewardsBefore[i] == stakers[i].oldRewards); } } function testStakingContractReplenishedCorrect() public { Staker[cheatingStakersCount] memory stakers = getOldStakers(); uint256 stakersRewardsSumBeforeExecution = getStakersRewardsSum(stakers); console2.log("Old stakers rewards sum before proposal execution: %s TORN", stakersRewardsSumBeforeExecution / _tornDecimals); uint256 stakingContractBalanceBeforeExecution = IERC20(_tokenAddress).balanceOf(_stakingAddress); console2.log("Staking contract balance before proposal execution: %s TORN", stakingContractBalanceBeforeExecution / _tornDecimals); createAndExecuteProposal(); uint256 stakersRewardsSumAfterExecution = getStakersRewardsSum(stakers); console2.log("\nOld stakers rewards sum after proposal exectuion: %s TORN", stakersRewardsSumAfterExecution / _tornDecimals); uint256 stakingContractBalanceAfterExecution = IERC20(_tokenAddress).balanceOf(_stakingAddress); console2.log("Staking contract balance after proposal execution: %s TORN", stakingContractBalanceAfterExecution / _tornDecimals); uint256 stakersRewardSumDifference = stakersRewardsSumAfterExecution - stakersRewardsSumBeforeExecution; uint256 stakingContractBalanceDifference = stakingContractBalanceAfterExecution - stakingContractBalanceBeforeExecution; console2.log("\nStaking contract replenish amount: %s TORN", stakingContractBalanceDifference / _tornDecimals); console2.log("Stakers restored rewards sum: %s TORN", stakersRewardSumDifference / _tornDecimals); require( stakersRewardSumDifference == stakingContractBalanceDifference, "Staking replenish sum doesn't match with stakers restored rewards sum" ); } function testAccumulatedRewardPerTornNotChanged() public { uint256 accumulatedRewardsPerTornBeforeExecution = staking.accumulatedRewardPerTorn(); console2.log("Accumulated reward per 1 TORN before proposal execution: %s", accumulatedRewardsPerTornBeforeExecution / _tornMaximumSupply); createAndExecuteProposal(); uint256 accumulatedRewardsPerTornAfterExecution = staking.accumulatedRewardPerTorn(); console2.log("Accumulated reward per 1 TORN after proposal execution: %s", accumulatedRewardsPerTornAfterExecution / _tornMaximumSupply); require(accumulatedRewardsPerTornBeforeExecution == accumulatedRewardsPerTornAfterExecution, "Accumulater reward per TORN changed"); } function testRewardAccrualsMechanismCorrect() public executeCurrentProposalBefore { IERC20 TORN = IERC20(_tokenAddress); uint256 toBurn = 10_000 ether; retrieveAndLockBalance(TEST_STAKER_PRIVATE_KEY, TEST_STAKER_ADDRESS, PROPOSAL_THRESHOLD); uint256 stakerLockedBalance = governance.lockedBalance(TEST_STAKER_ADDRESS); require(stakerLockedBalance == PROPOSAL_THRESHOLD, "Invalid test staker locked balance"); uint256 stakerRewardsBeforeBurning = staking.checkReward(TEST_STAKER_ADDRESS); console2.log("Staking rewards before burning: %s TORN", stakerRewardsBeforeBurning / _tornDecimals); burnTokens(_governanceAddress, toBurn, staking); uint256 stakerRewardsAfterBurning = staking.checkReward(TEST_STAKER_ADDRESS); console2.log( "Staking rewards after burning 10 000 TORN: %s TORN\n", stakerRewardsAfterBurning / _tornDecimals ); require(stakerRewardsAfterBurning > stakerRewardsBeforeBurning, "Rewards isn't changed after burning"); // All TORN, locked by users in Governance, is on the userVault contract balance uint256 governanceLockedAmount = TORN.balanceOf(governance.userVault()); uint256 receivedReward = stakerRewardsAfterBurning - stakerRewardsBeforeBurning; uint256 expectedRewards = stakerLockedBalance * toBurn / governanceLockedAmount; console2.log("Expected staking rewards: %s TORN", expectedRewards / _tornDecimals); console2.log("Staker received rewards: %s TORN\n", receivedReward / _tornDecimals); require(receivedReward == expectedRewards, "Expected and received rewards don't match"); } function testAccumulatedRewardCanBeUpdated() public executeCurrentProposalBefore { uint256 accumulatedRewardPerTornBeforeBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply; console2.log( "Accumulated reward per TORN right after proposal execution: %s TORN", accumulatedRewardPerTornBeforeBurning / _tornDecimals ); burnTokens(_governanceAddress, 10_000_000 ether, staking); uint256 accumulatedRewardPerTornAfterBurning = staking.accumulatedRewardPerTorn() / _tornMaximumSupply; console2.log( "Accumulated reward per TORN after burning 10 000 000 TORN: ~ %s TORN", accumulatedRewardPerTornAfterBurning / _tornDecimals ); require( accumulatedRewardPerTornAfterBurning > accumulatedRewardPerTornBeforeBurning, "Staking rewards isn't updated" ); } }