Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add before and after queue hook calls #72

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/AgoraGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ contract AgoraGovernor is
GovernorSettings(_votingDelay, _votingPeriod, _proposalThreshold)
GovernorTimelockControl(_timelock)
{
if (!_hooks.isValidHookAddress()) revert Hooks.HookAddressNotValid(address(_hooks));
if (!_hooks.isValidHookAddress()) {
revert Hooks.HookAddressNotValid(address(_hooks));
}

// This call is made after the inhereted constructors have been called
_hooks.beforeInitialize();
Expand Down Expand Up @@ -119,6 +121,24 @@ contract AgoraGovernor is
return beforeProposalId != 0 ? beforeProposalId : proposalId;
}

function queue(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash)
public
virtual
override
returns (uint256)
{
uint256 beforeProposalId = hooks.beforeQueue(targets, values, calldatas, descriptionHash);

uint256 proposalId = super.queue(targets, values, calldatas, descriptionHash);

uint256 afterProposalId = hooks.afterQueue(proposalId, targets, values, calldatas, descriptionHash);

// TODO: check that before and after proposal IDs are the same

// TODO: replace this with a flag on hook address
return beforeProposalId != 0 ? beforeProposalId : proposalId;
}

function execute(
address[] memory targets,
uint256[] memory values,
Expand Down
19 changes: 19 additions & 0 deletions src/interfaces/IHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ interface IHooks {
bytes32 descriptionHash
) external returns (bytes4, uint256);

/// @notice The hook called before a proposal is queued
function beforeQueue(
address sender,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) external returns (bytes4, uint256);

/// @notice The hook called after a proposal is queued
function afterQueue(
address sender,
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) external returns (bytes4, uint256);

/// @notice The hook called before a proposal is executed
function beforeExecute(
address sender,
Expand Down
72 changes: 61 additions & 11 deletions src/libraries/Hooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,25 @@ import {IHooks} from "src/interfaces/IHooks.sol";
library Hooks {
using Hooks for IHooks;

uint160 internal constant ALL_HOOK_MASK = uint160((1 << 12) - 1);
uint160 internal constant ALL_HOOK_MASK = uint160((1 << 14) - 1);

uint160 internal constant BEFORE_INITIALIZE_FLAG = 1 << 11;
uint160 internal constant AFTER_INITIALIZE_FLAG = 1 << 10;
uint160 internal constant BEFORE_INITIALIZE_FLAG = 1 << 13;
uint160 internal constant AFTER_INITIALIZE_FLAG = 1 << 12;

uint160 internal constant BEFORE_QUORUM_CALCULATION_FLAG = 1 << 9;
uint160 internal constant AFTER_QUORUM_CALCULATION_FLAG = 1 << 8;
uint160 internal constant BEFORE_QUORUM_CALCULATION_FLAG = 1 << 11;
uint160 internal constant AFTER_QUORUM_CALCULATION_FLAG = 1 << 10;

uint160 internal constant BEFORE_VOTE_FLAG = 1 << 7;
uint160 internal constant AFTER_VOTE_FLAG = 1 << 6;
uint160 internal constant BEFORE_VOTE_FLAG = 1 << 9;
uint160 internal constant AFTER_VOTE_FLAG = 1 << 8;

uint160 internal constant BEFORE_PROPOSE_FLAG = 1 << 5;
uint160 internal constant AFTER_PROPOSE_FLAG = 1 << 4;
uint160 internal constant BEFORE_PROPOSE_FLAG = 1 << 7;
uint160 internal constant AFTER_PROPOSE_FLAG = 1 << 6;

uint160 internal constant BEFORE_CANCEL_FLAG = 1 << 3;
uint160 internal constant AFTER_CANCEL_FLAG = 1 << 2;
uint160 internal constant BEFORE_CANCEL_FLAG = 1 << 5;
uint160 internal constant AFTER_CANCEL_FLAG = 1 << 4;

uint160 internal constant BEFORE_QUEUE_FLAG = 1 << 3;
uint160 internal constant AFTER_QUEUE_FLAG = 1 << 2;

uint160 internal constant BEFORE_EXECUTE_FLAG = 1 << 1;
uint160 internal constant AFTER_EXECUTE_FLAG = 1 << 0;
Expand All @@ -38,6 +41,8 @@ library Hooks {
bool afterPropose;
bool beforeCancel;
bool afterCancel;
bool beforeQueue;
bool afterQueue;
bool beforeExecute;
bool afterExecute;
}
Expand Down Expand Up @@ -68,6 +73,8 @@ library Hooks {
|| permissions.afterPropose != self.hasPermission(AFTER_PROPOSE_FLAG)
|| permissions.beforeCancel != self.hasPermission(BEFORE_CANCEL_FLAG)
|| permissions.afterCancel != self.hasPermission(AFTER_CANCEL_FLAG)
|| permissions.beforeQueue != self.hasPermission(BEFORE_QUEUE_FLAG)
|| permissions.afterQueue != self.hasPermission(AFTER_QUEUE_FLAG)
|| permissions.beforeExecute != self.hasPermission(BEFORE_EXECUTE_FLAG)
|| permissions.afterExecute != self.hasPermission(AFTER_EXECUTE_FLAG)
) {
Expand Down Expand Up @@ -298,6 +305,49 @@ library Hooks {
}
}

/// @notice calls beforeQueue hook if permissioned and validates return value
function beforeQueue(
IHooks self,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal noSelfCall(self) returns (uint256 returnedProposalId) {
if (self.hasPermission(BEFORE_QUEUE_FLAG)) {
bytes memory result = self.callHook(
abi.encodeCall(IHooks.beforeQueue, (msg.sender, targets, values, calldatas, descriptionHash))
);

// A length of 36 bytes is required to return a bytes4 and a 32 byte proposal ID
if (result.length != 36) revert InvalidHookResponse();

// Extract the proposal ID from the result
returnedProposalId = parseUint256(result);
}
}

/// @notice calls afterQueue hook if permissioned and validates return value
function afterQueue(
IHooks self,
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal noSelfCall(self) returns (uint256 returnedProposalId) {
if (self.hasPermission(AFTER_QUEUE_FLAG)) {
bytes memory result = self.callHook(
abi.encodeCall(IHooks.afterQueue, (msg.sender, proposalId, targets, values, calldatas, descriptionHash))
);

// A length of 36 bytes is required to return a bytes4 and a 32 byte proposal ID
if (result.length != 36) revert InvalidHookResponse();

// Extract the proposal ID from the result
returnedProposalId = parseUint256(result);
}
}

/// @notice calls beforeCancel hook if permissioned and validates return value
function beforeCancel(
IHooks self,
Expand Down
20 changes: 20 additions & 0 deletions test/mocks/MockHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ contract MockHooks is IHooks {
return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0);
}

function beforeQueue(address, address[] memory, uint256[] memory, bytes[] memory, bytes32)
external
override
returns (bytes4, uint256)
{
// beforeQueueData = hookData;
bytes4 selector = MockHooks.beforeQueue.selector;
return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0);
}

function afterQueue(address, uint256, address[] memory, uint256[] memory, bytes[] memory, bytes32)
external
override
returns (bytes4, uint256)
{
// afterQueueData = hookData;
bytes4 selector = MockHooks.afterQueue.selector;
return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0);
}

function beforeExecute(address, address[] memory, uint256[] memory, bytes[] memory, bytes32)
external
override
Expand Down
Loading