Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(GasEstimate): new base contract for gas estimation #133

Merged
merged 28 commits into from
Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
514306b
feat(GasEstimate): new base contract for gas estimation
re1ro Mar 13, 2024
557de0f
Update contracts/gas/GasEstimate.sol
re1ro Mar 13, 2024
c200bc4
Update contracts/interfaces/IGasEstimate.sol
re1ro Mar 13, 2024
42de430
feat(GasEstimate): using Optimism Ecotone gas model
re1ro Mar 13, 2024
a82bb15
feat(GasEstimate): using computed constants.
re1ro Mar 13, 2024
324c8f3
feat(GasEstimate): using computed constants.
re1ro Mar 13, 2024
f1b31f2
feat(GasEstimate): adding GMP params to the estimation
re1ro Mar 14, 2024
bac19e6
doc(GasEstimate): comment
re1ro Mar 14, 2024
e696cca
doc(GasEstimate): comment
re1ro Mar 14, 2024
824b7b2
test(GasEstimate): adding test contract
re1ro Mar 14, 2024
9ddb4b0
feat(IAxelarGasService): updating the interface with the latest
re1ro Mar 14, 2024
4b8eade
feat(IAxelarGasService): updating the interface with the latest
re1ro Mar 14, 2024
0249eb5
feat(IAxelarGasService): updating the interface with the latest
re1ro Mar 14, 2024
62a07f4
test(GasEstimate): adding test contract
re1ro Mar 14, 2024
8beb68b
feat(GasEstimate): adjusting Ecotone formula
re1ro Mar 14, 2024
cee8eae
style(solidity): prettier
re1ro Mar 14, 2024
626bc82
test(GasEstimate): adding test setup
re1ro Mar 15, 2024
520a82f
Update contracts/interfaces/IGasEstimate.sol
re1ro Mar 15, 2024
27150eb
Update contracts/interfaces/IGasEstimate.sol
re1ro Mar 15, 2024
e5b1ba3
Update contracts/interfaces/IGasEstimate.sol
re1ro Mar 15, 2024
aae5ca9
fix(GasEstimate): PR feedback
re1ro Mar 16, 2024
0822133
refactor(GasEstimate): axelarBaseFee
re1ro Mar 16, 2024
245ab99
Update contracts/gas/InterchainGasEstimation.sol
re1ro Mar 16, 2024
c2893da
Update test/gas/InterchainGasEstimation.js
re1ro Mar 16, 2024
c254fa9
Update contracts/gas/InterchainGasEstimation.sol
re1ro Mar 16, 2024
65dabad
fix(GasEstimate): PR feedback
re1ro Mar 16, 2024
fa4dadf
fix(GasEstimate): PR feedback
re1ro Mar 16, 2024
3c5e374
doc(GasEstimate): comments
re1ro Mar 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions contracts/gas/GasEstimate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IGasEstimate } from '../interfaces/IGasEstimate.sol';

/**
* @title AxelarGasService
* @notice This contract manages gas payments and refunds for cross-chain communication on the Axelar network.
* @dev The owner address of this contract should be the microservice that pays for gas.
* @dev Users pay gas for cross-chain calls, and the gasCollector can collect accumulated fees and/or refund users if needed.
*/
contract GasEstimate is IGasEstimate {
// keccak256('GasEstimate.Slot') - 1
bytes32 internal constant GAS_SERVICE_SLOT = 0x2fa150da4c9f4c3a28593398c65313dd42f63d0530ec6db4a2b46e6d837a3902;

struct GasServiceStorage {
mapping(string => GasInfo) gasPrices;
}

/**
* @notice Sets the gas price for a specific chain.
* @dev This function is called by the gas oracle to update the gas price for a specific chain.
* @param chain The name of the chain
* @param gasInfo The gas info for the chain
*/
function _setGasInfo(string calldata chain, GasInfo calldata gasInfo) internal {
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
emit GasInfoUpdated(chain, gasInfo);

_gasServiceStorage().gasPrices[chain] = gasInfo;
}

/**
* @notice Estimates the gas fee for a contract call on a destination chain.
* @param destinationChain Axelar registered name of the destination chain
* 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
* @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(
string calldata destinationChain,
string calldata, /* destinationAddress */
bytes calldata payload,
uint256 executionGasLimit
) external view returns (uint256 gasEstimate) {
GasServiceStorage storage slot = _gasServiceStorage();
GasInfo storage gasInfo = slot.gasPrices[destinationChain];

gasEstimate = gasInfo.baseFee + (executionGasLimit * gasInfo.relativeGasPrice);

// if chain is L2, compute L1 data fee using L1 gas price info
if (gasInfo.l1ToL2BaseFee != 0) {
gasEstimate += computeL1ToL2Fee(
destinationChain,
payload,
slot.gasPrices['ethereum'].relativeGasPrice,
gasInfo.l1ToL2BaseFee
);
}
}

/**
* @notice Computes the L1 to L2 fee for a contract call on a destination chain.
* @param destinationChain Axelar registered name of the destination chain
* @param payload The payload of the contract call
* param l1GasPrice The gas price on the source chain
* @param l1ToL2BaseFee The base fee for L1 to L2
* @return l1DataFee The L1 to L2 data fee
*/
function computeL1ToL2Fee(
string calldata destinationChain,
bytes calldata payload,
uint256, /* l1GasPrice */
uint256 l1ToL2BaseFee
) internal pure returns (uint256) {
if (keccak256(bytes(destinationChain)) == keccak256(bytes('optimism'))) {
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
return optimismL1ToL2Fee(payload, l1ToL2BaseFee);
}

revert UnsupportedL2Estimate(destinationChain);
}

/**
* @notice Computes the L1 to L2 fee for a contract call on the Optimism chain.
* @param payload The payload of the contract call
* @param l1ToL2BaseFee The base fee for L1 to L2
* @return l1DataFee The L1 to L2 data fee
*/
function optimismL1ToL2Fee(bytes calldata payload, uint256 l1ToL2BaseFee)
internal
pure
returns (uint256 l1DataFee)
{
/* Optimism Ecotone gas model https://docs.optimism.io/stack/transactions/fees#ecotone
tx_compressed_size = ((count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16)) / 16
weighted_gas_price = 16 * base_fee_scalar*base_fee + blob_base_fee_scalar * blob_base_fee
l1_data_fee = tx_compressed_size * weighted_gas_price

Reference implementation:
https://github.com/ethereum-optimism/optimism/blob/876e16ad04968f0bb641eb76f98eb77e7e1a3e16/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L138
*/

// The new base_fee_scalar is using old dynamic_overhead_multiplier value which is currently 0.684
// We are setting it to un upper bound of 0.7 to encounter for future fluctuations
uint256 scalarPrecision = 10**6;
uint256 baseFeeScalar = 7 * 10**5; // 7e5 : 1e6 = 0.7

// The blob_base_fee_scalar is currently set to 0. The blob gas model is being adopted by OP validators.
// https://eips.ethereum.org/EIPS/eip-4844
uint256 blobBaseFeeScalar = 0 * scalarPrecision;
uint256 blobBaseFee = 0;

// Calculating transaction size in bytes that will later be divided by 16 to compress the size
// 68 bytes for the TX RLP encoding overhead
uint256 txSize = 68 * 16;
// GMP executeWithToken call parameters
// 32 bytes for the commandId, 96 bytes for the sourceChain, 128 bytes for the sourceAddress, 96 bytes for token symbol, 32 bytes for amount
// Expecting half of the calldata bytes to be zeroes. So multiplying by 10 as an average of 4 and 16
txSize += (32 + 96 + 128 + 96 + 32) * 10;

for (uint256 i; i < payload.length; ++i) {
if (payload[i] == 0) {
txSize += 4; // 4 for each zero byte
} else {
txSize += 16; // 16 for each non-zero byte
}
}

uint256 weightedGasPrice = 16 * baseFeeScalar * l1ToL2BaseFee + blobBaseFeeScalar * blobBaseFee;

l1DataFee = (weightedGasPrice * txSize) / (16 * scalarPrecision); // 16 for txSize compression and scalar precision conversion
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice Get the storage slot for the GasServiceStorage struct
*/
function _gasServiceStorage() private pure returns (GasServiceStorage storage slot) {
assembly {
slot.slot := GAS_SERVICE_SLOT
}
}
}
19 changes: 14 additions & 5 deletions contracts/interfaces/IAxelarGasService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

pragma solidity ^0.8.0;

import { IUpgradable } from '../interfaces/IUpgradable.sol';
import { IGasEstimate } from './IGasEstimate.sol';
import { IUpgradable } from './IUpgradable.sol';

/**
* @title IAxelarGasService Interface
* @notice This is an interface for the AxelarGasService contract which manages gas payments
* and refunds for cross-chain communication on the Axelar network.
* @dev This interface inherits IUpgradable
*/
interface IAxelarGasService is IUpgradable {
error NothingReceived();
interface IAxelarGasService is IGasEstimate, IUpgradable {
error InvalidAddress();
error NotCollector();
error InvalidAmounts();
error InvalidGasUpdates();

event GasPaidForContractCall(
address indexed sourceAddress,
Expand Down Expand Up @@ -265,7 +266,7 @@ interface IAxelarGasService is IUpgradable {

/**
* @notice Pay for gas using native currency for an express contract call on a destination chain.
* @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @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
Expand All @@ -282,7 +283,7 @@ interface IAxelarGasService is IUpgradable {

/**
* @notice Pay for gas using native currency for an express contract call with tokens on a destination chain.
* @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
* @dev This function is called on the source chain before calling the gateway to execute a remote contract.
* @param sender The address making the payment
* @param destinationChain The target chain where the contract call with tokens will be made
* @param destinationAddress The target address on the destination chain
Expand Down Expand Up @@ -361,6 +362,14 @@ interface IAxelarGasService is IUpgradable {
address refundAddress
) external payable;

/**
* @notice Updates the gas price for a specific chain.
* @dev This function is called by the gas oracle to update the gas prices for a specific chains.
* @param chains Array of chain names
* @param gasUpdates Array of gas updates
*/
function updateGasInfo(string[] calldata chains, GasInfo[] calldata gasUpdates) external;

/**
* @notice Allows the gasCollector to collect accumulated fees from the contract.
* @dev Use address(0) as the token address for native currency.
Expand Down
41 changes: 41 additions & 0 deletions contracts/interfaces/IGasEstimate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title IAxelarGasService Interface
* @notice This is an interface for the AxelarGasService contract which manages gas payments
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
* and refunds for cross-chain communication on the Axelar network.
* @dev This interface inherits IUpgradable
*/
interface IGasEstimate {
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
re1ro marked this conversation as resolved.
Show resolved Hide resolved
error UnsupportedL2Estimate(string chain);

/**
* @notice Event emitted when the gas price for a specific chain is updated.
* @param chain The name of the chain
* @param info The gas info for the chain
*/
event GasInfoUpdated(string chain, GasInfo info);

struct GasInfo {
uint256 baseFee; // destination base_fee (in terms of src native gas token)
re1ro marked this conversation as resolved.
Show resolved Hide resolved
uint256 relativeGasPrice; // dest_gas_price * dest_token_market_price / src_token_market_price
uint256 l1ToL2BaseFee; // non-zero if the chain is an L2, and requires an L1 data fee overhead, (L1 is assumed to be ethereum)
}

/**
* @notice Estimates the gas fee for a cross-chain contract call.
* @param destinationChain Axelar registered name of the destination chain
* @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
* @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(
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
uint256 executionGasLimit
) external view returns (uint256 gasEstimate);
}
11 changes: 11 additions & 0 deletions contracts/test/gas/TestGasEstimate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { GasEstimate } from '../../gas/GasEstimate.sol';

contract TestGasEstimate is GasEstimate {
function updateGasInfo(string calldata chain, GasInfo calldata info) external {
_setGasInfo(chain, info);
}
}
Loading