Compare commits

..

2 Commits

Author SHA1 Message Date
59a13fec9d
remove governance feature from GhostERC20 contract
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-09-14 11:03:55 +03:00
b1e9be968e
emit event during the weaver insertion
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-09-14 10:46:40 +03:00
12 changed files with 68 additions and 780 deletions

View File

@ -3,7 +3,7 @@ set -e
AVAILABLE_NETWORKS=(anvil-localnet sepolia-testnet hoodi-testnet mordor-testnet) AVAILABLE_NETWORKS=(anvil-localnet sepolia-testnet hoodi-testnet mordor-testnet)
NETWORK="${AVAILABLE_NETWORKS[0]}" NETWORK="${AVAILABLE_NETWORKS[0]}"
START=1 START=1
END=13 END=12
VERIFY_NEEDED="--verify" VERIFY_NEEDED="--verify"
LEGACY_FLAGS="" LEGACY_FLAGS=""
@ -25,10 +25,9 @@ usage() {
echo "7) BondDepository" echo "7) BondDepository"
echo "8) StakingDistributor" echo "8) StakingDistributor"
echo "9) BondingCalculator" echo "9) BondingCalculator"
echo "10) Governor" echo "10) AfterPartyFirst"
echo "11) AfterPartyFirst" echo "11) AfterPartySecond"
echo "12) AfterPartySecond" echo "12) AfterPartyThird"
echo "13) AfterPartyThird"
echo "69) Gatekeeper" echo "69) Gatekeeper"
exit 1 exit 1
} }
@ -111,12 +110,10 @@ for i in $(seq $START $END); do
elif [ "9" == "$i" ]; then elif [ "9" == "$i" ]; then
CONTRACT="BondingCalculator" CONTRACT="BondingCalculator"
elif [ "10" == "$i" ]; then elif [ "10" == "$i" ]; then
CONTRACT="Governor"
elif [ "11" == "$i" ]; then
CONTRACT="AfterPartyFirst" CONTRACT="AfterPartyFirst"
elif [ "12" == "$i" ]; then elif [ "11" == "$i" ]; then
CONTRACT="AfterPartySecond" CONTRACT="AfterPartySecond"
elif [ "13" == "$i" ]; then elif [ "12" == "$i" ]; then
CONTRACT="AfterPartyThird" CONTRACT="AfterPartyThird"
elif [ "69" == "$i" ]; then elif [ "69" == "$i" ]; then
CONTRACT="Gatekeeper" CONTRACT="Gatekeeper"

View File

@ -109,14 +109,12 @@ contract Gatekeeper is IGatekeeper, Weaver, ReentrancyGuard {
return (publicKey, state.parity, state.session); return (publicKey, state.parity, state.session);
} }
function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) { function ghost(bytes32 receiver, uint256 amount) external override {
if (msg.sender != staking) revert NotStaking(); if (msg.sender != staking) revert NotStaking();
if (amount < EXISTENTIAL_DEPOSIT) revert NonExistentAmount(); if (amount < EXISTENTIAL_DEPOSIT) revert NonExistentAmount();
IStorageHistory(storageHistory).increaseBridgeIn(amount); IStorageHistory(storageHistory).increaseBridgeIn(amount);
_insertTreeNode(receiver, amount);
emit Ghosted(receiver, amount);
return _insertTreeNode(receiver, amount);
} }
function verify( function verify(

View File

@ -2,25 +2,17 @@
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
import {ERC20} from "@openzeppelin-contracts/token/ERC20/ERC20.sol"; import {ERC20} from "@openzeppelin-contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20Votes} from "@openzeppelin-contracts/token/ERC20/extensions/ERC20Votes.sol";
import {Nonces} from "@openzeppelin-contracts/utils/Nonces.sol";
import {ISTNK} from "./interfaces/ISTNK.sol"; import {ISTNK} from "./interfaces/ISTNK.sol";
import {IGHST} from "./interfaces/IGHST.sol"; import {IGHST} from "./interfaces/IGHST.sol";
import {FullMath} from "./libraries/FullMath.sol"; import {FullMath} from "./libraries/FullMath.sol";
contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes { contract Ghost is IGHST, ERC20 {
address public override staking; address public override staking;
address public override stnk; address public override stnk;
address private _initializer; address private _initializer;
error GhostDelegationIsBuiltIn(); constructor(address _stnk, string memory name, string memory symbol) ERC20(name, symbol) {
constructor(address _stnk, string memory name, string memory symbol)
ERC20(name, symbol)
ERC20Permit(name)
{
stnk = _stnk; stnk = _stnk;
_initializer = msg.sender; _initializer = msg.sender;
} }
@ -41,21 +33,6 @@ contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes {
_burn(_from, _amount); _burn(_from, _amount);
} }
function delegate(address) public override pure {
revert GhostDelegationIsBuiltIn();
}
function delegateBySig(
address,
uint256,
uint256,
uint8,
bytes32,
bytes32
) public override pure {
revert GhostDelegationIsBuiltIn();
}
function index() public view override returns (uint256) { function index() public view override returns (uint256) {
return ISTNK(stnk).index(); return ISTNK(stnk).index();
} }
@ -67,16 +44,4 @@ contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes {
function balanceTo(uint256 _amount) public view override returns (uint256) { function balanceTo(uint256 _amount) public view override returns (uint256) {
return FullMath.mulDiv(_amount, 1e18, index()); return FullMath.mulDiv(_amount, 1e18, index());
} }
function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
function delegates(address account) public pure override returns (address) {
return account;
}
function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
super._update(from, to, amount);
}
} }

View File

@ -14,7 +14,6 @@ interface IGatekeeper {
error InsufficientValue(); error InsufficientValue();
error ExecutionReverted(); error ExecutionReverted();
event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
event Recalled(address indexed receiver, uint256 indexed amount); event Recalled(address indexed receiver, uint256 indexed amount);
event Rotated(bytes32 indexed aggregatedPublicKey, uint8 indexed parity); event Rotated(bytes32 indexed aggregatedPublicKey, uint8 indexed parity);
event VoluntaryVerification(address indexed sender, uint256 indexed gasSpent); event VoluntaryVerification(address indexed sender, uint256 indexed gasSpent);
@ -29,6 +28,6 @@ interface IGatekeeper {
function govern(uint256 session, uint256 payload, bytes calldata call) external returns (bytes calldata); function govern(uint256 session, uint256 payload, bytes calldata call) external returns (bytes calldata);
function recall(uint256 session, uint256 amount, uint256 payload) external; function recall(uint256 session, uint256 amount, uint256 payload) external;
function rotate(uint256 session, bytes32 publicKey, uint8 parity) external; function rotate(uint256 session, bytes32 publicKey, uint8 parity) external;
function ghost(bytes32 receiver, uint256 amount) external returns (uint256); function ghost(bytes32 receiver, uint256 amount) external;
function initialize(address previousAddress) external; function initialize(address previousAddress) external;
} }

View File

@ -2,6 +2,8 @@
pragma solidity ^0.8.20; pragma solidity ^0.8.20;
interface IWeaver { interface IWeaver {
event ThreadInserted(uint256 index, uint256 session);
function currentWeavingSession() external view returns (uint256); function currentWeavingSession() external view returns (uint256);
function startWeavingSession() external view returns (uint256); function startWeavingSession() external view returns (uint256);
function getSlotValues(uint256 globalIndex, uint256 session, uint256 atBlock) external view returns (bytes32[] memory); function getSlotValues(uint256 globalIndex, uint256 session, uint256 atBlock) external view returns (bytes32[] memory);

View File

@ -14,8 +14,8 @@ contract WeaverMock is Weaver {
return _previousAddress; return _previousAddress;
} }
function insertTreeNode(bytes32 receiver, uint256 amount) external returns (uint256) { function insertTreeNode(bytes32 receiver, uint256 amount) external {
return _insertTreeNode(receiver, amount); _insertTreeNode(receiver, amount);
} }
function computeArgumentsHash( function computeArgumentsHash(

View File

@ -134,9 +134,10 @@ abstract contract Weaver is IWeaver {
return proof; return proof;
} }
function _insertTreeNode(bytes32 who, uint256 amount) internal returns (uint256 globalIndex) { function _insertTreeNode(bytes32 who, uint256 amount) internal {
uint256 session = currentWeavingSession; uint256 session = currentWeavingSession;
globalIndex = _filledEntries[session]; uint256 globalIndex = _filledEntries[session];
if (globalIndex >= ENTRIES) { if (globalIndex >= ENTRIES) {
unchecked { ++session; } unchecked { ++session; }
currentWeavingSession = session; currentWeavingSession = session;
@ -158,6 +159,8 @@ abstract contract Weaver is IWeaver {
_slotValues[session][slotIndex].push(preimage); _slotValues[session][slotIndex].push(preimage);
unchecked { ++_filledEntries[session]; } unchecked { ++_filledEntries[session]; }
emit ThreadInserted(globalIndex, session);
} }
function _computeArgumentsHash(uint256 i, uint256 a, bytes32 r) internal pure returns (bytes32) { function _computeArgumentsHash(uint256 i, uint256 a, bytes32 r) internal pure returns (bytes32) {

View File

@ -203,7 +203,7 @@ contract GatekeeperTest is Test {
Gatekeeper gatekeeper; Gatekeeper gatekeeper;
MockStaking staking; MockStaking staking;
event Ghosted(bytes32 indexed receiver, uint256 indexed amount); event ThreadInserted(uint256 index, uint256 session);
function setUp() public { function setUp() public {
vm.prank(ALICE, ALICE); vm.prank(ALICE, ALICE);
@ -247,7 +247,7 @@ contract GatekeeperTest is Test {
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
vm.expectEmit(true, true, true, false, address(gatekeeper)); vm.expectEmit(true, true, true, false, address(gatekeeper));
emit Ghosted(receiver, ghostAmount); emit ThreadInserted(0, 0);
vm.prank(ALICE); vm.prank(ALICE);
staking.runGhost(receiver, ghostAmount); staking.runGhost(receiver, ghostAmount);
} }

View File

@ -1,6 +1,6 @@
pragma solidity 0.8.20; pragma solidity 0.8.20;
import {Test} from "forge-std/Test.sol"; import {Test, Vm} from "forge-std/Test.sol";
import {Gatekeeper} from "../../src/Gatekeeper.sol"; import {Gatekeeper} from "../../src/Gatekeeper.sol";
import {Hashes} from "../../src/libraries/Hashes.sol"; import {Hashes} from "../../src/libraries/Hashes.sol";
@ -21,9 +21,9 @@ contract MockStaking {
governor = msg.sender; governor = msg.sender;
} }
function ghost(bytes32 receiver, uint256 amount) external returns (uint256) { function ghost(bytes32 receiver, uint256 amount) external {
require(msg.sender == governor); require(msg.sender == governor);
return gatekeeper.ghost(receiver, amount); gatekeeper.ghost(receiver, amount);
} }
function createNewGatekeeper() external { function createNewGatekeeper() external {
@ -127,25 +127,25 @@ contract GatekeeperWeaverTest is Test {
} }
function test_insertationWorksAsExpected() public { function test_insertationWorksAsExpected() public {
uint256 currentWeavingSession = gatekeeper.currentWeavingSession();
uint256 maxCount = gatekeeper.ENTRIES(); uint256 maxCount = gatekeeper.ENTRIES();
uint256 currentWeavingSession;
uint256 newWeavingSession;
uint256 globalIndex; uint256 globalIndex;
(bytes32[] memory whos, uint256[] memory amounts) = _prepareArrays(maxCount); (bytes32[] memory whos, uint256[] memory amounts) = _prepareArrays(maxCount);
vm.recordLogs();
for (uint256 i = 0; i < maxCount; i++) { for (uint256 i = 0; i < maxCount; i++) {
if (i % 5 == 0) { vm.roll(block.number + 1); } if (i % 5 == 0) { vm.roll(block.number + 1); }
globalIndex = _insertWithAssert(currentWeavingSession, amounts[i], whos[i]); (globalIndex, currentWeavingSession) = _insertWithAssert(amounts[i], whos[i]);
assertTrue(_verifyProof(globalIndex, currentWeavingSession, block.number, amounts[i], whos[i])); assertTrue(_verifyProof(globalIndex, currentWeavingSession, block.number, amounts[i], whos[i]));
} }
globalIndex = _insertWithAssert(currentWeavingSession, amounts[69], whos[69]); (globalIndex, newWeavingSession) = _insertWithAssert(amounts[69], whos[69]);
uint256 newSession = gatekeeper.currentWeavingSession(); assertEq(currentWeavingSession + 1, newWeavingSession);
assertEq(currentWeavingSession + 1, newSession);
vm.roll(block.number + 1337); vm.roll(block.number + 1337);
assertTrue(_verifyProof(globalIndex, newSession, block.number, amounts[69], whos[69])); assertTrue(_verifyProof(globalIndex, newWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(0, currentWeavingSession, block.number, amounts[0], whos[0])); assertTrue(_verifyProof(0, currentWeavingSession, block.number, amounts[0], whos[0]));
assertTrue(_verifyProof(69, currentWeavingSession, block.number, amounts[69], whos[69])); assertTrue(_verifyProof(69, currentWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, block.number, amounts[420], whos[420])); assertTrue(_verifyProof(420, currentWeavingSession, block.number, amounts[420], whos[420]));
@ -156,7 +156,7 @@ contract GatekeeperWeaverTest is Test {
assertTrue(_verifyProof(69, currentWeavingSession, 100, amounts[69], whos[69])); assertTrue(_verifyProof(69, currentWeavingSession, 100, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, 100, amounts[420], whos[420])); assertTrue(_verifyProof(420, currentWeavingSession, 100, amounts[420], whos[420]));
assertFalse(_verifyProof(globalIndex, newSession, 100, amounts[69], whos[69])); assertFalse(_verifyProof(globalIndex, newWeavingSession, 100, amounts[69], whos[69]));
assertFalse(_verifyProof(1337, currentWeavingSession, 100, amounts[1337], whos[1337])); assertFalse(_verifyProof(1337, currentWeavingSession, 100, amounts[1337], whos[1337]));
assertFalse(_verifyProof(2047, currentWeavingSession, 100, amounts[2047], whos[2047])); assertFalse(_verifyProof(2047, currentWeavingSession, 100, amounts[2047], whos[2047]));
@ -174,7 +174,7 @@ contract GatekeeperWeaverTest is Test {
staking.ghost(whos[i], amounts[i]); staking.ghost(whos[i], amounts[i]);
} }
assertTrue(_verifyProof(globalIndex, newSession, block.number, amounts[69], whos[69])); assertTrue(_verifyProof(globalIndex, newWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(0, currentWeavingSession, block.number, amounts[0], whos[0])); assertTrue(_verifyProof(0, currentWeavingSession, block.number, amounts[0], whos[0]));
assertTrue(_verifyProof(69, currentWeavingSession, block.number, amounts[69], whos[69])); assertTrue(_verifyProof(69, currentWeavingSession, block.number, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, block.number, amounts[420], whos[420])); assertTrue(_verifyProof(420, currentWeavingSession, block.number, amounts[420], whos[420]));
@ -185,7 +185,7 @@ contract GatekeeperWeaverTest is Test {
assertTrue(_verifyProof(69, currentWeavingSession, 100, amounts[69], whos[69])); assertTrue(_verifyProof(69, currentWeavingSession, 100, amounts[69], whos[69]));
assertTrue(_verifyProof(420, currentWeavingSession, 100, amounts[420], whos[420])); assertTrue(_verifyProof(420, currentWeavingSession, 100, amounts[420], whos[420]));
assertFalse(_verifyProof(globalIndex, newSession, 100, amounts[69], whos[69])); assertFalse(_verifyProof(globalIndex, newWeavingSession, 100, amounts[69], whos[69]));
assertFalse(_verifyProof(1337, currentWeavingSession, 100, amounts[1337], whos[1337])); assertFalse(_verifyProof(1337, currentWeavingSession, 100, amounts[1337], whos[1337]));
assertFalse(_verifyProof(2047, currentWeavingSession, 100, amounts[2047], whos[2047])); assertFalse(_verifyProof(2047, currentWeavingSession, 100, amounts[2047], whos[2047]));
@ -212,41 +212,43 @@ contract GatekeeperWeaverTest is Test {
} }
function _insertWithAssert( function _insertWithAssert(
uint256 session,
uint256 amount, uint256 amount,
bytes32 who bytes32 who
) private returns (uint256 globalIndex) { ) private returns (uint256 globalIndex, uint256 emittedSession) {
uint256 targetSlot = gatekeeper.filledEntries(session); uint256 preSession = gatekeeper.currentWeavingSession();
bytes32 previousHash = gatekeeper.treeNodesLatest(session, targetSlot);
uint256 prevEntries = gatekeeper.filledEntries(session); uint256 targetSlot = gatekeeper.filledEntries(preSession);
uint160 prevLength = gatekeeper.slotLengths(session, targetSlot); bytes32 previousHash = gatekeeper.treeNodesLatest(preSession, targetSlot);
uint256 prevEntries = gatekeeper.filledEntries(preSession);
uint160 prevLength = gatekeeper.slotLengths(preSession, targetSlot);
vm.prank(ALICE); vm.prank(ALICE);
globalIndex = staking.ghost(who, amount); staking.ghost(who, amount);
(globalIndex, emittedSession) = _getInsertedDataFromLogs();
if (preSession != emittedSession) {
assertEq(preSession + 1, emittedSession);
assertEq(emittedSession, gatekeeper.currentWeavingSession());
if (session + 1 == gatekeeper.currentWeavingSession()) {
assertEq(prevLength, gatekeeper.DEPTH()); assertEq(prevLength, gatekeeper.DEPTH());
assertEq(gatekeeper.slotLengths(session + 1, 0), 1); assertEq(gatekeeper.slotLengths(emittedSession, 0), 1);
assertEq(prevEntries, gatekeeper.ENTRIES()); assertEq(prevEntries, gatekeeper.ENTRIES());
assertEq(gatekeeper.filledEntries(session + 1), 1); assertEq(gatekeeper.filledEntries(emittedSession), 1);
assertEq(session + 1, gatekeeper.currentWeavingSession());
session += 1;
previousHash = bytes32(0); previousHash = bytes32(0);
prevLength = gatekeeper.slotLengths(session, 0);
} else { } else {
assertEq(prevLength + 1, gatekeeper.slotLengths(session, targetSlot)); assertEq(prevLength + 1, gatekeeper.slotLengths(emittedSession, targetSlot));
assertEq(prevEntries + 1, gatekeeper.filledEntries(session)); assertEq(prevEntries + 1, gatekeeper.filledEntries(emittedSession));
} }
{ {
bytes32 preimage1 = gatekeeper.slotValues(session, globalIndex); bytes32 preimage1 = gatekeeper.slotValues(emittedSession, globalIndex);
bytes32 preimage2 = gatekeeper.computePreimage(globalIndex, amount, who); bytes32 preimage2 = gatekeeper.computePreimage(globalIndex, amount, who);
assertEq(preimage1, preimage2); assertEq(preimage1, preimage2);
} }
{ {
bytes32 nodeHash1 = gatekeeper.treeNodesLatest(session, globalIndex); bytes32 nodeHash1 = gatekeeper.treeNodesLatest(emittedSession, globalIndex);
bytes32 preimageHash = Hashes.efficientKeccak256( bytes32 preimageHash = Hashes.efficientKeccak256(
gatekeeper.computePreimage(globalIndex, amount, who) gatekeeper.computePreimage(globalIndex, amount, who)
); );
@ -268,4 +270,17 @@ contract GatekeeperWeaverTest is Test {
return (whos, amounts); return (whos, amounts);
} }
function _getInsertedDataFromLogs() private returns (uint256 index, uint256 session) {
Vm.Log[] memory entries = vm.getRecordedLogs();
bytes32 targetTopic = keccak256("ThreadInserted(uint256,uint256)");
for (uint256 i = entries.length; i > 0; i--) {
if (entries[i - 1].topics.length > 0 && entries[i - 1].topics[0] == targetTopic) {
(index, session) = abi.decode(entries[i - 1].data, (uint256, uint256));
return (index, session);
}
}
revert("Target event not found in logs");
}
} }

View File

@ -1,647 +0,0 @@
pragma solidity 0.8.20;
import {Test} from "forge-std/Test.sol";
import {Strings} from "@openzeppelin-contracts/utils/Strings.sol";
import {Ghost} from "../../src/GhstERC20.sol";
import {Stinky} from "../../src/StinkyERC20.sol";
import {GhostGovernor} from "../../src/governance/GhostGovernor.sol";
import {IGovernor} from "@openzeppelin-contracts/governance/IGovernor.sol";
import {IVotes} from "@openzeppelin-contracts/governance/utils/IVotes.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
contract GhostGovernorExposed is GhostGovernor {
constructor(
IVotes _ghst,
uint48 _initialVoteExtension,
uint48 _initialVotingDelay,
uint32 _initialVotingPeriod,
uint256 _initialProposalThreshold,
uint256 _quorumFraction
) GhostGovernor(
_ghst,
_initialVoteExtension,
_initialVotingDelay,
_initialVotingPeriod,
_initialProposalThreshold,
_quorumFraction
) {}
function isValidDescriptionForProposer(
address proposer,
string memory description
) public view returns (bool) {
return super._isValidDescriptionForProposer(proposer, description);
}
function quorumReached(uint256 proposalId) public view returns (bool) {
return super._quorumReached(proposalId);
}
function voteSucceeded(uint256 proposalId) public view returns (bool) {
return super._voteSucceeded(proposalId);
}
}
contract GhostGovernorTest is Test {
using Strings for address;
using SafeERC20 for Ghost;
uint48 public constant VOTE_EXTENSION = 69;
uint48 public constant VOTING_DELAY = 420;
uint32 public constant VOTING_PERIOD = 1337;
uint256 public constant PROPOSAL_THRESHOLD = 69 * 1e18;
uint256 public constant QUORUM_FRACTION = 20; // percent
address constant INIT = 0x0000000000000000000000000000000000000001;
address constant ALICE = 0x0000000000000000000000000000000000000002;
address constant BOB = 0x0000000000000000000000000000000000000003;
address constant CAROL = 0x0000000000000000000000000000000000000004;
address constant DAVE = 0x0000000000000000000000000000000000000005;
address constant EVE = 0x0000000000000000000000000000000000000006;
GhostGovernorExposed public governor;
Stinky public stnk;
Ghost public ghst;
function setUp() public {
vm.startPrank(INIT);
stnk = new Stinky(10819917194513808e56, "Stinky Test Name", "STNKTST");
ghst = new Ghost(address(stnk), "Ghost Test Name", "GHSTTST");
ghst.initialize(INIT);
governor = new GhostGovernorExposed(
ghst,
VOTE_EXTENSION,
VOTING_DELAY,
VOTING_PERIOD,
PROPOSAL_THRESHOLD,
QUORUM_FRACTION
);
vm.stopPrank();
vm.prank(ALICE);
ghst.approve(address(governor), type(uint256).max);
vm.prank(BOB);
ghst.approve(address(governor), type(uint256).max);
vm.prank(CAROL);
ghst.approve(address(governor), type(uint256).max);
}
function test_correctGovernanceName() public view {
assertEq(governor.name(), "GhostGovernorV1");
}
function test_correctGovernanceVersion() public view {
assertEq(governor.version(), "1");
}
function test_correctGovernanceConfig() public view {
assertEq(governor.COUNTING_MODE(), "support=bravo&quorum=for");
}
function test_correctGovernanceProposalThreshold() public view {
assertEq(governor.proposalThreshold(), PROPOSAL_THRESHOLD);
}
function test_correctGovernanceVotingDelay() public view {
assertEq(governor.votingDelay(), VOTING_DELAY);
}
function test_correctGovernanceVotingPeriod() public view {
assertEq(governor.votingPeriod(), VOTING_PERIOD);
}
function test_correctGovernanceVoteExtension() public view {
assertEq(governor.lateQuorumVoteExtension(), VOTE_EXTENSION);
}
function test_correctGovernanceQuorumFraction() public view {
assertEq(governor.quorumNumerator(), QUORUM_FRACTION);
}
function test_validDescriptionForProposer(
string memory description,
address proposer,
bool includeProposer
) public view {
string memory finalDescription = description;
if (includeProposer) {
finalDescription = string.concat(
description,
"#proposer=",
Strings.toHexString(proposer)
);
}
bool isValid = governor.isValidDescriptionForProposer(proposer, finalDescription);
assertTrue(isValid);
}
function test_invalidDescriptionForProposer(
string memory description,
address commitProposer,
address actualProposer
) public view {
vm.assume(commitProposer != actualProposer);
string memory wrongDescription = string.concat(
description,
"#proposer=",
Strings.toHexString(commitProposer)
);
bool isValid = governor.isValidDescriptionForProposer(actualProposer, wrongDescription);
assertFalse(isValid);
}
function test_succeededOnAbsoluteMajority() public {
vm.startPrank(INIT);
ghst.mint(ALICE, PROPOSAL_THRESHOLD);
ghst.mint(BOB, 555 * 1e17);
ghst.mint(CAROL, 345 * 1e17 + 1);
ghst.mint(DAVE, 21 * 1e18);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
_waitForActive(proposalId);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
_castVoteWrapper(proposalId, BOB, 1, true, true);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Succeeded));
vm.expectRevert();
vm.prank(CAROL);
governor.castVote(proposalId, 0);
vm.expectRevert();
vm.prank(DAVE);
governor.castVote(proposalId, 0);
}
function test_defeatedOnAbsoluteMajority() public {
vm.startPrank(INIT);
ghst.mint(ALICE, PROPOSAL_THRESHOLD);
ghst.mint(BOB, 555 * 1e17);
ghst.mint(CAROL, 345 * 1e17 + 1);
ghst.mint(DAVE, 21 * 1e18);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
_waitForActive(proposalId);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
_castVoteWrapper(proposalId, BOB, 0, true, false);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
_castVoteWrapper(proposalId, CAROL, 0, true, false);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Defeated));
vm.expectRevert();
vm.prank(DAVE);
governor.castVote(proposalId, 1);
}
function test_onlyOneActiveProposal() public {
uint256 amount = PROPOSAL_THRESHOLD;
vm.startPrank(INIT);
ghst.mint(ALICE, amount);
ghst.mint(BOB, 2 * amount);
ghst.mint(CAROL, 3 * amount);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
_waitForActive(proposalId);
vm.prank(ALICE);
assertEq(governor.releaseLocked(proposalId), 0);
assertEq(governor.lockedAmounts(proposalId), amount);
assertEq(ghst.balanceOf(ALICE), 0);
(uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) = governor.proposalVotes(proposalId);
assertEq(againstVotes, 0);
assertEq(forVotes, amount);
assertEq(abstainVotes, 0);
_castVoteWrapper(proposalId, BOB, 1, true, true);
(againstVotes, forVotes, abstainVotes) = governor.proposalVotes(proposalId);
assertEq(againstVotes, 0);
assertEq(forVotes, 3 * amount);
assertEq(abstainVotes, 0);
assertEq(ghst.balanceOf(ALICE), 0);
assertEq(ghst.balanceOf(BOB), 2 * amount);
assertEq(ghst.balanceOf(CAROL), 3 * amount);
_waitForSucceed(proposalId);
vm.prank(ALICE);
assertEq(governor.releaseLocked(proposalId), amount);
assertEq(governor.lockedAmounts(proposalId), 0);
assertEq(ghst.balanceOf(ALICE), amount);
vm.prank(ALICE);
assertEq(governor.releaseLocked(proposalId), 0);
assertEq(governor.lockedAmounts(proposalId), 0);
assertEq(ghst.balanceOf(ALICE), amount);
assertEq(ghst.balanceOf(ALICE), amount);
assertEq(ghst.balanceOf(BOB), 2 * amount);
assertEq(ghst.balanceOf(CAROL), 3 * amount);
(proposalId,,,,) = _proposeDummy(BOB, 420);
_waitForActive(proposalId);
_castVoteWrapper(proposalId, ALICE, 1, true, true);
_castVoteWrapper(proposalId, CAROL, 1, true, true);
vm.roll(block.number + 3);
(uint256 newProposalId,,,,) = _proposeDummy(CAROL, 1337);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Succeeded));
assertEq(uint8(governor.state(newProposalId)), uint8(IGovernor.ProposalState.Pending));
assertEq(ghst.balanceOf(ALICE), amount);
assertEq(ghst.balanceOf(BOB), 0);
assertEq(ghst.balanceOf(CAROL), 0);
vm.prank(BOB);
assertEq(governor.releaseLocked(proposalId), 2 * amount);
assertEq(governor.lockedAmounts(proposalId), 0);
assertEq(ghst.balanceOf(BOB), 2 * amount);
vm.prank(CAROL);
assertEq(governor.releaseLocked(newProposalId), 0);
assertEq(governor.lockedAmounts(newProposalId), 3 * amount);
assertEq(ghst.balanceOf(CAROL), 0);
}
function test_proposalCountWorks() public {
assertEq(governor.proposalCount(), 0);
uint256 i = 1;
for (; i < 69; ) {
{
uint256 amount = governor.activeProposedLock() + PROPOSAL_THRESHOLD;
if (amount + ghst.totalSupply() > type(uint208).max) {
break;
}
vm.prank(INIT);
ghst.mint(ALICE, amount);
vm.roll(block.number + 1);
}
(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) = _proposeDummy(ALICE, i);
{
(
address[] memory thisTargets,
uint256[] memory thisValues,
bytes[] memory thisCalldatas,
bytes32 thisDescriptionHash
) = governor.proposalDetails(proposalId);
assertEq(thisTargets.length, targets.length);
assertEq(thisValues.length, values.length);
assertEq(thisCalldatas.length, calldatas.length);
assertEq(thisTargets[0], targets[0]);
assertEq(thisValues[0], values[0]);
assertEq(thisCalldatas[0], calldatas[0]);
assertEq(thisDescriptionHash, keccak256(bytes(description)));
}
{
(
uint256 otherProposalId,
address[] memory otherTargets,
uint256[] memory otherValues,
bytes[] memory otherCalldatas,
bytes32 otherDescriptionHash
) = governor.proposalDetailsAt(i - 1);
assertEq(otherTargets.length, targets.length);
assertEq(otherValues.length, values.length);
assertEq(otherCalldatas.length, calldatas.length);
assertEq(otherProposalId, governor.hashProposal(targets, values, calldatas, keccak256(bytes(description))));
assertEq(otherTargets[0], targets[0]);
assertEq(otherValues[0], values[0]);
assertEq(otherCalldatas[0], calldatas[0]);
assertEq(otherDescriptionHash, keccak256(bytes(description)));
}
assertEq(governor.proposalCount(), i);
unchecked { ++i; }
}
}
function test_preventLateQuorumWorks() public {
vm.startPrank(INIT);
ghst.mint(ALICE, PROPOSAL_THRESHOLD);
ghst.mint(BOB, 50 * 1e18);
ghst.mint(CAROL, 100 * 1e18);
ghst.mint(DAVE, 100 * 1e18);
ghst.mint(EVE, 500 * 1e18);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Pending));
vm.roll(block.number + VOTING_DELAY + 1);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
assertEq(governor.voteOf(proposalId, BOB), 0);
assertEq(governor.voteOf(proposalId, CAROL), 0);
assertEq(governor.voteOf(proposalId, DAVE), 0);
assertEq(governor.voteOf(proposalId, EVE), 0);
vm.roll(governor.proposalDeadline(proposalId));
_castVoteWrapper(proposalId, BOB, 1, false, true);
vm.roll(governor.proposalDeadline(proposalId));
_castVoteWrapper(proposalId, CAROL, 0, true, false);
vm.roll(governor.proposalDeadline(proposalId));
_castVoteWrapper(proposalId, DAVE, 1, true, false);
vm.roll(governor.proposalDeadline(proposalId));
_castVoteWrapper(proposalId, EVE, 1, true, true);
vm.prank(ALICE);
governor.execute(proposalId);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Executed));
assertEq(governor.voteOf(proposalId, BOB), 2);
assertEq(governor.voteOf(proposalId, CAROL), 1);
assertEq(governor.voteOf(proposalId, DAVE), 2);
assertEq(governor.voteOf(proposalId, EVE), 2);
}
function test_quorumNumeratorWorks() public {
uint256 numerator = governor.quorumNumerator();
uint256 denominator = governor.quorumDenominator();
uint256 currentBlock = block.number;
vm.startPrank(INIT);
ghst.mint(ALICE, 1 * 1e18);
ghst.mint(BOB, 69 * 1e18);
ghst.mint(CAROL, 420 * 1e18);
ghst.mint(DAVE, 1337 * 1e18);
ghst.mint(EVE, 69420 * 1e18);
vm.stopPrank();
vm.roll(currentBlock + 2);
assertEq(governor.quorum(currentBlock), 71247 * 1e18 * numerator / denominator);
}
function test_votingPowerTransfer() public {
vm.startPrank(INIT);
ghst.mint(ALICE, 35 * 1e18);
ghst.mint(BOB, 34 * 1e18);
vm.stopPrank();
vm.roll(block.number + 1);
vm.expectRevert();
vm.prank(ALICE);
ghst.delegate(ALICE);
vm.expectRevert();
vm.prank(BOB);
ghst.delegate(BOB);
_assertVotesEqualToBalance();
vm.prank(ALICE);
ghst.safeTransfer(BOB, 420);
_assertVotesEqualToBalance();
uint256 aliceBalance = ghst.balanceOf(ALICE);
vm.prank(ALICE);
ghst.safeTransfer(BOB, aliceBalance);
_assertVotesEqualToBalance();
vm.prank(BOB);
ghst.safeTransfer(CAROL, 1337);
_assertVotesEqualToBalance();
uint256 bobBalance = ghst.balanceOf(BOB);
vm.prank(BOB);
ghst.safeTransfer(CAROL, bobBalance);
_assertVotesEqualToBalance();
vm.expectRevert();
vm.prank(CAROL);
ghst.delegate(CAROL);
assertEq(ghst.getVotes(ALICE), 0);
assertEq(ghst.getVotes(BOB), 0);
assertEq(ghst.getVotes(CAROL), 69 * 1e18);
assertEq(ghst.balanceOf(ALICE), 0);
assertEq(ghst.balanceOf(BOB), 0);
assertEq(ghst.balanceOf(CAROL), 69 * 1e18);
vm.prank(INIT);
ghst.burn(CAROL, 69 * 1e18);
assertEq(ghst.getVotes(CAROL), 0);
assertEq(ghst.balanceOf(CAROL), 0);
assertEq(ghst.totalSupply(), 0);
}
function test_changesOfTotalSupplyHasNoAffectOnPastTotalSupply() public {
vm.prank(INIT);
ghst.mint(ALICE, 100);
vm.roll(block.number + 1);
assertEq(ghst.getPastTotalSupply(block.number - 1), 100);
assertEq(ghst.totalSupply(), 100);
vm.prank(INIT);
ghst.burn(ALICE, 50);
vm.roll(block.number + 1);
assertEq(ghst.getPastTotalSupply(block.number - 2), 100);
assertEq(ghst.getPastTotalSupply(block.number - 1), 50);
assertEq(ghst.totalSupply(), 50);
vm.prank(INIT);
ghst.mint(BOB, 150);
vm.roll(block.number + 1);
assertEq(ghst.getPastTotalSupply(block.number - 3), 100);
assertEq(ghst.getPastTotalSupply(block.number - 2), 50);
assertEq(ghst.getPastTotalSupply(block.number - 1), 200);
assertEq(ghst.totalSupply(), 200);
}
function test_proposeAutoForVote() public {
vm.startPrank(INIT);
ghst.mint(ALICE, PROPOSAL_THRESHOLD);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
(uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) = governor.proposalVotes(proposalId);
assertEq(forVotes, PROPOSAL_THRESHOLD);
assertEq(againstVotes, 0);
assertEq(abstainVotes, 0);
vm.roll(block.number + 1);
vm.expectRevert();
vm.prank(ALICE);
governor.castVote(proposalId, 1);
}
function test_releaseAfterDefeated() public {
vm.startPrank(INIT);
ghst.mint(ALICE, PROPOSAL_THRESHOLD);
ghst.mint(BOB, PROPOSAL_THRESHOLD);
ghst.mint(CAROL, PROPOSAL_THRESHOLD);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
assertEq(governor.releaseLocked(proposalId), 0);
assertEq(ghst.balanceOf(ALICE), 0);
assertEq(ghst.balanceOf(address(governor)), PROPOSAL_THRESHOLD);
_waitForActive(proposalId);
_castVoteWrapper(proposalId, BOB, 0, true, false);
_castVoteWrapper(proposalId, CAROL, 0, true, false);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Defeated));
assertEq(governor.releaseLocked(proposalId), PROPOSAL_THRESHOLD);
assertEq(ghst.balanceOf(ALICE), PROPOSAL_THRESHOLD);
assertEq(ghst.balanceOf(address(governor)), 0);
}
function test_releaseAfterSucceeded() public {
vm.startPrank(INIT);
ghst.mint(ALICE, PROPOSAL_THRESHOLD);
ghst.mint(BOB, PROPOSAL_THRESHOLD);
ghst.mint(CAROL, PROPOSAL_THRESHOLD);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
assertEq(governor.releaseLocked(proposalId), 0);
assertEq(ghst.balanceOf(ALICE), 0);
assertEq(ghst.balanceOf(address(governor)), PROPOSAL_THRESHOLD);
_waitForActive(proposalId);
_castVoteWrapper(proposalId, BOB, 1, true, true);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Succeeded));
assertEq(governor.releaseLocked(proposalId), PROPOSAL_THRESHOLD);
assertEq(ghst.balanceOf(ALICE), PROPOSAL_THRESHOLD);
assertEq(ghst.balanceOf(address(governor)), 0);
}
function test_releaseAfterDeadline() public {
vm.startPrank(INIT);
ghst.mint(ALICE, PROPOSAL_THRESHOLD);
ghst.mint(BOB, 420 * PROPOSAL_THRESHOLD);
vm.stopPrank();
vm.roll(block.number + 1);
(uint256 proposalId,,,,) = _proposeDummy(ALICE, 69);
assertEq(governor.releaseLocked(proposalId), 0);
assertEq(ghst.balanceOf(ALICE), 0);
assertEq(ghst.balanceOf(address(governor)), PROPOSAL_THRESHOLD);
_waitForActive(proposalId);
vm.roll(block.number + governor.proposalDeadline(proposalId));
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Defeated));
assertEq(governor.releaseLocked(proposalId), PROPOSAL_THRESHOLD);
assertEq(ghst.balanceOf(ALICE), PROPOSAL_THRESHOLD);
assertEq(ghst.balanceOf(address(governor)), 0);
}
function _proposeDummy(address who, uint256 index)
private
returns (
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
)
{
targets = new address[](1);
targets[0] = address(uint160(index + 1)); // forge-lint: disable-line(unsafe-typecast)
values = new uint256[](1);
values[0] = 0;
calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("setVotingDelay(uint48)", 0);
description = string.concat("Proposal #", Strings.toString(index));
vm.prank(who);
proposalId = governor.propose(
targets,
values,
calldatas,
description
);
vm.roll(block.number + 1);
}
function _castVoteWrapper(
uint256 proposalId,
address who,
uint8 vote,
bool isQuorumReached,
bool isVoteSucceeded
) private {
assertEq(governor.hasVoted(proposalId, who), false);
vm.prank(who);
governor.castVote(proposalId, vote);
assertEq(governor.hasVoted(proposalId, who), true);
assertEq(governor.quorumReached(proposalId), isQuorumReached);
assertEq(governor.voteSucceeded(proposalId), isVoteSucceeded);
}
function _assertVotesEqualToBalance() private view {
assertEq(ghst.getVotes(ALICE), ghst.balanceOf(ALICE));
assertEq(ghst.getVotes(BOB), ghst.balanceOf(BOB));
assertEq(ghst.getVotes(CAROL), ghst.balanceOf(CAROL));
}
function _waitForActive(uint256 proposalId) private {
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Pending));
vm.roll(block.number + VOTING_DELAY + 1);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
}
function _waitForSucceed(uint256 proposalId) private {
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Active));
vm.roll(governor.proposalDeadline(proposalId) + 1);
assertEq(uint8(governor.state(proposalId)), uint8(IGovernor.ProposalState.Succeeded));
}
}

View File

@ -86,7 +86,6 @@ contract StakingTest is Test {
event DistributorSet(address distributor); event DistributorSet(address distributor);
event WarmupSet(uint256 warmup); event WarmupSet(uint256 warmup);
event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
function setUp() public { function setUp() public {
vm.startPrank(INITIALIZER); vm.startPrank(INITIALIZER);
@ -640,23 +639,6 @@ contract StakingTest is Test {
assertEq(ghst.totalSupply(), 0); assertEq(ghst.totalSupply(), 0);
} }
function test_ghostTokensEmitsEvent() public {
_prepareAndRoll(ALICE, BIG_AMOUNT, true, true);
uint256 aliceBalance = stnk.balanceOf(ALICE);
vm.startPrank(ALICE);
stnk.approve(address(staking), aliceBalance);
uint256 ghstBalance = staking.wrap(ALICE, aliceBalance);
vm.stopPrank();
bytes32 receiver = bytes32(abi.encodePacked(ALICE));
vm.expectEmit(true, true, true, false, staking.gatekeeper());
emit Ghosted(receiver, ghstBalance);
vm.prank(ALICE);
staking.ghost(receiver, ghstBalance);
}
function test_breakoutLogicWorks() public { function test_breakoutLogicWorks() public {
uint256 initialIndex = staking.index(); uint256 initialIndex = staking.index();
bytes32 receiver = bytes32(abi.encodePacked(ALICE)); bytes32 receiver = bytes32(abi.encodePacked(ALICE));
@ -678,9 +660,6 @@ contract StakingTest is Test {
uint256 range = (payout * 3) / 100; uint256 range = (payout * 3) / 100;
requestedPayout = (pseudoRandom % range) + 1; requestedPayout = (pseudoRandom % range) + 1;
vm.expectEmit(true, true, true, false, staking.gatekeeper());
emit Ghosted(receiver, requestedPayout);
vm.prank(ALICE); vm.prank(ALICE);
staking.breakout(receiver, requestedPayout); staking.breakout(receiver, requestedPayout);
expectedPayout += requestedPayout; expectedPayout += requestedPayout;

View File

@ -7,18 +7,10 @@ import {Stinky} from "../../src/StinkyERC20.sol";
import {GhostAuthority} from "../../src/GhostAuthority.sol"; import {GhostAuthority} from "../../src/GhostAuthority.sol";
import {GhostStaking} from "../../src/Staking.sol"; import {GhostStaking} from "../../src/Staking.sol";
import {ERC20PermitTest} from "./Permit.t.sol";
import {ERC20AllowanceTest} from "./Allowance.t.sol"; import {ERC20AllowanceTest} from "./Allowance.t.sol";
import {ERC20TransferTest} from "./Transfer.t.sol"; import {ERC20TransferTest} from "./Transfer.t.sol";
import {ERC20VotesTest} from "./Votes.t.sol";
contract GhostTest is contract GhostTest is Test, ERC20AllowanceTest, ERC20TransferTest {
Test,
ERC20PermitTest,
ERC20AllowanceTest,
ERC20TransferTest,
ERC20VotesTest
{
uint256 constant public INITIAL_INDEX = 10819917194513808e56; uint256 constant public INITIAL_INDEX = 10819917194513808e56;
address constant INITIALIZER = 0x0000000000000000000000000000000000000001; address constant INITIALIZER = 0x0000000000000000000000000000000000000001;
@ -59,10 +51,8 @@ contract GhostTest is
ghst.initialize(address(staking)); ghst.initialize(address(staking));
vm.stopPrank(); vm.stopPrank();
initializePermit(address(ghst), AMOUNT, MAX_AMOUNT);
initializeAllowance(ALICE, BOB, address(ghst), AMOUNT, MAX_AMOUNT, AMOUNT); initializeAllowance(ALICE, BOB, address(ghst), AMOUNT, MAX_AMOUNT, AMOUNT);
initializeTransfer(ALICE, BOB, address(ghst), AMOUNT, MAX_AMOUNT); initializeTransfer(ALICE, BOB, address(ghst), AMOUNT, MAX_AMOUNT);
initializeVotes(ALICE, BOB, address(ghst), AMOUNT);
} }
function test_isConstructedCorrectly() public view { function test_isConstructedCorrectly() public view {
@ -157,19 +147,6 @@ contract GhostTest is
_mintTokens(who, value); _mintTokens(who, value);
} }
function _mintPermitTokens(address who, uint256 value) internal override {
_mintTokens(who, value);
}
function _mintVotesTokens(address who, uint256 value) internal override {
_mintTokens(who, value);
}
function _burnVotesTokens(address who, uint256 value) internal override {
vm.prank(address(staking));
ghst.burn(who, value);
}
function _getCurrentTotalSupply() internal override view returns (uint256) { function _getCurrentTotalSupply() internal override view returns (uint256) {
return ghst.totalSupply(); return ghst.totalSupply();
} }