// Source: contracts/governance/AxelarServiceGovernance.sol pragma solidity ^0.8.0; // SPDX-License-Identifier: MIT // File contracts/interfaces/IAxelarGateway.sol /** * @title IAxelarGateway * @dev Interface for the Axelar Gateway that supports general message passing and contract call execution. */ interface IAxelarGateway { /** * @notice Emitted when a contract call is made through the gateway. * @dev Logs the attempt to call a contract on another chain. * @param sender The address of the sender who initiated the contract call. * @param destinationChain The name of the destination chain. * @param destinationContractAddress The address of the contract on the destination chain. * @param payloadHash The keccak256 hash of the sent payload data. * @param payload The payload data used for the contract call. */ event ContractCall( address indexed sender, string destinationChain, string destinationContractAddress, bytes32 indexed payloadHash, bytes payload ); /** * @notice Sends a contract call to another chain. * @dev Initiates a cross-chain contract call through the gateway to the specified destination chain and contract. * @param destinationChain The name of the destination chain. * @param contractAddress The address of the contract on the destination chain. * @param payload The payload data to be used in the contract call. */ function callContract( string calldata destinationChain, string calldata contractAddress, bytes calldata payload ) external; /** * @notice Checks if a contract call is approved. * @dev Determines whether a given contract call, identified by the commandId and payloadHash, is approved. * @param commandId The identifier of the command to check. * @param sourceChain The name of the source chain. * @param sourceAddress The address of the sender on the source chain. * @param contractAddress The address of the contract where the call will be executed. * @param payloadHash The keccak256 hash of the payload data. * @return True if the contract call is approved, false otherwise. */ function isContractCallApproved( bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, address contractAddress, bytes32 payloadHash ) external view returns (bool); /** * @notice Validates and approves a contract call. * @dev Validates the given contract call information and marks it as approved if valid. * @param commandId The identifier of the command to validate. * @param sourceChain The name of the source chain. * @param sourceAddress The address of the sender on the source chain. * @param payloadHash The keccak256 hash of the payload data. * @return True if the contract call is validated and approved, false otherwise. */ function validateContractCall( bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes32 payloadHash ) external returns (bool); /** * @notice Checks if a command has been executed. * @dev Determines whether a command, identified by the commandId, has been executed. * @param commandId The identifier of the command to check. * @return True if the command has been executed, false otherwise. */ function isCommandExecuted(bytes32 commandId) external view returns (bool); } // File contracts/interfaces/IAxelarExecutable.sol /** * @title IAxelarExecutable * @dev Interface for a contract that is executable by Axelar Gateway's cross-chain message passing. * It defines a standard interface to execute commands sent from another chain. */ interface IAxelarExecutable { /** * @dev Thrown when a function is called with an invalid address. */ error InvalidAddress(); /** * @dev Thrown when the call is not approved by the Axelar Gateway. */ error NotApprovedByGateway(); /** * @notice Returns the address of the AxelarGateway contract. * @return The Axelar Gateway contract associated with this executable contract. */ function gateway() external view returns (IAxelarGateway); /** * @notice Executes the specified command sent from another chain. * @dev This function is called by the Axelar Gateway to carry out cross-chain commands. * Reverts if the call is not approved by the gateway or other checks fail. * @param commandId The identifier of the command to execute. * @param sourceChain The name of the source chain from where the command originated. * @param sourceAddress The address on the source chain that sent the command. * @param payload The payload of the command to be executed. This typically includes the function selector and encoded arguments. */ function execute( bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) external; } // File contracts/executable/AxelarExecutable.sol /** * @title AxelarExecutable * @dev Abstract contract to be inherited by contracts that need to execute cross-chain commands via Axelar's Gateway. * It implements the IAxelarExecutable interface. */ abstract contract AxelarExecutable is IAxelarExecutable { /// @dev Reference to the Axelar Gateway contract. address internal immutable gatewayAddress; /** * @dev Contract constructor that sets the Axelar Gateway address. * Reverts if the provided address is the zero address. * @param gateway_ The address of the Axelar Gateway contract. */ constructor(address gateway_) { if (gateway_ == address(0)) revert InvalidAddress(); gatewayAddress = gateway_; } /** * @notice Executes the cross-chain command after validating it with the Axelar Gateway. * @dev This function ensures the call is approved by Axelar Gateway before execution. * It uses a hash of the payload for validation and internally calls _execute for the actual command execution. * Reverts if the validation fails. * @param commandId The unique identifier of the cross-chain message being executed. * @param sourceChain The name of the source chain from which the message originated. * @param sourceAddress The address on the source chain that sent the message. * @param payload The payload of the message payload. */ function execute( bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) external virtual { bytes32 payloadHash = keccak256(payload); if (!gateway().validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway(); _execute(commandId, sourceChain, sourceAddress, payload); } /** * @dev Internal virtual function to be overridden by child contracts to execute the command. * It allows child contracts to define their custom command execution logic. * @param commandId The identifier of the command to execute. * @param sourceChain The name of the source chain from which the command originated. * @param sourceAddress The address on the source chain that sent the command. * @param payload The payload of the command to be executed. */ function _execute( bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) internal virtual; /** * @notice Returns the address of the AxelarGateway contract. * @return The Axelar Gateway instance. */ function gateway() public view returns (IAxelarGateway) { return IAxelarGateway(gatewayAddress); } } // File contracts/interfaces/ICaller.sol interface ICaller { error InvalidContract(address target); error InsufficientBalance(); error ExecutionFailed(); } // File contracts/interfaces/ITimeLock.sol /** * @title ITimeLock * @dev Interface for a TimeLock that enables function execution after a certain time has passed. */ interface ITimeLock { error InvalidTimeLockHash(); error TimeLockAlreadyScheduled(); error TimeLockNotReady(); /** * @notice Returns a minimum time delay at which the TimeLock may be scheduled. * @return uint Minimum scheduling delay time (in secs) from the current block timestamp */ function minimumTimeLockDelay() external view returns (uint256); /** * @notice Returns the timestamp after which the TimeLock may be executed. * @param hash The hash of the timelock * @return uint The timestamp after which the timelock with the given hash can be executed */ function getTimeLock(bytes32 hash) external view returns (uint256); } // File contracts/interfaces/IInterchainGovernance.sol /** * @title IInterchainGovernance Interface * @notice This interface extends IAxelarExecutable for interchain governance mechanisms. */ interface IInterchainGovernance is IAxelarExecutable, ICaller, ITimeLock { error NotGovernance(); error NotSelf(); error InvalidCommand(); error InvalidTarget(); error TokenNotSupported(); event ProposalScheduled( bytes32 indexed proposalHash, address indexed target, bytes callData, uint256 value, uint256 indexed eta ); event ProposalCancelled( bytes32 indexed proposalHash, address indexed target, bytes callData, uint256 value, uint256 indexed eta ); event ProposalExecuted( bytes32 indexed proposalHash, address indexed target, bytes callData, uint256 value, uint256 indexed timestamp ); /** * @notice Returns the name of the governance chain. * @return string The name of the governance chain */ function governanceChain() external view returns (string memory); /** * @notice Returns the address of the governance address. * @return string The address of the governance address */ function governanceAddress() external view returns (string memory); /** * @notice Returns the hash of the governance chain. * @return bytes32 The hash of the governance chain */ function governanceChainHash() external view returns (bytes32); /** * @notice Returns the hash of the governance address. * @return bytes32 The hash of the governance address */ function governanceAddressHash() external view returns (bytes32); /** * @notice Returns the ETA of a proposal. * @param target The address of the contract targeted by the proposal * @param callData The call data to be sent to the target contract * @param nativeValue The amount of native tokens to be sent to the target contract * @return uint256 The ETA of the proposal */ function getProposalEta( address target, bytes calldata callData, uint256 nativeValue ) external view returns (uint256); /** * @notice Executes a governance proposal. * @param targetContract The address of the contract targeted by the proposal * @param callData The call data to be sent to the target contract * @param value The amount of ETH to be sent to the target contract */ function executeProposal( address targetContract, bytes calldata callData, uint256 value ) external payable; /** * @notice Withdraws native token from the contract * @param recipient The address to send the native token to * @param amount The amount of native token to send * @dev This function is only callable by the contract itself after passing according proposal */ function withdraw(address recipient, uint256 amount) external; } // File contracts/libs/SafeNativeTransfer.sol error NativeTransferFailed(); /* * @title SafeNativeTransfer * @dev This library is used for performing safe native value transfers in Solidity by utilizing inline assembly. */ library SafeNativeTransfer { /* * @notice Perform a native transfer to a given address. * @param receiver The recipient address to which the amount will be sent. * @param amount The amount of native value to send. * @throws NativeTransferFailed error if transfer is not successful. */ function safeNativeTransfer(address receiver, uint256 amount) internal { bool success; assembly { success := call(gas(), receiver, amount, 0, 0, 0, 0) } if (!success) revert NativeTransferFailed(); } } // File contracts/libs/ContractAddress.sol library ContractAddress { function isContract(address contractAddress) internal view returns (bool) { bytes32 existingCodeHash = contractAddress.codehash; // https://eips.ethereum.org/EIPS/eip-1052 // keccak256('') == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 return existingCodeHash != bytes32(0) && existingCodeHash != 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; } } // File contracts/utils/Caller.sol contract Caller is ICaller { using ContractAddress for address; /** * @dev Calls a target address with specified calldata and optionally sends value. */ function _call( address target, bytes calldata callData, uint256 nativeValue ) internal returns (bytes memory) { if (!target.isContract()) revert InvalidContract(target); if (nativeValue > address(this).balance) revert InsufficientBalance(); (bool success, bytes memory data) = target.call{ value: nativeValue }(callData); if (!success) { revert ExecutionFailed(); } return data; } } // File contracts/utils/TimeLock.sol /** * @title TimeLock * @dev A contract that enables function execution after a certain time has passed. * Implements the {ITimeLock} interface. */ contract TimeLock is ITimeLock { bytes32 internal constant PREFIX_TIME_LOCK = keccak256('time-lock'); uint256 public immutable minimumTimeLockDelay; /** * @notice The constructor for the TimeLock. * @param minimumTimeDelay The minimum time delay (in secs) that must pass for the TimeLock to be executed */ constructor(uint256 minimumTimeDelay) { minimumTimeLockDelay = minimumTimeDelay; } /** * @notice Returns the timestamp after which the TimeLock may be executed. * @param hash The hash of the timelock * @return uint256 The timestamp after which the timelock with the given hash can be executed */ function getTimeLock(bytes32 hash) external view override returns (uint256) { return _getTimeLockEta(hash); } /** * @notice Schedules a new timelock. * @dev The timestamp will be set to the current block timestamp + minimum time delay, * if the provided timestamp is less than that. * @param hash The hash of the new timelock * @param eta The proposed Unix timestamp (in secs) after which the new timelock can be executed * @return uint The Unix timestamp (in secs) after which the new timelock can be executed */ function _scheduleTimeLock(bytes32 hash, uint256 eta) internal returns (uint256) { if (hash == 0) revert InvalidTimeLockHash(); if (_getTimeLockEta(hash) != 0) revert TimeLockAlreadyScheduled(); uint256 minimumEta = block.timestamp + minimumTimeLockDelay; if (eta < minimumEta) eta = minimumEta; _setTimeLockEta(hash, eta); return eta; } /** * @notice Cancels an existing timelock by setting its eta to zero. * @param hash The hash of the timelock to cancel */ function _cancelTimeLock(bytes32 hash) internal { if (hash == 0) revert InvalidTimeLockHash(); _setTimeLockEta(hash, 0); } /** * @notice Finalizes an existing timelock and sets its eta back to zero. * @dev To finalize, the timelock must currently exist and the required time delay * must have passed. * @param hash The hash of the timelock to finalize */ function _finalizeTimeLock(bytes32 hash) internal { uint256 eta = _getTimeLockEta(hash); if (hash == 0 || eta == 0) revert InvalidTimeLockHash(); if (block.timestamp < eta) revert TimeLockNotReady(); _setTimeLockEta(hash, 0); } /** * @dev Returns the timestamp after which the timelock with the given hash can be executed. */ function _getTimeLockEta(bytes32 hash) internal view returns (uint256 eta) { bytes32 key = keccak256(abi.encodePacked(PREFIX_TIME_LOCK, hash)); assembly { eta := sload(key) } } /** * @dev Sets a new timestamp for the timelock with the given hash. */ function _setTimeLockEta(bytes32 hash, uint256 eta) private { bytes32 key = keccak256(abi.encodePacked(PREFIX_TIME_LOCK, hash)); assembly { sstore(key, eta) } } } // File contracts/governance/InterchainGovernance.sol /** * @title Interchain Governance contract * @notice This contract handles cross-chain governance actions. It includes functionality * to create, cancel, and execute governance proposals. */ contract InterchainGovernance is AxelarExecutable, TimeLock, Caller, IInterchainGovernance { using SafeNativeTransfer for address; enum GovernanceCommand { ScheduleTimeLockProposal, CancelTimeLockProposal } string public governanceChain; string public governanceAddress; bytes32 public immutable governanceChainHash; bytes32 public immutable governanceAddressHash; /** * @notice Initializes the contract * @param gateway_ The address of the Axelar gateway contract * @param governanceChain_ The name of the governance chain * @param governanceAddress_ The address of the governance contract * @param minimumTimeDelay The minimum time delay for timelock operations */ constructor( address gateway_, string memory governanceChain_, string memory governanceAddress_, uint256 minimumTimeDelay ) AxelarExecutable(gateway_) TimeLock(minimumTimeDelay) { if (bytes(governanceChain_).length == 0 || bytes(governanceAddress_).length == 0) { revert InvalidAddress(); } governanceChain = governanceChain_; governanceAddress = governanceAddress_; governanceChainHash = keccak256(bytes(governanceChain_)); governanceAddressHash = keccak256(bytes(governanceAddress_)); } /** * @notice Modifier to check if the caller is the governance contract * @param sourceChain The source chain of the proposal, must equal the governance chain * @param sourceAddress The source address of the proposal, must equal the governance address */ modifier onlyGovernance(string calldata sourceChain, string calldata sourceAddress) { if ( keccak256(bytes(sourceChain)) != governanceChainHash || keccak256(bytes(sourceAddress)) != governanceAddressHash ) revert NotGovernance(); _; } /** * @notice Modifier to check if the caller is the contract itself */ modifier onlySelf() { if (msg.sender != address(this)) revert NotSelf(); _; } /** * @notice Returns the ETA of a proposal * @param target The address of the contract targeted by the proposal * @param callData The call data to be sent to the target contract * @param nativeValue The amount of native tokens to be sent to the target contract * @return uint256 The ETA of the proposal */ function getProposalEta( address target, bytes calldata callData, uint256 nativeValue ) external view returns (uint256) { return _getTimeLockEta(_getProposalHash(target, callData, nativeValue)); } /** * @notice Executes a proposal * @dev The proposal is executed by calling the target contract with calldata. Native value is * transferred with the call to the target contract. * @param target The target address of the contract to call * @param callData The data containing the function and arguments for the contract to call * @param nativeValue The amount of native token to send to the target contract */ function executeProposal( address target, bytes calldata callData, uint256 nativeValue ) external payable { bytes32 proposalHash = _getProposalHash(target, callData, nativeValue); _finalizeTimeLock(proposalHash); emit ProposalExecuted(proposalHash, target, callData, nativeValue, block.timestamp); _call(target, callData, nativeValue); } /** * @notice Withdraws native token from the contract * @param recipient The address to send the native token to * @param amount The amount of native token to send * @dev This function is only callable by the contract itself after passing according proposal */ function withdraw(address recipient, uint256 amount) external onlySelf { recipient.safeNativeTransfer(amount); } /** * @notice Internal function to execute a proposal action * @param sourceChain The source chain of the proposal, must equal the governance chain * @param sourceAddress The source address of the proposal, must equal the governance address * @param payload The payload of the proposal */ function _execute( bytes32, /* commandId */ string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) internal override onlyGovernance(sourceChain, sourceAddress) { (uint256 command, address target, bytes memory callData, uint256 nativeValue, uint256 eta) = abi.decode( payload, (uint256, address, bytes, uint256, uint256) ); if (target == address(0)) revert InvalidTarget(); _processCommand(command, target, callData, nativeValue, eta); } /** * @notice Internal function to process a governance command * @param commandType The type of the command, 0 for proposal creation and 1 for proposal cancellation * @param target The target address the proposal will call * @param callData The data the encodes the function and arguments to call on the target contract * @param nativeValue The nativeValue of native token to be sent to the target contract * @param eta The time after which the proposal can be executed */ function _processCommand( uint256 commandType, address target, bytes memory callData, uint256 nativeValue, uint256 eta ) internal virtual { bytes32 proposalHash = _getProposalHash(target, callData, nativeValue); if (commandType == uint256(GovernanceCommand.ScheduleTimeLockProposal)) { eta = _scheduleTimeLock(proposalHash, eta); emit ProposalScheduled(proposalHash, target, callData, nativeValue, eta); return; } else if (commandType == uint256(GovernanceCommand.CancelTimeLockProposal)) { _cancelTimeLock(proposalHash); emit ProposalCancelled(proposalHash, target, callData, nativeValue, eta); return; } else { revert InvalidCommand(); } } /** * @dev Get proposal hash using the target, callData, and nativeValue */ function _getProposalHash( address target, bytes memory callData, uint256 nativeValue ) internal pure returns (bytes32) { return keccak256(abi.encode(target, callData, nativeValue)); } /** * @notice Allow contract to receive native gas token */ receive() external payable {} } // File contracts/interfaces/IAxelarServiceGovernance.sol /** * @title IAxelarServiceGovernance Interface * @dev This interface extends IInterchainGovernance for operator proposal actions */ interface IAxelarServiceGovernance is IInterchainGovernance { error InvalidOperator(); error NotApproved(); error NotAuthorized(); event OperatorProposalApproved( bytes32 indexed proposalHash, address indexed targetContract, bytes callData, uint256 nativeValue ); event OperatorProposalCancelled( bytes32 indexed proposalHash, address indexed targetContract, bytes callData, uint256 nativeValue ); event OperatorProposalExecuted( bytes32 indexed proposalHash, address indexed targetContract, bytes callData, uint256 nativeValue ); event OperatorshipTransferred(address indexed oldOperator, address indexed newOperator); /** * @notice Returns whether an operator proposal has been approved * @param proposalHash The hash of the proposal * @return bool True if the proposal has been approved, False otherwise */ function operatorApprovals(bytes32 proposalHash) external view returns (bool); /** * @notice Returns whether an operator proposal has been approved * @param target The address of the contract targeted by the proposal * @param callData The call data to be sent to the target contract * @param nativeValue The amount of native tokens to be sent to the target contract * @return bool True if the proposal has been approved, False otherwise */ function isOperatorProposalApproved( address target, bytes calldata callData, uint256 nativeValue ) external view returns (bool); /** * @notice Executes an operator proposal * @param targetContract The target address the proposal will call * @param callData The data that encodes the function and arguments to call on the target contract */ function executeOperatorProposal( address targetContract, bytes calldata callData, uint256 value ) external payable; /** * @notice Transfers the operator address to a new address * @dev Only the current operator or the governance can call this function * @param newOperator The new operator address */ function transferOperatorship(address newOperator) external; } // File contracts/governance/AxelarServiceGovernance.sol /** * @title AxelarServiceGovernance Contract * @dev This contract is part of the Axelar Governance system, it inherits the Interchain Governance contract * with added functionality to approve and execute operator proposals. */ contract AxelarServiceGovernance is InterchainGovernance, IAxelarServiceGovernance { enum ServiceGovernanceCommand { ScheduleTimeLockProposal, CancelTimeLockProposal, ApproveOperatorProposal, CancelOperatorApproval } address public operator; mapping(bytes32 => bool) public operatorApprovals; modifier onlyOperator() { if (msg.sender != operator) revert NotAuthorized(); _; } modifier onlyOperatorOrSelf() { if (msg.sender != operator && msg.sender != address(this)) revert NotAuthorized(); _; } /** * @notice Initializes the contract. * @param gateway_ The address of the Axelar gateway contract * @param governanceChain_ The name of the governance chain * @param governanceAddress_ The address of the governance contract * @param minimumTimeDelay The minimum time delay for timelock operations * @param operator_ The operator address */ constructor( address gateway_, string memory governanceChain_, string memory governanceAddress_, uint256 minimumTimeDelay, address operator_ ) InterchainGovernance(gateway_, governanceChain_, governanceAddress_, minimumTimeDelay) { if (operator_ == address(0)) revert InvalidOperator(); operator = operator_; } /** * @notice Returns whether an operator proposal has been approved * @param target The address of the contract targeted by the proposal * @param callData The call data to be sent to the target contract * @param nativeValue The amount of native tokens to be sent to the target contract * @return bool True if the proposal has been approved, False otherwise */ function isOperatorProposalApproved( address target, bytes calldata callData, uint256 nativeValue ) external view returns (bool) { return operatorApprovals[_getProposalHash(target, callData, nativeValue)]; } /** * @notice Executes an operator proposal. * @param target The target address the proposal will call * @param callData The data that encodes the function and arguments to call on the target contract * @param nativeValue The value of native token to be sent to the target contract */ function executeOperatorProposal( address target, bytes calldata callData, uint256 nativeValue ) external payable onlyOperator { bytes32 proposalHash = _getProposalHash(target, callData, nativeValue); if (!operatorApprovals[proposalHash]) revert NotApproved(); operatorApprovals[proposalHash] = false; emit OperatorProposalExecuted(proposalHash, target, callData, nativeValue); _call(target, callData, nativeValue); } /** * @notice Transfers the operator address to a new address * @dev Only the current operator or the governance can call this function * @param newOperator The new operator address */ function transferOperatorship(address newOperator) external onlyOperatorOrSelf { if (newOperator == address(0)) revert InvalidOperator(); emit OperatorshipTransferred(operator, newOperator); operator = newOperator; } /** * @notice Internal function to process a governance command * @param commandType The type of the command * @param target The target address the proposal will call * @param callData The data the encodes the function and arguments to call on the target contract * @param nativeValue The value of native token to be sent to the target contract * @param eta The time after which the proposal can be executed */ function _processCommand( uint256 commandType, address target, bytes memory callData, uint256 nativeValue, uint256 eta ) internal override { bytes32 proposalHash = _getProposalHash(target, callData, nativeValue); if (commandType == uint256(ServiceGovernanceCommand.ScheduleTimeLockProposal)) { eta = _scheduleTimeLock(proposalHash, eta); emit ProposalScheduled(proposalHash, target, callData, nativeValue, eta); return; } else if (commandType == uint256(ServiceGovernanceCommand.CancelTimeLockProposal)) { _cancelTimeLock(proposalHash); emit ProposalCancelled(proposalHash, target, callData, nativeValue, eta); return; } else if (commandType == uint256(ServiceGovernanceCommand.ApproveOperatorProposal)) { operatorApprovals[proposalHash] = true; emit OperatorProposalApproved(proposalHash, target, callData, nativeValue); return; } else if (commandType == uint256(ServiceGovernanceCommand.CancelOperatorApproval)) { operatorApprovals[proposalHash] = false; emit OperatorProposalCancelled(proposalHash, target, callData, nativeValue); return; } else { revert InvalidCommand(); } } }