Skip to content

Commit

Permalink
feat: integrate timelock
Browse files Browse the repository at this point in the history
  • Loading branch information
sujithsomraaj committed Aug 25, 2023
1 parent 898f3a8 commit 06bd31d
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 32 deletions.
32 changes: 25 additions & 7 deletions src/MultiMessageReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "./interfaces/IBridgeReceiverAdapter.sol";
import "./interfaces/IMultiMessageReceiver.sol";
import "./interfaces/EIP5164/ExecutorAware.sol";
import "./interfaces/IGovernanceTimelock.sol";
import "./interfaces/IGAC.sol";

/// libraries
Expand Down Expand Up @@ -62,6 +63,14 @@ contract MultiMessageReceiver is IMultiMessageReceiver, ExecutorAware, Initializ
_;
}

/// @notice A modifier used for restricting the caller to just the governance timelock contract
modifier onlyGovernanceTimelock() {
if (msg.sender != gac.getGovernanceTimelock()) {
revert Error.CALLER_NOT_GOVERNANCE_TIMELOCK();
}
_;
}

/*/////////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -161,25 +170,30 @@ contract MultiMessageReceiver is IMultiMessageReceiver, ExecutorAware, Initializ
function executeMessage(bytes32 msgId) external {
ExecutionData memory _execData = msgReceived[msgId];

/// @dev validates if msg execution is not beyond expiration
if (block.timestamp > _execData.expiration) {
revert Error.MSG_EXECUTION_PASSED_DEADLINE();
}

/// @dev validates if msgId is already executed
if (isExecuted[msgId]) {
revert Error.MSG_ID_ALREADY_EXECUTED();
}

isExecuted[msgId] = true;

/// @dev validates message quorum
if (messageVotes[msgId] < quorum) {
revert Error.INVALID_QUORUM_FOR_EXECUTION();
}

(bool status,) = _execData.target.call(_execData.callData);

if (!status) {
revert Error.EXECUTION_FAILS_ON_DST();
}
/// @dev queues the action on timelock for execution
IGovernanceTimelock(gac.getGovernanceTimelock()).scheduleTransaction(
_execData.target,
0,
/// NOTE: should we ever send native fees
_execData.callData
);

emit MessageExecuted(msgId, _execData.target, _execData.nonce, _execData.callData);
}
Expand All @@ -188,7 +202,7 @@ contract MultiMessageReceiver is IMultiMessageReceiver, ExecutorAware, Initializ
/// @dev called by admin to update receiver bridge adapters on all other chains
function updateReceiverAdapter(address[] calldata _receiverAdapters, bool[] calldata _operations)
external
onlySelf
onlyGovernanceTimelock
{
uint256 len = _receiverAdapters.length;

Expand All @@ -206,7 +220,7 @@ contract MultiMessageReceiver is IMultiMessageReceiver, ExecutorAware, Initializ
}

/// @notice Update power quorum threshold of message execution.
function updateQuorum(uint64 _quorum) external onlySelf {
function updateQuorum(uint64 _quorum) external onlyGovernanceTimelock {
/// NOTE: should check 2/3 ?
if (_quorum > trustedExecutor.length || _quorum == 0) {
revert Error.INVALID_QUORUM_THRESHOLD();
Expand All @@ -217,6 +231,10 @@ contract MultiMessageReceiver is IMultiMessageReceiver, ExecutorAware, Initializ
emit quorumUpdated(oldValue, _quorum);
}

/*/////////////////////////////////////////////////////////////////
VIEW/READ-ONLY FUNCTIONS
////////////////////////////////////////////////////////////////*/

/// @notice view message info, return (executed, msgPower, delivered adapters)
function getMessageInfo(bytes32 msgId) public view returns (bool, uint256, string[] memory) {
uint256 msgCurrentVotes = messageVotes[msgId];
Expand Down
18 changes: 18 additions & 0 deletions src/controllers/GAC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ contract GAC is IGAC, Ownable {
/// @dev is the timelock to be used by multi-message receiver before execution
uint256 public msgTimelock;

/// @dev is the address to receive value refunds from remoteCall
address public refundAddress;

/// @notice is the MMA Core Contracts on the chain
/// @dev leveraged by bridge adapters for authentication
address public multiMessageSender;

/// @notice is the governance timelock contract on the chain to queue actions
address public governanceTimelock;

/// @dev is the allowed caller for the multi-message sender
address public allowedCaller;

Expand Down Expand Up @@ -122,6 +126,15 @@ contract GAC is IGAC, Ownable {
refundAddress = _refundAddress;
}

/// @inheritdoc IGAC
function setGovernanceTimelock(address _governanceTimelock) external override onlyOwner {
if (_governanceTimelock == address(0)) {
revert Error.ZERO_ADDRESS_INPUT();
}

governanceTimelock = _governanceTimelock;
}

/*///////////////////////////////////////////////////////////////
EXTERNAL VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -174,4 +187,9 @@ contract GAC is IGAC, Ownable {
function getMultiMessageCaller() external view returns (address _mmaCaller) {
_mmaCaller = allowedCaller;
}

/// @inheritdoc IGAC
function getGovernanceTimelock() external view returns (address _governanceTimelock) {
_governanceTimelock = governanceTimelock;
}
}
112 changes: 106 additions & 6 deletions src/controllers/GovernanceTimelock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@
pragma solidity >=0.8.9;

/// interfaces
import {IGovernanceTimelock} from "../interfaces/IGovernanceTimelock.sol";
import "../interfaces/IGovernanceTimelock.sol";

/// libraries
import "../libraries/Error.sol";

contract GovernanceTimelock is IGovernanceTimelock {
/*/////////////////////////////////////////////////////////////////
STATE VARIABLES
////////////////////////////////////////////////////////////////*/
uint256 public constant MINIMUM_DELAY = 2 days;
uint256 public constant MAXIMUM_DELAY = 14 days;

uint256 public txCounter;
uint256 public delay = MINIMUM_DELAY;

/// @dev the admin should be multi-message receiver
address public multiMessageReceiver;

mapping(uint256 txId => ScheduledTransaction) public scheduledTransaction;
mapping(uint256 txId => bool executed) public isExecuted;

/*/////////////////////////////////////////////////////////////////
MODIFIERS
Expand All @@ -21,19 +35,105 @@ contract GovernanceTimelock is IGovernanceTimelock {
_;
}

/// @notice A modifier used for restricting caller to mma receiver contract
modifier onlyMultiMessageReceiver() {
if (msg.sender != multiMessageReceiver) {
revert Error.CALLER_NOT_MULTI_MESSAGE_RECEIVER();
}
_;
}

/*/////////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////////*/

/// @param _multiMessageReceiver is the address of multiMessageReceiver
constructor(address _multiMessageReceiver) {
if (_multiMessageReceiver == address(0)) {
revert Error.ZERO_ADDRESS_INPUT();
}

multiMessageReceiver = _multiMessageReceiver;
}

/*/////////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
////////////////////////////////////////////////////////////////*/

/// @inheritdoc IGovernanceTimelock
function scheduleTransaction(address target, uint256 value, bytes memory data, uint256 eta) external override {}
function scheduleTransaction(address _target, uint256 _value, bytes memory _data)
external
override
onlyMultiMessageReceiver
{
if (_target == address(0)) {
revert Error.INVALID_TARGET();
}

/// increment tx counter
++txCounter;
uint256 eta = block.timestamp + delay;

scheduledTransaction[txCounter] = ScheduledTransaction(_target, _value, _data, eta);

emit TransactionScheduled(txCounter, _target, _value, _data, eta);
}

/// @inheritdoc IGovernanceTimelock
function executeTransaction(address target, uint256 value, bytes memory data, uint256 eta) external override {}
function executeTransaction(uint256 txId) external override {
/// @dev validates the txId
if (txId == 0 || txId > txCounter) {
revert Error.INVALID_TX_ID();
}

/// @dev checks if tx is already executed;
if (isExecuted[txId]) {
revert Error.TX_ALREADY_EXECUTED();
}

ScheduledTransaction memory transaction = scheduledTransaction[txId];

/// @dev checks timelock
if (transaction.eta < block.timestamp) {
revert Error.TX_TIMELOCKED();
}

isExecuted[txId] = true;

(bool status,) = transaction.target.call(transaction.data);

if (!status) {
revert Error.EXECUTION_FAILS_ON_DST();
}

emit TransactionExecuted(txId, transaction.target, transaction.value, transaction.data, transaction.eta);
}

/// @inheritdoc IGovernanceTimelock
function setDelay(uint256 delay) external override onlySelf {}
function setDelay(uint256 _delay) external override onlySelf {
if (delay < MINIMUM_DELAY) {
revert Error.INVALID_DELAY_MIN();
}

if (delay > MAXIMUM_DELAY) {
revert Error.INVALID_DELAY_MAX();
}

uint256 oldDelay = delay;
delay = _delay;

emit DelayUpdated(oldDelay, _delay);
}

/// @inheritdoc IGovernanceTimelock
function setAdmin(address newAdmin) external override onlySelf {}
}
function setAdmin(address _newAdmin) external override onlySelf {
if (_newAdmin == address(0)) {
revert Error.ZERO_TIMELOCK_ADMIN();
}

address oldAdmin = multiMessageReceiver;
multiMessageReceiver = _newAdmin;

emit AdminUpdated(oldAdmin, _newAdmin);
}
}
7 changes: 7 additions & 0 deletions src/interfaces/IGAC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ interface IGAC {
/// @param _refundAddress is the address to receive refunds from MMA sender
function setRefundAddress(address _refundAddress) external;

/// @dev sets the governance timelock address for queuing dst chain actions
/// @param _governanceTimelock is the address of governance timelock
function setGovernanceTimelock(address _governanceTimelock) external;

/*///////////////////////////////////////////////////////////////
EXTERNAL VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -82,4 +86,7 @@ interface IGAC {

/// @dev returns the refund address
function getRefundAddress() external view returns (address _refundAddress);

/// @dev returns the governance timelock address before execution on dst chains
function getGovernanceTimelock() external view returns (address _governanceTimelock);
}
41 changes: 30 additions & 11 deletions src/interfaces/IGovernanceTimelock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,43 @@ pragma solidity >=0.8.9;

/// @dev interface for governance timelock before execution events on dst chain
interface IGovernanceTimelock {
/*/////////////////////////////////////////////////////////////////
STRUCTS
////////////////////////////////////////////////////////////////*/
struct ScheduledTransaction {
address target;
uint256 value;
bytes data;
uint256 eta;
}

/*/////////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////////*/
event TransactionScheduled(uint256 txId, address target, uint256 value, bytes data, uint256 eta);
event TransactionExecuted(uint256 txId, address target, uint256 value, bytes data, uint256 eta);

event DelayUpdated(uint256 oldDelay, uint256 newDelay);
event AdminUpdated(address oldAdmin, address newAdmin);

/*/////////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
////////////////////////////////////////////////////////////////*/

/// @notice Schedules the provided transaction for execution after a specified ETA.
/// @dev this function can only be called by the bridge adapter on the remote chain
/// @param target the contract to call
/// @param value the amount to pass when calling target
/// @param data the abieencoded function selector and arguments data, to execute on target
/// @param eta the time after which this message can be executed. This has to at least be greater than the current blocktime + the pre-configured delay parameter
function scheduleTransaction(address target, uint256 value, bytes memory data, uint256 eta) external;
/// @param _target the contract to call
/// @param _value the amount to pass when calling target
/// @param _data the abieencoded function selector and arguments data, to execute on target
function scheduleTransaction(address _target, uint256 _value, bytes memory _data) external;

/// @notice Executes a previously scheduled transaction if it has reached its ETA.
/// @param target the contract to call
/// @param value the amount to pass when calling target
/// @param data the abiencoded function selector and arguments data, to execute on target
/// @param eta the time after which this message can be executed.
function executeTransaction(address target, uint256 value, bytes memory data, uint256 eta) external;
/// @param _txId is the unqiue identifier of the scheduled transaction
function executeTransaction(uint256 _txId) external;

/// @notice Updates the minimum delay for a transaction before it can be executed.
/// @dev This can only be invoked by through this timelock contract, thus requiring that an update go through the required time delay first.
function setDelay(uint256 delay) external;
function setDelay(uint256 _delay) external;

/// @notice Updates the admin.
/// @dev This can only be invoked by through this timelock contract, thus requiring that an update go through the required time delay first.
Expand Down
28 changes: 28 additions & 0 deletions src/libraries/Error.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,19 @@ library Error {
/// @dev is thrown if message execution fails on the destination chain
error EXECUTION_FAILS_ON_DST();

/// @dev is thrown if caller is not governance timelock contract
error CALLER_NOT_GOVERNANCE_TIMELOCK();

/*/////////////////////////////////////////////////////////////////
ADAPTER ERRORS
////////////////////////////////////////////////////////////////*/

/// @dev is thrown if caller is not multi message sender
error CALLER_NOT_MULTI_MESSAGE_SENDER();

/// @dev is thrown if caller is not multi message receiver
error CALLER_NOT_MULTI_MESSAGE_RECEIVER();

/// @dev is thrown if sender chain is not allowed on reciever adapter
error INVALID_SENDER_CHAIN_ID();

Expand Down Expand Up @@ -128,4 +134,26 @@ library Error {

/// @dev is thrown if contract call is invalid (for axelar)
error NOT_APPROVED_BY_GATEWAY();

/*/////////////////////////////////////////////////////////////////
TIMELOCK ERRORS
////////////////////////////////////////////////////////////////*/

/// @dev is thrown if the delay is less than minimum delay
error INVALID_DELAY_MIN();

/// @dev is thrown if the delay is more than maximum delay
error INVALID_DELAY_MAX();

/// @dev is thrown if the new admin is zero
error ZERO_TIMELOCK_ADMIN();

/// @dev is thrown if tx id is zero (or) invalid
error INVALID_TX_ID();

/// @dev is thrown if tx id is already executed
error TX_ALREADY_EXECUTED();

/// @dev is thrown if timelock period is not over
error TX_TIMELOCKED();
}
Loading

0 comments on commit 06bd31d

Please sign in to comment.