You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
pragma solidity >=0.8.0;
|
|
|
|
/*@@@@@@@ @@@@@@@@@
|
|
@@@@@@@@@ @@@@@@@@@
|
|
@@@@@@@@@ @@@@@@@@@
|
|
@@@@@@@@@ @@@@@@@@@
|
|
@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
@@@@@ HYPERLANE @@@@@@@
|
|
@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
@@@@@@@@@ @@@@@@@@@
|
|
@@@@@@@@@ @@@@@@@@@
|
|
@@@@@@@@@ @@@@@@@@@
|
|
@@@@@@@@@ @@@@@@@@*/
|
|
|
|
// ============ Internal Imports ============
|
|
import {Message} from "../../libs/Message.sol";
|
|
import {StandardHookMetadata} from "../libs/StandardHookMetadata.sol";
|
|
import {IGasOracle} from "../../interfaces/IGasOracle.sol";
|
|
import {IInterchainGasPaymaster} from "../../interfaces/IInterchainGasPaymaster.sol";
|
|
import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol";
|
|
import {AbstractPostDispatchHook} from "../libs/AbstractPostDispatchHook.sol";
|
|
import {Indexed} from "../../libs/Indexed.sol";
|
|
|
|
// ============ External Imports ============
|
|
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
|
|
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
|
|
/**
|
|
* @title InterchainGasPaymaster
|
|
* @notice Manages payments on a source chain to cover gas costs of relaying
|
|
* messages to destination chains and includes the gas overhead per destination
|
|
* @dev The intended use of this contract is to store overhead gas amounts for destination
|
|
* domains, e.g. Mailbox and ISM gas usage, such that users of this IGP are only required
|
|
* to specify the gas amount used by their own applications.
|
|
*/
|
|
contract InterchainGasPaymaster is
|
|
IInterchainGasPaymaster,
|
|
AbstractPostDispatchHook,
|
|
IGasOracle,
|
|
Indexed,
|
|
OwnableUpgradeable
|
|
{
|
|
using Address for address payable;
|
|
using Message for bytes;
|
|
using StandardHookMetadata for bytes;
|
|
// ============ Constants ============
|
|
|
|
/// @notice The scale of gas oracle token exchange rates.
|
|
uint256 internal constant TOKEN_EXCHANGE_RATE_SCALE = 1e10;
|
|
/// @notice default for user call if metadata not provided
|
|
uint256 internal immutable DEFAULT_GAS_USAGE = 50_000;
|
|
|
|
// ============ Public Storage ============
|
|
|
|
/// @notice Destination domain => gas oracle and overhead gas amount.
|
|
mapping(uint32 => DomainGasConfig) public destinationGasConfigs;
|
|
|
|
/// @notice The benficiary that can receive native tokens paid into this contract.
|
|
address public beneficiary;
|
|
|
|
// ============ Events ============
|
|
|
|
/**
|
|
* @notice Emitted when the gas oracle for a remote domain is set.
|
|
* @param remoteDomain The remote domain.
|
|
* @param gasOracle The gas oracle.
|
|
* @param gasOverhead The destination gas overhead.
|
|
*/
|
|
event DestinationGasConfigSet(
|
|
uint32 remoteDomain,
|
|
address gasOracle,
|
|
uint96 gasOverhead
|
|
);
|
|
|
|
/**
|
|
* @notice Emitted when the beneficiary is set.
|
|
* @param beneficiary The new beneficiary.
|
|
*/
|
|
event BeneficiarySet(address beneficiary);
|
|
|
|
struct DomainGasConfig {
|
|
IGasOracle gasOracle;
|
|
uint96 gasOverhead;
|
|
}
|
|
|
|
struct GasParam {
|
|
uint32 remoteDomain;
|
|
DomainGasConfig config;
|
|
}
|
|
|
|
// ============ External Functions ============
|
|
|
|
/// @inheritdoc IPostDispatchHook
|
|
function hookType() external pure override returns (uint8) {
|
|
return uint8(IPostDispatchHook.Types.INTERCHAIN_GAS_PAYMASTER);
|
|
}
|
|
|
|
/**
|
|
* @param _owner The owner of the contract.
|
|
* @param _beneficiary The beneficiary.
|
|
*/
|
|
function initialize(
|
|
address _owner,
|
|
address _beneficiary
|
|
) public initializer {
|
|
__Ownable_init();
|
|
_transferOwnership(_owner);
|
|
_setBeneficiary(_beneficiary);
|
|
}
|
|
|
|
/**
|
|
* @notice Transfers the entire native token balance to the beneficiary.
|
|
* @dev The beneficiary must be able to receive native tokens.
|
|
*/
|
|
function claim() external {
|
|
// Transfer the entire balance to the beneficiary.
|
|
(bool success, ) = beneficiary.call{value: address(this).balance}("");
|
|
require(success, "IGP: claim failed");
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the gas oracles for remote domains specified in the config array.
|
|
* @param _configs An array of configs including the remote domain and gas oracles to set.
|
|
*/
|
|
function setDestinationGasConfigs(
|
|
GasParam[] calldata _configs
|
|
) external onlyOwner {
|
|
uint256 _len = _configs.length;
|
|
for (uint256 i = 0; i < _len; i++) {
|
|
_setDestinationGasConfig(
|
|
_configs[i].remoteDomain,
|
|
_configs[i].config.gasOracle,
|
|
_configs[i].config.gasOverhead
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the beneficiary.
|
|
* @param _beneficiary The new beneficiary.
|
|
*/
|
|
function setBeneficiary(address _beneficiary) external onlyOwner {
|
|
_setBeneficiary(_beneficiary);
|
|
}
|
|
|
|
// ============ Public Functions ============
|
|
|
|
/**
|
|
* @notice Deposits msg.value as a payment for the relaying of a message
|
|
* to its destination chain.
|
|
* @dev Overpayment will result in a refund of native tokens to the _refundAddress.
|
|
* Callers should be aware that this may present reentrancy issues.
|
|
* @param _messageId The ID of the message to pay for.
|
|
* @param _destinationDomain The domain of the message's destination chain.
|
|
* @param _gasLimit The amount of destination gas to pay for.
|
|
* @param _refundAddress The address to refund any overpayment to.
|
|
*/
|
|
function payForGas(
|
|
bytes32 _messageId,
|
|
uint32 _destinationDomain,
|
|
uint256 _gasLimit,
|
|
address _refundAddress
|
|
) public payable override {
|
|
uint256 _requiredPayment = quoteGasPayment(
|
|
_destinationDomain,
|
|
_gasLimit
|
|
);
|
|
require(
|
|
msg.value >= _requiredPayment,
|
|
"IGP: insufficient interchain gas payment"
|
|
);
|
|
uint256 _overpayment = msg.value - _requiredPayment;
|
|
if (_overpayment > 0) {
|
|
require(_refundAddress != address(0), "no refund address");
|
|
payable(_refundAddress).sendValue(_overpayment);
|
|
}
|
|
|
|
emit GasPayment(
|
|
_messageId,
|
|
_destinationDomain,
|
|
_gasLimit,
|
|
_requiredPayment
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Quotes the amount of native tokens to pay for interchain gas.
|
|
* @param _destinationDomain The domain of the message's destination chain.
|
|
* @param _gasLimit The amount of destination gas to pay for.
|
|
* @return The amount of native tokens required to pay for interchain gas.
|
|
*/
|
|
function quoteGasPayment(
|
|
uint32 _destinationDomain,
|
|
uint256 _gasLimit
|
|
) public view virtual override returns (uint256) {
|
|
// Get the gas data for the destination domain.
|
|
(
|
|
uint128 _tokenExchangeRate,
|
|
uint128 _gasPrice
|
|
) = getExchangeRateAndGasPrice(_destinationDomain);
|
|
|
|
// The total cost quoted in destination chain's native token.
|
|
uint256 _destinationGasCost = _gasLimit * uint256(_gasPrice);
|
|
|
|
// Convert to the local native token.
|
|
return
|
|
(_destinationGasCost * _tokenExchangeRate) /
|
|
TOKEN_EXCHANGE_RATE_SCALE;
|
|
}
|
|
|
|
/**
|
|
* @notice Gets the token exchange rate and gas price from the configured gas oracle
|
|
* for a given destination domain.
|
|
* @param _destinationDomain The destination domain.
|
|
* @return tokenExchangeRate The exchange rate of the remote native token quoted in the local native token.
|
|
* @return gasPrice The gas price on the remote chain.
|
|
*/
|
|
function getExchangeRateAndGasPrice(
|
|
uint32 _destinationDomain
|
|
)
|
|
public
|
|
view
|
|
override
|
|
returns (uint128 tokenExchangeRate, uint128 gasPrice)
|
|
{
|
|
IGasOracle _gasOracle = destinationGasConfigs[_destinationDomain]
|
|
.gasOracle;
|
|
|
|
require(
|
|
address(_gasOracle) != address(0),
|
|
string.concat(
|
|
"Configured IGP doesn't support domain ",
|
|
Strings.toString(_destinationDomain)
|
|
)
|
|
);
|
|
|
|
return _gasOracle.getExchangeRateAndGasPrice(_destinationDomain);
|
|
}
|
|
|
|
/**
|
|
* @notice Returns the stored destinationGasOverhead added to the _gasLimit.
|
|
* @dev If there is no stored destinationGasOverhead, 0 is used. This is useful in the case
|
|
* the ISM deployer wants to subsidize the overhead gas cost. Then, can specify the gas oracle
|
|
* they want to use with the destination domain, but set the overhead to 0.
|
|
* @param _destinationDomain The domain of the message's destination chain.
|
|
* @param _gasLimit The amount of destination gas to pay for. This is only for application gas usage as
|
|
* the gas usage for the mailbox and the ISM is already accounted in the DomainGasConfig.gasOverhead
|
|
*/
|
|
function destinationGasLimit(
|
|
uint32 _destinationDomain,
|
|
uint256 _gasLimit
|
|
) public view returns (uint256) {
|
|
return
|
|
uint256(destinationGasConfigs[_destinationDomain].gasOverhead) +
|
|
_gasLimit;
|
|
}
|
|
|
|
// ============ Internal Functions ============
|
|
|
|
/// @inheritdoc AbstractPostDispatchHook
|
|
function _postDispatch(
|
|
bytes calldata metadata,
|
|
bytes calldata message
|
|
) internal override {
|
|
payForGas(
|
|
message.id(),
|
|
message.destination(),
|
|
destinationGasLimit(
|
|
message.destination(),
|
|
metadata.gasLimit(DEFAULT_GAS_USAGE)
|
|
),
|
|
metadata.refundAddress(message.senderAddress())
|
|
);
|
|
}
|
|
|
|
/// @inheritdoc AbstractPostDispatchHook
|
|
function _quoteDispatch(
|
|
bytes calldata metadata,
|
|
bytes calldata message
|
|
) internal view override returns (uint256) {
|
|
return
|
|
quoteGasPayment(
|
|
message.destination(),
|
|
destinationGasLimit(
|
|
message.destination(),
|
|
metadata.gasLimit(DEFAULT_GAS_USAGE)
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the beneficiary.
|
|
* @param _beneficiary The new beneficiary.
|
|
*/
|
|
function _setBeneficiary(address _beneficiary) internal {
|
|
beneficiary = _beneficiary;
|
|
emit BeneficiarySet(_beneficiary);
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the gas oracle and destination gas overhead for a remote domain.
|
|
* @param _remoteDomain The remote domain.
|
|
* @param _gasOracle The gas oracle.
|
|
* @param _gasOverhead The destination gas overhead.
|
|
*/
|
|
function _setDestinationGasConfig(
|
|
uint32 _remoteDomain,
|
|
IGasOracle _gasOracle,
|
|
uint96 _gasOverhead
|
|
) internal {
|
|
destinationGasConfigs[_remoteDomain] = DomainGasConfig(
|
|
_gasOracle,
|
|
_gasOverhead
|
|
);
|
|
emit DestinationGasConfigSet(
|
|
_remoteDomain,
|
|
address(_gasOracle),
|
|
_gasOverhead
|
|
);
|
|
}
|
|
}
|
|
|