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 * @title InterchainGasPaymaster
* @notice Manages payments on a source chain to cover gas costs of relaying * @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 contract InterchainGasPaymaster is
IInterchainGasPaymaster, IInterchainGasPaymaster,
@ -47,12 +50,12 @@ contract InterchainGasPaymaster is
/// @notice The scale of gas oracle token exchange rates. /// @notice The scale of gas oracle token exchange rates.
uint256 internal constant TOKEN_EXCHANGE_RATE_SCALE = 1e10; uint256 internal constant TOKEN_EXCHANGE_RATE_SCALE = 1e10;
/// @notice default for user call if metadata not provided /// @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 ============ // ============ Public Storage ============
/// @notice Keyed by remote domain, the gas oracle to use for the domain. /// @notice Destination domain => gas oracle and overhead gas amount.
mapping(uint32 => IGasOracle) public gasOracles; mapping(uint32 => DomainGasConfig) public destinationGasConfigs;
/// @notice The benficiary that can receive native tokens paid into this contract. /// @notice The benficiary that can receive native tokens paid into this contract.
address public beneficiary; address public beneficiary;
@ -63,8 +66,13 @@ contract InterchainGasPaymaster is
* @notice Emitted when the gas oracle for a remote domain is set. * @notice Emitted when the gas oracle for a remote domain is set.
* @param remoteDomain The remote domain. * @param remoteDomain The remote domain.
* @param gasOracle The gas oracle. * @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. * @notice Emitted when the beneficiary is set.
@ -72,9 +80,14 @@ contract InterchainGasPaymaster is
*/ */
event BeneficiarySet(address beneficiary); event BeneficiarySet(address beneficiary);
struct GasOracleConfig { struct DomainGasConfig {
IGasOracle gasOracle;
uint96 gasOverhead;
}
struct GasParam {
uint32 remoteDomain; uint32 remoteDomain;
address gasOracle; DomainGasConfig config;
} }
// ============ External Functions ============ // ============ External Functions ============
@ -99,20 +112,24 @@ contract InterchainGasPaymaster is
function claim() external { function claim() external {
// Transfer the entire balance to the beneficiary. // Transfer the entire balance to the beneficiary.
(bool success, ) = beneficiary.call{value: address(this).balance}(""); (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. * @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. * @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 external
onlyOwner onlyOwner
{ {
uint256 _len = _configs.length; uint256 _len = _configs.length;
for (uint256 i = 0; i < _len; i++) { 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. * Callers should be aware that this may present reentrancy issues.
* @param _messageId The ID of the message to pay for. * @param _messageId The ID of the message to pay for.
* @param _destinationDomain The domain of the message's destination chain. * @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. * @param _refundAddress The address to refund any overpayment to.
*/ */
function payForGas( function payForGas(
bytes32 _messageId, bytes32 _messageId,
uint32 _destinationDomain, uint32 _destinationDomain,
uint256 _gasAmount, uint256 _gasLimit,
address _refundAddress address _refundAddress
) public payable override { ) public payable override {
uint256 _requiredPayment = quoteGasPayment( uint256 _requiredPayment = quoteGasPayment(
_destinationDomain, _destinationDomain,
_gasAmount _gasLimit
); );
require( require(
msg.value >= _requiredPayment, msg.value >= _requiredPayment,
"insufficient interchain gas payment" "IGP: insufficient interchain gas payment"
); );
uint256 _overpayment = msg.value - _requiredPayment; uint256 _overpayment = msg.value - _requiredPayment;
if (_overpayment > 0) { if (_overpayment > 0) {
@ -159,7 +176,7 @@ contract InterchainGasPaymaster is
emit GasPayment( emit GasPayment(
_messageId, _messageId,
_destinationDomain, _destinationDomain,
_gasAmount, _gasLimit,
_requiredPayment _requiredPayment
); );
} }
@ -167,10 +184,10 @@ contract InterchainGasPaymaster is
/** /**
* @notice Quotes the amount of native tokens to pay for interchain gas. * @notice Quotes the amount of native tokens to pay for interchain gas.
* @param _destinationDomain The domain of the message's destination chain. * @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. * @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 public
view view
virtual virtual
@ -184,7 +201,7 @@ contract InterchainGasPaymaster is
) = getExchangeRateAndGasPrice(_destinationDomain); ) = getExchangeRateAndGasPrice(_destinationDomain);
// The total cost quoted in destination chain's native token. // 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. // Convert to the local native token.
return return
@ -205,7 +222,8 @@ contract InterchainGasPaymaster is
override override
returns (uint128 tokenExchangeRate, uint128 gasPrice) returns (uint128 tokenExchangeRate, uint128 gasPrice)
{ {
IGasOracle _gasOracle = gasOracles[_destinationDomain]; IGasOracle _gasOracle = destinationGasConfigs[_destinationDomain]
.gasOracle;
require( require(
address(_gasOracle) != address(0), address(_gasOracle) != address(0),
@ -218,6 +236,25 @@ contract InterchainGasPaymaster is
return _gasOracle.getExchangeRateAndGasPrice(_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 ============ // ============ Internal Functions ============
/// @inheritdoc AbstractPostDispatchHook /// @inheritdoc AbstractPostDispatchHook
@ -225,9 +262,15 @@ contract InterchainGasPaymaster is
internal internal
override override
{ {
uint256 gasLimit = metadata.gasLimit(DEFAULT_GAS_USAGE); payForGas(
address refundAddress = metadata.refundAddress(message.senderAddress()); message.id(),
payForGas(message.id(), message.destination(), gasLimit, refundAddress); message.destination(),
destinationGasLimit(
message.destination(),
metadata.gasLimit(DEFAULT_GAS_USAGE)
),
metadata.refundAddress(message.senderAddress())
);
} }
/// @inheritdoc AbstractPostDispatchHook /// @inheritdoc AbstractPostDispatchHook
@ -237,8 +280,14 @@ contract InterchainGasPaymaster is
override override
returns (uint256) returns (uint256)
{ {
uint256 gasLimit = metadata.gasLimit(DEFAULT_GAS_USAGE); return
return quoteGasPayment(message.destination(), gasLimit); 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 _remoteDomain The remote domain.
* @param _gasOracle The gas oracle. * @param _gasOracle The gas oracle.
* @param _gasOverhead The destination gas overhead.
*/ */
function _setGasOracle(uint32 _remoteDomain, address _gasOracle) internal { function _setDestinationGasConfig(
gasOracles[_remoteDomain] = IGasOracle(_gasOracle); uint32 _remoteDomain,
emit GasOracleSet(_remoteDomain, _gasOracle); 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); setDestinationGas(originRouter, remoteDomain, gas);
uint256 requiredPayment = gas * gasPrice; uint256 requiredPayment = gas * gasPrice;
vm.expectRevert("insufficient interchain gas payment"); vm.expectRevert("IGP: insufficient interchain gas payment");
originRouter.dispatch{value: requiredPayment - 1}(remoteDomain, ""); originRouter.dispatch{value: requiredPayment - 1}(remoteDomain, "");
vm.deal(address(this), requiredPayment + 1); vm.deal(address(this), requiredPayment + 1);

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save