diff --git a/scripts/DeployHamzaVault.s.sol b/scripts/DeployHamzaVault.s.sol index fde09a5..7fe618b 100644 --- a/scripts/DeployHamzaVault.s.sol +++ b/scripts/DeployHamzaVault.s.sol @@ -243,7 +243,7 @@ contract DeployHamzaVault is Script { bytes[] memory initActions = prepareBaalInitActions(pauseSharesOnInit, pauseLootOnInit, sharesToMintForSafe, vault); // 8) Summon Baal - newBaalAddr = summoner.summonBaal(initParams, initActions, uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % 100); + newBaalAddr = summoner.summonBaal(initParams, initActions, uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))) % 100); // fetch loot token address (Baal's "loot token") lootTokenAddr = address(Baal(newBaalAddr).lootToken()); diff --git a/test/Voting.t.sol b/test/Voting.t.sol index e55c7d7..1984350 100644 --- a/test/Voting.t.sol +++ b/test/Voting.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; -import "forge-std/Test.sol"; +import "./DeploymentSetup.t.sol"; import "@hamza-escrow/security/HatsSecurityContext.sol"; import "../src/tokens/GovernanceToken.sol"; import "../src/HamzaGovernor.sol"; @@ -11,39 +11,21 @@ import "@openzeppelin/contracts/governance/TimelockController.sol"; import { HamzaGovernor } from "../src/HamzaGovernor.sol"; import { Hats } from "@hats-protocol/Hats.sol"; -//TODO: inherit from DeploymentSetup -contract VotingTest is Test { - HatsSecurityContext securityContext; - GovernanceToken govToken; - HamzaGovernor governor; - TestToken lootToken; - SystemSettings systemSettings; - TimelockController timelock; +contract VotingTest is DeploymentSetup { string proposalDescription = "Test Proposal"; - address admin; address[] voters; address[] targets; uint256[] values; bytes[] calldatas; + address noVotingRightsAddr; - uint256 public adminHatId; - - enum ProposalState { - Pending, - Active, - Canceled, - Defeated, - Succeeded, - Queued, - Expired, - Executed - } - function setUp() public { - address[] memory empty; + function setUp() public override { + // Call DeploymentSetup's setUp function first + super.setUp(); - admin = address(0x12); + // Initialize voters voters.push(address(0x13)); voters.push(address(0x14)); voters.push(address(0x15)); @@ -53,199 +35,151 @@ contract VotingTest is Test { voters.push(address(0x19)); voters.push(address(0x20)); voters.push(address(0x21)); + + // Add an address that won't have voting rights + noVotingRightsAddr = address(0x22); + + // Use the existing securityContext, lootToken, and govToken from DeploymentSetup + HatsSecurityContext securityContextLocal = HatsSecurityContext(hatsCtx); + TestToken lootTokenLocal = TestToken(lootToken); + GovernanceToken govTokenLocal = GovernanceToken(govToken); - //security context - securityContext = createHatsSecurityContext(); - - //create tokens & mint - lootToken = new TestToken("LOOT", "LOOT"); - govToken = new GovernanceToken(securityContext, lootToken, "Hamg", "HAMG"); - - //mint tokens + // Mint loot tokens to voters for(uint256 n=0; n 100 ? quorumVotes - 100 : 0; + + while(votedPower < targetVotes && voterIndex < voters.length) { + vote(voters[voterIndex], proposal, 1); + votedPower += GovernanceToken(govToken).getVotes(voters[voterIndex]); + voterIndex++; + } + + // Roll forward to end voting period + vm.roll(block.number + 50401); + + // Should be defeated due to not meeting quorum + assertEq(uint256(HamzaGovernor(governor).state(proposal)), uint256(ProposalState.Defeated)); + } + + // Test proposal cancellation + function testProposalCancellation() public { + // Create proposal from the first voter + vm.startPrank(voters[0]); + uint256 proposal = HamzaGovernor(governor).propose(targets, values, calldatas, proposalDescription); + vm.stopPrank(); + + vm.roll(block.number + 2); + assertEq(uint256(HamzaGovernor(governor).state(proposal)), uint(ProposalState.Active)); + + // Cancel the proposal + vm.startPrank(voters[0]); + HamzaGovernor(governor).cancel(targets, values, calldatas, keccak256(bytes(proposalDescription))); + vm.stopPrank(); + + // Verify it's canceled + assertEq(uint256(HamzaGovernor(governor).state(proposal)), uint256(ProposalState.Canceled)); + + // Ensure we can't vote on canceled proposals + vm.startPrank(voters[1]); + vm.expectRevert(); + HamzaGovernor(governor).castVote(proposal, 1); + vm.stopPrank(); + } + + // Test that a voter can't vote twice + function testDoubleVotePrevention() public { + uint256 proposal = HamzaGovernor(governor).propose(targets, values, calldatas, proposalDescription); + vm.roll(block.number + 2); + + // First vote for + vote(voters[0], proposal, 1); + + // Try to vote again - should revert + vm.startPrank(voters[0]); + vm.expectRevert(); + HamzaGovernor(governor).castVote(proposal, 0); + vm.stopPrank(); + } + + // Test that an account without voting power can't vote + function testVoteWithoutPower() public { + uint256 proposal = HamzaGovernor(governor).propose(targets, values, calldatas, proposalDescription); + vm.roll(block.number + 2); + + // Try to vote from account with no voting power + vm.startPrank(noVotingRightsAddr); - //the fee has not changed at this point - assertEq(systemSettings.feeBps(), 1); + HamzaGovernor(governor).castVote(proposal, 1); + vm.stopPrank(); + + // Skip ahead + vm.roll(block.number + 50401); + + // If all others haven't voted, proposal should be defeated (zero votes) + assertEq(uint256(HamzaGovernor(governor).state(proposal)), uint256(ProposalState.Defeated)); + } + + // Test vote delegation functionality + function testVoteDelegation() public { + TestToken lootTokenLocal = TestToken(lootToken); + GovernanceToken govTokenLocal = GovernanceToken(govToken); + + // Delegate voter[1]'s votes to voter[0] + vm.startPrank(voters[1]); + govTokenLocal.delegate(voters[0]); + vm.stopPrank(); + + // Check that voter[0] now has their votes plus voter[1]'s votes + assertEq(govTokenLocal.getVotes(voters[0]), 200); // 100 + 100 + assertEq(govTokenLocal.getVotes(voters[1]), 0); // Delegated away + + // Create a proposal and vote with the delegated votes + uint256 proposal = HamzaGovernor(governor).propose(targets, values, calldatas, proposalDescription); + vm.roll(block.number + 2); + + // Only voter[0] votes (with delegated power) + vote(voters[0], proposal, 1); + + // Skip ahead + vm.roll(block.number + 50401); + + // If voter[0] has 200 votes and voted for the proposal, it should pass + assertEq(uint256(HamzaGovernor(governor).state(proposal)), uint256(ProposalState.Succeeded)); + + // Also verify the total voting power of voter[0] + assertEq(govTokenLocal.getVotes(voters[0]), 200); + } + + // Test that proposals can't be executed before the timelock has passed + function testTimelockEnforcement() public { + uint256 proposal = HamzaGovernor(governor).propose(targets, values, calldatas, proposalDescription); + vm.roll(block.number + 2); + + for(uint8 n=0; n