From 1a411f37a160fbf35d7f27ba5d8a87d87013ec2b Mon Sep 17 00:00:00 2001 From: Cory Dickson <cory.dickson@protonmail.com> Date: Tue, 14 Jan 2025 17:59:18 -0400 Subject: [PATCH] Add quorum call and proposal lifecycle --- src/BaseHook.sol | 4 +-- src/libraries/Hooks.sol | 12 ++++----- test/BaseHook.t.sol | 49 +++++++++++++++++++++++++++++++------ test/mocks/BaseHookMock.sol | 17 +++++++------ test/utils/Deployers.sol | 11 +-------- 5 files changed, 60 insertions(+), 33 deletions(-) diff --git a/src/BaseHook.sol b/src/BaseHook.sol index ebba787..e92b07c 100644 --- a/src/BaseHook.sol +++ b/src/BaseHook.sol @@ -45,12 +45,12 @@ abstract contract BaseHook is IHooks { } /// @inheritdoc IHooks - function beforeQuorumCalculation(address, uint256) external virtual returns (bytes4, uint256) { + function beforeQuorumCalculation(address, uint256) external view virtual returns (bytes4, uint256) { revert HookNotImplemented(); } /// @inheritdoc IHooks - function afterQuorumCalculation(address, uint256) external virtual returns (bytes4, uint256) { + function afterQuorumCalculation(address, uint256) external view virtual returns (bytes4, uint256) { revert HookNotImplemented(); } diff --git a/src/libraries/Hooks.sol b/src/libraries/Hooks.sol index 34ecf75..685f244 100644 --- a/src/libraries/Hooks.sol +++ b/src/libraries/Hooks.sol @@ -190,8 +190,8 @@ library Hooks { bytes memory result = self.staticCallHook(abi.encodeCall(IHooks.beforeQuorumCalculation, (msg.sender, timepoint))); - // A length of 36 bytes is required to return a bytes4 and a 32 byte proposal ID - if (result.length != 36) revert InvalidHookResponse(); + // A length must be greater than 5 bytes to return a bytes4 and a uint256 quorum value + if (result.length < 5) revert InvalidHookResponse(); // Extract the proposal ID from the result returnedQuorum = parseUint256(result); @@ -209,8 +209,8 @@ library Hooks { bytes memory result = self.staticCallHook(abi.encodeCall(IHooks.afterQuorumCalculation, (msg.sender, timepoint))); - // A length of 36 bytes is required to return a bytes4 and a 32 byte proposal ID - if (result.length != 36) revert InvalidHookResponse(); + // A length must be greater than 5 bytes to return a bytes4 and a uint256 quorum value + if (result.length < 5) revert InvalidHookResponse(); // Extract the proposal ID from the result returnedQuorum = parseUint256(result); @@ -276,7 +276,7 @@ library Hooks { ); // A length of 36 bytes is required to return a bytes4 and a 32 byte proposal ID - if (result.length != 36) revert InvalidHookResponse(); + // if (result.length != 36) revert InvalidHookResponse(); // Extract the proposal ID from the result returnedProposalId = parseUint256(result); @@ -298,7 +298,7 @@ library Hooks { ); // A length of 36 bytes is required to return a bytes4 and a 32 byte proposal ID - if (result.length != 36) revert InvalidHookResponse(); + // if (result.length != 36) revert InvalidHookResponse(); // Extract the proposal ID from the result returnedProposalId = parseUint256(result); diff --git a/test/BaseHook.t.sol b/test/BaseHook.t.sol index 8a564bf..983714b 100644 --- a/test/BaseHook.t.sol +++ b/test/BaseHook.t.sol @@ -17,12 +17,10 @@ contract BaseHookTest is Test, Deployers { hook = BaseHookMock( address( uint160( - Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG - | Hooks.BEFORE_QUORUM_CALCULATION_FLAG| Hooks.AFTER_QUORUM_CALCULATION_FLAG - | Hooks.BEFORE_VOTE_FLAG | Hooks.AFTER_VOTE_FLAG - | Hooks.BEFORE_PROPOSE_FLAG | Hooks.AFTER_PROPOSE_FLAG - | Hooks.BEFORE_CANCEL_FLAG | Hooks.AFTER_CANCEL_FLAG - | Hooks.BEFORE_QUEUE_FLAG | Hooks.AFTER_QUEUE_FLAG + Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_QUORUM_CALCULATION_FLAG + | Hooks.AFTER_QUORUM_CALCULATION_FLAG | Hooks.BEFORE_VOTE_FLAG | Hooks.AFTER_VOTE_FLAG + | Hooks.BEFORE_PROPOSE_FLAG | Hooks.AFTER_PROPOSE_FLAG | Hooks.BEFORE_CANCEL_FLAG + | Hooks.AFTER_CANCEL_FLAG | Hooks.BEFORE_QUEUE_FLAG | Hooks.AFTER_QUEUE_FLAG | Hooks.BEFORE_EXECUTE_FLAG | Hooks.AFTER_EXECUTE_FLAG ) ) @@ -31,15 +29,50 @@ contract BaseHookTest is Test, Deployers { deployCodeTo("test/mocks/BaseHookMock.sol:BaseHookMock", abi.encode(governorAddress), address(hook)); hookReverts = BaseHookMockReverts(address(0x1000000000000000000000000000000000003ffF)); - deployCodeTo("test/mocks/BaseHookMock.sol:BaseHookMockReverts", abi.encode(governorAddress), address(hookReverts)); + deployCodeTo( + "test/mocks/BaseHookMock.sol:BaseHookMockReverts", abi.encode(governorAddress), address(hookReverts) + ); } function test_initialize_succeeds() public { - // Deploy governor vm.expectEmit(address(hook)); emit BaseHookMock.BeforeInitialize(); vm.expectEmit(address(hook)); emit BaseHookMock.AfterInitialize(); deployGovernor(address(hook)); } + + function test_quorum_succeeds() public { + deployGovernor(address(hook)); + + vm.expectCall(address(hook), abi.encodeCall(hook.beforeQuorumCalculation, (0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 0))); + vm.expectCall(address(hook), abi.encodeCall(hook.afterQuorumCalculation, (0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 0))); + governor.quorum(0); + } + + function test_propose_succeeds(address _actor) public { + deployGovernor(address(hook)); + address[] memory targets = new address[](1); + targets[0] = address(this); + uint256[] memory values = new uint256[](1); + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = abi.encodeWithSelector(this.test_initialize_succeeds.selector); + + vm.prank(admin); + governor.setProposalThreshold(10); + + // Give actor enough tokens to meet proposal threshold. + vm.prank(minter); + token.mint(_actor, 100); + vm.startPrank(_actor); + token.delegate(_actor); + vm.roll(block.number + 1); + + uint256 proposalId; + vm.expectCall(address(hook), abi.encodeCall( + hook.beforePropose, (0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, targets, values, calldatas, "Test")) + ); + proposalId = governor.propose(targets, values, calldatas, "Test"); + vm.stopPrank(); + } } diff --git a/test/mocks/BaseHookMock.sol b/test/mocks/BaseHookMock.sol index 9e9b9f9..786df4d 100644 --- a/test/mocks/BaseHookMock.sol +++ b/test/mocks/BaseHookMock.sol @@ -8,8 +8,6 @@ import {AgoraGovernor} from "src/AgoraGovernor.sol"; contract BaseHookMock is BaseHook { event BeforeInitialize(); event AfterInitialize(); - event BeforeQuorumCalculation(); - event AfterQuorumCalculation(); event BeforeVote(); event AfterVote(); event BeforePropose(); @@ -35,16 +33,21 @@ contract BaseHookMock is BaseHook { function beforeQuorumCalculation(address, uint256 beforeQuorum) external + view virtual override returns (bytes4, uint256) { - emit BeforeQuorumCalculation(); return (this.beforeQuorumCalculation.selector, beforeQuorum); } - function afterQuorumCalculation(address, uint256 afterQuorum) external virtual override returns (bytes4, uint256) { - emit AfterQuorumCalculation(); + function afterQuorumCalculation(address, uint256 afterQuorum) + external + view + virtual + override + returns (bytes4, uint256) + { return (this.afterQuorumCalculation.selector, afterQuorum); } @@ -75,7 +78,7 @@ contract BaseHookMock is BaseHook { returns (bytes4, uint256) { emit BeforePropose(); - return (this.beforePropose.selector, 0); + return (this.beforePropose.selector, 1); } function afterPropose(address, uint256, address[] memory, uint256[] memory, bytes[] memory, string memory) @@ -85,7 +88,7 @@ contract BaseHookMock is BaseHook { returns (bytes4, uint256) { emit AfterPropose(); - return (this.afterPropose.selector, 0); + return (this.afterPropose.selector, 1); } function beforeCancel(address, address[] memory, uint256[] memory, bytes[] memory, bytes32) diff --git a/test/utils/Deployers.sol b/test/utils/Deployers.sol index 59dbdac..e7a94ab 100644 --- a/test/utils/Deployers.sol +++ b/test/utils/Deployers.sol @@ -49,18 +49,9 @@ contract Deployers is Test { // Deploy governor governor = new AgoraGovernorMock( - votingDelay, - votingPeriod, - proposalThreshold, - quorumNumerator, - token, - timelock, - admin, - manager, - IHooks(hook) + votingDelay, votingPeriod, proposalThreshold, quorumNumerator, token, timelock, admin, manager, IHooks(hook) ); vm.stopPrank(); } } -