pragma solidity 0.8.20; import {Test} from "forge-std/Test.sol"; import {Fatso} from "../../src/FatsoERC20.sol"; import {GhostAuthority} from "../../src/GhostAuthority.sol"; import {GhostTreasury} from "../../src/Treasury.sol"; import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol"; import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol"; import {ITreasury} from "../../src/interfaces/ITreasury.sol"; import {IUniswapV2Pair} from "@uniswap-v2-core/interfaces/IUniswapV2Pair.sol"; contract GhostTreasuryTest is Test { address public constant OWNER = 0x0000000000000000000000000000000000000001; address public constant GOVERNOR = 0x0000000000000000000000000000000000000002; address public constant GUARDIAN = 0x0000000000000000000000000000000000000003; address public constant ALICE = 0x0000000000000000000000000000000000000004; uint256 public constant AMOUNT = 69 * 1e18; Fatso ftso; ERC20Mock reserve; ERC20Mock liquidity; GhostTreasury treasury; GhostAuthority authority; GhostBondingCalculator calculator; function setUp() public { vm.startPrank(OWNER); authority = new GhostAuthority( GOVERNOR, GUARDIAN, OWNER, OWNER ); reserve = new ERC20Mock("Reserve Token", "RET"); liquidity = new ERC20Mock("Liquidity Token", "LDT"); ftso = new Fatso(address(authority), "Fatso", "FTSO"); treasury = new GhostTreasury(address(ftso), 69, address(authority)); calculator = new GhostBondingCalculator(address(ftso), 1, 1); vm.stopPrank(); vm.prank(GOVERNOR); authority.pushVault(address(treasury)); vm.startPrank(ALICE); reserve.mint(ALICE, AMOUNT); reserve.approve(address(treasury), type(uint256).max); ftso.approve(address(treasury), type(uint256).max); vm.stopPrank(); } function test_deposit_onlyIfApprovedTokenAndApprovedAddress() public { vm.expectRevert(); vm.prank(ALICE); treasury.deposit(address(reserve), AMOUNT, 0); vm.prank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0)); vm.expectRevert(); vm.prank(ALICE); treasury.deposit(address(reserve), AMOUNT, 0); vm.prank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); vm.prank(ALICE); uint256 send = treasury.deposit(address(reserve), AMOUNT, 0); assertEq(ftso.balanceOf(ALICE), send); assertEq(reserve.balanceOf(address(treasury)), AMOUNT); } function test_withdraw_onlyIfApprovedTokenAndApprovedAddress() public { vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0)); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); vm.stopPrank(); vm.prank(ALICE); treasury.deposit(address(reserve), AMOUNT, 0); vm.expectRevert(); vm.prank(ALICE); treasury.withdraw(address(reserve), AMOUNT); vm.prank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVESPENDER, ALICE, address(0)); vm.prank(ALICE); treasury.withdraw(address(reserve), AMOUNT); assertEq(ftso.balanceOf(ALICE), 0); assertEq(reserve.balanceOf(address(treasury)), 0); } function test_manage_onlyIfApprovedTokenAndApprovedAddress() public { vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0)); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); vm.stopPrank(); uint256 tokenValue = treasury.tokenValue(address(reserve), AMOUNT); vm.prank(ALICE); uint256 send = treasury.deposit(address(reserve), AMOUNT, tokenValue); vm.expectRevert(); vm.prank(ALICE); treasury.manage(address(reserve), AMOUNT); vm.prank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVEMANAGER, ALICE, address(0)); vm.prank(ALICE); treasury.manage(address(reserve), AMOUNT); assertEq(ftso.balanceOf(ALICE), send); assertEq(reserve.balanceOf(ALICE), AMOUNT); assertEq(reserve.balanceOf(address(treasury)), 0); } function test_mint_onlyIfApprovedTokenAndApprovedAddress() public { vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0)); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); vm.stopPrank(); uint256 tokenValue = treasury.tokenValue(address(reserve), AMOUNT); vm.prank(ALICE); uint256 send = treasury.deposit(address(reserve), AMOUNT, tokenValue); vm.expectRevert(); vm.prank(ALICE); treasury.mint(ALICE, 69); vm.prank(GOVERNOR); treasury.enable(ITreasury.STATUS.REWARDMANAGER, ALICE, address(0)); vm.prank(ALICE); treasury.mint(ALICE, 69); assertEq(ftso.balanceOf(ALICE), send + 69); assertEq(reserve.balanceOf(address(treasury)), AMOUNT); } function test_auditTreasuryReserves() public { vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0)); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); vm.stopPrank(); uint256 tokenValue = treasury.tokenValue(address(reserve), AMOUNT); vm.prank(ALICE); treasury.deposit(address(reserve), AMOUNT, tokenValue); vm.prank(GOVERNOR); treasury.auditReserves(); assertEq(treasury.permissions(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true); assertEq(treasury.registry(ITreasury.STATUS.RESERVETOKEN, 0), address(reserve)); assertEq( treasury.tokenValue(address(reserve), reserve.balanceOf(address(treasury))), treasury.totalReserves()); } function test_randomAddressCouldNotEnableStatusAndCalculator(address who) public { vm.assume(who != GOVERNOR); vm.expectRevert(); vm.prank(who); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(0)); vm.expectRevert(); vm.prank(who); treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity), address(calculator)); vm.expectRevert(); vm.prank(who); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender, address(0)); } function test_enableStatusAndCalculator() public { vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity), address(calculator)); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender, address(0)); vm.stopPrank(); assertEq(treasury.registry(ITreasury.STATUS.RESERVETOKEN, 0), address(reserve)); assertEq(treasury.registry(ITreasury.STATUS.LIQUIDITYTOKEN, 0), address(liquidity)); assertEq(treasury.bondCalculator(address(liquidity)), address(calculator)); vm.expectRevert(); treasury.registry(ITreasury.STATUS.RESERVEDEPOSITOR, 0); assertEq(treasury.permissions(ITreasury.STATUS.RESERVETOKEN, address(reserve)), true); assertEq(treasury.permissions(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)), true); assertEq(treasury.permissions(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender), true); } function test_randomAddressCouldNotDisableStatusByAddress(address who) public { vm.assume(who != GOVERNOR && who != GUARDIAN); vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity), address(calculator)); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender, address(0)); vm.stopPrank(); vm.expectRevert(); vm.prank(who); treasury.disable(ITreasury.STATUS.RESERVETOKEN, address(reserve)); vm.expectRevert(); vm.prank(who); treasury.disable(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)); vm.expectRevert(); vm.prank(who); treasury.disable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender); } function test_disableStatusByAddress() public { vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity), address(calculator)); treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender, address(0)); vm.stopPrank(); vm.startPrank(GOVERNOR); treasury.disable(ITreasury.STATUS.RESERVETOKEN, address(reserve)); treasury.disable(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)); treasury.disable(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender); vm.stopPrank(); assertEq(treasury.permissions(ITreasury.STATUS.RESERVETOKEN, address(reserve)), false); assertEq(treasury.permissions(ITreasury.STATUS.LIQUIDITYTOKEN, address(liquidity)), false); assertEq(treasury.permissions(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender), false); } function test_mainnet_disableReserveAndLiquidity() public { address realDexPair = 0xB20bd5D04BE54f870D5C0d3cA85d82b34B836405; vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, realDexPair, address(calculator)); vm.stopPrank(); assertEq(treasury.tokenValue(address(reserve), 1e18), 1e9); (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(realDexPair).getReserves(); uint256 reserves = reserve0 * reserve1 / 1e6; uint256 reserveEps = reserves * 1e5 / 1e7; // 1% uint256 totalSupply = IUniswapV2Pair(realDexPair).totalSupply(); uint256 liquidityValue = (treasury.tokenValue(realDexPair, totalSupply) / 2)**2; assertEq(liquidityValue + reserveEps >= reserves, true); assertEq(liquidityValue - reserveEps <= reserves, true); vm.startPrank(GOVERNOR); treasury.disable(ITreasury.STATUS.RESERVETOKEN, address(reserve)); treasury.disable(ITreasury.STATUS.LIQUIDITYTOKEN, realDexPair); vm.stopPrank(); assertEq(treasury.tokenValue(address(reserve), 1e18), 0); assertEq(treasury.tokenValue(realDexPair, 1e18), 0); } function test_mainnet_tokenValueIsCorret() public { address realDexPair = 0xB20bd5D04BE54f870D5C0d3cA85d82b34B836405; vm.startPrank(GOVERNOR); treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, realDexPair, address(calculator)); vm.stopPrank(); assertEq(treasury.tokenValue(address(reserve), 1e18), 1e9); (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(realDexPair).getReserves(); uint256 reserves = reserve0 * reserve1 / 1e6; uint256 reserveEps = reserves * 1e5 / 1e7; // 1% uint256 totalSupply = IUniswapV2Pair(realDexPair).totalSupply(); uint256 liquidityValue = (treasury.tokenValue(realDexPair, totalSupply) / 2)**2; assertEq(liquidityValue + reserveEps >= reserves, true); assertEq(liquidityValue - reserveEps <= reserves, true); } function test_randomAddressCouldNotTriggerTimelock(address who) public { vm.assume(who != GOVERNOR); vm.expectRevert(); vm.prank(who); treasury.toggleTimelock(); } function test_triggerTimelock() public { assertEq(treasury.timelockEnabled(), false); assertEq(treasury.onChainGovernanceTimelock(), 0); vm.prank(GOVERNOR); treasury.toggleTimelock(); uint256 queuedTime = block.number + 69 * 7; assertEq(treasury.timelockEnabled(), false); assertEq(treasury.onChainGovernanceTimelock(), queuedTime); vm.roll(queuedTime + 1); vm.prank(GOVERNOR); treasury.toggleTimelock(); assertEq(treasury.timelockEnabled(), true); assertEq(treasury.onChainGovernanceTimelock(), 0); vm.prank(GOVERNOR); treasury.toggleTimelock(); queuedTime = block.number + 69 * 7; assertEq(treasury.timelockEnabled(), true); assertEq(treasury.onChainGovernanceTimelock(), queuedTime); vm.roll(queuedTime + 1); vm.prank(GOVERNOR); treasury.toggleTimelock(); assertEq(treasury.timelockEnabled(), false); assertEq(treasury.onChainGovernanceTimelock(), 0); } }