Skip to content

Commit

Permalink
feat: new GovernorV3 contract
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmikko committed Oct 11, 2023
1 parent db5d756 commit 613057d
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 15 deletions.
215 changes: 215 additions & 0 deletions contracts/governance/GovernorV3sol
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();
}
}
80 changes: 80 additions & 0 deletions contracts/interfaces/IGovernorV3.sol
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;
}
17 changes: 17 additions & 0 deletions contracts/interfaces/ITimeLock.sol
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;
}
33 changes: 18 additions & 15 deletions contracts/test/helpers/IntegrationTestHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -229,21 +229,24 @@ contract IntegrationTestHelper is TestHelper, BalanceHelper, ConfigManager {
modifier attachAllV3CMTest() {
_attachCore();

address creditManagerAddr;
bool skipTest = false;

address[] memory cms = cr.getCreditManagers();
uint256 len = cms.length;
unchecked {
for (uint256 i = 0; i < len; i++) {
address poolAddr = cr.poolByIndex(i);
if (!_attachPool(poolAddr)) {
console.log("Skipped");
skipTest = true;
break;
} else {}
}
}
// address creditManagerAddr;
// bool skipTest = false;

// address[] memory cms = cr.getCreditManagers();
// uint256 len = cms.length;

// address[] memory pools = cr.getPools();
// unchecked {
// for (uint256 i = 0; i < len; i++) {
// address poolAddr = cr.poolByIndex(i);
// if (!_attachPool(poolAddr)) {
// console.log("Skipped");
// skipTest = true;
// break;
// } else {}
// }
// }
_;
}

function _setupCore() internal {
Expand Down

0 comments on commit 613057d

Please sign in to comment.