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();
     }
 }
-