Compare commits

...

12 Commits

Author SHA1 Message Date
a41cef0dfe
make definition of tokens name and symbol during the deployment
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-29 14:37:27 +03:00
07c752754b
make help and ability to use blockscout as verifier
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-29 14:35:48 +03:00
9e40d4ed4f
tests of the burning functionality
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 21:39:38 +03:00
15dfd49163
rename function to make it more self-describing
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:51:05 +03:00
a8790cc452
new tests for minting based on donations
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:50:01 +03:00
4bea615f8b
take into account that by default there is no donation anymore
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:42:23 +03:00
899b03ddde
conversionRate is not used by owner anymore
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:39:05 +03:00
3bb6904f54
try to remove re-entrancy from withdraw function
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:37:10 +03:00
3f0d1fb02e
remove unnecessary functions
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:32:41 +03:00
34fdb21d82
try to avoid re-entrancy on burn function
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:31:07 +03:00
941aba02fa
make owner to mint amount directly
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:18:06 +03:00
5541eda138
additional logic for burning reserve in exchange of native; initial draft
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-06-27 20:16:10 +03:00
14 changed files with 264 additions and 40 deletions

View File

@ -72,6 +72,18 @@ RESERVE_MINT_RATE=
RESERVE_TOKEN_NAME= RESERVE_TOKEN_NAME=
RESERVE_TOKEN_SYMBOL= RESERVE_TOKEN_SYMBOL=
## Name and symbol for the base token of the ghostDAO protocol
FATSO_TOKEN_NAME=
FATSO_TOKEN_SYMBOL=
## Name and symbol for the staking token of the ghostDAO protocol
STINKY_TOKEN_NAME=
STINKY_TOKEN_SYMBOL=
## Name and symbol for the bridging token of the ghostDAO protocol
GHOST_TOKEN_NAME=
GHOST_TOKEN_SYMBOL=
SEPOLIA_TEST_RPC_URL= SEPOLIA_TEST_RPC_URL=
SEPOLIA_TEST_API_KEY= SEPOLIA_TEST_API_KEY=
SEPOLIA_TEST_ENDPOINT= SEPOLIA_TEST_ENDPOINT=

View File

@ -6,6 +6,30 @@ START=1
END=12 END=12
VERIFY_NEEDED="--verify" VERIFY_NEEDED="--verify"
usage() {
echo "Correct deployer script usage:"
echo -e "\t-s, --start - from which contract deployment should start."
echo -e "\t-e, --end - last contract deployment to be done."
echo -e "\t-n, --network - network name to be deployed on."
echo -e "\t-b, --blockscout - should try to use blockscout as verifier."
echo -e "\nAvailable network names: ${AVAILIABLE_NETWORKS[@]}"
echo -e "\nContract enumeration:"
echo "0) Reserve"
echo "1) Authority"
echo "2) Fatso"
echo "3) Stinky"
echo "4) Ghost"
echo "5) Staking"
echo "6) Treasury"
echo "7) BondDepository"
echo "8) StakingDistributor"
echo "9) BondingCalculator"
echo "10) AfterPartyFirst"
echo "11) AfterPartySecond"
echo "12) AfterPartyThird"
exit 1
}
while [[ $# -gt 0 ]]; while [[ $# -gt 0 ]];
do do
case "${1}" in case "${1}" in
@ -24,6 +48,17 @@ do
shift shift
shift shift
;; ;;
-b|--blockscout)
VERIFY_NEEDED+=" --verifier blockscout"
shift
;;
-h|--help)
usage
;;
*)
echo -e "\nERROR: Unknown option provided\n"
usage
;;
esac esac
done done

View File

@ -7,9 +7,9 @@ import "./interfaces/IFTSO.sol";
import "./types/GhostAccessControlled.sol"; import "./types/GhostAccessControlled.sol";
contract Fatso is ERC20Permit, IFTSO, GhostAccessControlled { contract Fatso is ERC20Permit, IFTSO, GhostAccessControlled {
constructor(address _authority) constructor(address _authority, string memory name, string memory symbol)
ERC20("Fatso", "FTSO") ERC20(name, symbol)
ERC20Permit("Fatso") ERC20Permit(name)
GhostAccessControlled(IGhostAuthority(_authority)) GhostAccessControlled(IGhostAuthority(_authority))
{} {}

View File

@ -13,7 +13,10 @@ contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes {
address public override stnk; address public override stnk;
address private _initializer; address private _initializer;
constructor(address _stnk) ERC20("Ghost", "GHST") ERC20Permit("Ghost") { constructor(address _stnk, string memory name, string memory symbol)
ERC20(name, symbol)
ERC20Permit(name)
{
stnk = _stnk; stnk = _stnk;
_initializer = msg.sender; _initializer = msg.sender;
} }

View File

@ -27,7 +27,10 @@ contract Stinky is ISTNK, ERC20Permit {
mapping(address => uint256) private _shares; mapping(address => uint256) private _shares;
mapping(address => mapping(address => uint256)) private _allowedValue; mapping(address => mapping(address => uint256)) private _allowedValue;
constructor(uint256 usedIndex) ERC20("Stinky", "STNK") ERC20Permit("Stinky") { constructor(uint256 usedIndex, string memory name, string memory symbol)
ERC20(name, symbol)
ERC20Permit(name)
{
_initializer = msg.sender; _initializer = msg.sender;
_internalIndex = usedIndex; _internalIndex = usedIndex;
_totalSupply = INITIAL_SHARES_SUPPLY; _totalSupply = INITIAL_SHARES_SUPPLY;

View File

@ -5,7 +5,9 @@ import "@openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol";
contract Reserve is ERC20Permit { contract Reserve is ERC20Permit {
address private immutable _owner; address private immutable _owner;
uint256 public accumulatedDonation;
uint256 public conversionRate; uint256 public conversionRate;
uint256 public donationRate;
error OnlyOwner(); error OnlyOwner();
constructor( constructor(
@ -18,11 +20,11 @@ contract Reserve is ERC20Permit {
} }
fallback() external payable { fallback() external payable {
_innerMint(msg.sender, msg.value); _mint(msg.sender, msg.value);
} }
receive() external payable { receive() external payable {
_innerMint(msg.sender, msg.value); _mint(msg.sender, msg.value);
} }
function changeRate(uint256 rate) external { function changeRate(uint256 rate) external {
@ -30,22 +32,42 @@ contract Reserve is ERC20Permit {
conversionRate = rate; conversionRate = rate;
} }
function changeDonationRate(uint256 rate) external {
if (msg.sender != _owner) revert OnlyOwner();
donationRate = rate;
}
function withdraw(address payable receiver) external { function withdraw(address payable receiver) external {
if (msg.sender != _owner) revert OnlyOwner(); if (msg.sender != _owner) revert OnlyOwner();
(bool sent,) = receiver.call{ value: address(this).balance }(""); uint256 accumulatedDonationCached = accumulatedDonation;
accumulatedDonation = 0;
(bool sent,) = receiver.call{ value: accumulatedDonationCached }("");
require(sent, "Failed to send Ether"); require(sent, "Failed to send Ether");
} }
function superMint(address account, uint256 value) external { function superMint(address account, uint256 value) external {
if (msg.sender != _owner) revert OnlyOwner(); if (msg.sender != _owner) revert OnlyOwner();
_innerMint(account, value); _mint(account, value);
} }
function mint(address account) external payable { function mint(address account) external payable {
_innerMint(account, msg.value); uint256 donation = msg.value * donationRate / 1e5;
accumulatedDonation += donation;
_mint(account, msg.value * conversionRate);
} }
function _innerMint(address who, uint256 amount) internal { function burn(uint256 amount) external {
_mint(who, amount * conversionRate); _burn(msg.sender, amount);
uint256 valueDiff = address(this).balance - accumulatedDonation;
uint256 valueBack = amount * valueDiff / (amount + totalSupply());
(bool sent,) = msg.sender.call{ value: valueBack }("");
require(sent, "Failed to send Ether");
}
function estimateAmount(uint256 amount) external view returns (uint256) {
uint256 valueDiff = address(this).balance - accumulatedDonation;
return amount * valueDiff / totalSupply();
} }
} }

View File

@ -59,9 +59,9 @@ contract GhostBondDepositoryTest is Test {
vault vault
); );
reserve = new ERC20Mock("Reserve Token", "RET"); reserve = new ERC20Mock("Reserve Token", "RET");
ftso = new Fatso(address(authority)); ftso = new Fatso(address(authority), "Fatso", "FTSO");
stnk = new Stinky(INITIAL_INDEX); stnk = new Stinky(INITIAL_INDEX, "Stinky", "STNK");
ghst = new Ghost(address(stnk)); ghst = new Ghost(address(stnk), "Ghost", "GHST");
staking = new GhostStaking( staking = new GhostStaking(
address(ftso), address(ftso),
address(stnk), address(stnk),

View File

@ -47,9 +47,9 @@ contract StakingTest is Test {
policy, policy,
vault vault
); );
ftso = new Fatso(address(authority)); ftso = new Fatso(address(authority), "Fatso", "FTSO");
stnk = new Stinky(INITIAL_INDEX); stnk = new Stinky(INITIAL_INDEX, "Stinky", "STNK");
ghst = new Ghost(address(stnk)); ghst = new Ghost(address(stnk), "Ghost", "GHST");
staking = new GhostStaking( staking = new GhostStaking(
address(ftso), address(ftso),
address(stnk), address(stnk),

View File

@ -47,9 +47,9 @@ contract StakingDistributorTest is Test {
owner owner
); );
reserve = new ERC20Mock("Reserve Token", "RET"); reserve = new ERC20Mock("Reserve Token", "RET");
ftso = new Fatso(address(authority)); ftso = new Fatso(address(authority), "Fatso", "FTSO");
stnk = new Stinky(INITIAL_INDEX); stnk = new Stinky(INITIAL_INDEX, "Stinky", "STNK");
ghst = new Ghost(address(stnk)); ghst = new Ghost(address(stnk), "Ghost", "GHST");
staking = new GhostStaking( staking = new GhostStaking(
address(ftso), address(ftso),
address(stnk), address(stnk),

View File

@ -20,6 +20,9 @@ contract FatsoTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferTe
uint256 constant amount = 69; uint256 constant amount = 69;
uint256 constant maxAmount = type(uint256).max; uint256 constant maxAmount = type(uint256).max;
string constant name = "Fatso Test Name";
string constant symbol = "FTSOTST";
function setUp() public { function setUp() public {
authority = new GhostAuthority( authority = new GhostAuthority(
deployer, deployer,
@ -27,7 +30,7 @@ contract FatsoTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferTe
deployer, deployer,
vault vault
); );
token = new Fatso(address(authority)); token = new Fatso(address(authority), name, symbol);
initializePermit(address(token), amount, maxAmount); initializePermit(address(token), amount, maxAmount);
initializeAllowance(alice, bob, address(token), amount, maxAmount, amount); initializeAllowance(alice, bob, address(token), amount, maxAmount, amount);
initializeTransfer(alice, bob, address(token), amount, 0); initializeTransfer(alice, bob, address(token), amount, 0);
@ -48,8 +51,8 @@ contract FatsoTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferTe
} }
function test_correctlyConstructsAnERC20() public view { function test_correctlyConstructsAnERC20() public view {
assertEq(token.name(), "Fatso"); assertEq(token.name(), name);
assertEq(token.symbol(), "FTSO"); assertEq(token.symbol(), symbol);
assertEq(token.decimals(), 9); assertEq(token.decimals(), 9);
} }

View File

@ -28,6 +28,9 @@ contract GhostTest is
uint256 constant amount = 69; uint256 constant amount = 69;
uint256 constant maxAmount = type(uint256).max; uint256 constant maxAmount = type(uint256).max;
string constant name = "Ghost Test Name";
string constant symbol = "GHSTTST";
Stinky stnk; Stinky stnk;
Ghost ghst; Ghost ghst;
GhostAuthority public authority; GhostAuthority public authority;
@ -41,8 +44,8 @@ contract GhostTest is
initializer, initializer,
initializer initializer
); );
stnk = new Stinky(INITIAL_INDEX); stnk = new Stinky(INITIAL_INDEX, "Stinky", "STNK");
ghst = new Ghost(address(stnk)); ghst = new Ghost(address(stnk), name, symbol);
staking = new GhostStaking( staking = new GhostStaking(
address(0), address(0),
address(stnk), address(stnk),
@ -63,8 +66,8 @@ contract GhostTest is
} }
function test_isConstructedCorrectly() public view { function test_isConstructedCorrectly() public view {
assertEq(ghst.name(), "Ghost"); assertEq(ghst.name(), name);
assertEq(ghst.symbol(), "GHST"); assertEq(ghst.symbol(), symbol);
assertEq(ghst.decimals(), 18); assertEq(ghst.decimals(), 18);
assertEq(ghst.staking(), address(staking)); assertEq(ghst.staking(), address(staking));
assertEq(ghst.stnk(), address(stnk)); assertEq(ghst.stnk(), address(stnk));

View File

@ -35,6 +35,9 @@ contract ReserveTest is Test {
assertEq(reserve.symbol(), symbol); assertEq(reserve.symbol(), symbol);
assertEq(reserve.decimals(), 18); assertEq(reserve.decimals(), 18);
assertEq(reserve.totalSupply(), 0); assertEq(reserve.totalSupply(), 0);
assertEq(reserve.donationRate(), 0);
assertEq(reserve.conversionRate(), conversionRate);
} }
function test_mint_couldBeDoneWithValue() public { function test_mint_couldBeDoneWithValue() public {
@ -72,7 +75,7 @@ contract ReserveTest is Test {
assertEq(reserve.balanceOf(initializer), 0); assertEq(reserve.balanceOf(initializer), 0);
vm.prank(initializer); vm.prank(initializer);
reserve.superMint(initializer, 420 * 1e18); reserve.superMint(initializer, 420 * 1e18);
assertEq(reserve.balanceOf(initializer), 420 * 1e18 * conversionRate); assertEq(reserve.balanceOf(initializer), 420 * 1e18);
} }
function test_mint_superMintCouldNotBeDoneFromArbitraryAddress() public { function test_mint_superMintCouldNotBeDoneFromArbitraryAddress() public {
@ -83,6 +86,37 @@ contract ReserveTest is Test {
assertEq(reserve.balanceOf(aliceAddress), 0); assertEq(reserve.balanceOf(aliceAddress), 0);
} }
function test_mint_donationNotTakenIfRateNotSet() public {
assertEq(reserve.totalSupply(), 0);
assertEq(reserve.donationRate(), 0);
assertEq(reserve.accumulatedDonation(), 0);
deal(aliceAddress, sendAmount);
vm.prank(aliceAddress);
reserve.mint{ value: sendAmount }(aliceAddress);
assertEq(reserve.totalSupply(), sendAmount * conversionRate);
assertEq(reserve.donationRate(), 0);
assertEq(reserve.accumulatedDonation(), 0);
}
function test_mint_donationIsTakenIfRateExists() public {
assertEq(reserve.totalSupply(), 0);
assertEq(reserve.donationRate(), 0);
assertEq(reserve.accumulatedDonation(), 0);
vm.prank(initializer);
reserve.changeDonationRate(1e4); // 10%
deal(aliceAddress, sendAmount);
vm.prank(aliceAddress);
reserve.mint{ value: sendAmount }(aliceAddress);
assertEq(reserve.totalSupply(), sendAmount * conversionRate);
assertEq(reserve.donationRate(), 1e4);
assertEq(reserve.accumulatedDonation(), sendAmount * 1e4 / 1e5);
}
function test_rate_couldBeChangedByDeployer() public { function test_rate_couldBeChangedByDeployer() public {
assertEq(reserve.conversionRate(), conversionRate); assertEq(reserve.conversionRate(), conversionRate);
vm.prank(initializer); vm.prank(initializer);
@ -100,6 +134,8 @@ contract ReserveTest is Test {
function test_withdraw_couldBeDoneByDeployer() public { function test_withdraw_couldBeDoneByDeployer() public {
assertEq(address(reserve).balance, 0); assertEq(address(reserve).balance, 0);
vm.prank(initializer);
reserve.changeDonationRate(1e5);
deal(aliceAddress, sendAmount); deal(aliceAddress, sendAmount);
vm.prank(aliceAddress); vm.prank(aliceAddress);
@ -114,6 +150,28 @@ contract ReserveTest is Test {
assertEq(faucetAddress.balance, sendAmount); assertEq(faucetAddress.balance, sendAmount);
} }
function test_withdraw_onlyAccumulatedDonationsCouldBeWithdrawn() public {
assertEq(address(reserve).balance, 0);
vm.prank(initializer);
reserve.changeDonationRate(5 * 1e4); // 50%
deal(aliceAddress, sendAmount);
vm.prank(aliceAddress);
reserve.mint{ value: sendAmount }(aliceAddress);
assertEq(address(reserve).balance, sendAmount);
vm.prank(initializer);
reserve.withdraw(payable(faucetAddress));
assertEq(address(reserve).balance, sendAmount / 2);
assertEq(faucetAddress.balance, sendAmount / 2);
vm.prank(initializer);
reserve.withdraw(payable(faucetAddress));
assertEq(address(reserve).balance, sendAmount / 2);
assertEq(faucetAddress.balance, sendAmount / 2);
}
function test_withdraw_couldNotBeDoneByArbitraryAddress() public { function test_withdraw_couldNotBeDoneByArbitraryAddress() public {
assertEq(address(reserve).balance, 0); assertEq(address(reserve).balance, 0);
@ -130,4 +188,86 @@ contract ReserveTest is Test {
assertEq(address(reserve).balance, sendAmount); assertEq(address(reserve).balance, sendAmount);
assertEq(aliceAddress.balance, 0); assertEq(aliceAddress.balance, 0);
} }
function test_burn_shouldReturnFullAmountIfRateNotSet() public {
deal(aliceAddress, sendAmount);
vm.prank(aliceAddress);
reserve.mint{ value: sendAmount }(aliceAddress);
uint256 balance = reserve.balanceOf(aliceAddress);
assertEq(reserve.totalSupply(), sendAmount * conversionRate);
assertEq(reserve.totalSupply(), balance);
assertEq(reserve.accumulatedDonation(), 0);
assertEq(aliceAddress.balance, 0);
vm.prank(aliceAddress);
reserve.burn(balance);
assertEq(reserve.totalSupply(), 0);
assertEq(reserve.balanceOf(aliceAddress), 0);
assertEq(reserve.accumulatedDonation(), 0);
assertEq(aliceAddress.balance, sendAmount);
}
function test_burn_shouldTakeDonationInAccordanceToRate() public {
vm.prank(initializer);
reserve.changeDonationRate(5 * 1e4); // 50%
deal(aliceAddress, sendAmount);
vm.prank(aliceAddress);
reserve.mint{ value: sendAmount }(aliceAddress);
uint256 balance = reserve.balanceOf(aliceAddress);
assertEq(reserve.totalSupply(), sendAmount * conversionRate);
assertEq(reserve.totalSupply(), balance);
assertEq(reserve.accumulatedDonation(), sendAmount / 2);
assertEq(aliceAddress.balance, 0);
vm.prank(aliceAddress);
reserve.burn(balance);
assertEq(reserve.totalSupply(), 0);
assertEq(reserve.balanceOf(aliceAddress), 0);
assertEq(reserve.accumulatedDonation(), sendAmount / 2);
assertEq(aliceAddress.balance, sendAmount / 2);
}
function test_burn_multipleUsersAccumulateInTotal() public {
vm.prank(initializer);
reserve.changeDonationRate(5 * 1e4); // 50%
deal(aliceAddress, sendAmount);
vm.prank(aliceAddress);
reserve.mint{ value: sendAmount }(aliceAddress);
vm.prank(initializer);
reserve.changeRate(conversionRate * 9);
deal(bobAddress, sendAmount);
vm.prank(bobAddress);
reserve.mint{ value: sendAmount }(bobAddress);
assertEq(aliceAddress.balance, 0);
assertEq(bobAddress.balance, 0);
uint256 aliceBalance = reserve.balanceOf(aliceAddress);
uint256 bobBalance = reserve.balanceOf(bobAddress);
uint256 bobEstimation = reserve.estimateAmount(bobBalance);
uint256 aliceEstimation = reserve.estimateAmount(aliceBalance);
vm.prank(bobAddress);
reserve.burn(bobBalance);
vm.prank(aliceAddress);
reserve.burn(aliceBalance);
assertEq(bobAddress.balance, sendAmount * 9 / 10);
assertEq(bobAddress.balance, bobEstimation);
assertEq(aliceAddress.balance, sendAmount / 10);
assertEq(aliceAddress.balance, aliceEstimation);
assertEq(reserve.totalSupply(), 0);
assertEq(reserve.accumulatedDonation(), sendAmount);
assertEq(address(reserve).balance, sendAmount);
}
} }

View File

@ -35,6 +35,9 @@ contract StinkyTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferT
uint256 constant amount = 69; uint256 constant amount = 69;
uint256 constant maxAmount = type(uint256).max; uint256 constant maxAmount = type(uint256).max;
string constant name = "Stinky Test Name";
string constant symbol = "STNKTST";
event Transfer(address indexed from, address indexed to, uint256 value); event Transfer(address indexed from, address indexed to, uint256 value);
event LogStakingContractUpdated(address stakingContract); event LogStakingContractUpdated(address stakingContract);
@ -46,9 +49,9 @@ contract StinkyTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferT
initializer, initializer,
initializer initializer
); );
ftso = new Fatso(address(authority)); ftso = new Fatso(address(authority), "Fatso", "FTSO");
stnk = new Stinky(INITIAL_INDEX); stnk = new Stinky(INITIAL_INDEX, name, symbol);
ghst = new Ghost(address(stnk)); ghst = new Ghost(address(stnk), "Ghost", "GHST");
staking = new GhostStaking( staking = new GhostStaking(
address(ftso), address(ftso),
address(stnk), address(stnk),
@ -67,8 +70,8 @@ contract StinkyTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferT
} }
function test_isConstructedCorrectly() public view { function test_isConstructedCorrectly() public view {
assertEq(stnk.name(), "Stinky"); assertEq(stnk.name(), name);
assertEq(stnk.symbol(), "STNK"); assertEq(stnk.symbol(), symbol);
assertEq(stnk.decimals(), 9); assertEq(stnk.decimals(), 9);
assertEq(stnk.totalSupply(), TOTAL_INITIAL_SUPPLY); assertEq(stnk.totalSupply(), TOTAL_INITIAL_SUPPLY);
assertEq(stnk.index(), INITIAL_INDEX / (TOTAL_SHARES / INITIAL_SHARES_SUPPLY)); assertEq(stnk.index(), INITIAL_INDEX / (TOTAL_SHARES / INITIAL_SHARES_SUPPLY));

View File

@ -35,7 +35,7 @@ contract GhostTreasuryTest is Test {
); );
reserve = new ERC20Mock("Reserve Token", "RET"); reserve = new ERC20Mock("Reserve Token", "RET");
liquidity = new ERC20Mock("Liquidity Token", "LDT"); liquidity = new ERC20Mock("Liquidity Token", "LDT");
ftso = new Fatso(address(authority)); ftso = new Fatso(address(authority), "Fatso", "FTSO");
treasury = new GhostTreasury(address(ftso), 69, address(authority)); treasury = new GhostTreasury(address(ftso), 69, address(authority));
calculator = new GhostBondingCalculator(address(ftso)); calculator = new GhostBondingCalculator(address(ftso));
vm.stopPrank(); vm.stopPrank();