391 lines
14 KiB
Solidity
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);
|
|
}
|
|
}
|