326 lines
12 KiB
Solidity
326 lines
12 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 {GhostStaking} from "../../src/Staking.sol";
|
|
|
|
import {ERC20PermitTest} from "./Permit.t.sol";
|
|
import {ERC20AllowanceTest} from "./Allowance.t.sol";
|
|
import {ERC20TransferTest} from "./Transfer.t.sol";
|
|
|
|
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
|
|
|
|
contract StinkyTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferTest {
|
|
using SafeERC20 for Stinky;
|
|
|
|
uint256 private constant INITIAL_SHARES_SUPPLY = 5 * 10**15; // 15 or 16
|
|
uint256 private constant TOTAL_SHARES = type(uint256).max - (type(uint256).max % INITIAL_SHARES_SUPPLY);
|
|
|
|
uint256 constant public INITIAL_INDEX = 10819917194513808e56;
|
|
uint256 constant public TOTAL_INITIAL_SUPPLY = 5000000000000000;
|
|
|
|
Fatso public ftso;
|
|
Stinky public stnk;
|
|
Ghost public ghst;
|
|
GhostAuthority public authority;
|
|
GhostStaking public staking;
|
|
|
|
address constant INITIALIZER = 0x0000000000000000000000000000000000000001;
|
|
address constant ALICE = 0x0000000000000000000000000000000000000003;
|
|
address constant BOB = 0x0000000000000000000000000000000000000004;
|
|
address constant TREASURY = 0x0000000000000000000000000000000000000005;
|
|
uint256 constant AMOUNT = 69;
|
|
|
|
string constant NAME = "Stinky Test Name";
|
|
string constant SYMBOL = "STNKTST";
|
|
|
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
|
event LogStakingContractUpdated(address stakingContract);
|
|
|
|
function setUp() public {
|
|
vm.startPrank(INITIALIZER);
|
|
authority = new GhostAuthority(
|
|
INITIALIZER,
|
|
INITIALIZER,
|
|
INITIALIZER,
|
|
INITIALIZER
|
|
);
|
|
ftso = new Fatso(address(authority), "Fatso", "FTSO");
|
|
stnk = new Stinky(INITIAL_INDEX, NAME, SYMBOL);
|
|
ghst = new Ghost(address(stnk), "Ghost", "GHST");
|
|
staking = new GhostStaking(
|
|
address(ftso),
|
|
address(stnk),
|
|
address(ghst),
|
|
69,
|
|
1337,
|
|
1337,
|
|
address(authority)
|
|
);
|
|
ghst.initialize(address(staking));
|
|
vm.stopPrank();
|
|
|
|
initializePermit(address(stnk), AMOUNT, TOTAL_INITIAL_SUPPLY);
|
|
initializeAllowance(ALICE, BOB, address(stnk), AMOUNT, TOTAL_INITIAL_SUPPLY, TOTAL_INITIAL_SUPPLY);
|
|
initializeTransfer(ALICE, BOB, address(stnk), AMOUNT, TOTAL_INITIAL_SUPPLY);
|
|
}
|
|
|
|
function test_isConstructedCorrectly() public view {
|
|
assertEq(stnk.name(), NAME);
|
|
assertEq(stnk.symbol(), SYMBOL);
|
|
assertEq(stnk.decimals(), 9);
|
|
assertEq(stnk.totalSupply(), TOTAL_INITIAL_SUPPLY);
|
|
assertEq(stnk.index(), INITIAL_INDEX / (TOTAL_SHARES / INITIAL_SHARES_SUPPLY));
|
|
}
|
|
|
|
function test_initialization_couldBeDoneByInitializer() public {
|
|
vm.prank(INITIALIZER);
|
|
stnk.initialize(address(staking), TREASURY, address(ghst));
|
|
assertEq(stnk.staking(), address(staking));
|
|
assertEq(stnk.treasury(), TREASURY);
|
|
assertEq(stnk.ghst(), address(ghst));
|
|
}
|
|
|
|
function test_initialization_couldNotBeDoneByArbitraryAddress(address who) public {
|
|
vm.assume(who != INITIALIZER);
|
|
vm.expectRevert();
|
|
vm.prank(who);
|
|
stnk.initialize(who, who, who);
|
|
}
|
|
|
|
function test_initialization_couldBeDoneOnlyOnce() public {
|
|
vm.prank(INITIALIZER);
|
|
stnk.initialize(address(staking), TREASURY, address(ghst));
|
|
|
|
vm.expectRevert();
|
|
vm.prank(INITIALIZER);
|
|
stnk.initialize(INITIALIZER, INITIALIZER, INITIALIZER);
|
|
}
|
|
|
|
function test_initialization_correctTotalSharesOfStaking() public {
|
|
_initialize();
|
|
assertEq(stnk.balanceOf(address(staking)), 5000000000000000);
|
|
}
|
|
|
|
function test_initialization_emitsTransferEvent() public {
|
|
vm.expectEmit(true, true, true, true, address(stnk));
|
|
emit Transfer(address(0), address(staking), 5000000000000000);
|
|
_initialize();
|
|
}
|
|
|
|
function test_initialization_emitsLogStakingContractUpdatedEvent() public {
|
|
vm.expectEmit(true, true, true, true, address(stnk));
|
|
emit LogStakingContractUpdated(address(staking));
|
|
_initialize();
|
|
}
|
|
|
|
function test_circulatingSupplyIsCorrecBasedOnTotalSupply() public {
|
|
_mintTokens(ALICE, AMOUNT);
|
|
assertEq(stnk.circulatingSupply(), AMOUNT);
|
|
assertEq(stnk.balanceOf(address(staking)), TOTAL_INITIAL_SUPPLY - AMOUNT);
|
|
}
|
|
|
|
function test_circulatingSupplyIsCorrecBasedOnStakingBalance() public {
|
|
_mintTokens(ALICE, AMOUNT);
|
|
assertEq(stnk.circulatingSupply(), AMOUNT);
|
|
vm.prank(ALICE);
|
|
stnk.safeTransfer(address(staking), AMOUNT);
|
|
assertEq(stnk.circulatingSupply(), 0);
|
|
assertEq(stnk.balanceOf(address(staking)), TOTAL_INITIAL_SUPPLY);
|
|
}
|
|
|
|
function test_circulatingSupplyIsCorrectBasedOnSupplyInWarmup() public {
|
|
vm.startPrank(INITIALIZER);
|
|
stnk.initialize(address(staking), TREASURY, address(ghst));
|
|
staking.setWarmupPeriod(1337);
|
|
ftso.mint(ALICE, AMOUNT);
|
|
vm.stopPrank();
|
|
|
|
assertEq(stnk.circulatingSupply(), 0);
|
|
assertEq(ftso.totalSupply(), AMOUNT);
|
|
assertEq(stnk.totalSupply(), TOTAL_INITIAL_SUPPLY);
|
|
assertEq(stnk.balanceOf(address(staking)), TOTAL_INITIAL_SUPPLY);
|
|
|
|
vm.startPrank(ALICE);
|
|
ftso.approve(address(staking), AMOUNT);
|
|
staking.stake(AMOUNT, ALICE, true, true);
|
|
vm.stopPrank();
|
|
|
|
assertEq(stnk.circulatingSupply(), AMOUNT);
|
|
assertEq(ftso.totalSupply(), AMOUNT);
|
|
assertEq(stnk.totalSupply(), TOTAL_INITIAL_SUPPLY);
|
|
assertEq(stnk.balanceOf(address(staking)), TOTAL_INITIAL_SUPPLY);
|
|
}
|
|
|
|
function test_circulatingSupplyIsCorrectBasedOnGhostSupply() public {
|
|
vm.startPrank(INITIALIZER);
|
|
stnk.initialize(address(staking), TREASURY, address(ghst));
|
|
ftso.mint(ALICE, AMOUNT);
|
|
vm.stopPrank();
|
|
|
|
vm.startPrank(ALICE);
|
|
ftso.approve(address(staking), AMOUNT);
|
|
staking.stake(AMOUNT, ALICE, false, true);
|
|
vm.stopPrank();
|
|
|
|
uint256 balanceTo = AMOUNT * 1e18 / ghst.index();
|
|
assertEq(ghst.balanceOf(ALICE), balanceTo);
|
|
uint256 balanceFrom = ghst.totalSupply() * ghst.index() / 1e18;
|
|
assertEq(stnk.circulatingSupply(), balanceFrom);
|
|
}
|
|
|
|
function test_toGhstCorrect(uint256 amountToTest) public {
|
|
vm.assume(amountToTest < type(uint64).max);
|
|
_initialize();
|
|
uint256 balanceTo = amountToTest * 1e18 / ghst.index();
|
|
assertEq(stnk.toGhst(amountToTest), ghst.balanceTo(amountToTest));
|
|
assertEq(stnk.toGhst(amountToTest), balanceTo);
|
|
}
|
|
|
|
function test_fromGhstCorrect(uint256 amountToTest) public {
|
|
vm.assume(amountToTest < type(uint64).max);
|
|
_initialize();
|
|
uint256 balanceFrom = amountToTest * ghst.index() / 1e18;
|
|
assertEq(stnk.fromGhst(amountToTest), ghst.balanceFrom(amountToTest));
|
|
assertEq(stnk.fromGhst(amountToTest), balanceFrom);
|
|
}
|
|
|
|
function test_sharesForBalanceCorrect(uint256 amountToTest) public view {
|
|
vm.assume(amountToTest < TOTAL_INITIAL_SUPPLY);
|
|
uint256 sharesPerUnit = stnk.sharesForBalance(1);
|
|
assertEq(stnk.sharesForBalance(1), sharesPerUnit);
|
|
assertEq(stnk.sharesForBalance(amountToTest), amountToTest * sharesPerUnit);
|
|
}
|
|
|
|
function test_balance_forSharesCorrect(uint256 amountToTest) public view {
|
|
vm.assume(amountToTest < type(uint128).max);
|
|
uint256 sharesPerUnit = stnk.sharesForBalance(1);
|
|
assertEq(stnk.balanceForShares(amountToTest), amountToTest / sharesPerUnit);
|
|
}
|
|
|
|
function test_debt_couldBeChangedByTreasury() public {
|
|
_mintTokens(ALICE, AMOUNT);
|
|
assertEq(stnk.debtBalances(ALICE), 0);
|
|
vm.prank(TREASURY);
|
|
stnk.changeDebt(AMOUNT, ALICE, true);
|
|
assertEq(stnk.debtBalances(ALICE), AMOUNT);
|
|
vm.prank(TREASURY);
|
|
stnk.changeDebt(AMOUNT, ALICE, false);
|
|
assertEq(stnk.debtBalances(ALICE), 0);
|
|
}
|
|
|
|
function test_debt_couldNotBeChangeByArbitraryAddress(address someone) public {
|
|
vm.assume(someone != TREASURY);
|
|
_mintTokens(ALICE, AMOUNT);
|
|
assertEq(stnk.debtBalances(ALICE), 0);
|
|
vm.expectRevert();
|
|
vm.prank(someone);
|
|
stnk.changeDebt(AMOUNT, ALICE, true);
|
|
}
|
|
|
|
function test_balance_couldNotDropBelowDebt() public {
|
|
_mintTokens(ALICE, AMOUNT * 3);
|
|
vm.prank(TREASURY);
|
|
stnk.changeDebt(AMOUNT, ALICE, true);
|
|
assertEq(stnk.balanceOf(BOB), 0);
|
|
|
|
vm.prank(ALICE);
|
|
stnk.safeTransfer(BOB, AMOUNT);
|
|
assertEq(stnk.balanceOf(BOB), AMOUNT);
|
|
|
|
vm.expectRevert();
|
|
vm.prank(ALICE);
|
|
assertEq(stnk.transfer(BOB, AMOUNT * 2), false);
|
|
assertEq(stnk.balanceOf(BOB), AMOUNT);
|
|
assertEq(stnk.balanceOf(ALICE), AMOUNT * 2);
|
|
}
|
|
|
|
function test_rebase_couldNotBeDoneFromArbitraryAddress(address someone) public {
|
|
vm.assume(someone != address(staking));
|
|
vm.expectRevert();
|
|
vm.prank(someone);
|
|
stnk.rebase(AMOUNT, 1337);
|
|
}
|
|
|
|
function test_rebase_nothingChangesIfDeltaIsZero() public {
|
|
uint256 epoch = 1337;
|
|
uint256 prevSupply = stnk.totalSupply();
|
|
uint256 prevIndex = stnk.index();
|
|
_mintTokens(ALICE, AMOUNT);
|
|
assertEq(stnk.balanceOf(ALICE), AMOUNT);
|
|
|
|
vm.prank(address(staking));
|
|
stnk.rebase(0, epoch);
|
|
|
|
assertEq(stnk.totalSupply(), prevSupply);
|
|
assertEq(stnk.index(), prevIndex);
|
|
assertEq(stnk.balanceOf(ALICE), AMOUNT);
|
|
}
|
|
|
|
function test_rebase_changesIfProfitAndCirculatingSupply() public {
|
|
uint256 epoch = 1337;
|
|
_mintTokens(ALICE, AMOUNT);
|
|
assertEq(stnk.balanceOf(ALICE), AMOUNT);
|
|
vm.prank(address(staking));
|
|
stnk.rebase(AMOUNT, epoch);
|
|
assertEq(stnk.balanceOf(ALICE), AMOUNT * 2);
|
|
assertEq(stnk.totalSupply(), TOTAL_INITIAL_SUPPLY * 2);
|
|
}
|
|
|
|
function test_rebase_changesIfProfitAndNoCirculatingSupply() public {
|
|
uint256 epoch = 1337;
|
|
_initialize();
|
|
assertEq(stnk.balanceOf(address(staking)), TOTAL_INITIAL_SUPPLY);
|
|
vm.prank(address(staking));
|
|
stnk.rebase(AMOUNT, epoch);
|
|
assertEq(stnk.balanceOf(address(staking)), TOTAL_INITIAL_SUPPLY + AMOUNT);
|
|
assertEq(stnk.totalSupply(), TOTAL_INITIAL_SUPPLY + AMOUNT);
|
|
}
|
|
|
|
function test_rebase_couldNotExceedMaxSupply() public {
|
|
uint256 epoch = 1337;
|
|
_initialize();
|
|
assertEq(stnk.balanceOf(address(staking)), TOTAL_INITIAL_SUPPLY);
|
|
vm.prank(address(staking));
|
|
stnk.rebase(type(uint128).max, epoch);
|
|
assertEq(stnk.balanceOf(address(staking)), type(uint128).max);
|
|
assertEq(stnk.totalSupply(), type(uint128).max);
|
|
}
|
|
|
|
function _initialize() internal {
|
|
vm.prank(INITIALIZER);
|
|
stnk.initialize(address(staking), TREASURY, address(ghst));
|
|
assertEq(stnk.staking(), address(staking));
|
|
assertEq(stnk.treasury(), TREASURY);
|
|
assertEq(stnk.ghst(), address(ghst));
|
|
}
|
|
|
|
function _mintTokens(address who, uint256 value) internal {
|
|
_initialize();
|
|
uint256 totalSupply = stnk.totalSupply();
|
|
vm.prank(address(staking));
|
|
stnk.safeTransfer(who, value);
|
|
assertEq(stnk.totalSupply(), totalSupply);
|
|
}
|
|
|
|
function _mintTransferTokens(address who, uint256 value) internal override {
|
|
_mintTokens(who, value);
|
|
}
|
|
|
|
function _mintAllowanceTokens(address who, uint256 value) internal override {
|
|
_mintTokens(who, value);
|
|
}
|
|
|
|
function _mintPermitTokens(address who, uint256 value) internal override {
|
|
_mintTokens(who, value);
|
|
}
|
|
|
|
function _getCurrentTotalSupply() internal override pure returns (uint256) {
|
|
return TOTAL_INITIAL_SUPPLY;
|
|
}
|
|
}
|