pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; import {Fatso} from "../../src/FatsoERC20.sol"; import {Stinky} from "../../src/StinkyERC20.sol"; import {Ghost} from "../../src/GhstERC20.sol"; import {GhostAuthority} from "../../src/GhostAuthority.sol"; import {GhostDistributor} from "../../src/StakingDistributor.sol"; import {GhostTreasury} from "../../src/Treasury.sol"; import {GhostStaking} from "../../src/Staking.sol"; import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol"; import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol"; import {ITreasury} from "../../src/interfaces/ITreasury.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; GhostBondingCalculator calculator; 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), "Fatso", "FTSO"); stnk = new Stinky(INITIAL_INDEX, "Stinky", "STNK"); ghst = new Ghost(address(stnk), "Ghost", "GHST"); 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)); calculator = new GhostBondingCalculator(address(ftso), 1, 1); 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(calculator)); 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(); } }