diff --git a/src/Treasury.sol b/src/Treasury.sol index 38e5a78..3b4bd21 100644 --- a/src/Treasury.sol +++ b/src/Treasury.sol @@ -345,6 +345,11 @@ contract GhostTreasury is GhostAccessControlled, ITreasury { } } + function originalCoefficient() external view returns (uint256) { + address[] memory reserveTokens = registry[STATUS.RESERVETOKEN]; + return IBondingCalculator(bondCalculator[reserveTokens[0]]).fraction(); + } + function excessReserves() public view override returns (uint256) { return totalReserves - (IFTSO(ftso).totalSupply() - totalDebt); } @@ -355,7 +360,7 @@ contract GhostTreasury is GhostAccessControlled, ITreasury { ) public view override returns (uint256 value) { if (permissions[STATUS.LIQUIDITYTOKEN][token]) { value = IBondingCalculator(bondCalculator[token]).valuation(token, amount); - } else { + } else if (permissions[STATUS.RESERVETOKEN][token]) { value = amount * 1e9 / (10**IERC20Metadata(token).decimals()); value = value * IBondingCalculator(bondCalculator[token]).fraction() / 1e18; } diff --git a/test/treasury/Treasury.t.sol b/test/treasury/Treasury.t.sol index 2ed8d88..ff8abb3 100644 --- a/test/treasury/Treasury.t.sol +++ b/test/treasury/Treasury.t.sol @@ -239,7 +239,7 @@ contract GhostTreasuryTest is Test { assertEq(treasury.permissions(ITreasury.STATUS.RESERVEDEPOSITOR, msg.sender), false); } - function test_tokenValueIsCorret() public { + function test_mainnet_disableReserveAndLiquidity() public { address realDexPair = 0xB20bd5D04BE54f870D5C0d3cA85d82b34B836405; vm.startPrank(governor); @@ -247,12 +247,40 @@ contract GhostTreasuryTest is Test { treasury.enable(ITreasury.STATUS.LIQUIDITYTOKEN, realDexPair, address(calculator)); vm.stopPrank(); - uint256 totalSupply = IUniswapV2Pair(realDexPair).totalSupply(); 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); diff --git a/test/treasury/TreasuryCoefficienBigger.sol b/test/treasury/TreasuryCoefficienBigger.sol new file mode 100644 index 0000000..2707667 --- /dev/null +++ b/test/treasury/TreasuryCoefficienBigger.sol @@ -0,0 +1,95 @@ +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {StdChains} from "forge-std/StdChains.sol"; + +import "../../src/FatsoERC20.sol"; +import "../../src/GhostAuthority.sol"; +import "../../src/Treasury.sol"; +import "../../src/StandardBondingCalculator.sol"; +import "../../src/mocks/ERC20Mock.sol"; + +contract GhostTreasuryCoefficienBiggerTest 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; + + 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), 20, 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_auditTreasuryReservesWithCoefficient() public { + vm.startPrank(governor); + treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, alice, address(0)); + treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); + vm.stopPrank(); + + assertEq(treasury.totalReserves(), 0); + + uint256 tokenValue = treasury.tokenValue(address(reserve), amount); + vm.prank(alice); + IERC20(reserve).transfer(address(treasury), amount); + + assertEq(treasury.totalReserves(), 0); + + vm.prank(governor); + treasury.auditReserves(); + assertEq(treasury.totalReserves(), tokenValue); + + uint256 coefficient = treasury.originalCoefficient(); + uint256 decimals = IERC20Metadata(address(reserve)).decimals() + 9; + uint256 convertedReserves = treasury.totalReserves() * 10**decimals / coefficient; + uint256 convertedEps = convertedReserves * 1e5 / 1e12; // .000001% + + assertEq(convertedReserves + convertedEps >= convertedReserves, true); + assertEq(convertedReserves - convertedEps <= convertedReserves, true); + + assertEq(treasury.excessReserves(), tokenValue); + } + + function test_originalCoefficientCorrect() public { + vm.startPrank(governor); + treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, alice, address(0)); + treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); + vm.stopPrank(); + + uint256 estimation = 20 * 1e18; + uint256 estimationEps = estimation * 1e5 / 1e12; // .000001% + + assertEq(estimation + estimationEps >= treasury.originalCoefficient(), true); + assertEq(estimation - estimationEps <= treasury.originalCoefficient(), true); + } +} diff --git a/test/treasury/TreasuryCoefficientLesser.sol b/test/treasury/TreasuryCoefficientLesser.sol new file mode 100644 index 0000000..7be1f5e --- /dev/null +++ b/test/treasury/TreasuryCoefficientLesser.sol @@ -0,0 +1,121 @@ +pragma solidity 0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {StdChains} from "forge-std/StdChains.sol"; + +import "../../src/FatsoERC20.sol"; +import "../../src/GhostAuthority.sol"; +import "../../src/Treasury.sol"; +import "../../src/StandardBondingCalculator.sol"; +import "../../src/mocks/ERC20Mock.sol"; + +contract GhostTreasuryCoefficientLesserTest 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; + + 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, 20); + 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); + + vm.startPrank(governor); + treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, alice, address(0)); + treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); + vm.stopPrank(); + } + + function test_auditTreasuryReservesWithCoefficient() public { + vm.startPrank(governor); + treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, alice, address(0)); + treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); + vm.stopPrank(); + + assertEq(treasury.totalReserves(), 0); + + uint256 tokenValue = treasury.tokenValue(address(reserve), amount); + vm.prank(alice); + IERC20(reserve).transfer(address(treasury), amount); + + assertEq(treasury.totalReserves(), 0); + + vm.prank(governor); + treasury.auditReserves(); + assertEq(treasury.totalReserves(), tokenValue); + + uint256 coefficient = treasury.originalCoefficient(); + uint256 decimals = IERC20Metadata(address(reserve)).decimals() + 9; + uint256 convertedReserves = treasury.totalReserves() * 10**decimals / coefficient; + uint256 convertedEps = convertedReserves * 1e5 / 1e12; // .000001% + + assertEq(convertedReserves + convertedEps >= convertedReserves, true); + assertEq(convertedReserves - convertedEps <= convertedReserves, true); + + assertEq(treasury.excessReserves(), tokenValue); + } + + function test_originalCoefficientCorrect() public { + vm.startPrank(governor); + treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, alice, address(0)); + treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator)); + vm.stopPrank(); + + uint256 estimation = 1e18 / 20; + uint256 estimationEps = estimation * 1e5 / 1e12; // .000001% + + assertEq(estimation + estimationEps >= treasury.originalCoefficient(), true); + assertEq(estimation - estimationEps <= treasury.originalCoefficient(), true); + } +} diff --git a/tester.sh b/tester.sh index 0b8a89f..1a02c75 100644 --- a/tester.sh +++ b/tester.sh @@ -1,5 +1,5 @@ # exclude all tests dependant on rpc -forge test --no-match-test test_tokenValueIsCorret +forge test --no-match-test test_mainnet_ echo "" echo "#####################################" @@ -11,4 +11,4 @@ echo "" FORK_URL="${1:-https://eth-mainnet.alchemyapi.io/v2/pwc5rmJhrdoaSEfimoKEmsvOjKSmPDrP}" # run tests where rpc to the mainnet is needed -forge test --match-test test_tokenValueIsCorret --fork-url $FORK_URL +forge test --match-test test_mainnet_ --fork-url $FORK_URL