ghost-dao-contracts/test/bonding/BondDepositorty.t.sol
Uncle Fatso 46b33b4c75
initial push for smart-contracts
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-04-28 14:17:04 +03:00

391 lines
14 KiB
Solidity

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/BondDepository.sol";
import "../../src/mocks/ERC20Mock.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 initialMint = 10000000000000000000000000;
uint256 public constant initialDeposit = 1000000000000000000000000;
uint256 public constant capacity = 10000e9;
uint256 public constant initialPrice = 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;
address constant bob = 0x0000000000000000000000000000000000000008;
uint256 public constant vesting = 100;
uint256 public constant timeToConclusion = 60 * 60 * 24;
uint256 public constant depositInterval = 60 * 60 * 4;
uint256 public constant tuneInterval = 60 * 60;
uint256 public conclusion;
ERC20Mock reserve;
Fatso ftso;
Stinky stnk;
Ghost ghst;
GhostStaking staking;
GhostTreasury treasury;
GhostAuthority authority;
GhostBondDepository depository;
function setUp() public {
vm.startPrank(initializer);
authority = new GhostAuthority(
governor,
guardian,
policy,
vault
);
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));
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(0));
vm.stopPrank();
vm.startPrank(alice);
reserve.mint(alice, initialMint);
reserve.approve(address(treasury), type(uint256).max);
treasury.deposit(address(reserve), initialMint, treasury.tokenValue(address(reserve), initialMint) / 2);
assertEq(ftso.totalSupply(), treasury.baseSupply());
reserve.mint(alice, initialMint);
reserve.approve(address(depository), type(uint256).max);
vm.stopPrank();
conclusion = block.timestamp + timeToConclusion;
vm.prank(policy);
depository.create(
[capacity, initialPrice, buffer],
[vesting, conclusion],
address(reserve),
[uint32(depositInterval), uint32(tuneInterval)],
[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));
(, , uint48 length, , ,) = depository.metadatas(0);
assertEq(length, timeToConclusion);
}
function test_shouldSetMaxPayoutToCorrectPercentageOfCapacity() public view {
(, , , , uint256 maxPayout, ,) = depository.markets(0);
assertEq(maxPayout, capacity / 6);
}
function test_shouldReturnIdsOfAllMarkets() public {
vm.prank(policy);
depository.create(
[capacity, initialPrice, buffer],
[vesting, conclusion],
address(reserve),
[uint32(depositInterval), uint32(tuneInterval)],
[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, initialPrice, buffer],
[vesting, conclusion],
address(reserve),
[uint32(depositInterval), uint32(tuneInterval)],
[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), initialPrice);
}
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(depositInterval);
vm.prank(alice);
depository.deposit(0, 0, initialPrice, alice, alice);
(, , , uint256 newTotalDebt, , ,) = depository.markets(0);
assertEq(totalDebt > newTotalDebt, true);
}
function test_shouldStartAdjustmentIfBehindSchedule() public {
skip(depositInterval);
uint256 amount = 10_000 * 1e18;
vm.prank(alice);
depository.deposit(0, amount, initialPrice, 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, initialPrice, alice, alice);
skip(depositInterval);
(uint64 change, , ,) = depository.adjustments(0);
depository.deposit(0, amount, initialPrice, alice, alice);
vm.stopPrank();
(, uint64 newCtrlVariable, , ,) = depository.terms(0);
assertEq(newCtrlVariable, ctrlVariable - change);
}
function test_adjustmentShouldLowerControlVariableByHalfOfTuneInterval() public {
skip(depositInterval);
(, uint64 ctrlVariable, , ,) = depository.terms(0);
uint256 amount = 10_000 * 1e18;
vm.startPrank(alice);
depository.deposit(0, amount, initialPrice, alice, alice);
(uint64 change, , ,) = depository.adjustments(0);
skip(tuneInterval / 2);
depository.deposit(0, amount, initialPrice, 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, initialPrice, alice, alice);
(uint64 change, , ,) = depository.adjustments(0);
skip(tuneInterval / 2);
depository.deposit(0, amount, initialPrice, alice, alice);
skip(tuneInterval / 2);
depository.deposit(0, amount, initialPrice, 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, initialPrice, 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, initialPrice, 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, initialPrice, 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, initialPrice, alice, alice);
skip(depositInterval);
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, initialPrice * 2, alice, alice);
depository.deposit(0, amount, initialPrice * 2, alice, alice);
depository.deposit(0, amount, initialPrice * 2, alice, alice);
depository.deposit(0, amount, initialPrice * 2, alice, alice);
skip(depositInterval);
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, initialPrice, alice, alice);
assertEq(ghst.balanceOf(address(depository)), 0);
skip(depositInterval);
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, initialPrice, alice, alice);
skip(depositInterval);
uint256 newPrice = depository.marketPrice(0);
assertEq(newPrice < initialPrice, 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);
}
}