From 1fecdb29585e1071cdfe508b44d0995a9a6f23f2 Mon Sep 17 00:00:00 2001 From: Kiryl Yermakou Date: Fri, 22 Mar 2024 17:50:12 -0400 Subject: [PATCH 1/7] feat(GasEstimation): adding support for express fee --- .../InterchainGasEstimation.sol | 18 ++++++++----- contracts/interfaces/IAxelarGasService.sol | 25 +++++++++++++++++++ .../interfaces/IInterchainGasEstimation.sol | 14 ++++++----- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/contracts/gas-estimation/InterchainGasEstimation.sol b/contracts/gas-estimation/InterchainGasEstimation.sol index 76865333..cff34469 100644 --- a/contracts/gas-estimation/InterchainGasEstimation.sol +++ b/contracts/gas-estimation/InterchainGasEstimation.sol @@ -49,15 +49,18 @@ abstract contract InterchainGasEstimation is IInterchainGasEstimation { string calldata destinationChain, string calldata, /* destinationAddress */ bytes calldata payload, - uint256 executionGasLimit - ) external view returns (uint256 gasEstimate) { + uint256 executionGasLimit, + bool isExpress + ) public view returns (uint256 gasEstimate) { GasServiceStorage storage slot = _gasServiceStorage(); GasInfo storage gasInfo = slot.gasPrices[destinationChain]; - gasEstimate = gasInfo.axelarBaseFee + (executionGasLimit * gasInfo.relativeGasPrice); + gasEstimate = + (executionGasLimit * gasInfo.relativeGasPrice) + + (isExpress ? gasInfo.expressFee : gasInfo.axelarBaseFee); // if chain is L2, compute L1 data fee using L1 gas price info - if (gasInfo.gasEstimationType != GasEstimationType.Default) { + if (gasInfo.gasEstimationType != 0) { GasInfo storage l1GasInfo = slot.gasPrices['ethereum']; gasEstimate += computeL1DataFee( @@ -77,12 +80,15 @@ abstract contract InterchainGasEstimation is IInterchainGasEstimation { * @return l1DataFee The L1 to L2 data fee */ function computeL1DataFee( - GasEstimationType gasEstimationType, + uint256 gasEstimationType, bytes calldata payload, uint256 relativeGasPrice, uint256 relativeBlobBaseFee ) internal pure returns (uint256) { - if (gasEstimationType == GasEstimationType.OptimismEcotone) { + if (gasEstimationType > uint256(type(GasEstimationType).max)) + revert UnsupportedEstimationType(gasEstimationType); + + if (GasEstimationType(gasEstimationType) == GasEstimationType.OptimismEcotone) { return optimismEcotoneL1Fee(payload, relativeGasPrice, relativeBlobBaseFee); } diff --git a/contracts/interfaces/IAxelarGasService.sol b/contracts/interfaces/IAxelarGasService.sol index cfa62835..b5cf63bd 100644 --- a/contracts/interfaces/IAxelarGasService.sol +++ b/contracts/interfaces/IAxelarGasService.sol @@ -16,6 +16,7 @@ interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { error NotCollector(); error InvalidAmounts(); error InvalidGasUpdates(); + error InsufficientGasPayment(uint256 required, uint256 provided); event GasPaidForContractCall( address indexed sourceAddress, @@ -134,6 +135,30 @@ interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { uint256 amount ); + /** + * @notice Pay for gas for any type of contract execution on a destination chain. + * @dev This function is called on the source chain before calling the gateway to execute a remote contract. + * @dev If estimateOnChain is true, the function will estimate the gas cost and revert if the payment is insufficient. + * @param sender The address making the payment + * @param destinationChain The target chain where the contract call will be made + * @param destinationAddress The target address on the destination chain + * @param payload Data payload for the contract call + * @param executionGasLimit The gas limit for the contract call + * @param estimateOnChain Flag to enable on-chain gas estimation + * @param refundAddress The address where refunds, if any, should be sent + * @param params Additional parameters for gas payment + */ + function payGas( + address sender, + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + uint256 executionGasLimit, + bool estimateOnChain, + address refundAddress, + bytes calldata params + ) external payable; + /** * @notice Pay for gas using ERC20 tokens for a contract call on a destination chain. * @dev This function is called on the source chain before calling the gateway to execute a remote contract. diff --git a/contracts/interfaces/IInterchainGasEstimation.sol b/contracts/interfaces/IInterchainGasEstimation.sol index 1eb82b69..25608098 100644 --- a/contracts/interfaces/IInterchainGasEstimation.sol +++ b/contracts/interfaces/IInterchainGasEstimation.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.0; * which allows for estimating gas fees for cross-chain communication on the Axelar network. */ interface IInterchainGasEstimation { - error UnsupportedEstimationType(GasEstimationType feeType); + error UnsupportedEstimationType(uint256 feeType); /** * @notice Event emitted when the gas price for a specific chain is updated. @@ -23,10 +23,11 @@ interface IInterchainGasEstimation { } struct GasInfo { - GasEstimationType gasEstimationType; // Custom gas pricing rule, such as L1 data fee on L2s - uint256 axelarBaseFee; // destination axelar base fee for cross-chain message approval (in terms of src native gas token) - uint256 relativeGasPrice; // dest_gas_price * dest_token_market_price / src_token_market_price - uint256 relativeBlobBaseFee; // dest_blob_base_fee * dest_token_market_price / src_token_market_price + uint128 gasEstimationType; // Custom gas pricing rule, such as L1 data fee on L2s + uint128 axelarBaseFee; // axelar base fee for cross-chain message approval (in terms of src native gas token) + uint128 expressFee; // axelar express fee for cross-chain message approval and express execution + uint128 relativeGasPrice; // dest_gas_price * dest_token_market_price / src_token_market_price + uint128 relativeBlobBaseFee; // dest_blob_base_fee * dest_token_market_price / src_token_market_price } /** @@ -48,6 +49,7 @@ interface IInterchainGasEstimation { string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, - uint256 executionGasLimit + uint256 executionGasLimit, + bool isExpress ) external view returns (uint256 gasEstimate); } From 9a69414fd62bbf20118163c84cec2f0d67e7def5 Mon Sep 17 00:00:00 2001 From: Kiryl Yermakou Date: Fri, 22 Mar 2024 17:52:27 -0400 Subject: [PATCH 2/7] test(GasEstimation): adding support for express fee --- test/{gas => gas-estimation}/InterchainGasEstimation.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename test/{gas => gas-estimation}/InterchainGasEstimation.js (89%) diff --git a/test/gas/InterchainGasEstimation.js b/test/gas-estimation/InterchainGasEstimation.js similarity index 89% rename from test/gas/InterchainGasEstimation.js rename to test/gas-estimation/InterchainGasEstimation.js index d2640d84..c6897258 100644 --- a/test/gas/InterchainGasEstimation.js +++ b/test/gas-estimation/InterchainGasEstimation.js @@ -22,13 +22,14 @@ describe('InterchainGasEstimation', () => { }); it('should compute gas estimate correctly', async () => { - await gasEstimate.updateGasInfo(sourceChain, [0, 90000000000, 50000000000, 1]).then((tx) => tx.wait()); - await gasEstimate.updateGasInfo(destinationChain, [1, 90000, 5000, 0]).then((tx) => tx.wait()); + await gasEstimate.updateGasInfo(sourceChain, [0, 90000000000, 190000000000, 50000000000, 1]).then((tx) => tx.wait()); + await gasEstimate.updateGasInfo(destinationChain, [1, 90000, 190000, 5000, 0]).then((tx) => tx.wait()); const estimate = await gasEstimate.estimateGasFee( destinationChain, destinationAddress, '0x2534d1533c9ffce84d3174c1f846a4041d07b56d1e7b5cb7138e06fb42086325', 120000, + false, ); expect(estimate).to.equal(353400090264); From a52ece4a418261556673671eaba4ceb87d6dfdb0 Mon Sep 17 00:00:00 2001 From: Kiryl Yermakou Date: Fri, 22 Mar 2024 18:00:15 -0400 Subject: [PATCH 3/7] style(JS): prettier --- test/gas-estimation/InterchainGasEstimation.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/gas-estimation/InterchainGasEstimation.js b/test/gas-estimation/InterchainGasEstimation.js index c6897258..6f9cc15f 100644 --- a/test/gas-estimation/InterchainGasEstimation.js +++ b/test/gas-estimation/InterchainGasEstimation.js @@ -22,7 +22,9 @@ describe('InterchainGasEstimation', () => { }); it('should compute gas estimate correctly', async () => { - await gasEstimate.updateGasInfo(sourceChain, [0, 90000000000, 190000000000, 50000000000, 1]).then((tx) => tx.wait()); + await gasEstimate + .updateGasInfo(sourceChain, [0, 90000000000, 190000000000, 50000000000, 1]) + .then((tx) => tx.wait()); await gasEstimate.updateGasInfo(destinationChain, [1, 90000, 190000, 5000, 0]).then((tx) => tx.wait()); const estimate = await gasEstimate.estimateGasFee( destinationChain, From 2cda2c17402d9d22d9c40b5098e3517f2814d93c Mon Sep 17 00:00:00 2001 From: re1ro Date: Fri, 22 Mar 2024 18:15:22 -0400 Subject: [PATCH 4/7] Update contracts/interfaces/IAxelarGasService.sol Co-authored-by: Milap Sheth --- contracts/interfaces/IAxelarGasService.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IAxelarGasService.sol b/contracts/interfaces/IAxelarGasService.sol index b5cf63bd..e848b7b0 100644 --- a/contracts/interfaces/IAxelarGasService.sol +++ b/contracts/interfaces/IAxelarGasService.sol @@ -146,7 +146,7 @@ interface IAxelarGasService is IInterchainGasEstimation, IUpgradable { * @param executionGasLimit The gas limit for the contract call * @param estimateOnChain Flag to enable on-chain gas estimation * @param refundAddress The address where refunds, if any, should be sent - * @param params Additional parameters for gas payment + * @param params Additional parameters for gas payment. This can be left empty for normal contract call payments. */ function payGas( address sender, From 41a2deb53df571e4a945e128aa0ce9a38054f171 Mon Sep 17 00:00:00 2001 From: Kiryl Yermakou Date: Fri, 22 Mar 2024 23:21:11 -0400 Subject: [PATCH 5/7] feat(GasEstimation): adding generic params --- .../gas-estimation/InterchainGasEstimation.sol | 16 ++++++---------- .../interfaces/IInterchainGasEstimation.sol | 7 ++++--- test/gas-estimation/InterchainGasEstimation.js | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/contracts/gas-estimation/InterchainGasEstimation.sol b/contracts/gas-estimation/InterchainGasEstimation.sol index cff34469..235e5433 100644 --- a/contracts/gas-estimation/InterchainGasEstimation.sol +++ b/contracts/gas-estimation/InterchainGasEstimation.sol @@ -43,6 +43,7 @@ abstract contract InterchainGasEstimation is IInterchainGasEstimation { * param destinationAddress Destination contract address being called * @param executionGasLimit The gas limit to be used for the destination contract execution, * e.g. pass in 200k if your app consumes needs upto 200k for this contract call + * @param params Additional parameters for the gas estimation * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service. */ function estimateGasFee( @@ -50,17 +51,15 @@ abstract contract InterchainGasEstimation is IInterchainGasEstimation { string calldata, /* destinationAddress */ bytes calldata payload, uint256 executionGasLimit, - bool isExpress + bytes calldata params ) public view returns (uint256 gasEstimate) { GasServiceStorage storage slot = _gasServiceStorage(); GasInfo storage gasInfo = slot.gasPrices[destinationChain]; - gasEstimate = - (executionGasLimit * gasInfo.relativeGasPrice) + - (isExpress ? gasInfo.expressFee : gasInfo.axelarBaseFee); + gasEstimate = gasInfo.axelarBaseFee + (executionGasLimit * gasInfo.relativeGasPrice); // if chain is L2, compute L1 data fee using L1 gas price info - if (gasInfo.gasEstimationType != 0) { + if (gasInfo.gasEstimationType != GasEstimationType.Default) { GasInfo storage l1GasInfo = slot.gasPrices['ethereum']; gasEstimate += computeL1DataFee( @@ -80,15 +79,12 @@ abstract contract InterchainGasEstimation is IInterchainGasEstimation { * @return l1DataFee The L1 to L2 data fee */ function computeL1DataFee( - uint256 gasEstimationType, + GasEstimationType gasEstimationType, bytes calldata payload, uint256 relativeGasPrice, uint256 relativeBlobBaseFee ) internal pure returns (uint256) { - if (gasEstimationType > uint256(type(GasEstimationType).max)) - revert UnsupportedEstimationType(gasEstimationType); - - if (GasEstimationType(gasEstimationType) == GasEstimationType.OptimismEcotone) { + if (gasEstimationType == GasEstimationType.OptimismEcotone) { return optimismEcotoneL1Fee(payload, relativeGasPrice, relativeBlobBaseFee); } diff --git a/contracts/interfaces/IInterchainGasEstimation.sol b/contracts/interfaces/IInterchainGasEstimation.sol index 25608098..1bb3586a 100644 --- a/contracts/interfaces/IInterchainGasEstimation.sol +++ b/contracts/interfaces/IInterchainGasEstimation.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.0; * which allows for estimating gas fees for cross-chain communication on the Axelar network. */ interface IInterchainGasEstimation { - error UnsupportedEstimationType(uint256 feeType); + error UnsupportedEstimationType(GasEstimationType feeType); /** * @notice Event emitted when the gas price for a specific chain is updated. @@ -23,7 +23,7 @@ interface IInterchainGasEstimation { } struct GasInfo { - uint128 gasEstimationType; // Custom gas pricing rule, such as L1 data fee on L2s + GasEstimationType gasEstimationType; // Custom gas pricing rule, such as L1 data fee on L2s uint128 axelarBaseFee; // axelar base fee for cross-chain message approval (in terms of src native gas token) uint128 expressFee; // axelar express fee for cross-chain message approval and express execution uint128 relativeGasPrice; // dest_gas_price * dest_token_market_price / src_token_market_price @@ -43,6 +43,7 @@ interface IInterchainGasEstimation { * @param destinationAddress Destination contract address being called * @param executionGasLimit The gas limit to be used for the destination contract execution, * e.g. pass in 200k if your app consumes needs upto 200k for this contract call + * @param params Additional parameters for the gas estimation * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service. */ function estimateGasFee( @@ -50,6 +51,6 @@ interface IInterchainGasEstimation { string calldata destinationAddress, bytes calldata payload, uint256 executionGasLimit, - bool isExpress + bytes calldata params ) external view returns (uint256 gasEstimate); } diff --git a/test/gas-estimation/InterchainGasEstimation.js b/test/gas-estimation/InterchainGasEstimation.js index 6f9cc15f..b1aad48b 100644 --- a/test/gas-estimation/InterchainGasEstimation.js +++ b/test/gas-estimation/InterchainGasEstimation.js @@ -31,7 +31,7 @@ describe('InterchainGasEstimation', () => { destinationAddress, '0x2534d1533c9ffce84d3174c1f846a4041d07b56d1e7b5cb7138e06fb42086325', 120000, - false, + "0x", ); expect(estimate).to.equal(353400090264); From 0988cbd6890deb018ffa4d34ad14378338d917fa Mon Sep 17 00:00:00 2001 From: Kiryl Yermakou Date: Fri, 22 Mar 2024 23:26:02 -0400 Subject: [PATCH 6/7] feat(GasEstimation): adding generic params --- contracts/gas-estimation/InterchainGasEstimation.sol | 4 ++-- test/gas-estimation/InterchainGasEstimation.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/gas-estimation/InterchainGasEstimation.sol b/contracts/gas-estimation/InterchainGasEstimation.sol index 235e5433..f3e52cd0 100644 --- a/contracts/gas-estimation/InterchainGasEstimation.sol +++ b/contracts/gas-estimation/InterchainGasEstimation.sol @@ -43,7 +43,7 @@ abstract contract InterchainGasEstimation is IInterchainGasEstimation { * param destinationAddress Destination contract address being called * @param executionGasLimit The gas limit to be used for the destination contract execution, * e.g. pass in 200k if your app consumes needs upto 200k for this contract call - * @param params Additional parameters for the gas estimation + * param params Additional parameters for the gas estimation * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service. */ function estimateGasFee( @@ -51,7 +51,7 @@ abstract contract InterchainGasEstimation is IInterchainGasEstimation { string calldata, /* destinationAddress */ bytes calldata payload, uint256 executionGasLimit, - bytes calldata params + bytes calldata /* params */ ) public view returns (uint256 gasEstimate) { GasServiceStorage storage slot = _gasServiceStorage(); GasInfo storage gasInfo = slot.gasPrices[destinationChain]; diff --git a/test/gas-estimation/InterchainGasEstimation.js b/test/gas-estimation/InterchainGasEstimation.js index b1aad48b..dfb06720 100644 --- a/test/gas-estimation/InterchainGasEstimation.js +++ b/test/gas-estimation/InterchainGasEstimation.js @@ -31,7 +31,7 @@ describe('InterchainGasEstimation', () => { destinationAddress, '0x2534d1533c9ffce84d3174c1f846a4041d07b56d1e7b5cb7138e06fb42086325', 120000, - "0x", + '0x', ); expect(estimate).to.equal(353400090264); From ee12a5e9c04ac5283b9e54f09bb3472d60f48d1d Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 22 Mar 2024 23:43:57 -0400 Subject: [PATCH 7/7] Update contracts/interfaces/IInterchainGasEstimation.sol --- contracts/interfaces/IInterchainGasEstimation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IInterchainGasEstimation.sol b/contracts/interfaces/IInterchainGasEstimation.sol index 1bb3586a..f41bfc4e 100644 --- a/contracts/interfaces/IInterchainGasEstimation.sol +++ b/contracts/interfaces/IInterchainGasEstimation.sol @@ -8,7 +8,7 @@ pragma solidity ^0.8.0; * which allows for estimating gas fees for cross-chain communication on the Axelar network. */ interface IInterchainGasEstimation { - error UnsupportedEstimationType(GasEstimationType feeType); + error UnsupportedEstimationType(GasEstimationType gasEstimationType); /** * @notice Event emitted when the gas price for a specific chain is updated.