ghost-dao-contracts/test/bonding/BondDepositorty.t.sol
Uncle Fatso 8ac8b67767
get rid of annoying foundry warnings
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-03-09 00:37:41 +03:00

393 lines
14 KiB
Solidity

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 {GhostTreasury} from "../../src/Treasury.sol";
import {GhostStaking} from "../../src/Staking.sol";
import {GhostBondDepository} from "../../src/BondDepository.sol";
import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol";
import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol";
import {ITreasury} from "../../src/interfaces/ITreasury.sol";
contract GhostBondDepositoryTest is Test {
uint256 public constant TOTAL_INITIAL_SUPPLY = 5000000000000000;
uint256 public constant LARGE_APPROVAL = 100000000000000000000000000000000;
uint256 public constant INITIAL_INDEX = 10819917194513808e56;
uint48 public constant EPOCH_LENGTH = 2200;
uint48 public constant EPOCH_NUMBER = 1;
uint48 public constant EPOCH_END_TIME = 1337;
uint256 public constant INITIAL_MINT = 1000000000000000000000000;
uint256 public constant CAPACITY = 10000e9;
uint256 public constant INITIAL_PRICE = 400e9;
uint256 public constant BUFFER = 2e5;
address constant INITIALIZER = 0x0000000000000000000000000000000000000001;
address constant GOVERNOR = 0x0000000000000000000000000000000000000003;
address constant GUARDIAN = 0x0000000000000000000000000000000000000004;
address constant POLICY = 0x0000000000000000000000000000000000000005;
address constant VAULT = 0x0000000000000000000000000000000000000006;
address constant ALICE = 0x0000000000000000000000000000000000000007;
uint256 public constant VESTING = 100;
uint256 public constant TIME_TO_CONCLUSION = 60 * 60 * 24;
uint256 public constant DEPOSIT_INTERVAL = 60 * 60 * 4;
uint256 public constant TUNE_INTERVAL = 60 * 60;
uint256 public conclusion;
ERC20Mock reserve;
Fatso ftso;
Stinky stnk;
Ghost ghst;
GhostStaking staking;
GhostTreasury treasury;
GhostAuthority authority;
GhostBondDepository depository;
GhostBondingCalculator calculator;
function setUp() public {
vm.startPrank(INITIALIZER);
authority = new GhostAuthority(
GOVERNOR,
GUARDIAN,
POLICY,
VAULT
);
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);
stnk.initialize(address(staking), address(treasury), address(ghst));
ghst.initialize(address(staking));
depository = new GhostBondDepository(
address(authority),
address(ftso),
address(ghst),
address(staking),
address(treasury)
);
vm.stopPrank();
_createFirstBond();
}
function _createFirstBond() internal {
vm.startPrank(GOVERNOR);
authority.pushVault(address(treasury));
treasury.enable(ITreasury.STATUS.REWARDMANAGER, address(depository), address(0));
treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0));
treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator));
vm.stopPrank();
vm.startPrank(ALICE);
reserve.mint(ALICE, INITIAL_MINT);
reserve.approve(address(treasury), type(uint256).max);
treasury.deposit(address(reserve), INITIAL_MINT, treasury.tokenValue(address(reserve), INITIAL_MINT) / 2);
assertEq(ftso.totalSupply(), treasury.baseSupply());
reserve.mint(ALICE, INITIAL_MINT);
reserve.approve(address(depository), type(uint256).max);
vm.stopPrank();
conclusion = block.timestamp + TIME_TO_CONCLUSION;
vm.prank(POLICY);
depository.create(
[CAPACITY, INITIAL_PRICE, BUFFER],
[VESTING, conclusion],
address(reserve),
[uint32(DEPOSIT_INTERVAL), uint32(TUNE_INTERVAL)], // forge-lint: disable-line(unsafe-typecast)
[false, true]
);
}
function test_shouldCreateMarket() public view {
assertEq(depository.isLive(0), true);
}
function test_shouldConcludeInCorrectAmountOfTime() public view {
(, , , uint48 concludes,) = depository.terms(0);
assertEq(concludes, uint48(conclusion)); // forge-lint: disable-line(unsafe-typecast)
(, , uint48 length, , ,) = depository.metadatas(0);
assertEq(length, TIME_TO_CONCLUSION);
}
function test_shouldSetMaxPayoutToCorrectPercentageOfCapacity() public view {
(, , , , uint256 maxPayout, ,) = depository.markets(0);
assertEq(maxPayout, CAPACITY / 6);
}
function test_shouldReturnIdsOfAllMarkets() public {
vm.prank(POLICY);
depository.create(
[CAPACITY, INITIAL_PRICE, BUFFER],
[VESTING, conclusion],
address(reserve),
[uint32(DEPOSIT_INTERVAL), uint32(TUNE_INTERVAL)], // forge-lint: disable-line(unsafe-typecast)
[false, true]
);
uint256[] memory liveMarkets = depository.liveMarkets();
assertEq(liveMarkets.length, 2);
assertEq(liveMarkets[0], 0);
assertEq(liveMarkets[1], 1);
}
function test_shouldUpdateIdsOfMarkets() public {
vm.startPrank(POLICY);
depository.create(
[CAPACITY, INITIAL_PRICE, BUFFER],
[VESTING, conclusion],
address(reserve),
[uint32(DEPOSIT_INTERVAL), uint32(TUNE_INTERVAL)], // forge-lint: disable-line(unsafe-typecast)
[false, true]
);
depository.close(0);
vm.stopPrank();
uint256[] memory liveMarkets = depository.liveMarkets();
assertEq(liveMarkets.length, 1);
assertEq(liveMarkets[0], 1);
}
function test_shouldIncludeIdInLiveMarketsForQuotePeople() public view {
uint256[] memory liveMarketsFor = depository.liveMarketsFor(address(reserve));
assertEq(liveMarketsFor.length, 1);
assertEq(liveMarketsFor[0], 0);
}
function test_shouldStartWithPriceAtInitialPrice() public view {
assertEq(depository.marketPrice(0), INITIAL_PRICE);
}
function test_shouldGiveAccuratePayoutForPrice() public view {
uint256 price = depository.marketPrice(0);
uint256 amount = 10_000 * 1e18;
uint256 expectedPrice = amount / price;
assertEq(depository.payoutFor(0, amount), expectedPrice);
}
function test_shouldDecayDebt() public {
(, , , uint256 totalDebt, , ,) = depository.markets(0);
skip(DEPOSIT_INTERVAL);
vm.prank(ALICE);
depository.deposit(0, 0, INITIAL_PRICE, ALICE, ALICE);
(, , , uint256 newTotalDebt, , ,) = depository.markets(0);
assertEq(totalDebt > newTotalDebt, true);
}
function test_shouldStartAdjustmentIfBehindSchedule() public {
skip(DEPOSIT_INTERVAL);
uint256 amount = 10_000 * 1e18;
vm.prank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
(, , , bool active) = depository.adjustments(0);
assertEq(active, true);
}
function test_adjustmentShouldLowerControlVariableByChangeInTuneIntervalIfBehind() public {
(, uint64 ctrlVariable, , ,) = depository.terms(0);
uint256 amount = 10_000 * 1e18;
vm.startPrank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
skip(DEPOSIT_INTERVAL);
(uint64 change, , ,) = depository.adjustments(0);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
vm.stopPrank();
(, uint64 newCtrlVariable, , ,) = depository.terms(0);
assertEq(newCtrlVariable, ctrlVariable - change);
}
function test_adjustmentShouldLowerControlVariableByHalfOfTuneInterval() public {
skip(DEPOSIT_INTERVAL);
(, uint64 ctrlVariable, , ,) = depository.terms(0);
uint256 amount = 10_000 * 1e18;
vm.startPrank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
(uint64 change, , ,) = depository.adjustments(0);
skip(TUNE_INTERVAL / 2);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
(, uint64 newCtrlVariable, , ,) = depository.terms(0);
vm.stopPrank();
uint256 lowerBound = (ctrlVariable - change / 2) * 9999 / 10000;
assertEq(newCtrlVariable <= ctrlVariable - (change / 2), true);
assertEq(newCtrlVariable > lowerBound, true);
}
function test_adjustmentShouldContinueLoweringOverMultipleDepositsInSameInterval() public {
(, uint64 ctrlVariable, , ,) = depository.terms(0);
uint256 amount = 10_000 * 1e18;
vm.startPrank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
(uint64 change, , ,) = depository.adjustments(0);
skip(TUNE_INTERVAL / 2);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
skip(TUNE_INTERVAL / 2);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
vm.stopPrank();
(, uint64 newCtrlVariable, , ,) = depository.terms(0);
assertEq(newCtrlVariable, ctrlVariable - change);
}
function test_shouldAllowDeposit() public {
uint256 amount = 10_000 * 1e18;
vm.prank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
uint256[] memory arr = depository.indexesFor(ALICE);
assertEq(arr.length, 1);
}
function test_shouldNotAllowDepositGreaterThanMaxPayout() public {
uint256 amount = 6_700_000 * 1e18;
vm.expectRevert();
vm.prank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
}
function test_shouldNotRedeemAfterVested() public {
uint256 balance = ftso.balanceOf(ALICE);
uint256 amount = 10_000 * 1e18; // 10,000
vm.startPrank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
depository.redeemAll(ALICE, true);
vm.stopPrank();
assertEq(ftso.balanceOf(ALICE), balance);
}
function test_shouldRedeemAfterVested() public {
uint256 amount = 10_000 * 1e18; // 10,000
vm.startPrank(ALICE);
(uint256 expectedPayout, ,) = depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
skip(DEPOSIT_INTERVAL);
depository.redeemAll(ALICE, true);
vm.stopPrank();
uint256 aliceBalance = ghst.balanceOf(ALICE);
assertEq(aliceBalance >= ghst.balanceTo(expectedPayout), true);
assertEq(aliceBalance < ghst.balanceTo(expectedPayout * 10001 / 10000), true);
}
function test_shouldCorrectlyRedeemPartially() public {
uint256 amount = 1 * 1e18;
vm.startPrank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE * 2, ALICE, ALICE);
depository.deposit(0, amount, INITIAL_PRICE * 2, ALICE, ALICE);
depository.deposit(0, amount, INITIAL_PRICE * 2, ALICE, ALICE);
depository.deposit(0, amount, INITIAL_PRICE * 2, ALICE, ALICE);
skip(DEPOSIT_INTERVAL);
uint256[] memory indexesToRemove = new uint256[](1);
indexesToRemove[0] = 1;
uint256[] memory nextIndexToRemove = new uint256[](1);
nextIndexToRemove[0] = 3;
uint256[] memory allIndexes = depository.indexesFor(ALICE);
assertEq(allIndexes.length, 4);
assertEq(allIndexes[0], 0);
assertEq(allIndexes[1], 1);
assertEq(allIndexes[2], 2);
assertEq(allIndexes[3], 3);
depository.redeem(ALICE, true, indexesToRemove);
uint256[] memory allIndexesOneRemoved = depository.indexesFor(ALICE);
assertEq(allIndexesOneRemoved.length, 3);
assertEq(allIndexesOneRemoved[0], 0);
assertEq(allIndexesOneRemoved[1], 3);
assertEq(allIndexesOneRemoved[2], 2);
depository.redeem(ALICE, true, nextIndexToRemove);
uint256[] memory allIndexesTwoRemoved = depository.indexesFor(ALICE);
assertEq(allIndexesTwoRemoved.length, 2);
assertEq(allIndexesTwoRemoved[0], 0);
assertEq(allIndexesTwoRemoved[1], 2);
vm.stopPrank();
}
function test_afterSuccesfullWarmupAutoClaimExecuted() public {
uint256 amount = 10_000 * 1e18; // 10,000
vm.prank(GOVERNOR);
staking.setWarmupPeriod(1);
vm.startPrank(ALICE);
assertEq(ghst.balanceOf(ALICE), 0);
(uint256 expectedPayout, ,) = depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
assertEq(ghst.balanceOf(address(depository)), 0);
skip(DEPOSIT_INTERVAL);
staking.rebase();
depository.redeemAll(ALICE, true);
uint256 aliceBalance = ghst.balanceOf(ALICE);
assertEq(aliceBalance >= ghst.balanceTo(expectedPayout), true);
assertEq(aliceBalance < ghst.balanceTo(expectedPayout * 10001 / 10000), true);
vm.stopPrank();
}
function test_externalAccountCouldNotClaimFromWarmup() public {
vm.startPrank(ALICE);
vm.expectRevert();
staking.claim(address(depository), false);
vm.expectRevert();
staking.claim(address(depository), true);
vm.stopPrank();
}
function test_shouldDecayMaxPayoutInTargetDepositInterval() public {
(, , , , uint64 maxPayout, ,) = depository.markets(0);
uint256 price = depository.marketPrice(0);
uint256 amount = maxPayout * price;
vm.prank(ALICE);
depository.deposit(0, amount, INITIAL_PRICE, ALICE, ALICE);
skip(DEPOSIT_INTERVAL);
uint256 newPrice = depository.marketPrice(0);
assertEq(newPrice < INITIAL_PRICE, true);
}
function test_shouldCloseMarket() public {
(uint256 cap, , , , , ,) = depository.markets(0);
assertEq(cap > 0, true);
vm.prank(POLICY);
depository.close(0);
(uint256 newCap, , , , , ,) = depository.markets(0);
assertEq(newCap, 0);
}
}