pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; import "../../src/FatsoERC20.sol"; import "../../src/StinkyERC20.sol"; import "../../src/GhstERC20.sol"; import "../../src/GhostAuthority.sol"; import "../../src/StakingDistributor.sol"; import "../../src/Treasury.sol"; import "../../src/Staking.sol"; import "../../src/mocks/ERC20Mock.sol"; contract StakingDistributorTest is Test { address public constant owner = 0x0000000000000000000000000000000000000001; address public constant governor = 0x0000000000000000000000000000000000000002; address public constant guardian = 0x0000000000000000000000000000000000000003; address public constant other = 0x0000000000000000000000000000000000000004; address public constant alice = 0x0000000000000000000000000000000000000005; address public constant bob = 0x0000000000000000000000000000000000000006; uint48 public constant EPOCH_LENGTH = 2200; uint48 public constant EPOCH_NUMBER = 1; uint48 public constant EPOCH_END_TIME = 1337; uint256 public constant INITIAL_INDEX = 10819917194513808e56; uint256 public constant amount = 69 * 1e18; Fatso ftso; Stinky stnk; Ghost ghst; ERC20Mock reserve; GhostTreasury treasury; GhostStaking staking; GhostDistributor distributor; GhostAuthority authority; uint256 public constant TEN_PERCENT = 1e5; function setUp() public { vm.startPrank(owner); authority = new GhostAuthority( governor, guardian, owner, owner ); reserve = new ERC20Mock("Reserve Token", "RET"); ftso = new Fatso(address(authority)); stnk = new Stinky(INITIAL_INDEX); ghst = new Ghost(address(stnk)); staking = new GhostStaking( address(ftso), address(stnk), address(ghst), EPOCH_LENGTH, EPOCH_NUMBER, EPOCH_END_TIME, address(authority) ); treasury = new GhostTreasury(address(ftso), 69, address(authority)); distributor = new GhostDistributor( address(treasury), address(ftso), address(staking), address(authority), TEN_PERCENT ); stnk.initialize(address(staking), address(treasury), address(ghst)); ghst.initialize(address(staking)); vm.stopPrank(); vm.startPrank(governor); staking.setDistributor(address(distributor)); authority.pushVault(address(treasury)); vm.stopPrank(); } function test_distribute_onlyByStaking() public { _prepareTreasury(); vm.expectRevert(); vm.prank(other); distributor.distribute(); vm.prank(address(staking)); distributor.distribute(); } function test_bounty_onlyByGovernor(address who) public { vm.assume(who != governor); _prepareTreasury(); assertEq(ftso.balanceOf(address(staking)), 0); vm.prank(governor); distributor.setBounty(1337); vm.prank(address(staking)); distributor.retrieveBounty(); assertEq(ftso.balanceOf(address(staking)), 1337); } function test_nextRewardFor_zeroIfAddressIsEmpty() public view { assertEq(ftso.balanceOf(bob), 0); assertEq(distributor.nextRewardFor(bob), 0); } function test_nextRewardFor_nextRewardForAddress() public { _prepareTreasury(); vm.startPrank(bob); reserve.mint(bob, amount); reserve.approve(address(treasury), amount); uint256 send = treasury.deposit(address(reserve), amount, treasury.tokenValue(address(reserve), amount)); vm.stopPrank(); assertEq(ftso.balanceOf(bob), send); assertEq(distributor.nextRewardFor(bob), send * TEN_PERCENT / 1e6); } function test_setBounty_shouldRevertIfNotByGovernor(address who) public { vm.assume(who != governor); assertEq(distributor.bounty(), 0); vm.expectRevert(); vm.prank(who); distributor.setBounty(69); assertEq(distributor.bounty(), 0); } function test_setBounty_governorShouldSet() public { _prepareTreasury(); assertEq(distributor.bounty(), 0); vm.prank(governor); distributor.setBounty(1337); assertEq(distributor.bounty(), 1337); } function test_setPools_shouldRevertIfNotGovernor(address who) public { vm.assume(who != governor); address[] memory newPools = new address[](1); newPools[0] = who; vm.expectRevert(); vm.prank(who); distributor.setPools(newPools); } function test_setPools_governorShouldSet() public { address[] memory newPools = new address[](1); newPools[0] = other; vm.prank(governor); distributor.setPools(newPools); assertEq(distributor.pools(0), other); } function test_removePools_shouldRevertIfNotGovernor(address who) public { vm.assume(who != governor); address[] memory newPools = new address[](1); newPools[0] = other; vm.prank(governor); distributor.setPools(newPools); vm.expectRevert(); vm.prank(who); distributor.removePool(0); } function test_removePools_governorShouldRemove() public { address[] memory newPools = new address[](1); newPools[0] = other; vm.startPrank(governor); distributor.setPools(newPools); assertEq(distributor.pools(0), other); distributor.removePool(0); vm.stopPrank(); } function test_addPool_shouldRevertIfNotGovernor(address who) public { vm.assume(who != governor); vm.expectRevert(); vm.prank(who); distributor.addPool(other); } function test_addPool_shouldPushPoolWhenIndexTaken() public { vm.prank(governor); distributor.addPool(other); assertEq(distributor.pools(0), other); } function test_setAdjustment_shouldRevertIfNotGovernor(address who) public { vm.assume(who != governor && who != guardian); vm.expectRevert(); vm.prank(who); distributor.setAdjustment(69, 1337, true); } function test_setAdjustment_governorShouldIncreaseAdjustment() public { vm.prank(governor); distributor.setAdjustment(69, 1337, false); } function test_setAdjustment_shouldPopulateAdjustmentToRewardRateAfterRebase() public { vm.prank(governor); distributor.setAdjustment(69, TEN_PERCENT * 2, true); _prepareTreasury(); assertEq(distributor.rewardRate(), TEN_PERCENT); (,, uint256 end,) = staking.epoch(); skip(end); staking.rebase(); assertEq(distributor.rewardRate(), TEN_PERCENT + 69); } function test_setAdjustment_willNotAdjustIfRateIsZero() public { vm.prank(governor); distributor.setAdjustment(0, 1337, true); _prepareTreasury(); assertEq(distributor.rewardRate(), TEN_PERCENT); (,, uint256 end,) = staking.epoch(); skip(end); staking.rebase(); assertEq(distributor.rewardRate(), TEN_PERCENT); } function test_setAdjustment_willStopIncreaseWhenTargetMet() public { vm.prank(governor); distributor.setAdjustment(69, 1337, true); _prepareTreasury(); assertEq(distributor.rewardRate(), TEN_PERCENT); (,, uint256 end,) = staking.epoch(); skip(end); staking.rebase(); assertEq(distributor.rewardRate(), 1337); } function test_setAdjustment_willStopDecreaseWhenTargetMet() public { vm.prank(governor); distributor.setAdjustment(TEN_PERCENT, 1337, false); _prepareTreasury(); assertEq(distributor.rewardRate(), TEN_PERCENT); (,, uint256 end,) = staking.epoch(); skip(end); staking.rebase(); assertEq(distributor.rewardRate(), 1337); } function _prepareTreasury() internal { vm.startPrank(governor); treasury.enable(ITreasury.STATUS.REWARDMANAGER, address(distributor), address(0)); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, alice, address(0)); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, bob, address(0)); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(0)); vm.stopPrank(); vm.startPrank(alice); reserve.mint(alice, amount); reserve.approve(address(treasury), amount); treasury.deposit(address(reserve), amount, treasury.tokenValue(address(reserve), amount)); vm.stopPrank(); } }