-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
330 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
pragma solidity ^0.8.17; | ||
|
||
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; | ||
import {TimeLockTx, IGovernorV3} from "../interfaces/IGovernorV3.sol"; | ||
import {ITimeLock} from "../interfaces/ITimeLock.sol"; | ||
|
||
enum TxAction { | ||
Execute, | ||
Cancel | ||
} | ||
|
||
uint256 constant BATCH_SIZE_BITS = 16; | ||
|
||
contract GovernorV3 is IGovernorV3 { | ||
using EnumerableSet for EnumerableSet.AddressSet; | ||
|
||
address public immutable override timeLock; | ||
|
||
EnumerableSet.AddressSet internal queueAdmins; | ||
|
||
address public override vetoAdmin; | ||
|
||
/// Batches | ||
uint240 public override batchNum; | ||
|
||
mapping(bytes32 => uint256) public override batchedTransactions; | ||
mapping(uint240 => uint256) public override batchedTransactionsCount; | ||
|
||
modifier queueAdminOnly() { | ||
if (!queueAdmins.contains(msg.sender)) revert CallerNotQueueAdminException(); | ||
_; | ||
} | ||
|
||
modifier timeLockOnly() { | ||
if (msg.sender != timeLock) revert CallerNotTimelockException(); | ||
|
||
_; | ||
} | ||
|
||
modifier vetoAdminOnly() { | ||
if (msg.sender != vetoAdmin) revert CallerNotVetoAdminException(); | ||
|
||
_; | ||
} | ||
|
||
constructor(address _timeLock, address _queueAdmin, address _vetoAdmin) { | ||
timeLock = _timeLock; | ||
_addQueueAdmin(_queueAdmin); | ||
_updateVetoAdmin(_vetoAdmin); | ||
} | ||
|
||
// QUEUE | ||
|
||
function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external | ||
override | ||
queueAdminOnly | ||
returns (bytes32) | ||
{ | ||
return ITimeLock(timeLock).queueTransaction(target, value, signature, data, eta); | ||
} | ||
|
||
function sealBatch(TimeLockTx[] calldata txs) external override queueAdminOnly { | ||
uint256 len = txs.length; | ||
if (len >= 2 ** BATCH_SIZE_BITS) revert IncorrectBatchLengthException(); | ||
|
||
++batchNum; | ||
uint240 _batchNum = batchNum; | ||
|
||
uint256 batchShifted = uint256(_batchNum) << BATCH_SIZE_BITS; | ||
|
||
unchecked { | ||
for (uint256 i = 0; i < len; ++i) { | ||
TimeLockTx calldata ttx = txs[i]; | ||
bytes32 txHash = getTxHash(ttx); | ||
|
||
if (batchedTransactions[txHash] != 0) { | ||
revert TxHashCollisionException(ttx.target, ttx.value, ttx.signature, ttx.data, ttx.eta); | ||
} | ||
|
||
batchedTransactions[txHash] = batchShifted + i; | ||
} | ||
} | ||
|
||
batchedTransactionsCount[_batchNum] = len; | ||
|
||
emit SealBatch(batchNum, len); | ||
} | ||
|
||
// EXECUTE | ||
|
||
function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external | ||
payable | ||
override | ||
returns (bytes memory) | ||
{ | ||
return _operation(target, value, signature, data, eta, TxAction.Execute); | ||
} | ||
|
||
function executeBatch(TimeLockTx[] calldata txs) external payable override { | ||
uint240 _batchNum = _batch(txs, TxAction.Execute); | ||
emit ExecuteBatch(_batchNum); | ||
} | ||
|
||
// CANCELLATION | ||
function cancelTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external | ||
override | ||
vetoAdminOnly | ||
{ | ||
_operation(target, value, signature, data, eta, TxAction.Cancel); | ||
} | ||
|
||
function cancelBatch(TimeLockTx[] calldata txs) external override vetoAdminOnly { | ||
uint240 _batchNum = _batch(txs, TxAction.Cancel); | ||
emit CancelBatch(_batchNum); | ||
} | ||
|
||
// INTERNAL | ||
|
||
function _operation( | ||
address target, | ||
uint256 value, | ||
string memory signature, | ||
bytes memory data, | ||
uint256 eta, | ||
TxAction action | ||
) internal returns (bytes memory result) { | ||
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); | ||
if (batchedTransactions[txHash] != 0) revert TxCouldNotBeExecutedOutsideTheBatchException(); | ||
|
||
if (action == TxAction.Execute) { | ||
result = ITimeLock(timeLock).executeTransaction{value: value}(target, value, signature, data, eta); | ||
} else { | ||
ITimeLock(timeLock).cancelTransaction(target, value, signature, data, eta); | ||
} | ||
} | ||
|
||
function _batch(TimeLockTx[] calldata txs, TxAction action) internal override returns (uint240) { | ||
uint256 len = txs.length; | ||
if (len == 0) revert IncorrectBatchLengthException(); | ||
|
||
uint256 batchShifted = batchedTransactions[getTxHash(txs[0])]; | ||
uint240 _batchNum = uint240(batchShifted >> BATCH_SIZE_BITS); | ||
if (_batchNum == 0) revert BatchNotFoundException(); | ||
|
||
if (batchedTransactionsCount[_batchNum] != len) revert IncorrectBatchLengthException(); | ||
|
||
unchecked { | ||
for (uint256 i = 0; i < len; ++i) { | ||
TimeLockTx calldata ttx = txs[i]; | ||
bytes32 txHash = getTxHash(ttx); | ||
|
||
if (batchedTransactions[txHash] != batchShifted + i) { | ||
revert TxIncorrectOrder(ttx.target, ttx.value, ttx.signature, ttx.data, ttx.eta); | ||
} | ||
|
||
if (action == TxAction.Execute) { | ||
ITimeLock(timeLock).executeTransaction{value: ttx.value}( | ||
ttx.target, ttx.value, ttx.signature, ttx.data, ttx.eta | ||
); | ||
} else { | ||
ITimeLock(timeLock).cancelTransaction(ttx.target, ttx.value, ttx.signature, ttx.data, ttx.eta); | ||
} | ||
|
||
delete batchedTransactions[txHash]; | ||
} | ||
} | ||
|
||
delete batchedTransactionsCount[_batchNum]; | ||
|
||
return _batchNum; | ||
} | ||
|
||
// GETTER | ||
|
||
function getTxHash(TimeLockTx calldata ttx) public pure returns (bytes32) { | ||
return keccak256(abi.encode(ttx.target, ttx.value, ttx.signature, ttx.data, ttx.eta)); | ||
} | ||
|
||
/// Setting admins | ||
function addQueueAdmin(address _admin) external override timeLockOnly { | ||
_addQueueAdmin(_admin); | ||
} | ||
|
||
function _addQueueAdmin(address _admin) internal { | ||
if (!queueAdmins.contains(_admin)) { | ||
queueAdmins.add(_admin); | ||
emit AddQueueAdmin(_admin); | ||
} | ||
} | ||
|
||
function removeQueueAdmin(address _admin) external override timeLockOnly { | ||
if (queueAdmins.contains(_admin)) { | ||
if (queueAdmins.length() == 1) revert CantRemoveLastQueueAdminException(); | ||
|
||
queueAdmins.remove(_admin); | ||
emit RemoveQueueAdmin(_admin); | ||
} | ||
} | ||
|
||
function updateVetoAdmin(address _admin) external override timeLockOnly { | ||
_updateVetoAdmin(_admin); | ||
} | ||
|
||
function _updateVetoAdmin(address _vetoAdmin) internal { | ||
vetoAdmin = _vetoAdmin; | ||
emit UpdateVetoAdmin(vetoAdmin); | ||
} | ||
|
||
function claimTimeLockOwnership() external queueAdminOnly { | ||
ITimeLock(timeLock).acceptAdmin(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
pragma solidity ^0.8.17; | ||
|
||
struct TimeLockTx { | ||
address target; | ||
uint256 value; | ||
string signature; | ||
bytes data; | ||
uint256 eta; | ||
} | ||
|
||
interface IGovernorV3Events { | ||
event SealBatch(uint256 indexed batchNum, uint256 length); | ||
|
||
event ExecuteBatch(uint256 indexed batchNum); | ||
|
||
event CancelBatch(uint256 indexed batchNum); | ||
|
||
event AddQueueAdmin(address indexed admin); | ||
|
||
event RemoveQueueAdmin(address indexed admin); | ||
|
||
event UpdateVetoAdmin(address indexed vetoAdmin); | ||
} | ||
|
||
interface IGovernorV3 is IGovernorV3Events { | ||
error CallerNotQueueAdminException(); | ||
|
||
error CallerNotTimelockException(); | ||
|
||
error CallerNotVetoAdminException(); | ||
|
||
error CantRemoveLastQueueAdminException(); | ||
|
||
error TxHashCollisionException(address target, uint256 value, string signature, bytes data, uint256 eta); | ||
|
||
error TxIncorrectOrder(address target, uint256 value, string signature, bytes data, uint256 eta); | ||
|
||
error TxCouldNotBeExecutedOutsideTheBatchException(); | ||
|
||
error IncorrectBatchLengthException(); | ||
|
||
error BatchNotFoundException(); | ||
|
||
function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external | ||
returns (bytes32); | ||
|
||
function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external | ||
payable | ||
returns (bytes memory); | ||
|
||
function sealBatch(TimeLockTx[] calldata txs) external; | ||
|
||
function executeBatch(TimeLockTx[] calldata txs) external payable; | ||
|
||
function cancelBatch(TimeLockTx[] calldata txs) external; | ||
|
||
/// GETTERS | ||
|
||
function getTxHash(TimeLockTx calldata ttx) external pure returns (bytes32); | ||
|
||
function timeLock() external view returns (address); | ||
|
||
function vetoAdmin() external view returns (address); | ||
|
||
function batchNum() external view returns (uint240); | ||
|
||
function batchedTransactions(bytes32) external view returns (uint256); | ||
|
||
function batchedTransactionsCount(uint240) external view returns (uint256); | ||
|
||
// CONFIGURE | ||
|
||
function addQueueAdmin(address _admin) external; | ||
|
||
function removeQueueAdmin(address _admin) external; | ||
|
||
function updateVetoAdmin(address _admin) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
pragma solidity ^0.8.17; | ||
|
||
interface ITimeLock { | ||
function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external | ||
returns (bytes32); | ||
|
||
function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external | ||
payable | ||
returns (bytes memory); | ||
|
||
function cancelTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 eta) | ||
external; | ||
|
||
function acceptAdmin() external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters