Compare commits
No commits in common. "59a13fec9d0b1047959c91e5ec4a8e61d0b45d89" and "635853f170088bf4f84deda75512581ec1e081ff" have entirely different histories.
59a13fec9d
...
635853f170
15
deployer.sh
15
deployer.sh
@ -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=12
|
END=13
|
||||||
VERIFY_NEEDED="--verify"
|
VERIFY_NEEDED="--verify"
|
||||||
LEGACY_FLAGS=""
|
LEGACY_FLAGS=""
|
||||||
|
|
||||||
@ -25,9 +25,10 @@ usage() {
|
|||||||
echo "7) BondDepository"
|
echo "7) BondDepository"
|
||||||
echo "8) StakingDistributor"
|
echo "8) StakingDistributor"
|
||||||
echo "9) BondingCalculator"
|
echo "9) BondingCalculator"
|
||||||
echo "10) AfterPartyFirst"
|
echo "10) Governor"
|
||||||
echo "11) AfterPartySecond"
|
echo "11) AfterPartyFirst"
|
||||||
echo "12) AfterPartyThird"
|
echo "12) AfterPartySecond"
|
||||||
|
echo "13) AfterPartyThird"
|
||||||
echo "69) Gatekeeper"
|
echo "69) Gatekeeper"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@ -110,10 +111,12 @@ 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="AfterPartyFirst"
|
CONTRACT="Governor"
|
||||||
elif [ "11" == "$i" ]; then
|
elif [ "11" == "$i" ]; then
|
||||||
CONTRACT="AfterPartySecond"
|
CONTRACT="AfterPartyFirst"
|
||||||
elif [ "12" == "$i" ]; then
|
elif [ "12" == "$i" ]; then
|
||||||
|
CONTRACT="AfterPartySecond"
|
||||||
|
elif [ "13" == "$i" ]; then
|
||||||
CONTRACT="AfterPartyThird"
|
CONTRACT="AfterPartyThird"
|
||||||
elif [ "69" == "$i" ]; then
|
elif [ "69" == "$i" ]; then
|
||||||
CONTRACT="Gatekeeper"
|
CONTRACT="Gatekeeper"
|
||||||
|
|||||||
@ -109,12 +109,14 @@ 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 {
|
function ghost(bytes32 receiver, uint256 amount) external override returns (uint256) {
|
||||||
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(
|
||||||
|
|||||||
@ -2,17 +2,25 @@
|
|||||||
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 {
|
contract Ghost is IGHST, ERC20, ERC20Permit, ERC20Votes {
|
||||||
address public override staking;
|
address public override staking;
|
||||||
address public override stnk;
|
address public override stnk;
|
||||||
address private _initializer;
|
address private _initializer;
|
||||||
|
|
||||||
constructor(address _stnk, string memory name, string memory symbol) ERC20(name, symbol) {
|
error GhostDelegationIsBuiltIn();
|
||||||
|
|
||||||
|
constructor(address _stnk, string memory name, string memory symbol)
|
||||||
|
ERC20(name, symbol)
|
||||||
|
ERC20Permit(name)
|
||||||
|
{
|
||||||
stnk = _stnk;
|
stnk = _stnk;
|
||||||
_initializer = msg.sender;
|
_initializer = msg.sender;
|
||||||
}
|
}
|
||||||
@ -33,6 +41,21 @@ contract Ghost is IGHST, ERC20 {
|
|||||||
_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();
|
||||||
}
|
}
|
||||||
@ -44,4 +67,16 @@ contract Ghost is IGHST, ERC20 {
|
|||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ 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);
|
||||||
@ -28,6 +29,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;
|
function ghost(bytes32 receiver, uint256 amount) external returns (uint256);
|
||||||
function initialize(address previousAddress) external;
|
function initialize(address previousAddress) external;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,6 @@
|
|||||||
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);
|
||||||
|
|||||||
@ -14,8 +14,8 @@ contract WeaverMock is Weaver {
|
|||||||
return _previousAddress;
|
return _previousAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertTreeNode(bytes32 receiver, uint256 amount) external {
|
function insertTreeNode(bytes32 receiver, uint256 amount) external returns (uint256) {
|
||||||
_insertTreeNode(receiver, amount);
|
return _insertTreeNode(receiver, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeArgumentsHash(
|
function computeArgumentsHash(
|
||||||
|
|||||||
@ -134,10 +134,9 @@ abstract contract Weaver is IWeaver {
|
|||||||
return proof;
|
return proof;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _insertTreeNode(bytes32 who, uint256 amount) internal {
|
function _insertTreeNode(bytes32 who, uint256 amount) internal returns (uint256 globalIndex) {
|
||||||
uint256 session = currentWeavingSession;
|
uint256 session = currentWeavingSession;
|
||||||
uint256 globalIndex = _filledEntries[session];
|
globalIndex = _filledEntries[session];
|
||||||
|
|
||||||
if (globalIndex >= ENTRIES) {
|
if (globalIndex >= ENTRIES) {
|
||||||
unchecked { ++session; }
|
unchecked { ++session; }
|
||||||
currentWeavingSession = session;
|
currentWeavingSession = session;
|
||||||
@ -159,8 +158,6 @@ 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) {
|
||||||
|
|||||||
@ -203,7 +203,7 @@ contract GatekeeperTest is Test {
|
|||||||
Gatekeeper gatekeeper;
|
Gatekeeper gatekeeper;
|
||||||
MockStaking staking;
|
MockStaking staking;
|
||||||
|
|
||||||
event ThreadInserted(uint256 index, uint256 session);
|
event Ghosted(bytes32 indexed receiver, uint256 indexed amount);
|
||||||
|
|
||||||
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 ThreadInserted(0, 0);
|
emit Ghosted(receiver, ghostAmount);
|
||||||
vm.prank(ALICE);
|
vm.prank(ALICE);
|
||||||
staking.runGhost(receiver, ghostAmount);
|
staking.runGhost(receiver, ghostAmount);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
pragma solidity 0.8.20;
|
pragma solidity 0.8.20;
|
||||||
|
|
||||||
import {Test, Vm} from "forge-std/Test.sol";
|
import {Test} 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 {
|
function ghost(bytes32 receiver, uint256 amount) external returns (uint256) {
|
||||||
require(msg.sender == governor);
|
require(msg.sender == governor);
|
||||||
gatekeeper.ghost(receiver, amount);
|
return 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, currentWeavingSession) = _insertWithAssert(amounts[i], whos[i]);
|
globalIndex = _insertWithAssert(currentWeavingSession, 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, newWeavingSession) = _insertWithAssert(amounts[69], whos[69]);
|
globalIndex = _insertWithAssert(currentWeavingSession, amounts[69], whos[69]);
|
||||||
assertEq(currentWeavingSession + 1, newWeavingSession);
|
uint256 newSession = gatekeeper.currentWeavingSession();
|
||||||
|
|
||||||
|
assertEq(currentWeavingSession + 1, newSession);
|
||||||
vm.roll(block.number + 1337);
|
vm.roll(block.number + 1337);
|
||||||
|
|
||||||
assertTrue(_verifyProof(globalIndex, newWeavingSession, block.number, amounts[69], whos[69]));
|
assertTrue(_verifyProof(globalIndex, newSession, 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, newWeavingSession, 100, amounts[69], whos[69]));
|
assertFalse(_verifyProof(globalIndex, newSession, 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, newWeavingSession, block.number, amounts[69], whos[69]));
|
assertTrue(_verifyProof(globalIndex, newSession, 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, newWeavingSession, 100, amounts[69], whos[69]));
|
assertFalse(_verifyProof(globalIndex, newSession, 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,43 +212,41 @@ contract GatekeeperWeaverTest is Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _insertWithAssert(
|
function _insertWithAssert(
|
||||||
|
uint256 session,
|
||||||
uint256 amount,
|
uint256 amount,
|
||||||
bytes32 who
|
bytes32 who
|
||||||
) private returns (uint256 globalIndex, uint256 emittedSession) {
|
) private returns (uint256 globalIndex) {
|
||||||
uint256 preSession = gatekeeper.currentWeavingSession();
|
uint256 targetSlot = gatekeeper.filledEntries(session);
|
||||||
|
bytes32 previousHash = gatekeeper.treeNodesLatest(session, targetSlot);
|
||||||
uint256 targetSlot = gatekeeper.filledEntries(preSession);
|
uint256 prevEntries = gatekeeper.filledEntries(session);
|
||||||
bytes32 previousHash = gatekeeper.treeNodesLatest(preSession, targetSlot);
|
uint160 prevLength = gatekeeper.slotLengths(session, targetSlot);
|
||||||
uint256 prevEntries = gatekeeper.filledEntries(preSession);
|
|
||||||
uint160 prevLength = gatekeeper.slotLengths(preSession, targetSlot);
|
|
||||||
|
|
||||||
vm.prank(ALICE);
|
vm.prank(ALICE);
|
||||||
staking.ghost(who, amount);
|
globalIndex = 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(emittedSession, 0), 1);
|
assertEq(gatekeeper.slotLengths(session + 1, 0), 1);
|
||||||
assertEq(prevEntries, gatekeeper.ENTRIES());
|
assertEq(prevEntries, gatekeeper.ENTRIES());
|
||||||
assertEq(gatekeeper.filledEntries(emittedSession), 1);
|
assertEq(gatekeeper.filledEntries(session + 1), 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(emittedSession, targetSlot));
|
assertEq(prevLength + 1, gatekeeper.slotLengths(session, targetSlot));
|
||||||
assertEq(prevEntries + 1, gatekeeper.filledEntries(emittedSession));
|
assertEq(prevEntries + 1, gatekeeper.filledEntries(session));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
bytes32 preimage1 = gatekeeper.slotValues(emittedSession, globalIndex);
|
bytes32 preimage1 = gatekeeper.slotValues(session, 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(emittedSession, globalIndex);
|
bytes32 nodeHash1 = gatekeeper.treeNodesLatest(session, globalIndex);
|
||||||
bytes32 preimageHash = Hashes.efficientKeccak256(
|
bytes32 preimageHash = Hashes.efficientKeccak256(
|
||||||
gatekeeper.computePreimage(globalIndex, amount, who)
|
gatekeeper.computePreimage(globalIndex, amount, who)
|
||||||
);
|
);
|
||||||
@ -270,17 +268,4 @@ 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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
647
test/governance/GhostGovernor.t.sol
Normal file
647
test/governance/GhostGovernor.t.sol
Normal file
@ -0,0 +1,647 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -86,6 +86,7 @@ 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);
|
||||||
@ -639,6 +640,23 @@ 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));
|
||||||
@ -660,6 +678,9 @@ 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;
|
||||||
|
|||||||
@ -7,10 +7,18 @@ 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 Test, ERC20AllowanceTest, ERC20TransferTest {
|
contract GhostTest is
|
||||||
|
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;
|
||||||
@ -51,8 +59,10 @@ contract GhostTest is Test, ERC20AllowanceTest, ERC20TransferTest {
|
|||||||
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 {
|
||||||
@ -147,6 +157,19 @@ contract GhostTest is Test, ERC20AllowanceTest, ERC20TransferTest {
|
|||||||
_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();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user