diff --git a/solidity/contracts/hooks/igp/InterchainGasPaymaster.sol b/solidity/contracts/hooks/igp/InterchainGasPaymaster.sol index 512f36663..c6cf487db 100644 --- a/solidity/contracts/hooks/igp/InterchainGasPaymaster.sol +++ b/solidity/contracts/hooks/igp/InterchainGasPaymaster.sol @@ -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 + ); } } diff --git a/solidity/contracts/hooks/igp/OverheadIgp.sol b/solidity/contracts/hooks/igp/OverheadIgp.sol deleted file mode 100644 index a7268013e..000000000 --- a/solidity/contracts/hooks/igp/OverheadIgp.sol +++ /dev/null @@ -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); - } -} diff --git a/solidity/test/GasRouter.t.sol b/solidity/test/GasRouter.t.sol index 9b87b6c38..aaf73b2a3 100644 --- a/solidity/test/GasRouter.t.sol +++ b/solidity/test/GasRouter.t.sol @@ -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); diff --git a/solidity/test/igps/InterchainGasPaymaster.t.sol b/solidity/test/igps/InterchainGasPaymaster.t.sol index cd2dc3f06..66eec452f 100644 --- a/solidity/test/igps/InterchainGasPaymaster.t.sol +++ b/solidity/test/igps/InterchainGasPaymaster.t.sol @@ -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, diff --git a/solidity/test/igps/OverheadIgp.t.sol b/solidity/test/igps/OverheadIgp.t.sol deleted file mode 100644 index 769c3d1d5..000000000 --- a/solidity/test/igps/OverheadIgp.t.sol +++ /dev/null @@ -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); - } -} diff --git a/solidity/test/router.test.ts b/solidity/test/router.test.ts index 9163d8c7f..e7690254a 100644 --- a/solidity/test/router.test.ts +++ b/solidity/test/router.test.ts @@ -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 () => { diff --git a/typescript/infra/config/environments/mainnet2/igp.ts b/typescript/infra/config/environments/mainnet2/igp.ts index 6ef8545c8..e2410fe5e 100644 --- a/typescript/infra/config/environments/mainnet2/igp.ts +++ b/typescript/infra/config/environments/mainnet2/igp.ts @@ -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 = 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 = 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, + }; +}); diff --git a/typescript/infra/config/environments/test/igp.ts b/typescript/infra/config/environments/test/igp.ts index 31f5e5baf..833c86c24 100644 --- a/typescript/infra/config/environments/test/igp.ts +++ b/typescript/infra/config/environments/test/igp.ts @@ -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 = 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 = 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, + ), + ]), + ), + }; +}); diff --git a/typescript/infra/config/environments/testnet3/igp.ts b/typescript/infra/config/environments/testnet3/igp.ts index 9ce151551..047ebf630 100644 --- a/typescript/infra/config/environments/testnet3/igp.ts +++ b/typescript/infra/config/environments/testnet3/igp.ts @@ -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 = 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 = 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, + ), + ]), + ), + }; +}); diff --git a/typescript/infra/scripts/utils.ts b/typescript/infra/scripts/utils.ts index c2cbf41e8..8b553b64d 100644 --- a/typescript/infra/scripts/utils.ts +++ b/typescript/infra/scripts/utils.ts @@ -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; diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index 623bfc88c..b37686ef2 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -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>; core: ChainMap; - hook?: ChainMap; - igp: ChainMap; + hook?: ChainMap; + igp: ChainMap; owners: ChainMap
; infra: InfrastructureConfig; getMultiProvider: ( diff --git a/typescript/infra/src/govern/HyperlaneIgpGovernor.ts b/typescript/infra/src/govern/HyperlaneIgpGovernor.ts index 890002509..46a9ec0ff 100644 --- a/typescript/infra/src/govern/HyperlaneIgpGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneIgpGovernor.ts @@ -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
) { 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) diff --git a/typescript/sdk/src/gas/HyperlaneIgp.ts b/typescript/sdk/src/gas/HyperlaneIgp.ts index f08b4dd32..0f7c0e46e 100644 --- a/typescript/sdk/src/gas/HyperlaneIgp.ts +++ b/typescript/sdk/src/gas/HyperlaneIgp.ts @@ -84,7 +84,7 @@ export class HyperlaneIgp extends HyperlaneApp { destination: ChainName, gasAmount: BigNumber, ): Promise { - const igp = this.getContracts(origin).defaultIsmInterchainGasPaymaster; + const igp = this.getContracts(origin).interchainGasPaymaster; return this.quoteGasPaymentForIgp( origin, destination, diff --git a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts index 05d119384..6278ee2f6 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts @@ -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 { 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 { 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)) { diff --git a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts index 55c5254a9..6fa1947ed 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts @@ -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 { - 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 { return this.deployContract(chain, 'storageGasOracle', []); } async deployContracts( chain: ChainName, - config: OverheadIgpConfig, + config: IgpConfig, ): Promise> { // 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, }; } } diff --git a/typescript/sdk/src/gas/contracts.ts b/typescript/sdk/src/gas/contracts.ts index 360e29152..e33bfb5ed 100644 --- a/typescript/sdk/src/gas/contracts.ts +++ b/typescript/sdk/src/gas/contracts.ts @@ -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, }; diff --git a/typescript/sdk/src/gas/types.ts b/typescript/sdk/src/gas/types.ts index 8c7f62174..65a64a089 100644 --- a/typescript/sdk/src/gas/types.ts +++ b/typescript/sdk/src/gas/types.ts @@ -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; oracleKey: Address; upgrade?: UpgradeConfig; -}; - -export type OverheadIgpConfig = IgpConfig & { overhead: ChainMap; }; @@ -50,7 +47,7 @@ export interface IgpGasOraclesViolation extends IgpViolation { export interface IgpOverheadViolation extends IgpViolation { subType: IgpViolationType.Overhead; - contract: OverheadIgp; + contract: InterchainGasPaymaster; actual: ChainMap; expected: ChainMap; } diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index bddd4d025..ab32719b0 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -106,7 +106,6 @@ export { IgpOverheadViolation, IgpViolation, IgpViolationType, - OverheadIgpConfig, } from './gas/types'; export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer'; export { HookConfig, HookType, MerkleTreeHookConfig } from './hook/types';