Unify overheardIgp and igp (#2766)

### Description

- v3 uses the OverheadIgp as the default igp and removing an "inner" igp
makes it simpler to deploy and interact with
- `InterchainGasPaymaster` has a `setDestinationGasConfigs` which sets
it the overhead and the oracle.
- the gas overhead gets added it directly to the specified gasAmount.
- Downstream changes to the `typescript/*` module in the igpConfig,
checke, deployer, etc

### Drive-by changes

None

### Related issues

- precursor to https://github.com/hyperlane-xyz/issues/issues/623
- related to https://github.com/hyperlane-xyz/issues/issues/637
- missing changes to governor https://github.com/hyperlane-xyz/issues/issues/641

### Backward compatibility

Yes

### Testing

Fuzz
pull/2774/head
Kunal Arora 1 year ago committed by GitHub
parent f0a45bd6b1
commit a60ec18237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 117
      solidity/contracts/hooks/igp/InterchainGasPaymaster.sol
  2. 134
      solidity/contracts/hooks/igp/OverheadIgp.sol
  3. 2
      solidity/test/GasRouter.t.sol
  4. 544
      solidity/test/igps/InterchainGasPaymaster.t.sol
  5. 146
      solidity/test/igps/OverheadIgp.t.sol
  6. 2
      solidity/test/router.test.ts
  7. 41
      typescript/infra/config/environments/mainnet2/igp.ts
  8. 39
      typescript/infra/config/environments/test/igp.ts
  9. 39
      typescript/infra/config/environments/testnet3/igp.ts
  10. 2
      typescript/infra/scripts/utils.ts
  11. 8
      typescript/infra/src/config/environment.ts
  12. 36
      typescript/infra/src/govern/HyperlaneIgpGovernor.ts
  13. 2
      typescript/sdk/src/gas/HyperlaneIgp.ts
  14. 16
      typescript/sdk/src/gas/HyperlaneIgpChecker.ts
  15. 81
      typescript/sdk/src/gas/HyperlaneIgpDeployer.ts
  16. 2
      typescript/sdk/src/gas/contracts.ts
  17. 7
      typescript/sdk/src/gas/types.ts
  18. 1
      typescript/sdk/src/index.ts

@ -30,7 +30,10 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own
/**
* @title InterchainGasPaymaster
* @notice Manages payments on a source chain to cover gas costs of relaying
* messages to destination chains.
* 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,
@ -47,12 +50,12 @@ contract InterchainGasPaymaster is
/// @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 = 69_420;
uint256 internal immutable DEFAULT_GAS_USAGE = 50_000;
// ============ Public Storage ============
/// @notice Keyed by remote domain, the gas oracle to use for the domain.
mapping(uint32 => IGasOracle) public gasOracles;
/// @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;
@ -63,8 +66,13 @@ contract InterchainGasPaymaster is
* @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 GasOracleSet(uint32 indexed remoteDomain, address gasOracle);
event DestinationGasConfigSet(
uint32 remoteDomain,
address gasOracle,
uint96 gasOverhead
);
/**
* @notice Emitted when the beneficiary is set.
@ -72,9 +80,14 @@ contract InterchainGasPaymaster is
*/
event BeneficiarySet(address beneficiary);
struct GasOracleConfig {
struct DomainGasConfig {
IGasOracle gasOracle;
uint96 gasOverhead;
}
struct GasParam {
uint32 remoteDomain;
address gasOracle;
DomainGasConfig config;
}
// ============ External Functions ============
@ -99,20 +112,24 @@ contract InterchainGasPaymaster is
function claim() external {
// Transfer the entire balance to the beneficiary.
(bool success, ) = beneficiary.call{value: address(this).balance}("");
require(success, "!transfer");
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 setGasOracles(GasOracleConfig[] calldata _configs)
function setDestinationGasConfigs(GasParam[] calldata _configs)
external
onlyOwner
{
uint256 _len = _configs.length;
for (uint256 i = 0; i < _len; i++) {
_setGasOracle(_configs[i].remoteDomain, _configs[i].gasOracle);
_setDestinationGasConfig(
_configs[i].remoteDomain,
_configs[i].config.gasOracle,
_configs[i].config.gasOverhead
);
}
}
@ -133,22 +150,22 @@ contract InterchainGasPaymaster is
* 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 _gasAmount The amount of destination gas to pay for.
* @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 _gasAmount,
uint256 _gasLimit,
address _refundAddress
) public payable override {
uint256 _requiredPayment = quoteGasPayment(
_destinationDomain,
_gasAmount
_gasLimit
);
require(
msg.value >= _requiredPayment,
"insufficient interchain gas payment"
"IGP: insufficient interchain gas payment"
);
uint256 _overpayment = msg.value - _requiredPayment;
if (_overpayment > 0) {
@ -159,7 +176,7 @@ contract InterchainGasPaymaster is
emit GasPayment(
_messageId,
_destinationDomain,
_gasAmount,
_gasLimit,
_requiredPayment
);
}
@ -167,10 +184,10 @@ contract InterchainGasPaymaster is
/**
* @notice Quotes the amount of native tokens to pay for interchain gas.
* @param _destinationDomain The domain of the message's destination chain.
* @param _gasAmount The amount of destination gas to pay for.
* @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 _gasAmount)
function quoteGasPayment(uint32 _destinationDomain, uint256 _gasLimit)
public
view
virtual
@ -184,7 +201,7 @@ contract InterchainGasPaymaster is
) = getExchangeRateAndGasPrice(_destinationDomain);
// The total cost quoted in destination chain's native token.
uint256 _destinationGasCost = _gasAmount * uint256(_gasPrice);
uint256 _destinationGasCost = _gasLimit * uint256(_gasPrice);
// Convert to the local native token.
return
@ -205,7 +222,8 @@ contract InterchainGasPaymaster is
override
returns (uint128 tokenExchangeRate, uint128 gasPrice)
{
IGasOracle _gasOracle = gasOracles[_destinationDomain];
IGasOracle _gasOracle = destinationGasConfigs[_destinationDomain]
.gasOracle;
require(
address(_gasOracle) != address(0),
@ -218,6 +236,25 @@ contract InterchainGasPaymaster is
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
@ -225,9 +262,15 @@ contract InterchainGasPaymaster is
internal
override
{
uint256 gasLimit = metadata.gasLimit(DEFAULT_GAS_USAGE);
address refundAddress = metadata.refundAddress(message.senderAddress());
payForGas(message.id(), message.destination(), gasLimit, refundAddress);
payForGas(
message.id(),
message.destination(),
destinationGasLimit(
message.destination(),
metadata.gasLimit(DEFAULT_GAS_USAGE)
),
metadata.refundAddress(message.senderAddress())
);
}
/// @inheritdoc AbstractPostDispatchHook
@ -237,8 +280,14 @@ contract InterchainGasPaymaster is
override
returns (uint256)
{
uint256 gasLimit = metadata.gasLimit(DEFAULT_GAS_USAGE);
return quoteGasPayment(message.destination(), gasLimit);
return
quoteGasPayment(
message.destination(),
destinationGasLimit(
message.destination(),
metadata.gasLimit(DEFAULT_GAS_USAGE)
)
);
}
/**
@ -251,12 +300,24 @@ contract InterchainGasPaymaster is
}
/**
* @notice Sets the gas oracle for a remote domain.
* @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 _setGasOracle(uint32 _remoteDomain, address _gasOracle) internal {
gasOracles[_remoteDomain] = IGasOracle(_gasOracle);
emit GasOracleSet(_remoteDomain, _gasOracle);
function _setDestinationGasConfig(
uint32 _remoteDomain,
IGasOracle _gasOracle,
uint96 _gasOverhead
) internal {
destinationGasConfigs[_remoteDomain] = DomainGasConfig(
_gasOracle,
_gasOverhead
);
emit DestinationGasConfigSet(
_remoteDomain,
address(_gasOracle),
_gasOverhead
);
}
}

@ -1,134 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {IInterchainGasPaymaster} from "../../interfaces/IInterchainGasPaymaster.sol";
// ============ External Imports ============
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @notice An IGP that adds configured gas overheads to gas amounts and forwards
* calls to an "inner" IGP.
* @dev The intended use of this contract is to store overhead gas amounts for destination
* domains, e.g. Mailbox and/or ISM gas usage, such that users of this IGP are only required
* to specify the gas amount used by their own applications.
*/
contract OverheadIgp is IInterchainGasPaymaster, Ownable {
// ============ Constants ============
/// @notice The IGP that is called when paying for or quoting gas
/// after applying overhead gas amounts.
IInterchainGasPaymaster public immutable innerIgp;
// ============ Public Storage ============
/// @notice Destination domain => overhead gas amount on that domain.
mapping(uint32 => uint256) public destinationGasOverhead;
// ============ Events ============
/**
* @notice Emitted when an entry in the destinationGasOverhead mapping is set.
* @param domain The destination domain.
* @param gasOverhead The gas overhead amount on that domain.
*/
event DestinationGasOverheadSet(uint32 indexed domain, uint256 gasOverhead);
struct DomainConfig {
uint32 domain;
uint256 gasOverhead;
}
// ============ Constructor ============
constructor(address _innerIgp) {
innerIgp = IInterchainGasPaymaster(_innerIgp);
}
// ============ External Functions ============
/**
* @notice Adds the stored destinationGasOverhead to the _gasAmount and forwards the
* call to the innerIgp's `payForGas` function.
* @param _messageId The ID of the message to pay for.
* @param _destinationDomain The domain of the message's destination chain.
* @param _gasAmount The amount of destination gas to pay for. This should not
* consider any gas that is accounted for in the stored destinationGasOverhead.
* @param _refundAddress The address to refund any overpayment to.
*/
function payForGas(
bytes32 _messageId,
uint32 _destinationDomain,
uint256 _gasAmount,
address _refundAddress
) external payable {
innerIgp.payForGas{value: msg.value}(
_messageId,
_destinationDomain,
destinationGasAmount(_destinationDomain, _gasAmount),
_refundAddress
);
}
/**
* @notice Sets destination gas overheads for multiple domains.
* @dev Only callable by the owner.
* @param configs A list of destination domains and gas overheads.
*/
function setDestinationGasOverheads(DomainConfig[] calldata configs)
external
onlyOwner
{
for (uint256 i; i < configs.length; i++) {
_setDestinationGasOverhead(configs[i]);
}
}
// ============ Public Functions ============
/**
* @notice Adds the stored destinationGasOverhead to the _gasAmount and forwards the
* call to the innerIgp's `quoteGasPayment` function.
* @param _destinationDomain The domain of the message's destination chain.
* @param _gasAmount The amount of destination gas to pay for. This should not
* consider any gas that is accounted for in the stored destinationGasOverhead.
* @return The amount of native tokens required to pay for interchain gas.
*/
function quoteGasPayment(uint32 _destinationDomain, uint256 _gasAmount)
public
view
returns (uint256)
{
return
innerIgp.quoteGasPayment(
_destinationDomain,
destinationGasAmount(_destinationDomain, _gasAmount)
);
}
/**
* @notice Returns the stored destinationGasOverhead added to the _gasAmount.
* @dev If there is no stored destinationGasOverhead, 0 is used.
* @param _destinationDomain The domain of the message's destination chain.
* @param _gasAmount The amount of destination gas to pay for. This should not
* consider any gas that is accounted for in the stored destinationGasOverhead.
* @return The stored destinationGasOverhead added to the _gasAmount.
*/
function destinationGasAmount(uint32 _destinationDomain, uint256 _gasAmount)
public
view
returns (uint256)
{
return destinationGasOverhead[_destinationDomain] + _gasAmount;
}
/**
* @notice Sets the destination gas overhead for a single domain.
* @param config The destination domain and gas overhead.
*/
function _setDestinationGasOverhead(DomainConfig calldata config) internal {
destinationGasOverhead[config.domain] = config.gasOverhead;
emit DestinationGasOverheadSet(config.domain, config.gasOverhead);
}
}

@ -100,7 +100,7 @@ contract GasRouterTest is Test {
setDestinationGas(originRouter, remoteDomain, gas);
uint256 requiredPayment = gas * gasPrice;
vm.expectRevert("insufficient interchain gas payment");
vm.expectRevert("IGP: insufficient interchain gas payment");
originRouter.dispatch{value: requiredPayment - 1}(remoteDomain, "");
vm.deal(address(this), requiredPayment + 1);

@ -2,6 +2,7 @@
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import "forge-std/console.sol";
import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol";
import {Message} from "../../contracts/libs/Message.sol";
@ -17,13 +18,15 @@ contract InterchainGasPaymasterTest is Test {
using MessageUtils for bytes;
InterchainGasPaymaster igp;
StorageGasOracle oracle;
StorageGasOracle testOracle;
address constant beneficiary = address(0x444444);
uint32 constant testOriginDomain = 22222;
uint32 constant testDestinationDomain = 11111;
uint256 constant testGasAmount = 300000;
uint256 constant testGasLimit = 300000;
uint96 constant testGasOverhead = 123000;
uint256 constant DEFAULT_GAS_USAGE = 50_000;
uint128 constant TEST_EXCHANGE_RATE = 1e10; // 1.0 exchange rate (remote token has exact same value as local)
uint128 constant TEST_GAS_PRICE = 150; // 150 wei gas price
bytes constant testMessage = "hello world";
@ -31,26 +34,32 @@ contract InterchainGasPaymasterTest is Test {
0x6ae9a99190641b9ed0c07143340612dde0e9cb7deaa5fe07597858ae9ba5fd7f;
address constant testRefundAddress = address(0xc0ffee);
bytes testEncodedMessage;
address ALICE = address(0x1); // alice the adversary
uint256 blockNumber;
event GasPayment(
bytes32 indexed messageId,
uint32 indexed destinationDomain,
uint256 gasAmount,
uint256 gasLimit,
uint256 payment
);
event GasOracleSet(uint32 indexed remoteDomain, address gasOracle);
event BeneficiarySet(address beneficiary);
event DestinationGasConfigSet(
uint32 remoteDomain,
address gasOracle,
uint96 gasOverhead
);
function setUp() public {
blockNumber = block.number;
igp = new InterchainGasPaymaster();
igp.initialize(address(this), beneficiary);
oracle = new StorageGasOracle();
setGasOracle(testDestinationDomain, address(oracle));
testOracle = new StorageGasOracle();
setTestDestinationGasConfig(
testDestinationDomain,
testOracle,
testGasOverhead
);
testEncodedMessage = _encodeTestMessage();
}
@ -72,97 +81,211 @@ contract InterchainGasPaymasterTest is Test {
igp.initialize(address(this), beneficiary);
}
// ============ quoteDispatch ============
function testQuoteDispatch_defaultGasLimit() public {
setRemoteGasData(
function testdestinationGasLimit(uint96 _gasOverhead) public {
setTestDestinationGasConfig(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
TEST_GAS_PRICE
testOracle,
_gasOverhead
);
assertEq(
igp.destinationGasLimit(testDestinationDomain, testGasLimit),
_gasOverhead + testGasLimit
);
// 150 (gas_price) * 69_420 (default_gas_limit) = 10_413_000
assertEq(igp.quoteDispatch("", testEncodedMessage), 10_413_000);
}
function testQuoteDispatch_customWithMetadata() public {
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
TEST_GAS_PRICE
function testdestinationGasLimit_whenOverheadNotSet(uint32 _otherDomains)
public
{
vm.assume(_otherDomains != testDestinationDomain);
assertEq(
igp.destinationGasLimit(_otherDomains, testGasLimit),
testGasLimit
);
}
bytes memory metadata = StandardHookMetadata.formatMetadata(
0,
uint256(testGasAmount), // gas limit
testRefundAddress, // refund address,
bytes("")
);
// 150 * 300_000 = 45_000_000
assertEq(igp.quoteDispatch(metadata, testEncodedMessage), 45_000_000);
// ============ setBeneficiary ============
function testSetBeneficiary() public {
address _newBeneficiary = address(0xbeeeeee);
vm.expectEmit(true, false, false, true);
emit BeneficiarySet(_newBeneficiary);
igp.setBeneficiary(_newBeneficiary);
assertEq(igp.beneficiary(), _newBeneficiary);
}
// ============ postDispatch ============
function testSetBeneficiaryRevertsIfNotOwner() public {
address _newBeneficiary = address(0xbeeeeee);
function testPostDispatch_defaultGasLimit() public {
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
// Repurpose the refund address as a non-owner to prank as
vm.prank(testRefundAddress);
vm.expectRevert("Ownable: caller is not the owner");
igp.setBeneficiary(_newBeneficiary);
}
// ============ getExchangeRateAndGasPrice ============
function testGetExchangeRateAndGasPrice() public {
uint128 _tokenExchangeRate = 1 * TEST_EXCHANGE_RATE;
// 1 wei gas price
uint128 _gasPrice = 1;
setRemoteGasData(testDestinationDomain, _tokenExchangeRate, _gasPrice);
(uint128 _actualTokenExchangeRate, uint128 _actualGasPrice) = igp
.getExchangeRateAndGasPrice(testDestinationDomain);
assertEq(_actualTokenExchangeRate, _tokenExchangeRate);
assertEq(_actualGasPrice, _gasPrice);
}
function testGetExchangeRateAndGasPriceRevertsIfNoGasOracleSet() public {
uint32 _unknownDomain = 22222;
vm.expectRevert("Configured IGP doesn't support domain 22222");
igp.getExchangeRateAndGasPrice(_unknownDomain);
}
// ============ setDestinationGasConfigs ============
function testSetDestinationGasConfigs(
uint32 _domain1,
uint32 _domain2,
uint96 _gasOverhead1,
uint96 _gasOverhead2
) public {
vm.assume(_domain1 != _domain2);
StorageGasOracle oracle1 = new StorageGasOracle();
StorageGasOracle oracle2 = new StorageGasOracle();
InterchainGasPaymaster.GasParam[]
memory params = new InterchainGasPaymaster.GasParam[](2);
params[0] = InterchainGasPaymaster.GasParam(
_domain1,
InterchainGasPaymaster.DomainGasConfig(oracle1, _gasOverhead1)
);
params[1] = InterchainGasPaymaster.GasParam(
_domain2,
InterchainGasPaymaster.DomainGasConfig(oracle2, _gasOverhead2)
);
uint256 _igpBalanceBefore = address(igp).balance;
uint256 _refundAddressBalanceBefore = address(this).balance;
uint256 _quote = igp.quoteGasPayment(testDestinationDomain, 69_420);
// Data = remoteDomain, gasOracle, gasOverhead
vm.expectEmit(false, false, false, true, address(igp));
emit DestinationGasConfigSet(
params[0].remoteDomain,
address(params[0].config.gasOracle),
params[0].config.gasOverhead
);
vm.expectEmit(false, false, false, true, address(igp));
emit DestinationGasConfigSet(
params[1].remoteDomain,
address(params[1].config.gasOracle),
params[1].config.gasOverhead
);
uint256 _overpayment = 21000;
igp.setDestinationGasConfigs(params);
igp.postDispatch{value: _quote + _overpayment}("", testEncodedMessage);
(IGasOracle actualOracle1, uint96 actualGasOverhead1) = igp
.destinationGasConfigs(_domain1);
assertEq(address(actualOracle1), address(oracle1));
assertEq(actualGasOverhead1, _gasOverhead1);
uint256 _igpBalanceAfter = address(igp).balance;
uint256 _refundAddressBalanceAfter = address(this).balance;
assertEq(_igpBalanceAfter - _igpBalanceBefore, _quote);
assertEq(
_refundAddressBalanceBefore - _refundAddressBalanceAfter,
_quote
(IGasOracle actualOracle2, uint96 actualGasOverhead2) = igp
.destinationGasConfigs(_domain2);
assertEq(address(actualOracle2), address(oracle2));
assertEq(actualGasOverhead2, _gasOverhead2);
}
function testSetDestinationGasConfigs_reverts_notOwner(
uint32 _domain1,
uint32 _domain2,
uint96 _gasOverhead1,
uint96 _gasOverhead2
) public {
InterchainGasPaymaster.GasParam[]
memory params = new InterchainGasPaymaster.GasParam[](2);
params[0] = InterchainGasPaymaster.GasParam(
_domain1,
InterchainGasPaymaster.DomainGasConfig(testOracle, _gasOverhead1)
);
params[1] = InterchainGasPaymaster.GasParam(
_domain2,
InterchainGasPaymaster.DomainGasConfig(testOracle, _gasOverhead2)
);
vm.expectRevert("Ownable: caller is not the owner");
vm.prank(ALICE);
igp.setDestinationGasConfigs(params);
}
function testPostDispatch_customWithMetadata() public {
// ============ quoteGasPayment ============
function testQuoteGasPaymentSimilarExchangeRate() public {
// Testing when exchange rates are relatively close
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
2 * 1e9, // 0.2 exchange rate (remote token less valuable)
TEST_GAS_PRICE * 1e9 // 150 gwei gas price
);
uint256 _igpBalanceBefore = address(igp).balance;
uint256 _refundAddressBalanceBefore = testRefundAddress.balance;
uint256 _quote = igp.quoteGasPayment(
testDestinationDomain,
testGasAmount
// 300,000 destination gas
// 150 gwei = 150000000000 wei
// 300,000 * 150000000000 = 45000000000000000 (0.045 remote eth)
// Using the 0.2 token exchange rate, meaning the local native token
// is 5x more valuable than the remote token:
// 45000000000000000 * 0.2 = 9000000000000000 (0.009 local eth)
assertEq(
igp.quoteGasPayment(testDestinationDomain, testGasLimit),
9000000000000000
);
}
uint256 _overpayment = 25000;
bytes memory metadata = StandardHookMetadata.formatMetadata(
0,
uint256(testGasAmount), // gas limit
testRefundAddress, // refund address
bytes("")
function testQuoteGasPaymentRemoteVeryExpensive() public {
// Testing when the remote token is much more valuable & there's a super high gas price
setRemoteGasData(
testDestinationDomain,
5000 * TEST_EXCHANGE_RATE,
1500 * 1e9 // 1500 gwei gas price
);
bytes memory message = _encodeTestMessage();
igp.postDispatch{value: _quote + _overpayment}(metadata, message);
// 300,000 destination gas
// 1500 gwei = 1500000000000 wei
// 300,000 * 1500000000000 = 450000000000000000 (0.45 remote eth)
// Using the 5000 token exchange rate, meaning the remote native token
// is 5000x more valuable than the local token:
// 450000000000000000 * 5000 = 2250000000000000000000 (2250 local eth)
assertEq(
igp.quoteGasPayment(testDestinationDomain, testGasLimit),
2250000000000000000000
);
}
uint256 _igpBalanceAfter = address(igp).balance;
uint256 _refundAddressBalanceAfter = testRefundAddress.balance;
function testQuoteGasPaymentRemoteVeryCheap() public {
// Testing when the remote token is much less valuable & there's a low gas price
setRemoteGasData(
testDestinationDomain,
4 * 1e8, // 0.04 exchange rate (remote token much less valuable)
1 * 1e8 // 0.1 gwei gas price
);
assertEq(_igpBalanceAfter - _igpBalanceBefore, _quote);
// 300,000 destination gas
// 0.1 gwei = 100000000 wei
// 300,000 * 100000000 = 30000000000000 (0.00003 remote eth)
// Using the 0.04 token exchange rate, meaning the remote native token
// is 0.04x the price of the local token:
// 30000000000000 * 0.04 = 1200000000000 (0.0000012 local eth)
assertEq(
_refundAddressBalanceAfter - _refundAddressBalanceBefore,
_overpayment
igp.quoteGasPayment(testDestinationDomain, testGasLimit),
1200000000000
);
}
function testQuoteGasPaymentRevertsIfNoGasOracleSet() public {
uint32 _unknownDomain = 22222;
vm.expectRevert("Configured IGP doesn't support domain 22222");
igp.quoteGasPayment(_unknownDomain, testGasLimit);
}
// ============ payForGas ============
function testPayForGas() public {
@ -177,7 +300,7 @@ contract InterchainGasPaymasterTest is Test {
uint256 _quote = igp.quoteGasPayment(
testDestinationDomain,
testGasAmount
testGasLimit
);
// Intentional overpayment
uint256 _overpayment = 54321;
@ -186,13 +309,13 @@ contract InterchainGasPaymasterTest is Test {
emit GasPayment(
testMessageId,
testDestinationDomain,
testGasAmount,
testGasLimit,
_quote
);
igp.payForGas{value: _quote + _overpayment}(
testMessageId,
testDestinationDomain,
testGasAmount,
testGasLimit,
testRefundAddress
);
@ -206,133 +329,209 @@ contract InterchainGasPaymasterTest is Test {
);
}
function testPayForGasRevertsIfPaymentInsufficient() public {
function testPayForGas_reverts_ifPaymentInsufficient() public {
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
);
vm.expectRevert("insufficient interchain gas payment");
vm.expectRevert("IGP: insufficient interchain gas payment");
// Pay no msg.value
igp.payForGas{value: 0}(
testMessageId,
testDestinationDomain,
testGasAmount,
testGasLimit,
testRefundAddress
);
}
// ============ quoteGasPayment ============
function testQuoteGasPaymentSimilarExchangeRate() public {
// Testing when exchange rates are relatively close
function testPayForGas_withOverhead(uint128 _gasLimit, uint96 _gasOverhead)
public
{
setRemoteGasData(
testDestinationDomain,
2 * 1e9, // 0.2 exchange rate (remote token less valuable)
TEST_GAS_PRICE * 1e9 // 150 gwei gas price
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
);
setTestDestinationGasConfig(
testDestinationDomain,
testOracle,
_gasOverhead
);
// 300,000 destination gas
// 150 gwei = 150000000000 wei
// 300,000 * 150000000000 = 45000000000000000 (0.045 remote eth)
// Using the 0.2 token exchange rate, meaning the local native token
// is 5x more valuable than the remote token:
// 45000000000000000 * 0.2 = 9000000000000000 (0.009 local eth)
assertEq(
igp.quoteGasPayment(testDestinationDomain, testGasAmount),
9000000000000000
uint256 gasWithOverhead = uint256(_gasOverhead) + _gasLimit;
uint256 _quote = igp.quoteGasPayment(
testDestinationDomain,
gasWithOverhead
);
vm.deal(address(this), _quote);
uint256 _igpBalanceBefore = address(igp).balance;
vm.expectEmit(true, true, false, true);
emit GasPayment(
testMessageId,
testDestinationDomain,
gasWithOverhead,
_quote
);
igp.payForGas{value: _quote}(
testMessageId,
testDestinationDomain,
gasWithOverhead,
msg.sender
);
uint256 _igpBalanceAfter = address(igp).balance;
assertEq(_igpBalanceAfter - _igpBalanceBefore, _quote);
}
function testQuoteGasPaymentRemoteVeryExpensive() public {
// Testing when the remote token is much more valuable & there's a super high gas price
// ============ quoteDispatch ============
function testQuoteDispatch_defaultGasLimit() public {
setRemoteGasData(
testDestinationDomain,
5000 * TEST_EXCHANGE_RATE,
1500 * 1e9 // 1500 gwei gas price
1 * TEST_EXCHANGE_RATE,
TEST_GAS_PRICE
);
// 300,000 destination gas
// 1500 gwei = 1500000000000 wei
// 300,000 * 1500000000000 = 450000000000000000 (0.45 remote eth)
// Using the 5000 token exchange rate, meaning the remote native token
// is 5000x more valuable than the local token:
// 450000000000000000 * 5000 = 2250000000000000000000 (2250 local eth)
assertEq(
igp.quoteGasPayment(testDestinationDomain, testGasAmount),
2250000000000000000000
);
// 150 (gas_price) * 50_000 + 123_000 (default_gas_limit) = 25_950_000
assertEq(igp.quoteDispatch("", testEncodedMessage), 25_950_000);
}
function testQuoteGasPaymentRemoteVeryCheap() public {
// Testing when the remote token is much less valuable & there's a low gas price
function testQuoteDispatch_customWithMetadata() public {
setRemoteGasData(
testDestinationDomain,
4 * 1e8, // 0.04 exchange rate (remote token much less valuable)
1 * 1e8 // 0.1 gwei gas price
1 * TEST_EXCHANGE_RATE,
TEST_GAS_PRICE
);
// 300,000 destination gas
// 0.1 gwei = 100000000 wei
// 300,000 * 100000000 = 30000000000000 (0.00003 remote eth)
// Using the 0.04 token exchange rate, meaning the remote native token
// is 0.04x the price of the local token:
// 30000000000000 * 0.04 = 1200000000000 (0.0000012 local eth)
assertEq(
igp.quoteGasPayment(testDestinationDomain, testGasAmount),
1200000000000
bytes memory metadata = StandardHookMetadata.formatMetadata(
0,
uint256(testGasLimit), // gas limit
testRefundAddress, // refund address,
bytes("")
);
// 150 * (300_000 + 123_000) = 45_000_000
assertEq(igp.quoteDispatch(metadata, testEncodedMessage), 63_450_000);
}
function testQuoteGasPaymentRevertsIfNoGasOracleSet() public {
uint32 _unknownDomain = 22222;
// ============ postDispatch ============
vm.expectRevert("Configured IGP doesn't support domain 22222");
igp.quoteGasPayment(_unknownDomain, testGasAmount);
function testPostDispatch_defaultGasLimit() public {
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
);
uint256 _igpBalanceBefore = address(igp).balance;
uint256 _refundAddressBalanceBefore = address(this).balance;
uint256 _quote = igp.quoteDispatch("", testEncodedMessage);
uint256 _overpayment = 21000;
igp.postDispatch{value: _quote + _overpayment}("", testEncodedMessage);
uint256 _igpBalanceAfter = address(igp).balance;
uint256 _refundAddressBalanceAfter = address(this).balance;
assertEq(_igpBalanceAfter - _igpBalanceBefore, _quote);
assertEq(
_refundAddressBalanceBefore - _refundAddressBalanceAfter,
_quote
);
}
// ============ setGasOracles ============
function testPostDispatch_customWithMetadata() public {
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
);
function testSetGasOracle() public {
uint32 _remoteDomain = 22222;
uint256 _igpBalanceBefore = address(igp).balance;
uint256 _refundAddressBalanceBefore = testRefundAddress.balance;
vm.expectEmit(true, true, false, true);
emit GasOracleSet(_remoteDomain, address(oracle));
setGasOracle(_remoteDomain, address(oracle));
uint256 _overpayment = 25000;
bytes memory metadata = StandardHookMetadata.formatMetadata(
0,
uint256(testGasLimit), // gas limit
testRefundAddress, // refund address
bytes("")
);
bytes memory message = _encodeTestMessage();
assertEq(address(igp.gasOracles(_remoteDomain)), address(oracle));
}
uint256 _quote = igp.quoteDispatch(metadata, testEncodedMessage);
igp.postDispatch{value: _quote + _overpayment}(metadata, message);
function testSetGasOracleRevertsIfNotOwner() public {
uint32 _remoteDomain = 22222;
// Repurpose the refund address as a non-owner to prank as
vm.prank(testRefundAddress);
uint256 _igpBalanceAfter = address(igp).balance;
uint256 _refundAddressBalanceAfter = testRefundAddress.balance;
vm.expectRevert("Ownable: caller is not the owner");
setGasOracle(_remoteDomain, address(oracle));
assertEq(_igpBalanceAfter - _igpBalanceBefore, _quote);
assertEq(
_refundAddressBalanceAfter - _refundAddressBalanceBefore,
_overpayment
);
}
// ============ setBeneficiary ============
function testPostDispatch__withOverheadSet(uint96 _gasOverhead) public {
vm.deal(address(this), _gasOverhead + DEFAULT_GAS_USAGE);
function testSetBeneficiary() public {
address _newBeneficiary = address(0xbeeeeee);
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
);
setTestDestinationGasConfig(
testDestinationDomain,
testOracle,
_gasOverhead
);
vm.expectEmit(true, false, false, true);
emit BeneficiarySet(_newBeneficiary);
igp.setBeneficiary(_newBeneficiary);
uint256 _igpBalanceBefore = address(igp).balance;
uint256 _quote = igp.quoteGasPayment(
testDestinationDomain,
igp.destinationGasLimit(testDestinationDomain, DEFAULT_GAS_USAGE)
);
assertEq(igp.beneficiary(), _newBeneficiary);
igp.postDispatch{value: _quote}("", testEncodedMessage);
uint256 _igpBalanceAfter = address(igp).balance;
assertEq(_igpBalanceAfter - _igpBalanceBefore, _quote);
}
function testSetBeneficiaryRevertsIfNotOwner() public {
address _newBeneficiary = address(0xbeeeeee);
function testPostDispatch_customWithMetadataAndOverhead(uint96 _gasOverhead)
public
{
vm.deal(address(this), _gasOverhead + testGasLimit);
// Repurpose the refund address as a non-owner to prank as
vm.prank(testRefundAddress);
setRemoteGasData(
testDestinationDomain,
1 * TEST_EXCHANGE_RATE,
1 // 1 wei gas price
);
setTestDestinationGasConfig(
testDestinationDomain,
testOracle,
_gasOverhead
);
vm.expectRevert("Ownable: caller is not the owner");
igp.setBeneficiary(_newBeneficiary);
uint256 _igpBalanceBefore = address(igp).balance;
uint256 _quote = igp.quoteGasPayment(
testDestinationDomain,
igp.destinationGasLimit(testDestinationDomain, testGasLimit)
);
bytes memory metadata = StandardHookMetadata.formatMetadata(
0,
uint256(testGasLimit), // gas limit
testRefundAddress, // refund address
bytes("")
);
bytes memory message = _encodeTestMessage();
igp.postDispatch{value: _quote}(metadata, message);
uint256 _igpBalanceAfter = address(igp).balance;
assertEq(_igpBalanceAfter - _igpBalanceBefore, _quote);
}
// ============ claim ============
@ -346,12 +545,13 @@ contract InterchainGasPaymasterTest is Test {
// Pay some funds into the IGP
uint256 _quote = igp.quoteGasPayment(
testDestinationDomain,
testGasAmount
testGasLimit
);
console.log("quote", _quote);
igp.payForGas{value: _quote}(
testMessageId,
testDestinationDomain,
testGasAmount,
testGasLimit,
testRefundAddress
);
@ -363,37 +563,21 @@ contract InterchainGasPaymasterTest is Test {
assertEq(address(igp).balance, 0);
}
// ============ getExchangeRateAndGasPrice ============
function testGetExchangeRateAndGasPrice() public {
uint128 _tokenExchangeRate = 1 * TEST_EXCHANGE_RATE;
// 1 wei gas price
uint128 _gasPrice = 1;
setRemoteGasData(testDestinationDomain, _tokenExchangeRate, _gasPrice);
(uint128 _actualTokenExchangeRate, uint128 _actualGasPrice) = igp
.getExchangeRateAndGasPrice(testDestinationDomain);
assertEq(_actualTokenExchangeRate, _tokenExchangeRate);
assertEq(_actualGasPrice, _gasPrice);
}
function testGetExchangeRateAndGasPriceRevertsIfNoGasOracleSet() public {
uint32 _unknownDomain = 22222;
vm.expectRevert("Configured IGP doesn't support domain 22222");
igp.getExchangeRateAndGasPrice(_unknownDomain);
}
// ============ Helper functions ============
function setGasOracle(uint32 _remoteDomain, address _gasOracle) internal {
InterchainGasPaymaster.GasOracleConfig[]
memory _configs = new InterchainGasPaymaster.GasOracleConfig[](1);
_configs[0] = InterchainGasPaymaster.GasOracleConfig({
remoteDomain: _remoteDomain,
gasOracle: _gasOracle
});
igp.setGasOracles(_configs);
function setTestDestinationGasConfig(
uint32 _remoteDomain,
IGasOracle _gasOracle,
uint96 _gasOverhead
) internal {
InterchainGasPaymaster.GasParam[]
memory params = new InterchainGasPaymaster.GasParam[](1);
params[0] = InterchainGasPaymaster.GasParam(
_remoteDomain,
InterchainGasPaymaster.DomainGasConfig(_gasOracle, _gasOverhead)
);
igp.setDestinationGasConfigs(params);
}
function setRemoteGasData(
@ -401,7 +585,7 @@ contract InterchainGasPaymasterTest is Test {
uint128 _tokenExchangeRate,
uint128 _gasPrice
) internal {
oracle.setRemoteGasData(
testOracle.setRemoteGasData(
StorageGasOracle.RemoteGasDataConfig({
remoteDomain: _remoteDomain,
tokenExchangeRate: _tokenExchangeRate,

@ -1,146 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {OverheadIgp} from "../../contracts/hooks/igp/OverheadIgp.sol";
import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol";
contract OverheadIgpTest is Test {
OverheadIgp igp;
TestInterchainGasPaymaster innerIgp;
bytes32 constant testMessageId =
bytes32(
0xf00000000000000000000000000000000000000000000000000000000000000f
);
uint32 constant testDestinationDomain = 1234;
uint256 constant testGasOverhead = 123000;
uint256 constant testGasAmount = 50000;
address constant nonOwner = 0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe;
event InnerIgpSet(address innerIgp);
event DestinationGasOverheadSet(uint32 indexed domain, uint256 gasOverhead);
function setUp() public {
innerIgp = new TestInterchainGasPaymaster();
igp = new OverheadIgp(address(innerIgp));
}
function testInnerIgpSet() public {
assertEq(address(igp.innerIgp()), address(innerIgp));
}
function testPayForGas() public {
setTestDestinationGasOverhead();
uint256 testPayment = 123456789;
vm.expectCall(
address(innerIgp),
testPayment,
abi.encodeCall(
innerIgp.payForGas,
(
testMessageId,
testDestinationDomain,
testGasOverhead + testGasAmount,
msg.sender
)
)
);
igp.payForGas{value: testPayment}(
testMessageId,
testDestinationDomain,
testGasAmount,
msg.sender
);
}
function testQuoteGasPayment() public {
setTestDestinationGasOverhead();
vm.expectCall(
address(innerIgp),
abi.encodeCall(
innerIgp.quoteGasPayment,
(testDestinationDomain, testGasOverhead + testGasAmount)
)
);
igp.quoteGasPayment(testDestinationDomain, testGasAmount);
}
function testDestinationGasAmount() public {
setTestDestinationGasOverhead();
assertEq(
igp.destinationGasAmount(testDestinationDomain, testGasAmount),
testGasOverhead + testGasAmount
);
}
// Test that it doesn't revert, and just doesn't add any value to the
// provided gas amount
function testDestinationGasAmountWhenOverheadNotSet() public {
assertEq(
igp.destinationGasAmount(testDestinationDomain, testGasAmount),
testGasAmount
);
}
function testSetDestinationGasAmounts() public {
OverheadIgp.DomainConfig[]
memory configs = new OverheadIgp.DomainConfig[](2);
configs[0] = OverheadIgp.DomainConfig(
testDestinationDomain,
testGasOverhead
);
configs[1] = OverheadIgp.DomainConfig(4321, 432100);
// Topic 0 = event signature
// Topic 1 = indexed domain
// Topic 2 = not set
// Data = gas amount
vm.expectEmit(true, true, false, true);
emit DestinationGasOverheadSet(
configs[0].domain,
configs[0].gasOverhead
);
vm.expectEmit(true, true, false, true);
emit DestinationGasOverheadSet(
configs[1].domain,
configs[1].gasOverhead
);
igp.setDestinationGasOverheads(configs);
}
function testSetDestinationGasAmountsNotOwner() public {
OverheadIgp.DomainConfig[]
memory configs = new OverheadIgp.DomainConfig[](2);
configs[0] = OverheadIgp.DomainConfig(
testDestinationDomain,
testGasOverhead
);
configs[1] = OverheadIgp.DomainConfig(4321, 432100);
vm.expectRevert("Ownable: caller is not the owner");
vm.prank(nonOwner);
igp.setDestinationGasOverheads(configs);
}
// ============ Helper Functions ============
function setTestDestinationGasOverhead() internal {
OverheadIgp.DomainConfig[]
memory configs = new OverheadIgp.DomainConfig[](1);
configs[0] = OverheadIgp.DomainConfig(
testDestinationDomain,
testGasOverhead
);
igp.setDestinationGasOverheads(configs);
}
}

@ -192,7 +192,7 @@ describe('Router', async () => {
it('reverts on insufficient payment', async () => {
await expect(
router.dispatch(destination, body, { value: payment.sub(1) }),
).to.be.revertedWith('insufficient interchain gas payment');
).to.be.revertedWith('IGP: insufficient interchain gas payment');
});
it('reverts when dispatching a message to an unenrolled remote router', async () => {

@ -1,7 +1,7 @@
import {
ChainMap,
GasOracleContractType,
OverheadIgpConfig,
IgpConfig,
defaultMultisigIsmConfigs,
multisigIsmVerificationCost,
} from '@hyperlane-xyz/sdk';
@ -24,24 +24,21 @@ function getGasOracles(local: MainnetChains) {
);
}
export const igp: ChainMap<OverheadIgpConfig> = objMap(
owners,
(chain, owner) => {
return {
owner,
oracleKey: DEPLOYER_ADDRESS,
beneficiary: KEY_FUNDER_ADDRESS,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
exclude(chain, supportedChainNames).map((remote) => [
remote,
multisigIsmVerificationCost(
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
),
]),
),
upgrade: core[chain].upgrade,
};
},
);
export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, owner) => {
return {
owner,
oracleKey: DEPLOYER_ADDRESS,
beneficiary: KEY_FUNDER_ADDRESS,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
exclude(chain, supportedChainNames).map((remote) => [
remote,
multisigIsmVerificationCost(
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
),
]),
),
upgrade: core[chain].upgrade,
};
});

@ -1,7 +1,7 @@
import {
ChainMap,
GasOracleContractType,
OverheadIgpConfig,
IgpConfig,
multisigIsmVerificationCost,
} from '@hyperlane-xyz/sdk';
import { exclude, objMap } from '@hyperlane-xyz/utils';
@ -19,23 +19,20 @@ function getGasOracles(local: TestChains) {
);
}
export const igp: ChainMap<OverheadIgpConfig> = objMap(
owners,
(chain, owner) => {
return {
owner,
oracleKey: owner,
beneficiary: owner,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
exclude(chain, chainNames).map((remote) => [
remote,
multisigIsmVerificationCost(
multisigIsm[remote].threshold,
multisigIsm[remote].validators.length,
),
]),
),
};
},
);
export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, owner) => {
return {
owner,
oracleKey: owner,
beneficiary: owner,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
exclude(chain, chainNames).map((remote) => [
remote,
multisigIsmVerificationCost(
multisigIsm[remote].threshold,
multisigIsm[remote].validators.length,
),
]),
),
};
});

@ -1,7 +1,7 @@
import {
ChainMap,
GasOracleContractType,
OverheadIgpConfig,
IgpConfig,
defaultMultisigIsmConfigs,
multisigIsmVerificationCost,
} from '@hyperlane-xyz/sdk';
@ -19,23 +19,20 @@ function getGasOracles(local: TestnetChains) {
);
}
export const igp: ChainMap<OverheadIgpConfig> = objMap(
owners,
(chain, owner) => {
return {
owner,
oracleKey: owner,
beneficiary: owner,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
exclude(chain, supportedChainNames).map((remote) => [
remote,
multisigIsmVerificationCost(
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
),
]),
),
};
},
);
export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, owner) => {
return {
owner,
oracleKey: owner,
beneficiary: owner,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
exclude(chain, supportedChainNames).map((remote) => [
remote,
multisigIsmVerificationCost(
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
),
]),
),
};
});

@ -339,7 +339,7 @@ export async function getRouterConfig(
? await multiProvider.getSignerAddress(chain)
: owners[chain],
mailbox: core.getContracts(chain).mailbox.address,
hook: igp.getContracts(chain).defaultIsmInterchainGasPaymaster.address,
hook: igp.getContracts(chain).interchainGasPaymaster.address,
};
}
return config;

@ -5,10 +5,10 @@ import {
ChainMetadata,
ChainName,
CoreConfig,
HookConfig,
HyperlaneEnvironment,
MerkleTreeHookConfig,
IgpConfig,
MultiProvider,
OverheadIgpConfig,
} from '@hyperlane-xyz/sdk';
import { Address } from '@hyperlane-xyz/utils';
@ -37,8 +37,8 @@ export type EnvironmentConfig = {
// Each AgentConfig, keyed by the context
agents: Partial<Record<Contexts, RootAgentConfig>>;
core: ChainMap<CoreConfig>;
hook?: ChainMap<MerkleTreeHookConfig>;
igp: ChainMap<OverheadIgpConfig>;
hook?: ChainMap<HookConfig>;
igp: ChainMap<IgpConfig>;
owners: ChainMap<Address>;
infra: InfrastructureConfig;
getMultiProvider: (

@ -1,15 +1,17 @@
import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core';
import { BigNumber, ethers } from 'ethers';
import { InterchainGasPaymaster } from '@hyperlane-xyz/core';
import {
ChainMap,
ChainName,
HyperlaneIgp,
HyperlaneIgpChecker,
IgpBeneficiaryViolation,
IgpConfig,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
OverheadIgpConfig,
} from '@hyperlane-xyz/sdk';
import { Address } from '@hyperlane-xyz/utils';
@ -17,7 +19,7 @@ import { HyperlaneAppGovernor } from '../govern/HyperlaneAppGovernor';
export class HyperlaneIgpGovernor extends HyperlaneAppGovernor<
HyperlaneIgp,
OverheadIgpConfig
IgpConfig
> {
constructor(checker: HyperlaneIgpChecker, owners: ChainMap<Address>) {
super(checker, owners);
@ -53,7 +55,7 @@ export class HyperlaneIgpGovernor extends HyperlaneAppGovernor<
case IgpViolationType.GasOracles: {
const gasOraclesViolation = violation as IgpGasOraclesViolation;
const configs: InterchainGasPaymaster.GasOracleConfigStruct[] = [];
const configs: InterchainGasPaymaster.GasParamStruct[] = [];
for (const [remote, expected] of Object.entries(
gasOraclesViolation.expected,
)) {
@ -61,14 +63,17 @@ export class HyperlaneIgpGovernor extends HyperlaneAppGovernor<
configs.push({
remoteDomain: remoteId,
gasOracle: expected,
config: {
gasOracle: expected,
gasOverhead: 0, // TODO: fix to use the retrieved gas overhead
},
});
}
this.pushCall(gasOraclesViolation.chain, {
to: gasOraclesViolation.contract.address,
data: gasOraclesViolation.contract.interface.encodeFunctionData(
'setGasOracles',
'setDestinationGasConfigs',
[configs],
),
description: `Setting ${Object.keys(gasOraclesViolation.expected)
@ -84,20 +89,21 @@ export class HyperlaneIgpGovernor extends HyperlaneAppGovernor<
}
case IgpViolationType.Overhead: {
const overheadViolation = violation as IgpOverheadViolation;
const configs: OverheadIgp.DomainConfigStruct[] = Object.entries(
const configs: InterchainGasPaymaster.GasParamStruct[] = Object.entries(
violation.expected,
).map(
([remote, gasOverhead]) =>
({
domain: this.checker.multiProvider.getDomainId(remote),
gasOverhead: gasOverhead,
} as OverheadIgp.DomainConfigStruct),
);
).map(([remote, gasOverhead]) => ({
remoteDomain: this.checker.multiProvider.getDomainId(remote),
// TODO: fix to use the retrieved gas oracle
config: {
gasOracle: ethers.constants.AddressZero,
gasOverhead: BigNumber.from(gasOverhead),
},
}));
this.pushCall(violation.chain, {
to: overheadViolation.contract.address,
data: overheadViolation.contract.interface.encodeFunctionData(
'setDestinationGasOverheads',
'setDestinationGasConfigs',
[configs],
),
description: `Setting ${Object.keys(violation.expected)

@ -84,7 +84,7 @@ export class HyperlaneIgp extends HyperlaneApp<IgpFactories> {
destination: ChainName,
gasAmount: BigNumber,
): Promise<BigNumber> {
const igp = this.getContracts(origin).defaultIsmInterchainGasPaymaster;
const igp = this.getContracts(origin).interchainGasPaymaster;
return this.quoteGasPaymentForIgp(
origin,
destination,

@ -11,15 +11,15 @@ import { HyperlaneIgp } from './HyperlaneIgp';
import {
GasOracleContractType,
IgpBeneficiaryViolation,
IgpConfig,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolationType,
OverheadIgpConfig,
} from './types';
export class HyperlaneIgpChecker extends HyperlaneAppChecker<
HyperlaneIgp,
OverheadIgpConfig
IgpConfig
> {
async checkChain(chain: ChainName): Promise<void> {
await this.checkDomainOwnership(chain);
@ -72,8 +72,8 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker<
await this.checkBytecode(
chain,
'OverheadIGP',
contracts.defaultIsmInterchainGasPaymaster.address,
'InterchainGasPaymaster',
contracts.interchainGasPaymaster.address,
[BytecodeHash.OVERHEAD_IGP_BYTECODE_HASH],
(bytecode) =>
// Remove the address of the wrapped IGP from the bytecode
@ -88,7 +88,7 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker<
async checkOverheadInterchainGasPaymaster(local: ChainName): Promise<void> {
const coreContracts = this.app.getContracts(local);
const defaultIsmIgp = coreContracts.defaultIsmInterchainGasPaymaster;
const defaultIsmIgp = coreContracts.interchainGasPaymaster;
// Construct the violation, updating the actual & expected
// objects as violations are found.
@ -108,8 +108,9 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker<
const expectedOverhead = this.configMap[local].overhead[remote];
const remoteId = this.multiProvider.getDomainId(remote);
const existingOverhead = await defaultIsmIgp.destinationGasOverhead(
const existingOverhead = await defaultIsmIgp.destinationGasLimit(
remoteId,
0,
);
if (!existingOverhead.eq(expectedOverhead)) {
const remoteChain = remote as ChainName;
@ -144,7 +145,8 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker<
const remotes = this.app.remoteChains(local);
for (const remote of remotes) {
const remoteId = this.multiProvider.getDomainId(remote);
const actualGasOracle = await igp.gasOracles(remoteId);
const destinationGasConfigs = await igp.destinationGasConfigs(remoteId);
const actualGasOracle = destinationGasConfigs.gasOracle;
const expectedGasOracle = this.getGasOracleAddress(local, remote);
if (eqAddress(actualGasOracle, expectedGasOracle)) {

@ -3,13 +3,12 @@ import { ethers } from 'ethers';
import {
InterchainGasPaymaster,
OverheadIgp,
ProxyAdmin,
StorageGasOracle,
TimelockController,
TimelockController__factory,
} from '@hyperlane-xyz/core';
import { Address, eqAddress } from '@hyperlane-xyz/utils';
import { eqAddress } from '@hyperlane-xyz/utils';
import { HyperlaneContracts } from '../contracts/types';
import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
@ -17,10 +16,10 @@ import { MultiProvider } from '../providers/MultiProvider';
import { ChainName } from '../types';
import { IgpFactories, igpFactories } from './contracts';
import { IgpConfig, OverheadIgpConfig } from './types';
import { IgpConfig } from './types';
export class HyperlaneIgpDeployer extends HyperlaneDeployer<
OverheadIgpConfig,
IgpConfig,
IgpFactories
> {
constructor(multiProvider: MultiProvider) {
@ -45,80 +44,45 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer<
[owner, beneficiary],
);
const gasOracleConfigsToSet: InterchainGasPaymaster.GasOracleConfigStruct[] =
[];
const gasParamsToSet: InterchainGasPaymaster.GasParamStruct[] = [];
const remotes = Object.keys(config.gasOracleType);
for (const remote of remotes) {
const remoteId = this.multiProvider.getDomainId(remote);
const currentGasOracle = await igp.gasOracles(remoteId);
if (!eqAddress(currentGasOracle, storageGasOracle.address)) {
gasOracleConfigsToSet.push({
const newGasOverhead = config.overhead[remote];
const currentGasConfig = await igp.destinationGasConfigs(remoteId);
if (
!eqAddress(currentGasConfig.gasOracle, storageGasOracle.address) ||
!currentGasConfig.gasOverhead.eq(newGasOverhead)
) {
gasParamsToSet.push({
remoteDomain: remoteId,
gasOracle: storageGasOracle.address,
config: {
gasOverhead: newGasOverhead,
gasOracle: storageGasOracle.address,
},
});
}
}
if (gasOracleConfigsToSet.length > 0) {
if (gasParamsToSet.length > 0) {
await this.runIfOwner(chain, igp, async () =>
this.multiProvider.handleTx(
chain,
igp.setGasOracles(gasOracleConfigsToSet),
igp.setDestinationGasConfigs(gasParamsToSet),
),
);
}
return igp;
}
async deployOverheadIgp(
chain: ChainName,
interchainGasPaymasterAddress: Address,
config: OverheadIgpConfig,
): Promise<OverheadIgp> {
const overheadInterchainGasPaymaster = await this.deployContract(
chain,
'defaultIsmInterchainGasPaymaster',
[interchainGasPaymasterAddress],
);
// Only set gas overhead configs if they differ from what's on chain
const configs: OverheadIgp.DomainConfigStruct[] = [];
const remotes = Object.keys(config.overhead);
for (const remote of remotes) {
const remoteDomain = this.multiProvider.getDomainId(remote);
const gasOverhead = config.overhead[remote];
const existingOverhead =
await overheadInterchainGasPaymaster.destinationGasOverhead(
remoteDomain,
);
if (!existingOverhead.eq(gasOverhead)) {
configs.push({ domain: remoteDomain, gasOverhead });
}
}
if (configs.length > 0) {
await this.runIfOwner(chain, overheadInterchainGasPaymaster, () =>
this.multiProvider.handleTx(
chain,
overheadInterchainGasPaymaster.setDestinationGasOverheads(
configs,
this.multiProvider.getTransactionOverrides(chain),
),
),
);
}
return overheadInterchainGasPaymaster;
}
async deployStorageGasOracle(chain: ChainName): Promise<StorageGasOracle> {
return this.deployContract(chain, 'storageGasOracle', []);
}
async deployContracts(
chain: ChainName,
config: OverheadIgpConfig,
config: IgpConfig,
): Promise<HyperlaneContracts<IgpFactories>> {
// NB: To share ProxyAdmins with HyperlaneCore, ensure the ProxyAdmin
// is loaded into the contract cache.
@ -152,13 +116,7 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer<
storageGasOracle,
config,
);
const overheadIgp = await this.deployOverheadIgp(
chain,
interchainGasPaymaster.address,
config,
);
await this.transferOwnershipOfContracts(chain, config.owner, {
overheadIgp,
interchainGasPaymaster,
});
@ -173,7 +131,6 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer<
timelockController,
storageGasOracle,
interchainGasPaymaster,
defaultIsmInterchainGasPaymaster: overheadIgp,
};
}
}

@ -1,6 +1,5 @@
import {
InterchainGasPaymaster__factory,
OverheadIgp__factory,
StorageGasOracle__factory,
} from '@hyperlane-xyz/core';
@ -8,7 +7,6 @@ import { proxiedFactories } from '../router/types';
export const igpFactories = {
interchainGasPaymaster: new InterchainGasPaymaster__factory(),
defaultIsmInterchainGasPaymaster: new OverheadIgp__factory(),
storageGasOracle: new StorageGasOracle__factory(),
...proxiedFactories,
};

@ -1,6 +1,6 @@
import { BigNumber } from 'ethers';
import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core';
import { InterchainGasPaymaster } from '@hyperlane-xyz/core';
import type { Address } from '@hyperlane-xyz/utils';
import { UpgradeConfig } from '../deploy/proxy';
@ -17,9 +17,6 @@ export type IgpConfig = {
gasOracleType: ChainMap<GasOracleContractType>;
oracleKey: Address;
upgrade?: UpgradeConfig;
};
export type OverheadIgpConfig = IgpConfig & {
overhead: ChainMap<number>;
};
@ -50,7 +47,7 @@ export interface IgpGasOraclesViolation extends IgpViolation {
export interface IgpOverheadViolation extends IgpViolation {
subType: IgpViolationType.Overhead;
contract: OverheadIgp;
contract: InterchainGasPaymaster;
actual: ChainMap<BigNumber>;
expected: ChainMap<BigNumber>;
}

@ -106,7 +106,6 @@ export {
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
OverheadIgpConfig,
} from './gas/types';
export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer';
export { HookConfig, HookType, MerkleTreeHookConfig } from './hook/types';

Loading…
Cancel
Save