From 62af38c79fbccfe02febd4e73cb02f693d533889 Mon Sep 17 00:00:00 2001 From: Uncle Fatso Date: Thu, 19 Feb 2026 19:04:04 +0300 Subject: [PATCH] ability to release locked funds based on state Signed-off-by: Uncle Fatso --- src/governance/GhostGovernor.sol | 7 ++- test/governance/GhostGovernor.t.sol | 77 ++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/governance/GhostGovernor.sol b/src/governance/GhostGovernor.sol index 0503b71..48a0c27 100644 --- a/src/governance/GhostGovernor.sol +++ b/src/governance/GhostGovernor.sol @@ -73,7 +73,12 @@ contract GhostGovernor is uint256 releaseAmount = lockedAmounts[proposalId]; address proposer = proposalProposer(proposalId); - if (releaseAmount > 0 && clock() > proposalDeadline(proposalId)) { + bytes32 currentProposalState = _encodeStateBitmap(state(proposalId)); + bytes32 activePendingMask = + _encodeStateBitmap(ProposalState.Pending) | + _encodeStateBitmap(ProposalState.Active); + + if (releaseAmount > 0 && (currentProposalState & activePendingMask == 0)) { lockedAmounts[proposalId] = 0; IERC20(address(token())).transfer(proposer, releaseAmount); return releaseAmount; diff --git a/test/governance/GhostGovernor.t.sol b/test/governance/GhostGovernor.t.sol index 610c107..978528f 100644 --- a/test/governance/GhostGovernor.t.sol +++ b/test/governance/GhostGovernor.t.sol @@ -268,13 +268,6 @@ contract GhostGovernorTest is Test { assertEq(ghst.balanceOf(bob), 0); assertEq(ghst.balanceOf(carol), 0); - vm.prank(bob); - assertEq(governor.releaseLocked(proposalId), 0); - assertEq(governor.lockedAmounts(proposalId), 2 * amount); - assertEq(ghst.balanceOf(bob), 0); - - vm.roll(governor.proposalDeadline(proposalId) + 1); - vm.prank(bob); assertEq(governor.releaseLocked(proposalId), 2 * amount); assertEq(governor.lockedAmounts(proposalId), 0); @@ -496,7 +489,6 @@ contract GhostGovernorTest is Test { function test_proposeAutoForVote() public { vm.startPrank(init); ghst.mint(alice, PROPOSAL_THRESHOLD); - ghst.mint(bob, 69 * PROPOSAL_THRESHOLD); vm.stopPrank(); vm.roll(block.number + 1); @@ -514,6 +506,75 @@ contract GhostGovernorTest is Test { 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 (