Skip to content

Commit

Permalink
feat: simple executor contract and governorExecutor extension
Browse files Browse the repository at this point in the history
Still possible to simplify governor base contract for execute set of functions
Deployment cost down by 50% compared to timelock
#11
  • Loading branch information
DuBento committed Jul 30, 2023
1 parent cfc7093 commit f175964
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 82 deletions.
162 changes: 162 additions & 0 deletions blockchain/contracts/DAO/Executor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: Apache-2.0
// Based on Openzeppelin Contracts (last updated v4.9.0)

pragma solidity ^0.8.19;

import "../custom/Ownable.sol";
import "../OpenZeppelin/utils/Address.sol";

contract Executor is Ownable {
// Type declarations
// State variables
bool private locked;

// Events

/**
* @dev Emitted when a call is performed as part of operation `id`.
*/
event CallExecuted(
bytes32 indexed id,
uint256 indexed index,
address target,
uint256 value,
bytes data
);

// Errors
/**
* @dev Prevent reentrant calls.
*/
error ReentrantCall();

/**
* @dev Mismatch between the parameters length for an operation call.
*/
error ExecutorInvalidOperationLength(
uint256 targets,
uint256 payloads,
uint256 values
);

// Modifiers
modifier nonReentrant() {
if (locked) revert ReentrantCall();
locked = true;
_;
locked = false;
}

// Functions
//* constructor
//* receive function
//* fallback function (if exists)
//* external
//* public
/**
* @dev Execute an (ready) operation containing a single transaction.
*
* Emits a {CallExecuted} event.
*
* Only owner can execute, usually Governor Contract.
*/
function execute(
address target,
uint256 value,
bytes calldata payload,
bytes32 predecessor,
bytes32 salt
) public payable virtual nonReentrant onlyOwner {
bytes32 id = hashOperation(target, value, payload, predecessor, salt);

_execute(target, value, payload);
emit CallExecuted(id, 0, target, value, payload);
}

/**
* @dev Execute an (ready) operation containing a batch of transactions.
*
* Emits one {CallExecuted} event per transaction in the batch.
*
* Only owner can execute, usually Governor Contract.
*/
function executeBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public payable virtual nonReentrant onlyOwner {
if (
targets.length != values.length || targets.length != payloads.length
) {
revert ExecutorInvalidOperationLength(
targets.length,
payloads.length,
values.length
);
}

bytes32 id = hashOperationBatch(
targets,
values,
payloads,
predecessor,
salt
);

for (uint256 i = 0; i < targets.length; ++i) {
address target = targets[i];
uint256 value = values[i];
bytes calldata payload = payloads[i];
_execute(target, value, payload);
emit CallExecuted(id, i, target, value, payload);
}
}

/**
* @dev Returns the identifier of an operation containing a single
* transaction.
*/
function hashOperation(
address target,
uint256 value,
bytes calldata data,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return keccak256(abi.encode(target, value, data, predecessor, salt));
}

/**
* @dev Returns the identifier of an operation containing a batch of
* transactions.
*/
function hashOperationBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata payloads,
bytes32 predecessor,
bytes32 salt
) public pure virtual returns (bytes32) {
return
keccak256(abi.encode(targets, values, payloads, predecessor, salt));
}

//* internal
/**
* @dev Execute an operation's call.
*/
function _execute(
address target,
uint256 value,
bytes calldata data
) internal virtual {
(bool success, bytes memory returndata) = target.call{value: value}(
data
);
Address.verifyCallResult(success, returndata);
}
//* private
//* asserts
}
37 changes: 30 additions & 7 deletions blockchain/contracts/DAO/GovernorContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "./governance/extensions/GovernorCountingSimple.sol";
import "./governance/extensions/GovernorVotes.sol";
import "./governance/extensions/GovernorVotesQuorumFraction.sol";
import "./governance/extensions/GovernorSettings.sol";
import "./governance/extensions/GovernorExecutor.sol";
import "./governance/Governor.sol";

contract GovernorContract is
Expand All @@ -13,17 +14,20 @@ contract GovernorContract is
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorExecutor
{
constructor(
IVotes _token,
uint256 _votingDelay,
uint256 _votingPeriod,
uint256 _quorumFraction
IVotes token_,
Executor executor_,
uint256 votingDelay_,
uint256 votingPeriod_,
uint256 quorumFraction_
)
Governor("GovernorContract")
GovernorSettings(_votingDelay, _votingPeriod, 0)
GovernorVotes(_token)
GovernorVotesQuorumFraction(_quorumFraction)
GovernorSettings(votingDelay_, votingPeriod_, 0)
GovernorVotes(token_)
GovernorVotesQuorumFraction(quorumFraction_)
GovernorExecutor(executor_)
{}

// The following functions are overrides required by Solidity.
Expand Down Expand Up @@ -65,4 +69,23 @@ contract GovernorContract is
{
return super.proposalThreshold();
}

function _execute(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorExecutor) {
super._execute(proposalId, targets, values, calldatas, descriptionHash);
}

function _executor()
internal
view
override(Governor, GovernorExecutor)
returns (address)
{
return super._executor();
}
}
95 changes: 42 additions & 53 deletions blockchain/contracts/DAO/governance/Governor.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.1) (governance/Governor.sol)
// SPDX-License-Identifier: Apache-2.0
// Based on Openzeppelin Contracts (last updated v4.9.1)

pragma solidity ^0.8.19;

Expand All @@ -12,6 +12,7 @@ import "../../OpenZeppelin/utils/math/SafeCast.sol";
import "../../OpenZeppelin/utils/Address.sol";
import "../../OpenZeppelin/utils/Context.sol";
import "./IGovernor.sol";

/**
* @dev Core of the governance system, designed to be extended though various modules.
*
Expand All @@ -21,7 +22,7 @@ import "./IGovernor.sol";
* - A voting module must implement {_getVotes}
* - Additionally, {votingPeriod} must also be implemented
*
* _Available since v4.3._
* Modified version of governance contracts from OpenZeppelin v4.9.1
*/
abstract contract Governor is
Context,
Expand All @@ -31,8 +32,6 @@ abstract contract Governor is
IERC721Receiver,
IERC1155Receiver
{
using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque;

bytes32 public constant BALLOT_TYPEHASH =
keccak256("Ballot(uint256 proposalId,uint8 support)");
bytes32 public constant EXTENDED_BALLOT_TYPEHASH =
Expand Down Expand Up @@ -62,12 +61,6 @@ abstract contract Governor is
/// @custom:oz-retyped-from mapping(uint256 => Governor.ProposalCore)
mapping(uint256 => ProposalCore) private _proposals;

// This queue keeps track of the governor operating on itself. Calls to functions protected by the
// {onlyGovernance} modifier needs to be whitelisted in this queue. Whitelisting is set in {_beforeExecute},
// consumed by the {onlyGovernance} modifier and eventually reset in {_afterExecute}. This ensures that the
// execution of {onlyGovernance} protected calls can only be achieved through successful proposals.
DoubleEndedQueue.Bytes32Deque private _governanceCall;

/**
* @dev Restricts a function so it can only be executed through governance proposals. For example, governance
* parameter setters in {GovernorSettings} are protected using this modifier.
Expand All @@ -82,11 +75,6 @@ abstract contract Governor is
if (_executor() != _msgSender()) {
revert GovernorOnlyExecutor(_msgSender());
}
if (_executor() != address(this)) {
bytes32 msgDataHash = keccak256(_msgData());
// loop until popping the expected operation - throw if deque is empty (operation not authorized)
while (_governanceCall.popFront() != msgDataHash) {}
}
_;
}

Expand Down Expand Up @@ -418,9 +406,7 @@ abstract contract Governor is

emit ProposalExecuted(proposalId);

_beforeExecute(proposalId, targets, values, calldatas, descriptionHash);
_execute(proposalId, targets, values, calldatas, descriptionHash);
_afterExecute(proposalId, targets, values, calldatas, descriptionHash);

return proposalId;
}
Expand Down Expand Up @@ -472,41 +458,43 @@ abstract contract Governor is
}
}

/**
* @dev Hook before execution is triggered.
*/
function _beforeExecute(
uint256 /* proposalId */,
address[] memory targets,
uint256[] memory /* values */,
bytes[] memory calldatas,
bytes32 /*descriptionHash*/
) internal virtual {
if (_executor() != address(this)) {
for (uint256 i = 0; i < targets.length; ++i) {
if (targets[i] == address(this)) {
_governanceCall.pushBack(keccak256(calldatas[i]));
}
}
}
}

/**
* @dev Hook after execution is triggered.
*/
function _afterExecute(
uint256 /* proposalId */,
address[] memory /* targets */,
uint256[] memory /* values */,
bytes[] memory /* calldatas */,
bytes32 /*descriptionHash*/
) internal virtual {
if (_executor() != address(this)) {
if (!_governanceCall.empty()) {
_governanceCall.clear();
}
}
}
// TODO remove

// /**
// * @dev Hook before execution is triggered.
// */
// function _beforeExecute(
// uint256 /* proposalId */,
// address[] memory targets,
// uint256[] memory /* values */,
// bytes[] memory calldatas,
// bytes32 /*descriptionHash*/
// ) internal virtual {
// if (_executor() != address(this)) {
// for (uint256 i = 0; i < targets.length; ++i) {
// if (targets[i] == address(this)) {
// _governanceCall.pushBack(keccak256(calldatas[i]));
// }
// }
// }
// }

// /**
// * @dev Hook after execution is triggered.
// */
// function _afterExecute(
// uint256 /* proposalId */,
// address[] memory /* targets */,
// uint256[] memory /* values */,
// bytes[] memory /* calldatas */,
// bytes32 /*descriptionHash*/
// ) internal virtual {
// if (_executor() != address(this)) {
// if (!_governanceCall.empty()) {
// _governanceCall.clear();
// }
// }
// }

/**
* @dev Internal cancel mechanism: locks up the proposal timer, preventing it from being re-submitted. Marks it as
Expand Down Expand Up @@ -731,6 +719,7 @@ abstract contract Governor is
Address.verifyCallResult(success, returndata);
}

// TODO remove
/**
* @dev Address through which the governor executes action. Will be overloaded by module that execute actions
* through another contract such as a timelock.
Expand Down
Loading

0 comments on commit f175964

Please sign in to comment.