From 820e1afcd355b770eb5b75c7c766361f277a3f71 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Tue, 14 Jan 2025 15:25:25 +0000 Subject: [PATCH 01/23] it aint pretty and it dont work, but it sorta works --- .../config/environments/test/gas-oracle.ts | 23 +-- .../sealevel-helpers/print-gas-oracles.ts | 42 +++++ typescript/infra/src/config/gas-oracle.ts | 176 ++++++++++++------ typescript/sdk/src/consts/igp.ts | 13 +- .../sdk/src/gas/HyperlaneIgpDeployer.ts | 4 +- typescript/sdk/src/gas/oracle/types.ts | 4 +- typescript/sdk/src/gas/utils.ts | 93 +++++++-- typescript/sdk/src/hook/EvmHookModule.ts | 4 +- typescript/sdk/src/index.ts | 4 +- typescript/sdk/src/warp/WarpCore.ts | 6 +- typescript/utils/src/amount.ts | 18 +- typescript/utils/src/index.ts | 1 + 12 files changed, 274 insertions(+), 114 deletions(-) create mode 100644 typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts diff --git a/typescript/infra/config/environments/test/gas-oracle.ts b/typescript/infra/config/environments/test/gas-oracle.ts index 65c09d9cdb..e3211e3f73 100644 --- a/typescript/infra/config/environments/test/gas-oracle.ts +++ b/typescript/infra/config/environments/test/gas-oracle.ts @@ -1,11 +1,6 @@ -import { BigNumber, ethers } from 'ethers'; +import { BigNumber as BigNumberJs } from 'bignumber.js'; -import { - ChainMap, - ChainName, - GasPriceConfig, - TOKEN_EXCHANGE_RATE_DECIMALS, -} from '@hyperlane-xyz/sdk'; +import { ChainMap, GasPriceConfig } from '@hyperlane-xyz/sdk'; import { AllStorageGasOracleConfigs, @@ -14,10 +9,7 @@ import { import { testChainNames } from './chains.js'; -const TEST_TOKEN_EXCHANGE_RATE = ethers.utils.parseUnits( - '1', - TOKEN_EXCHANGE_RATE_DECIMALS, -); +const TEST_TOKEN_EXCHANGE_RATE = new BigNumberJs('1'); const TEST_GAS_PRICE_CONFIG: GasPriceConfig = { amount: '2', decimals: 9, // gwei @@ -29,16 +21,9 @@ const gasPrices: ChainMap = { test3: TEST_GAS_PRICE_CONFIG, }; -function getTokenExchangeRate( - _local: ChainName, - _remote: ChainName, -): BigNumber { - return TEST_TOKEN_EXCHANGE_RATE; -} - export const storageGasOracleConfig: AllStorageGasOracleConfigs = getAllStorageGasOracleConfigs( testChainNames, gasPrices, - getTokenExchangeRate, + (_local, _remote) => TEST_TOKEN_EXCHANGE_RATE, ); diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts new file mode 100644 index 0000000000..76982de9d7 --- /dev/null +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -0,0 +1,42 @@ +import { objMap } from '@hyperlane-xyz/utils'; + +import { getArgs } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +// This script exists to print the chain metadata configs for a given environment +// so they can easily be copied into the Sealevel tooling. :'( + +async function main() { + const args = await getArgs().argv; + + const environmentConfig = getEnvironmentConfig(args.environment); + + // Construct a nested map of origin -> destination -> { oracleConfig, overhead } + const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { + console.log('origin', origin, 'igpConfig', igpConfig); + let a = objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { + console.log('origin', origin, 'destination', destination); + console.log( + 'oracleConfig', + oracleConfig, + 'overhead', + igpConfig?.overhead?.[destination], + ); + return { + oracleConfig, + overhead: igpConfig?.overhead?.[destination], + }; + }); + // console.log('a', a, 'origin'); + return 'sup'; + }); + + console.log('do we get here ?'); + + console.log(JSON.stringify(gasOracles, null, 2)); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 622ad18b36..860ce0ef85 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -1,17 +1,23 @@ +import { BigNumber as BigNumberJs } from 'bignumber.js'; import chalk from 'chalk'; import { BigNumber, ethers } from 'ethers'; import { + ChainGasOracleParams, ChainMap, ChainName, GasPriceConfig, StorageGasOracleConfig, TOKEN_EXCHANGE_RATE_SCALE, defaultMultisigConfigs, + getLocalStorageGasOracleConfig, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; -import { isEthereumProtocolChain } from '../utils/utils.js'; +import { + isEthereumProtocolChain, + mustGetChainNativeToken, +} from '../utils/utils.js'; // gas oracle configs for each chain, which includes // a map for each chain's remote chains @@ -33,57 +39,27 @@ function getLocalStorageGasOracleConfigOverride( local: ChainName, remotes: ChainName[], gasPrices: ChainMap, - getTokenExchangeRate: (local: ChainName, remote: ChainName) => BigNumber, + getTokenExchangeRate: (local: ChainName, remote: ChainName) => BigNumberJs, getTokenUsdPrice?: (chain: ChainName) => number, getOverhead?: (local: ChainName, remote: ChainName) => number, ): ChainMap { - return remotes.reduce((agg, remote) => { - let exchangeRate = getTokenExchangeRate(local, remote); - if (!gasPrices[remote]) { - // Will run into this case when adding new chains - console.warn(chalk.yellow(`No gas price set for ${remote}`)); - return agg; - } - - // First parse as a number, so we have floating point precision. - // Recall it's possible to have gas prices that are not integers, even - // after converting to the "wei" version of the token. - let gasPrice = - parseFloat(gasPrices[remote].amount) * - Math.pow(10, gasPrices[remote].decimals); - if (isNaN(gasPrice)) { - throw new Error( - `Invalid gas price for chain ${remote}: ${gasPrices[remote]}`, - ); - } - - // We have very little precision and ultimately need an integer value for - // the gas price that will be set on-chain. We scale up the gas price and - // scale down the exchange rate by the same factor. - if (gasPrice < 10 && gasPrice % 1 !== 0) { - // Scale up the gas price by 1e4 - const gasPriceScalingFactor = 1e4; - - // Check that there's no significant underflow when applying - // this to the exchange rate: - const adjustedExchangeRate = exchangeRate.div(gasPriceScalingFactor); - const recoveredExchangeRate = adjustedExchangeRate.mul( - gasPriceScalingFactor, - ); - if (recoveredExchangeRate.mul(100).div(exchangeRate).lt(99)) { - throw new Error('Too much underflow when downscaling exchange rate'); - } - - // Apply the scaling factor - exchangeRate = adjustedExchangeRate; - gasPrice *= gasPriceScalingFactor; - } - - // Our integer gas price. - let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); + const gasOracleParams = [local, ...remotes].reduce((agg, remote) => { + agg[remote] = { + gasPrice: gasPrices[remote], + nativeToken: { + price: getTokenUsdPrice ? getTokenUsdPrice(remote).toString() : '0', + decimals: mustGetChainNativeToken(remote).decimals, + }, + }; + return agg; + }, {} as ChainMap); - // If we have access to these, let's use the USD prices to apply some minimum - // typical USD payment heuristics. + const gasPriceModifier = ( + local: ChainName, + remote: ChainName, + exchangeRate: BigNumber, + gasPrice: BigNumber, + ) => { if (getTokenUsdPrice && getOverhead) { const typicalRemoteGasAmount = getTypicalRemoteGasAmount( local, @@ -92,7 +68,7 @@ function getLocalStorageGasOracleConfigOverride( ); const typicalIgpQuoteUsd = getUsdQuote( local, - gasPriceBn, + gasPrice, exchangeRate, typicalRemoteGasAmount, getTokenUsdPrice, @@ -104,20 +80,102 @@ function getLocalStorageGasOracleConfigOverride( const minIgpQuote = ethers.utils.parseEther( (minUsdCost / getTokenUsdPrice(local)).toPrecision(8), ); - gasPriceBn = minIgpQuote + return minIgpQuote .mul(TOKEN_EXCHANGE_RATE_SCALE) .div(exchangeRate.mul(typicalRemoteGasAmount)); } } + return gasPrice; + }; - return { - ...agg, - [remote]: { - tokenExchangeRate: exchangeRate, - gasPrice: gasPriceBn, - }, - }; - }, {}); + return getLocalStorageGasOracleConfig({ + local, + gasOracleParams, + exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, + gasPriceModifier, + }); + + // return remotes.reduce((agg, remote) => { + // let exchangeRate = getTokenExchangeRate(local, remote); + // if (!gasPrices[remote]) { + // // Will run into this case when adding new chains + // console.warn(chalk.yellow(`No gas price set for ${remote}`)); + // return agg; + // } + + // // First parse as a number, so we have floating point precision. + // // Recall it's possible to have gas prices that are not integers, even + // // after converting to the "wei" version of the token. + // let gasPrice = + // parseFloat(gasPrices[remote].amount) * + // Math.pow(10, gasPrices[remote].decimals); + // if (isNaN(gasPrice)) { + // throw new Error( + // `Invalid gas price for chain ${remote}: ${gasPrices[remote]}`, + // ); + // } + + // // We have very little precision and ultimately need an integer value for + // // the gas price that will be set on-chain. We scale up the gas price and + // // scale down the exchange rate by the same factor. + // if (gasPrice < 10 && gasPrice % 1 !== 0) { + // // Scale up the gas price by 1e4 + // const gasPriceScalingFactor = 1e4; + + // // Check that there's no significant underflow when applying + // // this to the exchange rate: + // const adjustedExchangeRate = exchangeRate.div(gasPriceScalingFactor); + // const recoveredExchangeRate = adjustedExchangeRate.mul( + // gasPriceScalingFactor, + // ); + // if (recoveredExchangeRate.mul(100).div(exchangeRate).lt(99)) { + // throw new Error('Too much underflow when downscaling exchange rate'); + // } + + // // Apply the scaling factor + // exchangeRate = adjustedExchangeRate; + // gasPrice *= gasPriceScalingFactor; + // } + + // // Our integer gas price. + // let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); + + // // If we have access to these, let's use the USD prices to apply some minimum + // // typical USD payment heuristics. + // if (getTokenUsdPrice && getOverhead) { + // const typicalRemoteGasAmount = getTypicalRemoteGasAmount( + // local, + // remote, + // getOverhead, + // ); + // const typicalIgpQuoteUsd = getUsdQuote( + // local, + // gasPriceBn, + // exchangeRate, + // typicalRemoteGasAmount, + // getTokenUsdPrice, + // ); + + // const minUsdCost = getMinUsdCost(local, remote); + // if (typicalIgpQuoteUsd < minUsdCost) { + // // Adjust the gasPrice to meet the minimum cost + // const minIgpQuote = ethers.utils.parseEther( + // (minUsdCost / getTokenUsdPrice(local)).toPrecision(8), + // ); + // gasPriceBn = minIgpQuote + // .mul(TOKEN_EXCHANGE_RATE_SCALE) + // .div(exchangeRate.mul(typicalRemoteGasAmount)); + // } + // } + + // return { + // ...agg, + // [remote]: { + // tokenExchangeRate: exchangeRate, + // gasPrice: gasPriceBn, + // }, + // }; + // }, {}); } export function getTypicalRemoteGasAmount( @@ -204,7 +262,7 @@ export function getOverhead( export function getAllStorageGasOracleConfigs( chainNames: ChainName[], gasPrices: ChainMap, - getTokenExchangeRate: (local: ChainName, remote: ChainName) => BigNumber, + getTokenExchangeRate: (local: ChainName, remote: ChainName) => BigNumberJs, getTokenUsdPrice?: (chain: ChainName) => number, getOverhead?: (local: ChainName, remote: ChainName) => number, ): AllStorageGasOracleConfigs { diff --git a/typescript/sdk/src/consts/igp.ts b/typescript/sdk/src/consts/igp.ts index 52dac668d0..8d75f4f7f0 100644 --- a/typescript/sdk/src/consts/igp.ts +++ b/typescript/sdk/src/consts/igp.ts @@ -1,8 +1,15 @@ import { ethers } from 'ethers'; -export const TOKEN_EXCHANGE_RATE_DECIMALS = 10; +export const TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM = 10; -export const TOKEN_EXCHANGE_RATE_SCALE = ethers.utils.parseUnits( +export const TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM = ethers.utils.parseUnits( '1', - TOKEN_EXCHANGE_RATE_DECIMALS, + TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM, +); + +export const TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL = 19; + +export const TOKEN_EXCHANGE_RATE_SCALE_SEALEVEL = ethers.utils.parseUnits( + '1', + TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL, ); diff --git a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts index 6283d87fa4..0e05db8b25 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts @@ -7,7 +7,7 @@ import { } from '@hyperlane-xyz/core'; import { eqAddress, rootLogger } from '@hyperlane-xyz/utils'; -import { TOKEN_EXCHANGE_RATE_SCALE } from '../consts/igp.js'; +import { TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM } from '../consts/igp.js'; import { HyperlaneContracts } from '../contracts/types.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; @@ -144,7 +144,7 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< const exampleRemoteGasCost = desiredData.tokenExchangeRate .mul(desiredData.gasPrice) .mul(exampleRemoteGas) - .div(TOKEN_EXCHANGE_RATE_SCALE); + .div(TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM); this.logger.info( `${chain} -> ${remote}: ${exampleRemoteGas} remote gas cost: ${ethers.utils.formatEther( exampleRemoteGasCost, diff --git a/typescript/sdk/src/gas/oracle/types.ts b/typescript/sdk/src/gas/oracle/types.ts index 99669b7ea3..383bf8adc2 100644 --- a/typescript/sdk/src/gas/oracle/types.ts +++ b/typescript/sdk/src/gas/oracle/types.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import { z } from 'zod'; -import { TOKEN_EXCHANGE_RATE_DECIMALS } from '../../consts/igp.js'; +import { TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM } from '../../consts/igp.js'; export const StorageGasOracleConfigSchema = z.object({ gasPrice: z.string(), @@ -26,7 +26,7 @@ export const formatGasOracleConfig = ( } => ({ tokenExchangeRate: ethers.utils.formatUnits( config.tokenExchangeRate, - TOKEN_EXCHANGE_RATE_DECIMALS, + TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM, ), gasPrice: ethers.utils.formatUnits(config.gasPrice, 'gwei'), }); diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index 6f007398c5..d66f83ca2f 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -1,11 +1,13 @@ import { Provider } from '@ethersproject/providers'; +import { BigNumber as BigNumberJs } from 'bignumber.js'; +import { assert } from 'console'; import { BigNumber, ethers } from 'ethers'; import { ProtocolType, convertDecimals, objMap } from '@hyperlane-xyz/utils'; import { - TOKEN_EXCHANGE_RATE_DECIMALS, - TOKEN_EXCHANGE_RATE_SCALE, + TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM, + TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL, } from '../consts/igp.js'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; import { AgentCosmosGasPrice } from '../metadata/agentConfig.js'; @@ -114,30 +116,62 @@ export function getTokenExchangeRateFromValues({ tokenPrices: ChainMap; exchangeRateMarginPct: number; decimals: { local: number; remote: number }; -}): BigNumber { +}): BigNumberJs { // Workaround for chicken-egg dependency problem. // We need to provide some default value here to satisfy the config on initial load, // whilst knowing that it will get overwritten when a script actually gets run. const defaultValue = '1'; - const localValue = ethers.utils.parseUnits( - tokenPrices[local] ?? defaultValue, - TOKEN_EXCHANGE_RATE_DECIMALS, - ); - const remoteValue = ethers.utils.parseUnits( - tokenPrices[remote] ?? defaultValue, - TOKEN_EXCHANGE_RATE_DECIMALS, - ); + const localValue = new BigNumberJs(tokenPrices[local] ?? defaultValue); + const remoteValue = new BigNumberJs(tokenPrices[remote] ?? defaultValue); + console.log( + 'yeet 1', + local, + remote, + 'localValue', + localValue.toString(), + 'remoteValue', + remoteValue.toString(), + ); // This does not yet account for decimals! - let exchangeRate = remoteValue.mul(TOKEN_EXCHANGE_RATE_SCALE).div(localValue); + let exchangeRate = remoteValue.div(localValue); + console.log('yeet 2', local, remote, 'exchangeRate', exchangeRate.toString()); // Apply the premium - exchangeRate = exchangeRate.mul(100 + exchangeRateMarginPct).div(100); + exchangeRate = exchangeRate.times(100 + exchangeRateMarginPct).div(100); + console.log('yeet 3', local, remote, 'exchangeRate', exchangeRate.toString()); - return BigNumber.from( - convertDecimals(decimals.remote, decimals.local, exchangeRate.toString()), + const value = convertDecimals(decimals.remote, decimals.local, exchangeRate); + console.log('yeet 4', local, remote, 'value', value.toString()); + assert( + value.isGreaterThan(0), + 'Exchange rate must be greater than 0, possible loss of precision', ); + return value; +} + +function getProtocolSpecificExchangeRate( + exchangeRate: BigNumberJs, + protocolType: ProtocolType, +): BigNumber { + let multiplierDecimals = 1; + if (protocolType === ProtocolType.Ethereum) { + multiplierDecimals = TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM; + } else if (protocolType === ProtocolType.Sealevel) { + multiplierDecimals = TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL; + } else { + // TODO: add support for other protocols. Default to Ethereum for now. + multiplierDecimals = TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM; + } + const multiplier = new BigNumberJs(10).pow(multiplierDecimals); + const integer = exchangeRate + .times(multiplier) + .integerValue(BigNumberJs.ROUND_FLOOR) + .toString(10); + return BigNumber.from(integer); } +// The move is to somehow scale up the gas price and scale down the exchange rate by the same factor. + // Gets the StorageGasOracleConfig for each remote chain for a particular local chain. // Accommodates small non-integer gas prices by scaling up the gas price // and scaling down the exchange rate by the same factor. @@ -145,10 +179,17 @@ export function getLocalStorageGasOracleConfig({ local, gasOracleParams, exchangeRateMarginPct, + gasPriceModifier, }: { local: ChainName; gasOracleParams: ChainMap; exchangeRateMarginPct: number; + gasPriceModifier?: ( + local: ChainName, + remote: ChainName, + exchangeRate: BigNumber, + gasPrice: BigNumber, + ) => BigNumber; }): ChainMap { const remotes = Object.keys(gasOracleParams).filter( (remote) => remote !== local, @@ -160,7 +201,7 @@ export function getLocalStorageGasOracleConfig({ const localDecimals = gasOracleParams[local].nativeToken.decimals; return remotes.reduce((agg, remote) => { const remoteDecimals = gasOracleParams[remote].nativeToken.decimals; - let exchangeRate = getTokenExchangeRateFromValues({ + let exchangeRateFloat = getTokenExchangeRateFromValues({ local, remote, tokenPrices, @@ -168,6 +209,20 @@ export function getLocalStorageGasOracleConfig({ decimals: { local: localDecimals, remote: remoteDecimals }, }); + console.log( + 'exchangeRateFloat before protocol specific', + exchangeRateFloat.toString(), + ); + let exchangeRate = getProtocolSpecificExchangeRate( + exchangeRateFloat, + // TODO need to get this + ProtocolType.Ethereum, + ); + console.log( + 'exchangeRate after protocol specific', + exchangeRate.toString(), + ); + // First parse as a number, so we have floating point precision. // Recall it's possible to have gas prices that are not integers, even // after converting to the "wei" version of the token. @@ -203,7 +258,11 @@ export function getLocalStorageGasOracleConfig({ } // Our integer gas price. - const gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); + let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); + + if (gasPriceModifier) { + gasPriceBn = gasPriceModifier(local, remote, exchangeRate, gasPriceBn); + } return { ...agg, diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index eddbb1c788..8dfb1af2ce 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -35,7 +35,7 @@ import { rootLogger, } from '@hyperlane-xyz/utils'; -import { TOKEN_EXCHANGE_RATE_SCALE } from '../consts/igp.js'; +import { TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM } from '../consts/igp.js'; import { HyperlaneAddresses } from '../contracts/types.js'; import { HyperlaneModule, @@ -491,7 +491,7 @@ export class EvmHookModule extends HyperlaneModule< const exampleRemoteGasCost = BigNumber.from(target.tokenExchangeRate) .mul(target.gasPrice) .mul(exampleRemoteGas) - .div(TOKEN_EXCHANGE_RATE_SCALE); + .div(TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM); this.logger.info( `${ this.chain diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index ce71e653c6..690635b0fe 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -9,8 +9,8 @@ export { export { S3Config, S3Receipt, S3Wrapper } from './aws/s3.js'; export { S3Validator } from './aws/validator.js'; export { - TOKEN_EXCHANGE_RATE_DECIMALS, - TOKEN_EXCHANGE_RATE_SCALE, + TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM as TOKEN_EXCHANGE_RATE_DECIMALS, + TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM as TOKEN_EXCHANGE_RATE_SCALE, } from './consts/igp.js'; export { MAILBOX_VERSION } from './consts/mailbox.js'; export { diff --git a/typescript/sdk/src/warp/WarpCore.ts b/typescript/sdk/src/warp/WarpCore.ts index 1dfa2eac56..368db1a22f 100644 --- a/typescript/sdk/src/warp/WarpCore.ts +++ b/typescript/sdk/src/warp/WarpCore.ts @@ -5,7 +5,7 @@ import { HexString, ProtocolType, assert, - convertDecimals, + convertDecimalsIntegerString, convertToProtocolAddress, isValidAddress, isZeroishAddress, @@ -502,7 +502,7 @@ export class WarpCore { ); } - const destinationBalanceInOriginDecimals = convertDecimals( + const destinationBalanceInOriginDecimals = convertDecimalsIntegerString( destinationToken.decimals, originToken.decimals, destinationBalance.toString(), @@ -679,7 +679,7 @@ export class WarpCore { // Convert the minDestinationTransferAmount to an origin amount const minOriginTransferAmount = destinationToken.amount( - convertDecimals( + convertDecimalsIntegerString( originToken.decimals, destinationToken.decimals, minDestinationTransferAmount.toString(), diff --git a/typescript/utils/src/amount.ts b/typescript/utils/src/amount.ts index ab8a3b3347..6ac3cb0035 100644 --- a/typescript/utils/src/amount.ts +++ b/typescript/utils/src/amount.ts @@ -111,25 +111,33 @@ export function eqAmountApproximate( * @param value The value to convert. * @returns `value` represented with `toDecimals` decimals in string type. */ -export function convertDecimals( +export function convertDecimalsIntegerString( fromDecimals: number, toDecimals: number, value: BigNumber.Value, ): string { + const converted = convertDecimals(fromDecimals, toDecimals, value); + return converted.integerValue(BigNumber.ROUND_FLOOR).toString(10); +} + +export function convertDecimals( + fromDecimals: number, + toDecimals: number, + value: BigNumber.Value, +): BigNumber { const amount = BigNumber(value); - if (fromDecimals === toDecimals) return amount.toString(10); + if (fromDecimals === toDecimals) return amount; else if (fromDecimals > toDecimals) { const difference = fromDecimals - toDecimals; return amount .div(BigNumber(10).pow(difference)) - .integerValue(BigNumber.ROUND_FLOOR) - .toString(10); + .integerValue(BigNumber.ROUND_FLOOR); } // fromDecimals < toDecimals else { const difference = toDecimals - fromDecimals; - return amount.times(BigNumber(10).pow(difference)).toString(10); + return amount.times(BigNumber(10).pow(difference)); } } diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index f4bd9779cb..4c7f5724db 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -43,6 +43,7 @@ export { export { addBufferToGasLimit, convertDecimals, + convertDecimalsIntegerString, eqAmountApproximate, fromWei, fromWeiRounded, From 12b2caf00dad006fa21a7af8d8c6631f121b64ce Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Tue, 14 Jan 2025 16:18:33 +0000 Subject: [PATCH 02/23] printing gas oracles mostly works, gonna do a little cleanup --- .../infra/config/environments/mainnet3/igp.ts | 25 ++++++------ .../infra/config/environments/testnet4/igp.ts | 25 +++++++----- .../sealevel-helpers/print-gas-oracles.ts | 40 +++++++++---------- typescript/infra/src/config/gas-oracle.ts | 24 +++++------ typescript/sdk/src/gas/utils.ts | 9 +++++ 5 files changed, 66 insertions(+), 57 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 5c2dfd6efc..8d282d0da7 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -42,19 +42,20 @@ export function getOverheadWithOverrides(local: ChainName, remote: ChainName) { const storageGasOracleConfig: AllStorageGasOracleConfigs = getAllStorageGasOracleConfigs( supportedChainNames, + tokenPrices, gasPrices, - (local, remote) => - getTokenExchangeRateFromValues({ - local, - remote, - tokenPrices, - exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, - decimals: { - local: mustGetChainNativeToken(local).decimals, - remote: mustGetChainNativeToken(remote).decimals, - }, - }), - (local) => parseFloat(tokenPrices[local]), + // (local, remote) => + // getTokenExchangeRateFromValues({ + // local, + // remote, + // tokenPrices, + // exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, + // decimals: { + // local: mustGetChainNativeToken(local).decimals, + // remote: mustGetChainNativeToken(remote).decimals, + // }, + // }), + // (local) => parseFloat(tokenPrices[local]), (local, remote) => getOverheadWithOverrides(local, remote), ); diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index 0cabe5b69a..c03ae0e6c9 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -25,18 +25,21 @@ const tokenPrices: ChainMap = rawTokenPrices; export const storageGasOracleConfig: AllStorageGasOracleConfigs = getAllStorageGasOracleConfigs( supportedChainNames, + tokenPrices, gasPrices, - (local, remote) => - getTokenExchangeRateFromValues({ - local, - remote, - tokenPrices, - exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, - decimals: { - local: mustGetChainNativeToken(local).decimals, - remote: mustGetChainNativeToken(remote).decimals, - }, - }), + // (local, remote) => { + // console.log('testnet4 tokenPrices before!!', tokenPrices); + // return getTokenExchangeRateFromValues({ + // local, + // remote, + // tokenPrices, + // exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, + // decimals: { + // local: mustGetChainNativeToken(local).decimals, + // remote: mustGetChainNativeToken(remote).decimals, + // }, + // }) + // } ); export const igp: ChainMap = objMap( diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index 76982de9d7..e1a42c8695 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -11,29 +11,29 @@ async function main() { const environmentConfig = getEnvironmentConfig(args.environment); - // Construct a nested map of origin -> destination -> { oracleConfig, overhead } - const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { - console.log('origin', origin, 'igpConfig', igpConfig); - let a = objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { - console.log('origin', origin, 'destination', destination); - console.log( - 'oracleConfig', - oracleConfig, - 'overhead', - igpConfig?.overhead?.[destination], - ); - return { - oracleConfig, - overhead: igpConfig?.overhead?.[destination], - }; - }); - // console.log('a', a, 'origin'); - return 'sup'; - }); + // // Construct a nested map of origin -> destination -> { oracleConfig, overhead } + // const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { + // console.log('origin', origin, 'igpConfig', igpConfig); + // let a = objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { + // console.log('origin', origin, 'destination', destination); + // console.log( + // 'oracleConfig', + // oracleConfig, + // 'overhead', + // igpConfig?.overhead?.[destination], + // ); + // return { + // oracleConfig, + // overhead: igpConfig?.overhead?.[destination], + // }; + // }); + // // console.log('a', a, 'origin'); + // return 'sup'; + // }); console.log('do we get here ?'); - console.log(JSON.stringify(gasOracles, null, 2)); + // console.log(JSON.stringify(gasOracles, null, 2)); } main().catch((err) => { diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 860ce0ef85..cffa4757ee 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -38,16 +38,15 @@ export const TYPICAL_HANDLE_GAS_USAGE = 50_000; function getLocalStorageGasOracleConfigOverride( local: ChainName, remotes: ChainName[], + tokenPrices: ChainMap, gasPrices: ChainMap, - getTokenExchangeRate: (local: ChainName, remote: ChainName) => BigNumberJs, - getTokenUsdPrice?: (chain: ChainName) => number, getOverhead?: (local: ChainName, remote: ChainName) => number, ): ChainMap { const gasOracleParams = [local, ...remotes].reduce((agg, remote) => { agg[remote] = { gasPrice: gasPrices[remote], nativeToken: { - price: getTokenUsdPrice ? getTokenUsdPrice(remote).toString() : '0', + price: tokenPrices[remote], decimals: mustGetChainNativeToken(remote).decimals, }, }; @@ -60,25 +59,25 @@ function getLocalStorageGasOracleConfigOverride( exchangeRate: BigNumber, gasPrice: BigNumber, ) => { - if (getTokenUsdPrice && getOverhead) { + if (getOverhead) { const typicalRemoteGasAmount = getTypicalRemoteGasAmount( local, remote, getOverhead, ); + const localTokenUsdPrice = parseFloat(tokenPrices[local]); const typicalIgpQuoteUsd = getUsdQuote( - local, + localTokenUsdPrice, gasPrice, exchangeRate, typicalRemoteGasAmount, - getTokenUsdPrice, ); const minUsdCost = getMinUsdCost(local, remote); if (typicalIgpQuoteUsd < minUsdCost) { // Adjust the gasPrice to meet the minimum cost const minIgpQuote = ethers.utils.parseEther( - (minUsdCost / getTokenUsdPrice(local)).toPrecision(8), + (minUsdCost / localTokenUsdPrice).toPrecision(8), ); return minIgpQuote .mul(TOKEN_EXCHANGE_RATE_SCALE) @@ -225,18 +224,17 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { } function getUsdQuote( - local: ChainName, + localTokenUsdPrice: number, gasPrice: BigNumber, exchangeRate: BigNumber, remoteGasAmount: number, - getTokenUsdPrice: (chain: ChainName) => number, ): number { const quote = gasPrice .mul(exchangeRate) .mul(remoteGasAmount) .div(TOKEN_EXCHANGE_RATE_SCALE); const quoteUsd = - getTokenUsdPrice(local) * parseFloat(ethers.utils.formatEther(quote)); + localTokenUsdPrice * parseFloat(ethers.utils.formatEther(quote)); return quoteUsd; } @@ -261,9 +259,8 @@ export function getOverhead( // Gets the map of remote gas oracle configs for each local chain export function getAllStorageGasOracleConfigs( chainNames: ChainName[], + tokenPrices: ChainMap, gasPrices: ChainMap, - getTokenExchangeRate: (local: ChainName, remote: ChainName) => BigNumberJs, - getTokenUsdPrice?: (chain: ChainName) => number, getOverhead?: (local: ChainName, remote: ChainName) => number, ): AllStorageGasOracleConfigs { return chainNames.filter(isEthereumProtocolChain).reduce((agg, local) => { @@ -273,9 +270,8 @@ export function getAllStorageGasOracleConfigs( [local]: getLocalStorageGasOracleConfigOverride( local, remotes, + tokenPrices, gasPrices, - getTokenExchangeRate, - getTokenUsdPrice, getOverhead, ), }; diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index d66f83ca2f..fcd9288fe4 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -117,6 +117,7 @@ export function getTokenExchangeRateFromValues({ exchangeRateMarginPct: number; decimals: { local: number; remote: number }; }): BigNumberJs { + console.log('bruh tokenPrices', tokenPrices); // Workaround for chicken-egg dependency problem. // We need to provide some default value here to satisfy the config on initial load, // whilst knowing that it will get overwritten when a script actually gets run. @@ -124,14 +125,22 @@ export function getTokenExchangeRateFromValues({ const localValue = new BigNumberJs(tokenPrices[local] ?? defaultValue); const remoteValue = new BigNumberJs(tokenPrices[remote] ?? defaultValue); + if (localValue.isZero() || remoteValue.isZero()) { + console.log('exchangeRateMarginPct', exchangeRateMarginPct, decimals); + // print stacktrace + console.trace(); + } + console.log( 'yeet 1', local, remote, 'localValue', localValue.toString(), + tokenPrices[local], 'remoteValue', remoteValue.toString(), + tokenPrices[remote], ); // This does not yet account for decimals! let exchangeRate = remoteValue.div(localValue); From ee4c7c940ba7851180ab0842ef7c514577e79e5e Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Tue, 14 Jan 2025 16:21:55 +0000 Subject: [PATCH 03/23] a little cweanup --- .../infra/config/environments/mainnet3/igp.ts | 12 --- .../config/environments/test/gas-oracle.ts | 16 ++-- .../infra/config/environments/testnet4/igp.ts | 19 +---- typescript/infra/src/config/gas-oracle.ts | 82 ------------------- 4 files changed, 9 insertions(+), 120 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 8d282d0da7..9805db7af0 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -44,18 +44,6 @@ const storageGasOracleConfig: AllStorageGasOracleConfigs = supportedChainNames, tokenPrices, gasPrices, - // (local, remote) => - // getTokenExchangeRateFromValues({ - // local, - // remote, - // tokenPrices, - // exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, - // decimals: { - // local: mustGetChainNativeToken(local).decimals, - // remote: mustGetChainNativeToken(remote).decimals, - // }, - // }), - // (local) => parseFloat(tokenPrices[local]), (local, remote) => getOverheadWithOverrides(local, remote), ); diff --git a/typescript/infra/config/environments/test/gas-oracle.ts b/typescript/infra/config/environments/test/gas-oracle.ts index e3211e3f73..1145bef960 100644 --- a/typescript/infra/config/environments/test/gas-oracle.ts +++ b/typescript/infra/config/environments/test/gas-oracle.ts @@ -1,5 +1,3 @@ -import { BigNumber as BigNumberJs } from 'bignumber.js'; - import { ChainMap, GasPriceConfig } from '@hyperlane-xyz/sdk'; import { @@ -9,12 +7,18 @@ import { import { testChainNames } from './chains.js'; -const TEST_TOKEN_EXCHANGE_RATE = new BigNumberJs('1'); +const TEST_TOKEN_EXCHANGE_RATE = '1'; const TEST_GAS_PRICE_CONFIG: GasPriceConfig = { amount: '2', decimals: 9, // gwei }; +const tokenPrices: ChainMap = { + test1: TEST_TOKEN_EXCHANGE_RATE, + test2: TEST_TOKEN_EXCHANGE_RATE, + test3: TEST_TOKEN_EXCHANGE_RATE, +}; + const gasPrices: ChainMap = { test1: TEST_GAS_PRICE_CONFIG, test2: TEST_GAS_PRICE_CONFIG, @@ -22,8 +26,4 @@ const gasPrices: ChainMap = { }; export const storageGasOracleConfig: AllStorageGasOracleConfigs = - getAllStorageGasOracleConfigs( - testChainNames, - gasPrices, - (_local, _remote) => TEST_TOKEN_EXCHANGE_RATE, - ); + getAllStorageGasOracleConfigs(testChainNames, tokenPrices, gasPrices); diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index c03ae0e6c9..1aef975710 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -23,24 +23,7 @@ import rawTokenPrices from './tokenPrices.json'; const tokenPrices: ChainMap = rawTokenPrices; export const storageGasOracleConfig: AllStorageGasOracleConfigs = - getAllStorageGasOracleConfigs( - supportedChainNames, - tokenPrices, - gasPrices, - // (local, remote) => { - // console.log('testnet4 tokenPrices before!!', tokenPrices); - // return getTokenExchangeRateFromValues({ - // local, - // remote, - // tokenPrices, - // exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, - // decimals: { - // local: mustGetChainNativeToken(local).decimals, - // remote: mustGetChainNativeToken(remote).decimals, - // }, - // }) - // } - ); + getAllStorageGasOracleConfigs(supportedChainNames, tokenPrices, gasPrices); export const igp: ChainMap = objMap( owners, diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index cffa4757ee..506a4dd1c0 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -93,88 +93,6 @@ function getLocalStorageGasOracleConfigOverride( exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, gasPriceModifier, }); - - // return remotes.reduce((agg, remote) => { - // let exchangeRate = getTokenExchangeRate(local, remote); - // if (!gasPrices[remote]) { - // // Will run into this case when adding new chains - // console.warn(chalk.yellow(`No gas price set for ${remote}`)); - // return agg; - // } - - // // First parse as a number, so we have floating point precision. - // // Recall it's possible to have gas prices that are not integers, even - // // after converting to the "wei" version of the token. - // let gasPrice = - // parseFloat(gasPrices[remote].amount) * - // Math.pow(10, gasPrices[remote].decimals); - // if (isNaN(gasPrice)) { - // throw new Error( - // `Invalid gas price for chain ${remote}: ${gasPrices[remote]}`, - // ); - // } - - // // We have very little precision and ultimately need an integer value for - // // the gas price that will be set on-chain. We scale up the gas price and - // // scale down the exchange rate by the same factor. - // if (gasPrice < 10 && gasPrice % 1 !== 0) { - // // Scale up the gas price by 1e4 - // const gasPriceScalingFactor = 1e4; - - // // Check that there's no significant underflow when applying - // // this to the exchange rate: - // const adjustedExchangeRate = exchangeRate.div(gasPriceScalingFactor); - // const recoveredExchangeRate = adjustedExchangeRate.mul( - // gasPriceScalingFactor, - // ); - // if (recoveredExchangeRate.mul(100).div(exchangeRate).lt(99)) { - // throw new Error('Too much underflow when downscaling exchange rate'); - // } - - // // Apply the scaling factor - // exchangeRate = adjustedExchangeRate; - // gasPrice *= gasPriceScalingFactor; - // } - - // // Our integer gas price. - // let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); - - // // If we have access to these, let's use the USD prices to apply some minimum - // // typical USD payment heuristics. - // if (getTokenUsdPrice && getOverhead) { - // const typicalRemoteGasAmount = getTypicalRemoteGasAmount( - // local, - // remote, - // getOverhead, - // ); - // const typicalIgpQuoteUsd = getUsdQuote( - // local, - // gasPriceBn, - // exchangeRate, - // typicalRemoteGasAmount, - // getTokenUsdPrice, - // ); - - // const minUsdCost = getMinUsdCost(local, remote); - // if (typicalIgpQuoteUsd < minUsdCost) { - // // Adjust the gasPrice to meet the minimum cost - // const minIgpQuote = ethers.utils.parseEther( - // (minUsdCost / getTokenUsdPrice(local)).toPrecision(8), - // ); - // gasPriceBn = minIgpQuote - // .mul(TOKEN_EXCHANGE_RATE_SCALE) - // .div(exchangeRate.mul(typicalRemoteGasAmount)); - // } - // } - - // return { - // ...agg, - // [remote]: { - // tokenExchangeRate: exchangeRate, - // gasPrice: gasPriceBn, - // }, - // }; - // }, {}); } export function getTypicalRemoteGasAmount( From e080a978efc7dd68762c3e866d7cbe2dab225961 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 15 Jan 2025 10:07:10 +0000 Subject: [PATCH 04/23] some cleanup, getting there --- .../infra/config/environments/mainnet3/igp.ts | 7 +-- .../infra/config/environments/testnet4/igp.ts | 9 +--- .../sealevel-helpers/print-gas-oracles.ts | 45 ++++++++++--------- typescript/infra/src/config/gas-oracle.ts | 7 ++- typescript/sdk/src/gas/utils.ts | 11 ++--- 5 files changed, 36 insertions(+), 43 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 9805db7af0..55cc31c250 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -4,22 +4,19 @@ import { ChainTechnicalStack, HookType, IgpConfig, - getTokenExchangeRateFromValues, } from '@hyperlane-xyz/sdk'; import { exclude, objMap } from '@hyperlane-xyz/utils'; import { AllStorageGasOracleConfigs, - EXCHANGE_RATE_MARGIN_PCT, getAllStorageGasOracleConfigs, getOverhead, } from '../../../src/config/gas-oracle.js'; -import { mustGetChainNativeToken } from '../../../src/utils/utils.js'; import { getChain } from '../../registry.js'; import { ethereumChainNames } from './chains.js'; import gasPrices from './gasPrices.json'; -import { DEPLOYER, ethereumChainOwners } from './owners.js'; +import { DEPLOYER, chainOwners } from './owners.js'; import { supportedChainNames } from './supportedChainNames.js'; import rawTokenPrices from './tokenPrices.json'; @@ -48,7 +45,7 @@ const storageGasOracleConfig: AllStorageGasOracleConfigs = ); export const igp: ChainMap = objMap( - ethereumChainOwners, + chainOwners, (local, owner): IgpConfig => ({ type: HookType.INTERCHAIN_GAS_PAYMASTER, ...owner, diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index 1aef975710..92fe27ddd7 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -1,18 +1,11 @@ -import { - ChainMap, - HookType, - IgpConfig, - getTokenExchangeRateFromValues, -} from '@hyperlane-xyz/sdk'; +import { ChainMap, HookType, IgpConfig } from '@hyperlane-xyz/sdk'; import { Address, exclude, objMap } from '@hyperlane-xyz/utils'; import { AllStorageGasOracleConfigs, - EXCHANGE_RATE_MARGIN_PCT, getAllStorageGasOracleConfigs, getOverhead, } from '../../../src/config/gas-oracle.js'; -import { mustGetChainNativeToken } from '../../../src/utils/utils.js'; import { ethereumChainNames } from './chains.js'; import gasPrices from './gasPrices.json'; diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index e1a42c8695..c1ddefcc80 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -1,4 +1,4 @@ -import { objMap } from '@hyperlane-xyz/utils'; +import { objMap, stringifyObject } from '@hyperlane-xyz/utils'; import { getArgs } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; @@ -11,29 +11,32 @@ async function main() { const environmentConfig = getEnvironmentConfig(args.environment); - // // Construct a nested map of origin -> destination -> { oracleConfig, overhead } - // const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { - // console.log('origin', origin, 'igpConfig', igpConfig); - // let a = objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { - // console.log('origin', origin, 'destination', destination); - // console.log( - // 'oracleConfig', - // oracleConfig, - // 'overhead', - // igpConfig?.overhead?.[destination], - // ); - // return { - // oracleConfig, - // overhead: igpConfig?.overhead?.[destination], - // }; - // }); - // // console.log('a', a, 'origin'); - // return 'sup'; - // }); + // Construct a nested map of origin -> destination -> { oracleConfig, overhead } + const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { + console.log('origin', origin, 'igpConfig', igpConfig); + let a = objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { + console.log('origin', origin, 'destination', destination); + console.log( + 'oracleConfig', + oracleConfig, + 'overhead', + igpConfig?.overhead?.[destination], + ); + return { + oracleConfig, + overhead: igpConfig?.overhead?.[destination], + }; + }); + // console.log('a', a, 'origin'); + // return 'sup'; + return a; + }); console.log('do we get here ?'); - // console.log(JSON.stringify(gasOracles, null, 2)); + console.log('keys?', Object.keys(gasOracles)); + + console.log(stringifyObject(gasOracles, 'yaml')); } main().catch((err) => { diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 506a4dd1c0..7a11c5ab5f 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -13,6 +13,7 @@ import { getLocalStorageGasOracleConfig, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { isEthereumProtocolChain, @@ -89,6 +90,8 @@ function getLocalStorageGasOracleConfigOverride( return getLocalStorageGasOracleConfig({ local, + localProtocolType: ProtocolType.Ethereum, + // getChain(local).protocolType, gasOracleParams, exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, gasPriceModifier, @@ -181,7 +184,9 @@ export function getAllStorageGasOracleConfigs( gasPrices: ChainMap, getOverhead?: (local: ChainName, remote: ChainName) => number, ): AllStorageGasOracleConfigs { - return chainNames.filter(isEthereumProtocolChain).reduce((agg, local) => { + // return chainNames.filter(isEthereumProtocolChain). + + return chainNames.reduce((agg, local) => { const remotes = chainNames.filter((chain) => local !== chain); return { ...agg, diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index fcd9288fe4..ce3455ba59 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -117,7 +117,6 @@ export function getTokenExchangeRateFromValues({ exchangeRateMarginPct: number; decimals: { local: number; remote: number }; }): BigNumberJs { - console.log('bruh tokenPrices', tokenPrices); // Workaround for chicken-egg dependency problem. // We need to provide some default value here to satisfy the config on initial load, // whilst knowing that it will get overwritten when a script actually gets run. @@ -125,12 +124,6 @@ export function getTokenExchangeRateFromValues({ const localValue = new BigNumberJs(tokenPrices[local] ?? defaultValue); const remoteValue = new BigNumberJs(tokenPrices[remote] ?? defaultValue); - if (localValue.isZero() || remoteValue.isZero()) { - console.log('exchangeRateMarginPct', exchangeRateMarginPct, decimals); - // print stacktrace - console.trace(); - } - console.log( 'yeet 1', local, @@ -186,11 +179,13 @@ function getProtocolSpecificExchangeRate( // and scaling down the exchange rate by the same factor. export function getLocalStorageGasOracleConfig({ local, + localProtocolType, gasOracleParams, exchangeRateMarginPct, gasPriceModifier, }: { local: ChainName; + localProtocolType: ProtocolType; gasOracleParams: ChainMap; exchangeRateMarginPct: number; gasPriceModifier?: ( @@ -225,7 +220,7 @@ export function getLocalStorageGasOracleConfig({ let exchangeRate = getProtocolSpecificExchangeRate( exchangeRateFloat, // TODO need to get this - ProtocolType.Ethereum, + localProtocolType, ); console.log( 'exchangeRate after protocol specific', From 9dd4108c577348eef4a1bddd0cd9ba1c86cb20c4 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 15 Jan 2025 12:11:23 +0000 Subject: [PATCH 05/23] it's hideous but seemingly works --- .../hyperlane-sealevel-igp/src/accounts.rs | 21 +++ .../sealevel-helpers/print-gas-oracles.ts | 10 +- typescript/infra/src/config/gas-oracle.ts | 122 ++++++++++---- typescript/sdk/src/consts/igp.ts | 7 + typescript/sdk/src/gas/oracle/types.ts | 11 ++ typescript/sdk/src/gas/utils.ts | 159 ++++++++++++------ typescript/sdk/src/index.ts | 4 +- typescript/utils/src/amount.ts | 4 +- 8 files changed, 245 insertions(+), 93 deletions(-) diff --git a/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs b/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs index ba08e2c8d4..aadc15d81a 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs @@ -339,4 +339,25 @@ mod test { let result = convert_decimals(num, from_decimals, to_decimals); assert_eq!(result, U256::from(0u128)); } + + // fn test_igp_quoting_to_ethereum() { + // let igp = Igp { + // bump_seed: 0, + // salt: H256::default(), + // owner: None, + // beneficiary: Pubkey::new_unique(), + // gas_oracles: { + // let mut gas_oracles = HashMap::new(); + // gas_oracles.insert( + // 1, + // GasOracle::RemoteGasData(RemoteGasData { + // token_exchange_rate: 15000000000000000000, + // gas_price: 10000000000, + // token_decimals: 18, + // }), + // ); + // gas_oracles + // }, + // }; + // } } diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index c1ddefcc80..96287de49d 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -14,7 +14,10 @@ async function main() { // Construct a nested map of origin -> destination -> { oracleConfig, overhead } const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { console.log('origin', origin, 'igpConfig', igpConfig); - let a = objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { + if (!igpConfig.oracleConfig) { + return {}; + } + return objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { console.log('origin', origin, 'destination', destination); console.log( 'oracleConfig', @@ -27,14 +30,11 @@ async function main() { overhead: igpConfig?.overhead?.[destination], }; }); - // console.log('a', a, 'origin'); - // return 'sup'; - return a; }); console.log('do we get here ?'); - console.log('keys?', Object.keys(gasOracles)); + console.log('keys?', stringifyObject(Object.keys(gasOracles))); console.log(stringifyObject(gasOracles, 'yaml')); } diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 7a11c5ab5f..8d0d741aee 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -7,14 +7,23 @@ import { ChainMap, ChainName, GasPriceConfig, + ProtocolAgnositicGasOracleConfig, StorageGasOracleConfig, - TOKEN_EXCHANGE_RATE_SCALE, defaultMultisigConfigs, getLocalStorageGasOracleConfig, + getProtocolSpecificExchangeRateScale, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import { + ProtocolType, + assert, + convertDecimals, + convertDecimalsIntegerString, + fromWei, + toWei, +} from '@hyperlane-xyz/utils'; +import { getChain } from '../../config/registry.js'; import { isEthereumProtocolChain, mustGetChainNativeToken, @@ -43,6 +52,11 @@ function getLocalStorageGasOracleConfigOverride( gasPrices: ChainMap, getOverhead?: (local: ChainName, remote: ChainName) => number, ): ChainMap { + const localProtocolType = getChain(local).protocol; + const localExchangeRateScale = + getProtocolSpecificExchangeRateScale(localProtocolType); + const localNativeTokenDecimals = mustGetChainNativeToken(local).decimals; + const gasOracleParams = [local, ...remotes].reduce((agg, remote) => { agg[remote] = { gasPrice: gasPrices[remote], @@ -57,9 +71,8 @@ function getLocalStorageGasOracleConfigOverride( const gasPriceModifier = ( local: ChainName, remote: ChainName, - exchangeRate: BigNumber, - gasPrice: BigNumber, - ) => { + gasOracleConfig: ProtocolAgnositicGasOracleConfig, + ): BigNumberJs.Value => { if (getOverhead) { const typicalRemoteGasAmount = getTypicalRemoteGasAmount( local, @@ -69,29 +82,48 @@ function getLocalStorageGasOracleConfigOverride( const localTokenUsdPrice = parseFloat(tokenPrices[local]); const typicalIgpQuoteUsd = getUsdQuote( localTokenUsdPrice, - gasPrice, - exchangeRate, + localExchangeRateScale, + localNativeTokenDecimals, + localProtocolType, + gasOracleConfig, typicalRemoteGasAmount, ); const minUsdCost = getMinUsdCost(local, remote); if (typicalIgpQuoteUsd < minUsdCost) { // Adjust the gasPrice to meet the minimum cost - const minIgpQuote = ethers.utils.parseEther( - (minUsdCost / localTokenUsdPrice).toPrecision(8), + const minIgpQuoteWei = toWei( + new BigNumberJs(minUsdCost).div(localTokenUsdPrice), + localNativeTokenDecimals, ); - return minIgpQuote - .mul(TOKEN_EXCHANGE_RATE_SCALE) - .div(exchangeRate.mul(typicalRemoteGasAmount)); + console.log('minIgpQuoteWei', minIgpQuoteWei); + let newGasPrice = new BigNumberJs(minIgpQuoteWei) + .times(localExchangeRateScale.toString()) + .div( + new BigNumberJs(gasOracleConfig.tokenExchangeRate).times( + typicalRemoteGasAmount, + ), + ); + console.log('newGasPrice', newGasPrice); + if (localProtocolType === ProtocolType.Sealevel) { + // On Sealevel, the exchange rate doesn't consider decimals. + // We therefor explicitly convert decimals to remote decimals. + newGasPrice = convertDecimals( + localNativeTokenDecimals, + gasOracleConfig.tokenDecimals, + newGasPrice.toString(), + ); + // assert(newGasPrice.gt(0), 'newGasPrice must be greater than 0'); + return newGasPrice; + } } } - return gasPrice; + return gasOracleConfig.gasPrice; }; return getLocalStorageGasOracleConfig({ local, - localProtocolType: ProtocolType.Ethereum, - // getChain(local).protocolType, + localProtocolType, gasOracleParams, exchangeRateMarginPct: EXCHANGE_RATE_MARGIN_PCT, gasPriceModifier, @@ -146,16 +178,27 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { function getUsdQuote( localTokenUsdPrice: number, - gasPrice: BigNumber, - exchangeRate: BigNumber, + localExchangeRateScale: BigNumber, + localNativeTokenDecimals: number, + localProtocolType: ProtocolType, + gasOracleConfig: ProtocolAgnositicGasOracleConfig, remoteGasAmount: number, ): number { - const quote = gasPrice - .mul(exchangeRate) + let quote = BigNumber.from(gasOracleConfig.gasPrice) + .mul(gasOracleConfig.tokenExchangeRate) .mul(remoteGasAmount) - .div(TOKEN_EXCHANGE_RATE_SCALE); + .div(localExchangeRateScale) + .toString(); + if (localProtocolType === ProtocolType.Sealevel) { + // Convert decimals to local decimals + quote = convertDecimals( + gasOracleConfig.tokenDecimals, + localNativeTokenDecimals, + quote, + ).toString(); + } const quoteUsd = - localTokenUsdPrice * parseFloat(ethers.utils.formatEther(quote)); + localTokenUsdPrice * parseFloat(fromWei(quote, localNativeTokenDecimals)); return quoteUsd; } @@ -186,17 +229,28 @@ export function getAllStorageGasOracleConfigs( ): AllStorageGasOracleConfigs { // return chainNames.filter(isEthereumProtocolChain). - return chainNames.reduce((agg, local) => { - const remotes = chainNames.filter((chain) => local !== chain); - return { - ...agg, - [local]: getLocalStorageGasOracleConfigOverride( - local, - remotes, - tokenPrices, - gasPrices, - getOverhead, - ), - }; - }, {}) as AllStorageGasOracleConfigs; + return chainNames + .filter((chain) => { + // For now, only support Ethereum and Sealevel chains. + // Cosmos chains should be supported in the future, but at the moment + // are more subject to loss of precision issues in the exchange rate, + // where we'd need to scale the gas price accordingly. + const protocol = getChain(chain).protocol; + return ( + protocol === ProtocolType.Ethereum || protocol === ProtocolType.Sealevel + ); + }) + .reduce((agg, local) => { + const remotes = chainNames.filter((chain) => local !== chain); + return { + ...agg, + [local]: getLocalStorageGasOracleConfigOverride( + local, + remotes, + tokenPrices, + gasPrices, + getOverhead, + ), + }; + }, {}) as AllStorageGasOracleConfigs; } diff --git a/typescript/sdk/src/consts/igp.ts b/typescript/sdk/src/consts/igp.ts index 8d75f4f7f0..351fc7b49f 100644 --- a/typescript/sdk/src/consts/igp.ts +++ b/typescript/sdk/src/consts/igp.ts @@ -13,3 +13,10 @@ export const TOKEN_EXCHANGE_RATE_SCALE_SEALEVEL = ethers.utils.parseUnits( '1', TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL, ); + +export const TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS = 10; + +export const TOKEN_EXCHANGE_RATE_SCALE_COSMOS = ethers.utils.parseUnits( + '1', + TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS, +); diff --git a/typescript/sdk/src/gas/oracle/types.ts b/typescript/sdk/src/gas/oracle/types.ts index 383bf8adc2..3117c37216 100644 --- a/typescript/sdk/src/gas/oracle/types.ts +++ b/typescript/sdk/src/gas/oracle/types.ts @@ -13,6 +13,17 @@ export type StorageGasOracleConfig = z.output< typeof StorageGasOracleConfigSchema >; +export const ProtocolAgnositicGasOracleConfigSchema = + StorageGasOracleConfigSchema.extend({ + // The number of decimals of the remote native token. + tokenDecimals: z.number(), + }); + +// Gas data to configure on a single destination chain. +export type ProtocolAgnositicGasOracleConfig = z.output< + typeof ProtocolAgnositicGasOracleConfigSchema +>; + export type OracleData = { tokenExchangeRate: ethers.BigNumber; gasPrice: ethers.BigNumber; diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index ce3455ba59..f31ea8d27f 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -6,6 +6,7 @@ import { BigNumber, ethers } from 'ethers'; import { ProtocolType, convertDecimals, objMap } from '@hyperlane-xyz/utils'; import { + TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS, TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM, TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL, } from '../consts/igp.js'; @@ -15,7 +16,7 @@ import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { ChainMap, ChainName } from '../types.js'; import { getCosmosRegistryChain } from '../utils/cosmos.js'; -import { StorageGasOracleConfig } from './oracle/types.js'; +import { ProtocolAgnositicGasOracleConfig } from './oracle/types.js'; export interface GasPriceConfig { amount: string; @@ -103,19 +104,19 @@ export async function getCosmosChainGasPrice( }; } -// Gets the exchange rate of the remote quoted in local tokens -export function getTokenExchangeRateFromValues({ +// Gets the exchange rate of the remote quoted in local tokens, not accounting for decimals. +function getTokenExchangeRateFromValues({ local, remote, tokenPrices, exchangeRateMarginPct, - decimals, -}: { +}: // decimals, +{ local: ChainName; remote: ChainName; tokenPrices: ChainMap; exchangeRateMarginPct: number; - decimals: { local: number; remote: number }; + // decimals: { local: number; remote: number }; }): BigNumberJs { // Workaround for chicken-egg dependency problem. // We need to provide some default value here to satisfy the config on initial load, @@ -142,28 +143,44 @@ export function getTokenExchangeRateFromValues({ exchangeRate = exchangeRate.times(100 + exchangeRateMarginPct).div(100); console.log('yeet 3', local, remote, 'exchangeRate', exchangeRate.toString()); - const value = convertDecimals(decimals.remote, decimals.local, exchangeRate); - console.log('yeet 4', local, remote, 'value', value.toString()); + // const value = convertDecimals(decimals.remote, decimals.local, exchangeRate); + // console.log('yeet 4', local, remote, 'value', value.toString()); assert( - value.isGreaterThan(0), + exchangeRate.isGreaterThan(0), 'Exchange rate must be greater than 0, possible loss of precision', ); - return value; + return exchangeRate; +} + +function getProtocolSpecificExchangeRateDecimals( + protocolType: ProtocolType, +): number { + switch (protocolType) { + case ProtocolType.Ethereum: + return TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM; + case ProtocolType.Sealevel: + return TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL; + case ProtocolType.Cosmos: + return TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS; + default: + throw new Error(`Unsupported protocol type: ${protocolType}`); + } +} + +export function getProtocolSpecificExchangeRateScale( + protocolType: ProtocolType, +): BigNumber { + return BigNumber.from(10).pow( + getProtocolSpecificExchangeRateDecimals(protocolType), + ); } function getProtocolSpecificExchangeRate( exchangeRate: BigNumberJs, protocolType: ProtocolType, ): BigNumber { - let multiplierDecimals = 1; - if (protocolType === ProtocolType.Ethereum) { - multiplierDecimals = TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM; - } else if (protocolType === ProtocolType.Sealevel) { - multiplierDecimals = TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL; - } else { - // TODO: add support for other protocols. Default to Ethereum for now. - multiplierDecimals = TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM; - } + const multiplierDecimals = + getProtocolSpecificExchangeRateDecimals(protocolType); const multiplier = new BigNumberJs(10).pow(multiplierDecimals); const integer = exchangeRate .times(multiplier) @@ -191,10 +208,9 @@ export function getLocalStorageGasOracleConfig({ gasPriceModifier?: ( local: ChainName, remote: ChainName, - exchangeRate: BigNumber, - gasPrice: BigNumber, - ) => BigNumber; -}): ChainMap { + gasOracleConfig: ProtocolAgnositicGasOracleConfig, + ) => BigNumberJs.Value; +}): ChainMap { const remotes = Object.keys(gasOracleParams).filter( (remote) => remote !== local, ); @@ -210,9 +226,18 @@ export function getLocalStorageGasOracleConfig({ remote, tokenPrices, exchangeRateMarginPct, - decimals: { local: localDecimals, remote: remoteDecimals }, + // decimals: { local: localDecimals, remote: remoteDecimals }, }); + if (localProtocolType !== ProtocolType.Sealevel) { + // On all chains other than Sealevel, we need to adjust the exchange rate for decimals + exchangeRateFloat = convertDecimals( + remoteDecimals, + localDecimals, + exchangeRateFloat, + ); + } + console.log( 'exchangeRateFloat before protocol specific', exchangeRateFloat.toString(), @@ -239,41 +264,75 @@ export function getLocalStorageGasOracleConfig({ ); } - // We have very little precision and ultimately need an integer value for - // the gas price that will be set on-chain. We scale up the gas price and - // scale down the exchange rate by the same factor. - if (gasPrice < 10 && gasPrice % 1 !== 0) { - // Scale up the gas price by 1e4 - const gasPriceScalingFactor = 1e4; - - // Check that there's no significant underflow when applying - // this to the exchange rate: - const adjustedExchangeRate = exchangeRate.div(gasPriceScalingFactor); - const recoveredExchangeRate = adjustedExchangeRate.mul( - gasPriceScalingFactor, - ); - if (recoveredExchangeRate.mul(100).div(exchangeRate).lt(99)) { - throw new Error('Too much underflow when downscaling exchange rate'); + const adjustForPrecisionLoss = ( + newGasPriceValue: BigNumberJs.Value, + newExchangeRate: BigNumber, + ): ProtocolAgnositicGasOracleConfig => { + let newGasPrice = new BigNumberJs(newGasPriceValue); + // We have very little precision and ultimately need an integer value for + // the gas price that will be set on-chain. We scale up the gas price and + // scale down the exchange rate by the same factor. + if (newGasPrice.lt(10) && newGasPrice.mod(1) !== new BigNumberJs(0)) { + // Scale up the gas price by 1e4 + const gasPriceScalingFactor = 1e4; + + // Check that there's no significant underflow when applying + // this to the exchange rate: + const adjustedExchangeRate = newExchangeRate.div(gasPriceScalingFactor); + const recoveredExchangeRate = adjustedExchangeRate.mul( + gasPriceScalingFactor, + ); + if (recoveredExchangeRate.mul(100).div(newExchangeRate).lt(99)) { + throw new Error('Too much underflow when downscaling exchange rate'); + } + + newGasPrice = newGasPrice.times(gasPriceScalingFactor); } - // Apply the scaling factor - exchangeRate = adjustedExchangeRate; - gasPrice *= gasPriceScalingFactor; - } + const newGasPriceInteger = newGasPrice.integerValue( + BigNumberJs.ROUND_CEIL, + ); + assert( + newGasPriceInteger.gt(0), + 'Gas price must be greater than 0, possible loss of precision', + ); + + return { + tokenExchangeRate: newExchangeRate.toString(), + gasPrice: newGasPriceInteger.toString(), + tokenDecimals: remoteDecimals, + }; + }; // Our integer gas price. - let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); + // let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); + + // let gasOracleConfig: ProtocolAgnositicGasOracleConfig = { + // gasPrice: gasPriceBn.toString(), + // tokenExchangeRate: exchangeRate.toString(), + // tokenDecimals: remoteDecimals, + // }; + + let gasOracleConfig = adjustForPrecisionLoss(gasPrice, exchangeRate); if (gasPriceModifier) { - gasPriceBn = gasPriceModifier(local, remote, exchangeRate, gasPriceBn); + gasOracleConfig = adjustForPrecisionLoss( + gasPriceModifier(local, remote, gasOracleConfig), + BigNumber.from(gasOracleConfig.tokenExchangeRate), + ); } return { ...agg, - [remote]: { - tokenExchangeRate: exchangeRate.toString(), - gasPrice: gasPriceBn.toString(), - }, + [remote]: gasOracleConfig, }; - }, {} as ChainMap); + }, {} as ChainMap); } + +// class ProtocolAgnosticGasOracleConfigClass { +// constructor(readonly config: ProtocolAgnositicGasOracleConfig) {} + +// getProtocolSpecificConfig(protocol: ProtocolType): any { + +// } +// } diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 690635b0fe..e6fed8323a 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -129,6 +129,8 @@ export { HyperlaneIgpDeployer } from './gas/HyperlaneIgpDeployer.js'; export { StorageGasOracleConfig, StorageGasOracleConfigSchema, + ProtocolAgnositicGasOracleConfig, + ProtocolAgnositicGasOracleConfigSchema, } from './gas/oracle/types.js'; export { CoinGeckoTokenPriceGetter } from './gas/token-prices.js'; export { @@ -422,7 +424,7 @@ export { getCosmosChainGasPrice, getGasPrice, getLocalStorageGasOracleConfig, - getTokenExchangeRateFromValues, + getProtocolSpecificExchangeRateScale, NativeTokenPriceConfig, } from './gas/utils.js'; export { GcpValidator } from './gcp/validator.js'; diff --git a/typescript/utils/src/amount.ts b/typescript/utils/src/amount.ts index 6ac3cb0035..742dd127c4 100644 --- a/typescript/utils/src/amount.ts +++ b/typescript/utils/src/amount.ts @@ -130,9 +130,7 @@ export function convertDecimals( if (fromDecimals === toDecimals) return amount; else if (fromDecimals > toDecimals) { const difference = fromDecimals - toDecimals; - return amount - .div(BigNumber(10).pow(difference)) - .integerValue(BigNumber.ROUND_FLOOR); + return amount.div(BigNumber(10).pow(difference)); } // fromDecimals < toDecimals else { From c833035fab442aa9564087eefe1a9164f5ea1af3 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 15 Jan 2025 13:24:29 +0000 Subject: [PATCH 06/23] evm quotes are the same --- typescript/infra/src/config/gas-oracle.ts | 2 +- typescript/sdk/src/gas/utils.ts | 104 +++++++++++++--------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 8d0d741aee..c343c5040f 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -114,8 +114,8 @@ function getLocalStorageGasOracleConfigOverride( newGasPrice.toString(), ); // assert(newGasPrice.gt(0), 'newGasPrice must be greater than 0'); - return newGasPrice; } + return newGasPrice; } } return gasOracleConfig.gasPrice; diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index f31ea8d27f..67b17715c3 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -231,6 +231,14 @@ export function getLocalStorageGasOracleConfig({ if (localProtocolType !== ProtocolType.Sealevel) { // On all chains other than Sealevel, we need to adjust the exchange rate for decimals + console.log( + 'exchangeRateFloat before convertDecimals', + exchangeRateFloat.toString(), + 'localDecimals', + localDecimals, + 'remoteDecimals', + remoteDecimals, + ); exchangeRateFloat = convertDecimals( remoteDecimals, localDecimals, @@ -264,46 +272,6 @@ export function getLocalStorageGasOracleConfig({ ); } - const adjustForPrecisionLoss = ( - newGasPriceValue: BigNumberJs.Value, - newExchangeRate: BigNumber, - ): ProtocolAgnositicGasOracleConfig => { - let newGasPrice = new BigNumberJs(newGasPriceValue); - // We have very little precision and ultimately need an integer value for - // the gas price that will be set on-chain. We scale up the gas price and - // scale down the exchange rate by the same factor. - if (newGasPrice.lt(10) && newGasPrice.mod(1) !== new BigNumberJs(0)) { - // Scale up the gas price by 1e4 - const gasPriceScalingFactor = 1e4; - - // Check that there's no significant underflow when applying - // this to the exchange rate: - const adjustedExchangeRate = newExchangeRate.div(gasPriceScalingFactor); - const recoveredExchangeRate = adjustedExchangeRate.mul( - gasPriceScalingFactor, - ); - if (recoveredExchangeRate.mul(100).div(newExchangeRate).lt(99)) { - throw new Error('Too much underflow when downscaling exchange rate'); - } - - newGasPrice = newGasPrice.times(gasPriceScalingFactor); - } - - const newGasPriceInteger = newGasPrice.integerValue( - BigNumberJs.ROUND_CEIL, - ); - assert( - newGasPriceInteger.gt(0), - 'Gas price must be greater than 0, possible loss of precision', - ); - - return { - tokenExchangeRate: newExchangeRate.toString(), - gasPrice: newGasPriceInteger.toString(), - tokenDecimals: remoteDecimals, - }; - }; - // Our integer gas price. // let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); @@ -313,12 +281,17 @@ export function getLocalStorageGasOracleConfig({ // tokenDecimals: remoteDecimals, // }; - let gasOracleConfig = adjustForPrecisionLoss(gasPrice, exchangeRate); + let gasOracleConfig = adjustForPrecisionLoss( + gasPrice, + exchangeRate, + remoteDecimals, + ); if (gasPriceModifier) { gasOracleConfig = adjustForPrecisionLoss( gasPriceModifier(local, remote, gasOracleConfig), BigNumber.from(gasOracleConfig.tokenExchangeRate), + remoteDecimals, ); } @@ -329,6 +302,55 @@ export function getLocalStorageGasOracleConfig({ }, {} as ChainMap); } +function adjustForPrecisionLoss( + gasPriceValue: BigNumberJs.Value, + exchangeRate: BigNumber, + remoteDecimals: number, +): ProtocolAgnositicGasOracleConfig { + let newGasPrice = new BigNumberJs(gasPriceValue); + let newExchangeRate = exchangeRate; + // We have very little precision and ultimately need an integer value for + // the gas price that will be set on-chain. We scale up the gas price and + // scale down the exchange rate by the same factor. + if (newGasPrice.lt(10) && newGasPrice.mod(1) !== new BigNumberJs(0)) { + // Scale up the gas price by 1e4 + const gasPriceScalingFactor = 1e4; + + // Check that there's no significant underflow when applying + // this to the exchange rate: + const adjustedExchangeRate = newExchangeRate.div(gasPriceScalingFactor); + const recoveredExchangeRate = adjustedExchangeRate.mul( + gasPriceScalingFactor, + ); + if (recoveredExchangeRate.mul(100).div(newExchangeRate).lt(99)) { + throw new Error('Too much underflow when downscaling exchange rate'); + } + + console.log( + 'gasPriceValue', + gasPriceValue, + 'newGasPrice', + newGasPrice.toString(), + 'gasPriceScalingFactor', + gasPriceScalingFactor.toString(), + ); + newGasPrice = newGasPrice.times(gasPriceScalingFactor); + newExchangeRate = adjustedExchangeRate; + } + + const newGasPriceInteger = newGasPrice.integerValue(BigNumberJs.ROUND_CEIL); + assert( + newGasPriceInteger.gt(0), + 'Gas price must be greater than 0, possible loss of precision', + ); + + return { + tokenExchangeRate: newExchangeRate.toString(), + gasPrice: newGasPriceInteger.toString(), + tokenDecimals: remoteDecimals, + }; +} + // class ProtocolAgnosticGasOracleConfigClass { // constructor(readonly config: ProtocolAgnositicGasOracleConfig) {} From 29f8041a3b059cd248584d87c87750475048c851 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 15 Jan 2025 14:38:25 +0000 Subject: [PATCH 07/23] IGP configure script, woo --- rust/sealevel/client/src/core.rs | 93 +---------- rust/sealevel/client/src/igp.rs | 157 +++++++++++++++++- rust/sealevel/client/src/main.rs | 15 ++ .../hyperlane-sealevel-igp/src/accounts.rs | 21 --- typescript/infra/scripts/agent-utils.ts | 7 + .../sealevel-helpers/print-gas-oracles.ts | 12 +- .../print-multisig-ism-config.ts | 2 - .../warp-routes/generate-warp-config.ts | 14 +- typescript/infra/src/config/gas-oracle.ts | 7 + 9 files changed, 203 insertions(+), 125 deletions(-) diff --git a/rust/sealevel/client/src/core.rs b/rust/sealevel/client/src/core.rs index 72ecfcb5ca..44e615ba89 100644 --- a/rust/sealevel/client/src/core.rs +++ b/rust/sealevel/client/src/core.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; use solana_sdk::{compute_budget, compute_budget::ComputeBudgetInstruction}; -use std::collections::HashMap; use std::{fs::File, path::Path}; use crate::cmd_utils::get_compute_unit_price_micro_lamports_for_chain_name; @@ -17,7 +16,6 @@ use crate::{ Context, CoreCmd, CoreDeploy, CoreSubCmd, }; use hyperlane_core::H256; -use hyperlane_sealevel_igp::accounts::{SOL_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE}; pub(crate) fn adjust_gas_price_if_needed(chain_name: &str, ctx: &mut Context) { if chain_name.eq("solanamainnet") { @@ -206,56 +204,10 @@ fn deploy_validator_announce( program_id } +// Deploys the IGP program and initializes the zero salt IGP and overhead IGP accounts. +// Configuration of gas oracles is expected to be done separately. #[allow(clippy::too_many_arguments)] fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, Pubkey, Pubkey) { - use hyperlane_sealevel_igp::{ - accounts::{GasOracle, RemoteGasData}, - instruction::{GasOracleConfig, GasOverheadConfig}, - }; - - let mut gas_oracle_configs = core - .gas_oracle_config_file - .as_deref() - .map(|p| { - let file = File::open(p).expect("Failed to open oracle config file"); - serde_json::from_reader::<_, Vec>(file) - .expect("Failed to parse oracle config file") - }) - .unwrap_or_default() - .into_iter() - .filter(|c| c.domain != core.local_domain) - .map(|c| (c.domain, c)) - .collect::>(); - for &remote in &core.remote_domains { - gas_oracle_configs - .entry(remote) - .or_insert_with(|| GasOracleConfig { - domain: remote, - gas_oracle: Some(GasOracle::RemoteGasData(RemoteGasData { - token_exchange_rate: TOKEN_EXCHANGE_RATE_SCALE, - gas_price: 1, - token_decimals: SOL_DECIMALS, - })), - }); - } - let gas_oracle_configs = gas_oracle_configs.into_values().collect::>(); - - let overhead_configs = core - .overhead_config_file - .as_deref() - .map(|p| { - let file = File::open(p).expect("Failed to open overhead config file"); - serde_json::from_reader::<_, Vec>(file) - .expect("Failed to parse overhead config file") - }) - .unwrap_or_default() - .into_iter() - .filter(|c| c.destination_domain != core.local_domain) - .map(|c| (c.destination_domain, c)) - .collect::>() // dedup - .into_values() - .collect::>(); - let program_id = deploy_program( ctx.payer_keypair_path(), key_dir, @@ -319,47 +271,6 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, println!("Initialized overhead IGP account {}", overhead_igp_account); - if !gas_oracle_configs.is_empty() { - let domains = gas_oracle_configs - .iter() - .map(|c| c.domain) - .collect::>(); - let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( - program_id, - igp_account, - ctx.payer_pubkey, - gas_oracle_configs, - ) - .unwrap(); - - ctx.new_txn().add(instruction).send_with_payer(); - - println!("Set gas oracle for remote domains {domains:?}",); - } else { - println!("Skipping settings gas oracle config"); - } - - if !overhead_configs.is_empty() { - let domains = overhead_configs - .iter() - .map(|c| c.destination_domain) - .collect::>(); - - let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( - program_id, - overhead_igp_account, - ctx.payer_pubkey, - overhead_configs, - ) - .unwrap(); - - ctx.new_txn().add(instruction).send_with_payer(); - - println!("Set gas overheads for remote domains {domains:?}",) - } else { - println!("Skipping setting gas overheads"); - } - (program_id, overhead_igp_account, igp_account) } diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index 60f51479d9..14a876e13b 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -24,13 +24,19 @@ use hyperlane_core::{KnownHyperlaneDomain, H256}; use hyperlane_sealevel_igp::{ accounts::{ - GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount, - ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, + GasOracle, GasPaymentAccount, Igp, IgpAccount, InterchainGasPaymasterType, OverheadIgp, OverheadIgpAccount, ProgramDataAccount as IgpProgramDataAccount, RemoteGasData }, igp_program_data_pda_seeds, instruction::{GasOracleConfig, GasOverheadConfig}, }; +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct GasOracleConfigWithOverhead { + oracle_config: RemoteGasData, + overhead: Option +} + #[derive(Debug, Serialize, Deserialize, Default)] struct IgpAccountsArtifacts { salt: H256, @@ -440,6 +446,16 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { ) .send_with_payer(); } + IgpSubCmd::Configure(args) => { + configure_igp_and_overhead_igp( + &mut ctx, + args.program_id, + args.local_chain, + &args.gas_oracle_config_file, + &args.chain_config_file, + args.account_salt, + ); + } } } @@ -644,3 +660,140 @@ fn init_and_configure_overhead_igp_account( overhead_igp_account } + +fn configure_igp_and_overhead_igp( + ctx: &mut Context, + program_id: Pubkey, + local_chain: String, + gas_oracle_config_file: &Path, + chain_config_path: &Path, + account_salt: Option, +) { + let chain_configs = read_json::>(chain_config_path); + + let gas_oracle_configs = read_json::>>(gas_oracle_config_file); + let gas_oracle_config = gas_oracle_configs.get(&local_chain).unwrap(); + + let salt = account_salt.unwrap_or_else(H256::zero); + + let (igp_account_pubkey, _bump) = Pubkey::find_program_address( + hyperlane_sealevel_igp::igp_pda_seeds!(salt), + &program_id, + ); + let igp_account = ctx + .client + .get_account_with_commitment(&igp_account_pubkey, ctx.commitment) + .unwrap() + .value + .expect( + "IGP account not found. Make sure you are connected to the right RPC.", + ); + let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) + .unwrap() + .into_inner(); + + let (overhead_igp_account_pubkey, _bump) = Pubkey::find_program_address( + hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), + &program_id, + ); + let overhead_igp_account = ctx + .client + .get_account_with_commitment(&overhead_igp_account_pubkey, ctx.commitment) + .unwrap() + .value + .expect( + "Overhead IGP account not found. Make sure you are connected to the right RPC.", + ); + let overhead_igp_account = OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) + .unwrap() + .into_inner(); + + // Set IGP configurations + println!("Setting IGP configurations for IGP account {} and overhead IGP account {}", igp_account_pubkey, overhead_igp_account_pubkey); + + for (remote, config) in gas_oracle_config.iter() { + let remote_domain = chain_configs.get(remote).unwrap().domain_id(); + let gas_oracle_config = GasOracleConfig { + domain: remote_domain, + gas_oracle: Some(GasOracle::RemoteGasData(config.oracle_config.clone())), + }; + + if !igp_gas_oracle_matches(&igp_account, remote_domain, &gas_oracle_config) { + println!("Setting gas oracle for remote domain {:?} ({:?}) with config {:?}", remote, remote_domain, gas_oracle_config); + // For simplicity and to always be well within max tx sizes, just send one config at a time + let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + program_id, + igp_account_pubkey, + ctx.payer_pubkey, + vec![gas_oracle_config], + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas oracle for remote domain {:?} ({:?})", remote, remote_domain); + } + + let overhead_config = GasOverheadConfig { + destination_domain: remote_domain, + gas_overhead: config.overhead, + }; + + if !overhead_igp_config_matches(&overhead_igp_account, remote_domain, &overhead_config) { + println!("Setting gas overhead for remote domain {:?} ({:?}) with config {:?}", remote, remote_domain, overhead_config); + // For simplicity and to always be well within max tx sizes, just send one config at a time + let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( + program_id, + overhead_igp_account_pubkey, + ctx.payer_pubkey, + vec![overhead_config], + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas overhead for remote domain {:?} ({:?})", remote, remote_domain); + } + } +} + +fn igp_gas_oracle_matches( + igp_account: &Igp, + remote_domain: u32, + gas_oracle_config: &GasOracleConfig, +) -> bool { + if let Some(existing_config) = igp_account.gas_oracles.get(&remote_domain) { + if existing_config == gas_oracle_config.gas_oracle.as_ref().unwrap() { + println!("Gas oracle for remote domain {:?} already set", remote_domain); + return true; + } else { + println!( + "Gas oracle for remote domain {:?} already set, but different: {:?}", + remote_domain, existing_config + ); + return false; + } + } + false +} + + +fn overhead_igp_config_matches( + overhead_igp_account: &OverheadIgp, + remote_domain: u32, + gas_overhead_config: &GasOverheadConfig, +) -> bool { + if let Some(existing_config) = overhead_igp_account.gas_overheads.get(&remote_domain) { + if existing_config == &gas_overhead_config.gas_overhead.unwrap() { + println!("Gas overhead for remote domain {:?} already set", remote_domain); + return true; + } else { + println!( + "Gas overhead for remote domain {:?} already set, but different: {:?}", + remote_domain, existing_config + ); + return false; + } + } + false +} diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index c9ea55b548..8a654c4e18 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -404,6 +404,7 @@ enum IgpSubCmd { DestinationGasOverhead(DestinationGasOverheadArgs), TransferIgpOwnership(TransferIgpOwnership), TransferOverheadIgpOwnership(TransferIgpOwnership), + Configure(ConfigureIgpArgs), } #[derive(Args)] @@ -555,6 +556,20 @@ struct SetGasOverheadArgs { gas_overhead: u64, } +#[derive(Args)] +struct ConfigureIgpArgs { + #[arg(long)] + program_id: Pubkey, + #[arg(long)] + local_chain: String, + #[arg(long)] + gas_oracle_config_file: PathBuf, + #[arg(long)] + chain_config_file: PathBuf, + #[arg(long)] + account_salt: Option, +} + #[derive(Args)] struct ValidatorAnnounceCmd { #[command(subcommand)] diff --git a/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs b/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs index aadc15d81a..ba08e2c8d4 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-igp/src/accounts.rs @@ -339,25 +339,4 @@ mod test { let result = convert_decimals(num, from_decimals, to_decimals); assert_eq!(result, U256::from(0u128)); } - - // fn test_igp_quoting_to_ethereum() { - // let igp = Igp { - // bump_seed: 0, - // salt: H256::default(), - // owner: None, - // beneficiary: Pubkey::new_unique(), - // gas_oracles: { - // let mut gas_oracles = HashMap::new(); - // gas_oracles.insert( - // 1, - // GasOracle::RemoteGasData(RemoteGasData { - // token_exchange_rate: 15000000000000000000, - // gas_price: 10000000000, - // token_decimals: 18, - // }), - // ); - // gas_oracles - // }, - // }; - // } } diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 0d65657a9f..889d6875ad 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -177,6 +177,13 @@ export function withChainsRequired( return withChains(args, chainOptions).demandOption('chains'); } +export function withOutFile(args: Argv) { + return args + .describe('outFile', 'output file') + .string('outFile') + .alias('o', 'outFile'); +} + export function withWarpRouteId(args: Argv) { return args.describe('warpRouteId', 'warp route id').string('warpRouteId'); } diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index 96287de49d..4ce9186b6f 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -1,15 +1,16 @@ import { objMap, stringifyObject } from '@hyperlane-xyz/utils'; -import { getArgs } from '../agent-utils.js'; +import { writeJsonAtPath } from '../../src/utils/utils.js'; +import { getArgs, withOutFile } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; // This script exists to print the chain metadata configs for a given environment // so they can easily be copied into the Sealevel tooling. :'( async function main() { - const args = await getArgs().argv; + const { environment, outFile } = await withOutFile(getArgs()).argv; - const environmentConfig = getEnvironmentConfig(args.environment); + const environmentConfig = getEnvironmentConfig(environment); // Construct a nested map of origin -> destination -> { oracleConfig, overhead } const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { @@ -37,6 +38,11 @@ async function main() { console.log('keys?', stringifyObject(Object.keys(gasOracles))); console.log(stringifyObject(gasOracles, 'yaml')); + + if (outFile) { + console.log(`Writing config to ${outFile}`); + writeJsonAtPath(outFile, gasOracles); + } } main().catch((err) => { diff --git a/typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts b/typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts index 69646ff013..bc3e634c77 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-multisig-ism-config.ts @@ -39,8 +39,6 @@ async function main() { } } - console.warn; - console.log(JSON.stringify(config, null, 2)); } diff --git a/typescript/infra/scripts/warp-routes/generate-warp-config.ts b/typescript/infra/scripts/warp-routes/generate-warp-config.ts index 077cef0d4c..2ecb1b56f9 100644 --- a/typescript/infra/scripts/warp-routes/generate-warp-config.ts +++ b/typescript/infra/scripts/warp-routes/generate-warp-config.ts @@ -4,15 +4,17 @@ import { WarpRouteDeployConfigSchema } from '@hyperlane-xyz/sdk'; import { getWarpConfig } from '../../config/warp.js'; import { writeYamlAtPath } from '../../src/utils/utils.js'; -import { getArgs, withWarpRouteIdRequired } from '../agent-utils.js'; +import { + getArgs, + withOutFile, + withWarpRouteIdRequired, +} from '../agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from '../core-utils.js'; async function main() { - const { warpRouteId, environment, outFile } = await withWarpRouteIdRequired( - getArgs(), - ) - .string('outFile') - .describe('outFile', 'The file to write the config to').argv; + const { warpRouteId, environment, outFile } = await withOutFile( + withWarpRouteIdRequired(getArgs()), + ).argv; const { multiProvider } = await getHyperlaneCore(environment); const envConfig = getEnvironmentConfig(environment); diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index c343c5040f..0943b6f837 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -147,6 +147,11 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { minUsdCost = Math.max(minUsdCost, 1.5); } + // For all SVM chains, min cost is 0.50 USD to cover rent needs + if (getChain(remote).protocol === ProtocolType.Sealevel) { + minUsdCost = Math.max(minUsdCost, 0.5); + } + const remoteMinCostOverrides: ChainMap = { // For Ethereum L2s, we need to account for the L1 DA costs that // aren't accounted for directly in the gas price. @@ -167,6 +172,8 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { taiko: 0.5, // Nexus adjustment neutron: 0.5, + // For Solana, min cost is 1.50 USD + solanamainnet: 1.5, }; const override = remoteMinCostOverrides[remote]; if (override !== undefined) { From 458d83a40d735f56b7331177dbf5e0fa827e7053 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Wed, 15 Jan 2025 15:43:06 +0000 Subject: [PATCH 08/23] nits --- rust/sealevel/client/src/igp.rs | 93 ++++--- .../environments/mainnet3/chain-config.json | 236 +++++++++++++++++- 2 files changed, 293 insertions(+), 36 deletions(-) diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index 14a876e13b..b856c2cceb 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -24,7 +24,8 @@ use hyperlane_core::{KnownHyperlaneDomain, H256}; use hyperlane_sealevel_igp::{ accounts::{ - GasOracle, GasPaymentAccount, Igp, IgpAccount, InterchainGasPaymasterType, OverheadIgp, OverheadIgpAccount, ProgramDataAccount as IgpProgramDataAccount, RemoteGasData + GasOracle, GasPaymentAccount, Igp, IgpAccount, InterchainGasPaymasterType, OverheadIgp, + OverheadIgpAccount, ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, }, igp_program_data_pda_seeds, instruction::{GasOracleConfig, GasOverheadConfig}, @@ -34,7 +35,7 @@ use hyperlane_sealevel_igp::{ #[serde(rename_all = "camelCase")] struct GasOracleConfigWithOverhead { oracle_config: RemoteGasData, - overhead: Option + overhead: Option, } #[derive(Debug, Serialize, Deserialize, Default)] @@ -671,23 +672,21 @@ fn configure_igp_and_overhead_igp( ) { let chain_configs = read_json::>(chain_config_path); - let gas_oracle_configs = read_json::>>(gas_oracle_config_file); + let gas_oracle_configs = read_json::< + HashMap>, + >(gas_oracle_config_file); let gas_oracle_config = gas_oracle_configs.get(&local_chain).unwrap(); let salt = account_salt.unwrap_or_else(H256::zero); - let (igp_account_pubkey, _bump) = Pubkey::find_program_address( - hyperlane_sealevel_igp::igp_pda_seeds!(salt), - &program_id, - ); + let (igp_account_pubkey, _bump) = + Pubkey::find_program_address(hyperlane_sealevel_igp::igp_pda_seeds!(salt), &program_id); let igp_account = ctx .client .get_account_with_commitment(&igp_account_pubkey, ctx.commitment) .unwrap() .value - .expect( - "IGP account not found. Make sure you are connected to the right RPC.", - ); + .expect("IGP account not found. Make sure you are connected to the right RPC."); let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) .unwrap() .into_inner(); @@ -701,15 +700,16 @@ fn configure_igp_and_overhead_igp( .get_account_with_commitment(&overhead_igp_account_pubkey, ctx.commitment) .unwrap() .value - .expect( - "Overhead IGP account not found. Make sure you are connected to the right RPC.", - ); + .expect("Overhead IGP account not found. Make sure you are connected to the right RPC."); let overhead_igp_account = OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) .unwrap() .into_inner(); // Set IGP configurations - println!("Setting IGP configurations for IGP account {} and overhead IGP account {}", igp_account_pubkey, overhead_igp_account_pubkey); + println!( + "Setting IGP configurations for IGP account {} and overhead IGP account {}", + igp_account_pubkey, overhead_igp_account_pubkey + ); for (remote, config) in gas_oracle_config.iter() { let remote_domain = chain_configs.get(remote).unwrap().domain_id(); @@ -718,20 +718,27 @@ fn configure_igp_and_overhead_igp( gas_oracle: Some(GasOracle::RemoteGasData(config.oracle_config.clone())), }; - if !igp_gas_oracle_matches(&igp_account, remote_domain, &gas_oracle_config) { - println!("Setting gas oracle for remote domain {:?} ({:?}) with config {:?}", remote, remote_domain, gas_oracle_config); + if !igp_gas_oracle_matches(&igp_account, &remote, remote_domain, &gas_oracle_config) { + println!( + "Setting gas oracle for remote domain {:?} ({:?}) with config {:?}", + remote, remote_domain, gas_oracle_config + ); // For simplicity and to always be well within max tx sizes, just send one config at a time - let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( - program_id, - igp_account_pubkey, - ctx.payer_pubkey, - vec![gas_oracle_config], - ) - .unwrap(); + let instruction = + hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + program_id, + igp_account_pubkey, + ctx.payer_pubkey, + vec![gas_oracle_config], + ) + .unwrap(); ctx.new_txn().add(instruction).send_with_payer(); - println!("Set gas oracle for remote domain {:?} ({:?})", remote, remote_domain); + println!( + "Set gas oracle for remote domain {:?} ({:?})", + remote, remote_domain + ); } let overhead_config = GasOverheadConfig { @@ -739,8 +746,16 @@ fn configure_igp_and_overhead_igp( gas_overhead: config.overhead, }; - if !overhead_igp_config_matches(&overhead_igp_account, remote_domain, &overhead_config) { - println!("Setting gas overhead for remote domain {:?} ({:?}) with config {:?}", remote, remote_domain, overhead_config); + if !overhead_igp_config_matches( + &overhead_igp_account, + &remote, + remote_domain, + &overhead_config, + ) { + println!( + "Setting gas overhead for remote domain {:?} ({:?}) with config {:?}", + remote, remote_domain, overhead_config + ); // For simplicity and to always be well within max tx sizes, just send one config at a time let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( program_id, @@ -752,24 +767,31 @@ fn configure_igp_and_overhead_igp( ctx.new_txn().add(instruction).send_with_payer(); - println!("Set gas overhead for remote domain {:?} ({:?})", remote, remote_domain); + println!( + "Set gas overhead for remote domain {:?} ({:?})", + remote, remote_domain + ); } } } fn igp_gas_oracle_matches( igp_account: &Igp, + remote: &String, remote_domain: u32, gas_oracle_config: &GasOracleConfig, ) -> bool { if let Some(existing_config) = igp_account.gas_oracles.get(&remote_domain) { if existing_config == gas_oracle_config.gas_oracle.as_ref().unwrap() { - println!("Gas oracle for remote domain {:?} already set", remote_domain); + println!( + "Gas oracle for remote domain {:?} ({:?}) already correctly set", + remote, remote_domain + ); return true; } else { println!( - "Gas oracle for remote domain {:?} already set, but different: {:?}", - remote_domain, existing_config + "Gas oracle for remote domain {:?} ({:?}) already set, but different. Current value: {:?}", + remote, remote_domain, existing_config ); return false; } @@ -777,20 +799,23 @@ fn igp_gas_oracle_matches( false } - fn overhead_igp_config_matches( overhead_igp_account: &OverheadIgp, + remote: &String, remote_domain: u32, gas_overhead_config: &GasOverheadConfig, ) -> bool { if let Some(existing_config) = overhead_igp_account.gas_overheads.get(&remote_domain) { if existing_config == &gas_overhead_config.gas_overhead.unwrap() { - println!("Gas overhead for remote domain {:?} already set", remote_domain); + println!( + "Gas overhead for remote domain {:?} ({:?}) already correctly set", + remote, remote_domain + ); return true; } else { println!( - "Gas overhead for remote domain {:?} already set, but different: {:?}", - remote_domain, existing_config + "Gas overhead for remote domain {:?} ({:?}) already set, but different. Current value: {:?}", + remote, remote_domain, existing_config ); return false; } diff --git a/rust/sealevel/environments/mainnet3/chain-config.json b/rust/sealevel/environments/mainnet3/chain-config.json index 0688d72632..e3abbd89c6 100644 --- a/rust/sealevel/environments/mainnet3/chain-config.json +++ b/rust/sealevel/environments/mainnet3/chain-config.json @@ -243,6 +243,45 @@ ], "technicalStack": "arbitrumnitro" }, + "artela": { + "blockExplorers": [ + { + "apiUrl": "https://artscan.artela.network/api", + "family": "blockscout", + "name": "Artela Explorer", + "url": "https://artscan.artela.network" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 11820, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Artela", + "domainId": 11820, + "gasCurrencyCoinGeckoId": "artela", + "name": "artela", + "nativeToken": { + "decimals": 18, + "name": "Artela", + "symbol": "ART" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://node-euro.artela.network/rpc" + }, + { + "http": "https://node-hongkong.artela.network/rpc" + } + ], + "technicalStack": "other" + }, "arthera": { "blockExplorers": [ { @@ -1621,6 +1660,7 @@ "displayName": "Form", "domainId": 478, "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://prod.form.keypersafe.xyz/", "name": "form", "nativeToken": { "decimals": 18, @@ -1802,6 +1842,42 @@ ], "technicalStack": "arbitrumnitro" }, + "guru": { + "blockExplorers": [ + { + "apiUrl": "https://blockscout.gurunetwork.ai/api", + "family": "blockscout", + "name": "Guru Explorer", + "url": "https://blockscout.gurunetwork.ai" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 5 + }, + "chainId": 260, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Guru Network", + "domainId": 260, + "gasCurrencyCoinGeckoId": "guru-network", + "name": "guru", + "nativeToken": { + "decimals": 18, + "name": "Guru Network", + "symbol": "GURU" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.gurunetwork.ai/archive/260" + } + ], + "technicalStack": "opstack" + }, "harmony": { "blockExplorers": [ { @@ -1847,6 +1923,42 @@ ], "technicalStack": "other" }, + "hemi": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.hemi.xyz/api", + "family": "blockscout", + "name": "Hemi Explorer", + "url": "https://explorer.hemi.xyz" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 12, + "reorgPeriod": 5 + }, + "chainId": 43111, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Hemi Network", + "domainId": 43111, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "hemi", + "nativeToken": { + "decimals": 18, + "name": "Ether", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.hemi.network/rpc" + } + ], + "technicalStack": "other" + }, "immutablezkevmmainnet": { "blockExplorers": [ { @@ -1955,6 +2067,7 @@ "displayName": "Ink", "domainId": 57073, "gasCurrencyCoinGeckoId": "ethereum", + "gnosisSafeTransactionServiceUrl": "https://safe-transaction-ink.safe.global/", "name": "ink", "nativeToken": { "decimals": 18, @@ -2021,6 +2134,9 @@ } ], "rpcUrls": [ + { + "http": "https://injective-rpc.publicnode.com:443" + }, { "http": "https://sentry.tm.injective.network:443" } @@ -2692,6 +2808,47 @@ ], "technicalStack": "other" }, + "nero": { + "blockExplorers": [ + { + "apiUrl": "https://api.neroscan.io/api", + "family": "etherscan", + "name": "Neroscan", + "url": "https://www.neroscan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 5 + }, + "chainId": 1689, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Nero", + "domainId": 1689, + "gasCurrencyCoinGeckoId": "nerochain", + "gnosisSafeTransactionServiceUrl": "https://multisign.nerochain.io/txs/", + "name": "nero", + "nativeToken": { + "decimals": 18, + "name": "Nero", + "symbol": "NERO" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.nerochain.io" + } + ], + "technicalStack": "other", + "transactionOverrides": { + "maxFeePerGas": 10000000000, + "maxPriorityFeePerGas": 1000000000 + } + }, "neutron": { "bech32Prefix": "neutron", "blockExplorers": [ @@ -3574,10 +3731,10 @@ "soneium": { "blockExplorers": [ { - "apiUrl": "https://explorer.soneium.org/api", + "apiUrl": "https://soneium.blockscout.com/api", "family": "blockscout", "name": "Soneium Explorer", - "url": "https://explorer.soneium.org" + "url": "https://soneium.blockscout.com" } ], "blocks": { @@ -3965,6 +4122,45 @@ ], "technicalStack": "other" }, + "torus": { + "blockExplorers": [ + { + "apiUrl": "https://api.blockscout.torus.network/api", + "family": "blockscout", + "name": "Torus Explorer", + "url": "https://blockscout.torus.network" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 8, + "reorgPeriod": "finalized" + }, + "chainId": 21000, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Torus", + "domainId": 21000, + "gasCurrencyCoinGeckoId": "torus", + "name": "torus", + "nativeToken": { + "decimals": 18, + "name": "Torus", + "symbol": "TORUS" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://api-hyperlane.nodes.torus.network" + }, + { + "http": "https://api.torus.network" + } + ], + "technicalStack": "polkadotsubstrate" + }, "treasure": { "blockExplorers": [ { @@ -4228,6 +4424,42 @@ ], "technicalStack": "polygoncdk" }, + "xpla": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.xpla.io/mainnet/api", + "family": "other", + "name": "XPLA Explorer", + "url": "https://explorer.xpla.io/mainnet" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 6, + "reorgPeriod": 5 + }, + "chainId": 37, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "XPLA", + "domainId": 37, + "gasCurrencyCoinGeckoId": "xpla", + "name": "xpla", + "nativeToken": { + "decimals": 18, + "name": "XPLA", + "symbol": "XPLA" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://dimension-evm-rpc.xpla.dev" + } + ], + "technicalStack": "other" + }, "zeronetwork": { "blockExplorers": [ { From d966e64f998e57db1e37ec9d5667f4ce84bd2a11 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 11:19:05 +0000 Subject: [PATCH 09/23] some measures in e2e --- rust/main/utils/run-locally/src/solana.rs | 52 ++--- rust/sealevel/client/src/igp.rs | 204 ++++-------------- rust/sealevel/client/src/main.rs | 10 +- .../local-e2e/gas-oracle-configs.json | 34 +-- .../environments/local-e2e/overheads.json | 10 - 5 files changed, 91 insertions(+), 219 deletions(-) delete mode 100644 rust/sealevel/environments/local-e2e/overheads.json diff --git a/rust/main/utils/run-locally/src/solana.rs b/rust/main/utils/run-locally/src/solana.rs index 3e32524cdd..a2ca137546 100644 --- a/rust/main/utils/run-locally/src/solana.rs +++ b/rust/main/utils/run-locally/src/solana.rs @@ -75,7 +75,6 @@ pub const SOLANA_CHECKPOINT_LOCATION: &str = const SOLANA_GAS_ORACLE_CONFIG_FILE: &str = "../sealevel/environments/local-e2e/gas-oracle-configs.json"; -const SOLANA_OVERHEAD_CONFIG_FILE: &str = "../sealevel/environments/local-e2e/overheads.json"; // Install the CLI tools and return the path to the bin dir. #[apply(as_task)] @@ -233,23 +232,17 @@ pub fn start_solana_test_validator( .cmd("deploy") .arg("environment", SOLANA_ENV_NAME) .arg("environments-dir", SOLANA_ENVS_DIR) - .arg("built-so-dir", SBF_OUT_PATH) - .arg("overhead-config-file", SOLANA_OVERHEAD_CONFIG_FILE); + .arg("built-so-dir", SBF_OUT_PATH); sealevel_client_deploy_core .clone() .arg("local-domain", SOLANA_LOCAL_CHAIN_ID) - .arg( - "remote-domains", - [SOLANA_REMOTE_CHAIN_ID, "9913371", "9913372", "9913373"].join(","), - ) .arg("chain", "sealeveltest1") .run() .join(); sealevel_client_deploy_core .arg("local-domain", SOLANA_REMOTE_CHAIN_ID) - .arg("remote-domains", SOLANA_LOCAL_CHAIN_ID) .arg("chain", "sealeveltest2") .run() .join(); @@ -294,40 +287,47 @@ pub fn start_solana_test_validator( .run() .join(); + // Deterministic due to checked-in keys for e2e + const IGP_PROGRAM_ID: &str = "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U"; + // So we can test paying for gas with a different IGP account + const ALTERNATIVE_SALT: &str = + "0x0000000000000000000000000000000000000000000000000000000000000001"; + const ALTERNATIVE_IGP_ACCOUNT: &str = "8EniU8dQaGQ3HWWtT77V7hrksheygvEu6TtzJ3pX1nKM"; + sealevel_client .clone() .cmd("igp") .cmd("init-igp-account") - .arg("program-id", "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U") + .arg("program-id", IGP_PROGRAM_ID) .arg("environment", SOLANA_ENV_NAME) .arg("environments-dir", SOLANA_ENVS_DIR) .arg("chain", "sealeveltest1") - .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE) - .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) - .arg( - "account-salt", - "0x0000000000000000000000000000000000000000000000000000000000000001", - ) + // .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE) + // .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) + .arg("account-salt", ALTERNATIVE_SALT) .run() .join(); sealevel_client + .clone() .cmd("igp") .cmd("init-overhead-igp-account") - .arg("program-id", "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U") + .arg("program-id", IGP_PROGRAM_ID) .arg("environment", SOLANA_ENV_NAME) .arg("environments-dir", SOLANA_ENVS_DIR) .arg("chain", "sealeveltest1") - .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE) - .arg("overhead-config-file", SOLANA_OVERHEAD_CONFIG_FILE) - .arg( - "inner-igp-account", - "8EniU8dQaGQ3HWWtT77V7hrksheygvEu6TtzJ3pX1nKM", - ) - .arg( - "account-salt", - "0x0000000000000000000000000000000000000000000000000000000000000001", - ) + .arg("inner-igp-account", ALTERNATIVE_IGP_ACCOUNT) + .arg("account-salt", ALTERNATIVE_SALT) + .run() + .join(); + + sealevel_client + .cmd("igp") + .cmd("configure") + .arg("program-id", IGP_PROGRAM_ID) + .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) + .arg("chain", "sealeveltest1") + .arg("account-salt", ALTERNATIVE_SALT) .run() .join(); diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index b856c2cceb..bf333ff038 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -24,8 +24,8 @@ use hyperlane_core::{KnownHyperlaneDomain, H256}; use hyperlane_sealevel_igp::{ accounts::{ - GasOracle, GasPaymentAccount, Igp, IgpAccount, InterchainGasPaymasterType, OverheadIgp, - OverheadIgpAccount, ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, + GasOracle, GasPaymentAccount, IgpAccount, InterchainGasPaymasterType, OverheadIgpAccount, + ProgramDataAccount as IgpProgramDataAccount, RemoteGasData, }, igp_program_data_pda_seeds, instruction::{GasOracleConfig, GasOverheadConfig}, @@ -33,6 +33,7 @@ use hyperlane_sealevel_igp::{ #[derive(Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// Compatible with the format of our TS-generated configs. struct GasOracleConfigWithOverhead { oracle_config: RemoteGasData, overhead: Option, @@ -116,16 +117,7 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { }) .unwrap_or_else(|| get_context_salt(init.context.as_ref())); - let chain_configs = - read_json::>(&init.chain_config_file); - - let igp_account = init_and_configure_igp_account( - &mut ctx, - init.program_id, - chain_configs.get(&init.chain).unwrap().domain_id(), - salt, - init.gas_oracle_config_file, - ); + let igp_account = init_igp_account(&mut ctx, init.program_id, salt); let artifacts = IgpAccountsArtifacts { salt, @@ -162,17 +154,8 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { }) .unwrap_or_else(|| get_context_salt(init.context.as_ref())); - let chain_configs = - read_json::>(&init.chain_config_file); - - let overhead_igp_account = init_and_configure_overhead_igp_account( - &mut ctx, - init.program_id, - init.inner_igp_account, - chain_configs.get(&init.chain).unwrap().domain_id(), - salt, - init.overhead_config_file, - ); + let overhead_igp_account = + init_overhead_igp_account(&mut ctx, init.program_id, init.inner_igp_account, salt); let artifacts = IgpAccountsArtifacts { salt, @@ -451,7 +434,7 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { configure_igp_and_overhead_igp( &mut ctx, args.program_id, - args.local_chain, + args.chain, &args.gas_oracle_config_file, &args.chain_config_file, args.account_salt, @@ -502,25 +485,7 @@ fn deploy_igp_program( program_id } -fn init_and_configure_igp_account( - ctx: &mut Context, - program_id: Pubkey, - local_domain: u32, - salt: H256, - gas_oracle_config_file: Option, -) -> Pubkey { - let gas_oracle_configs = gas_oracle_config_file - .as_deref() - .map(|p| { - let file = File::open(p).expect("Failed to open oracle config file"); - serde_json::from_reader::<_, Vec>(file) - .expect("Failed to parse oracle config file") - }) - .unwrap_or_default() - .into_iter() - .filter(|c| c.domain != local_domain) - .collect::>(); - +fn init_igp_account(ctx: &mut Context, program_id: Pubkey, salt: H256) -> Pubkey { // Initialize IGP with the given salt let (igp_account_pda, _igp_account_bump) = Pubkey::find_program_address(hyperlane_sealevel_igp::igp_pda_seeds!(salt), &program_id); @@ -554,54 +519,15 @@ fn init_and_configure_igp_account( ); } - if !gas_oracle_configs.is_empty() { - // TODO: idempotency - - let domains = gas_oracle_configs - .iter() - .map(|c| c.domain) - .collect::>(); - let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( - program_id, - igp_account_pda, - ctx.payer_pubkey, - gas_oracle_configs, - ) - .unwrap(); - - ctx.new_txn().add(instruction).send_with_payer(); - - println!("Set gas oracle for remote domains {domains:?}",); - } else { - println!("Skipping settings gas oracle config"); - } - igp_account_pda } -fn init_and_configure_overhead_igp_account( +fn init_overhead_igp_account( ctx: &mut Context, program_id: Pubkey, inner_igp_account: Pubkey, - local_domain: u32, salt: H256, - overhead_config_file: Option, ) -> Pubkey { - let overhead_configs = overhead_config_file - .as_deref() - .map(|p| { - let file = File::open(p).expect("Failed to open overhead config file"); - serde_json::from_reader::<_, Vec>(file) - .expect("Failed to parse overhead config file") - }) - .unwrap_or_default() - .into_iter() - .filter(|c| c.destination_domain != local_domain) - .map(|c| (c.destination_domain, c)) - .collect::>() // dedup - .into_values() - .collect::>(); - let (overhead_igp_account, _) = Pubkey::find_program_address( hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), &program_id, @@ -636,32 +562,11 @@ fn init_and_configure_overhead_igp_account( ); } - if !overhead_configs.is_empty() { - // TODO: idempotency - - let domains = overhead_configs - .iter() - .map(|c| c.destination_domain) - .collect::>(); - - let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( - program_id, - overhead_igp_account, - ctx.payer_pubkey, - overhead_configs, - ) - .unwrap(); - - ctx.new_txn().add(instruction).send_with_payer(); - - println!("Set gas overheads for remote domains {domains:?}",) - } else { - println!("Skipping setting gas overheads"); - } - overhead_igp_account } +/// Idempotently applies gas oracles to the IGP account and overheads to the Overhead IGP +/// account relating to the IGP account salt. fn configure_igp_and_overhead_igp( ctx: &mut Context, program_id: Pubkey, @@ -718,7 +623,13 @@ fn configure_igp_and_overhead_igp( gas_oracle: Some(GasOracle::RemoteGasData(config.oracle_config.clone())), }; - if !igp_gas_oracle_matches(&igp_account, &remote, remote_domain, &gas_oracle_config) { + // Gas oracle on the IGP account + if !map_configuration_matches( + &igp_account.gas_oracles, + &remote, + remote_domain, + gas_oracle_config.gas_oracle.as_ref(), + ) { println!( "Setting gas oracle for remote domain {:?} ({:?}) with config {:?}", remote, remote_domain, gas_oracle_config @@ -741,17 +652,17 @@ fn configure_igp_and_overhead_igp( ); } - let overhead_config = GasOverheadConfig { - destination_domain: remote_domain, - gas_overhead: config.overhead, - }; - - if !overhead_igp_config_matches( - &overhead_igp_account, + // Overhead on the Overhead IGP account + if !map_configuration_matches( + &overhead_igp_account.gas_overheads, &remote, remote_domain, - &overhead_config, + config.overhead.as_ref(), ) { + let overhead_config = GasOverheadConfig { + destination_domain: remote_domain, + gas_overhead: config.overhead, + }; println!( "Setting gas overhead for remote domain {:?} ({:?}) with config {:?}", remote, remote_domain, overhead_config @@ -775,50 +686,27 @@ fn configure_igp_and_overhead_igp( } } -fn igp_gas_oracle_matches( - igp_account: &Igp, +fn map_configuration_matches( + existing_map: &HashMap, remote: &String, remote_domain: u32, - gas_oracle_config: &GasOracleConfig, -) -> bool { - if let Some(existing_config) = igp_account.gas_oracles.get(&remote_domain) { - if existing_config == gas_oracle_config.gas_oracle.as_ref().unwrap() { - println!( - "Gas oracle for remote domain {:?} ({:?}) already correctly set", - remote, remote_domain - ); - return true; - } else { - println!( - "Gas oracle for remote domain {:?} ({:?}) already set, but different. Current value: {:?}", - remote, remote_domain, existing_config - ); - return false; - } - } - false -} - -fn overhead_igp_config_matches( - overhead_igp_account: &OverheadIgp, - remote: &String, - remote_domain: u32, - gas_overhead_config: &GasOverheadConfig, -) -> bool { - if let Some(existing_config) = overhead_igp_account.gas_overheads.get(&remote_domain) { - if existing_config == &gas_overhead_config.gas_overhead.unwrap() { - println!( - "Gas overhead for remote domain {:?} ({:?}) already correctly set", - remote, remote_domain - ); - return true; - } else { - println!( - "Gas overhead for remote domain {:?} ({:?}) already set, but different. Current value: {:?}", - remote, remote_domain, existing_config - ); - return false; - } + new_config: Option<&T>, +) -> bool +where + T: PartialEq + std::fmt::Debug, +{ + let existing_config = existing_map.get(&remote_domain); + if existing_config == new_config { + println!( + "Configuration for remote domain {:?} ({:?}) matches expected config: {:?}", + remote, remote_domain, new_config + ); + true + } else { + println!( + "Configuration for remote domain {:?} ({:?}) does not match expected config. Current value: {:?}, expected value: {:?}", + remote, remote_domain, existing_config, new_config + ); + false } - false } diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index 8a654c4e18..eda64e9cb1 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -426,12 +426,8 @@ struct InitIgpAccountArgs { #[arg(long)] chain: String, #[arg(long)] - chain_config_file: PathBuf, - #[arg(long)] context: Option, #[arg(long)] - gas_oracle_config_file: Option, - #[arg(long)] account_salt: Option, // optional salt for deterministic account creation } @@ -444,14 +440,10 @@ struct InitOverheadIgpAccountArgs { #[arg(long)] chain: String, #[arg(long)] - chain_config_file: PathBuf, - #[arg(long)] inner_igp_account: Pubkey, #[arg(long)] context: Option, #[arg(long)] - overhead_config_file: Option, - #[arg(long)] account_salt: Option, // optional salt for deterministic account creation } @@ -561,7 +553,7 @@ struct ConfigureIgpArgs { #[arg(long)] program_id: Pubkey, #[arg(long)] - local_chain: String, + chain: String, #[arg(long)] gas_oracle_config_file: PathBuf, #[arg(long)] diff --git a/rust/sealevel/environments/local-e2e/gas-oracle-configs.json b/rust/sealevel/environments/local-e2e/gas-oracle-configs.json index 1d69e4c3bf..e916230498 100644 --- a/rust/sealevel/environments/local-e2e/gas-oracle-configs.json +++ b/rust/sealevel/environments/local-e2e/gas-oracle-configs.json @@ -1,20 +1,22 @@ -[ - { - "domain": 13375, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "10000000000000000000", - "gasPrice": "0", - "tokenDecimals": 18 +{ + "sealeveltest1": { + "sealeveltest2": { + "gasOracle": { + "tokenExchangeRate": "1000000000000000000", + "gasPrice": "1", + "tokenDecimals": 9 + }, + "overhead": 100000 } }, - { - "domain": 13376, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "10000000000000000000", - "gasPrice": "0", - "tokenDecimals": 18 + "sealeveltest2": { + "sealeveltest1": { + "gasOracle": { + "tokenExchangeRate": "1000000000000000000", + "gasPrice": "1", + "tokenDecimals": 9 + }, + "overhead": 100000 } } -] +} diff --git a/rust/sealevel/environments/local-e2e/overheads.json b/rust/sealevel/environments/local-e2e/overheads.json deleted file mode 100644 index 653fbf55f2..0000000000 --- a/rust/sealevel/environments/local-e2e/overheads.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "destinationDomain": 13375, - "gasOverhead": 10000 - }, - { - "destinationDomain": 13376, - "gasOverhead": 10000 - } -] From 876cec158bbd06e62f774f586b38ab61c2624d35 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 12:11:22 +0000 Subject: [PATCH 10/23] some kleenup --- .../infra/config/environments/mainnet3/igp.ts | 1 + .../config/environments/test/gas-oracle.ts | 8 +- .../infra/config/environments/testnet4/igp.ts | 8 +- .../sealevel-helpers/print-gas-oracles.ts | 25 +-- typescript/infra/src/config/gas-oracle.ts | 108 +++++++------ typescript/sdk/src/consts/igp.ts | 27 +++- .../sdk/src/gas/HyperlaneIgpDeployer.ts | 6 +- typescript/sdk/src/gas/oracle/types.ts | 10 +- typescript/sdk/src/gas/utils.ts | 148 +++++------------- typescript/sdk/src/index.ts | 7 +- 10 files changed, 161 insertions(+), 187 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 55cc31c250..c29f35d7b9 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -42,6 +42,7 @@ const storageGasOracleConfig: AllStorageGasOracleConfigs = tokenPrices, gasPrices, (local, remote) => getOverheadWithOverrides(local, remote), + true, ); export const igp: ChainMap = objMap( diff --git a/typescript/infra/config/environments/test/gas-oracle.ts b/typescript/infra/config/environments/test/gas-oracle.ts index 1145bef960..e2ee97f0a5 100644 --- a/typescript/infra/config/environments/test/gas-oracle.ts +++ b/typescript/infra/config/environments/test/gas-oracle.ts @@ -26,4 +26,10 @@ const gasPrices: ChainMap = { }; export const storageGasOracleConfig: AllStorageGasOracleConfigs = - getAllStorageGasOracleConfigs(testChainNames, tokenPrices, gasPrices); + getAllStorageGasOracleConfigs( + testChainNames, + tokenPrices, + gasPrices, + (_local, _remote) => 0, + false, + ); diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index 92fe27ddd7..f06a3e4b87 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -16,7 +16,13 @@ import rawTokenPrices from './tokenPrices.json'; const tokenPrices: ChainMap = rawTokenPrices; export const storageGasOracleConfig: AllStorageGasOracleConfigs = - getAllStorageGasOracleConfigs(supportedChainNames, tokenPrices, gasPrices); + getAllStorageGasOracleConfigs( + supportedChainNames, + tokenPrices, + gasPrices, + (local, remote) => getOverhead(local, remote, ethereumChainNames), + false, + ); export const igp: ChainMap = objMap( owners, diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index 4ce9186b6f..b05ec3cc02 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -4,7 +4,7 @@ import { writeJsonAtPath } from '../../src/utils/utils.js'; import { getArgs, withOutFile } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; -// This script exists to print the chain metadata configs for a given environment +// This script exists to print the gas oracle configs for a given environment // so they can easily be copied into the Sealevel tooling. :'( async function main() { @@ -14,29 +14,16 @@ async function main() { // Construct a nested map of origin -> destination -> { oracleConfig, overhead } const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { - console.log('origin', origin, 'igpConfig', igpConfig); + // If there's no oracle config, don't do anything for this origin if (!igpConfig.oracleConfig) { return {}; } - return objMap(igpConfig.oracleConfig, (destination, oracleConfig) => { - console.log('origin', origin, 'destination', destination); - console.log( - 'oracleConfig', - oracleConfig, - 'overhead', - igpConfig?.overhead?.[destination], - ); - return { - oracleConfig, - overhead: igpConfig?.overhead?.[destination], - }; - }); + return objMap(igpConfig.oracleConfig, (destination, oracleConfig) => ({ + oracleConfig, + overhead: igpConfig?.overhead?.[destination], + })); }); - console.log('do we get here ?'); - - console.log('keys?', stringifyObject(Object.keys(gasOracles))); - console.log(stringifyObject(gasOracles, 'yaml')); if (outFile) { diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 0943b6f837..704b691a23 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -11,7 +11,7 @@ import { StorageGasOracleConfig, defaultMultisigConfigs, getLocalStorageGasOracleConfig, - getProtocolSpecificExchangeRateScale, + getProtocolExchangeRateScale, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; import { @@ -50,13 +50,15 @@ function getLocalStorageGasOracleConfigOverride( remotes: ChainName[], tokenPrices: ChainMap, gasPrices: ChainMap, - getOverhead?: (local: ChainName, remote: ChainName) => number, + getOverhead: (local: ChainName, remote: ChainName) => number, + applyMinUsdCost: boolean, ): ChainMap { const localProtocolType = getChain(local).protocol; const localExchangeRateScale = - getProtocolSpecificExchangeRateScale(localProtocolType); + getProtocolExchangeRateScale(localProtocolType); const localNativeTokenDecimals = mustGetChainNativeToken(local).decimals; + // Construct the gas oracle params for each remote chain const gasOracleParams = [local, ...remotes].reduce((agg, remote) => { agg[remote] = { gasPrice: gasPrices[remote], @@ -68,57 +70,69 @@ function getLocalStorageGasOracleConfigOverride( return agg; }, {} as ChainMap); + // Modifier to adjust the gas price to meet minimum USD cost requirements. const gasPriceModifier = ( local: ChainName, remote: ChainName, gasOracleConfig: ProtocolAgnositicGasOracleConfig, ): BigNumberJs.Value => { - if (getOverhead) { - const typicalRemoteGasAmount = getTypicalRemoteGasAmount( - local, - remote, - getOverhead, + if (!applyMinUsdCost) { + return gasOracleConfig.gasPrice; + } + + const typicalRemoteGasAmount = getTypicalRemoteGasAmount( + local, + remote, + getOverhead, + ); + const localTokenUsdPrice = parseFloat(tokenPrices[local]); + const typicalIgpQuoteUsd = getUsdQuote( + localTokenUsdPrice, + localExchangeRateScale, + localNativeTokenDecimals, + localProtocolType, + gasOracleConfig, + typicalRemoteGasAmount, + ); + + const minUsdCost = getMinUsdCost(local, remote); + + // If the quote is already above the minimum cost, don't adjust the gas price! + if (typicalIgpQuoteUsd >= minUsdCost) { + return gasOracleConfig.gasPrice; + } + + // If we've gotten here, the quote is less than the minimum cost and we + // need to adjust the gas price. + + // The minimum quote we want on the origin, in the lowest origin denomination. + const minIgpQuoteWei = toWei( + new BigNumberJs(minUsdCost).div(localTokenUsdPrice), + localNativeTokenDecimals, + ); + // The new gas price that will give us the minimum quote. + // We use a BigNumberJs to allow for non-integer gas prices. + // Later in the process, this is made integer-friendly. + // This calculation expects that the token exchange rate accounts + // for decimals. + let newGasPrice = new BigNumberJs(minIgpQuoteWei) + .times(localExchangeRateScale.toString()) + .div( + new BigNumberJs(gasOracleConfig.tokenExchangeRate).times( + typicalRemoteGasAmount, + ), ); - const localTokenUsdPrice = parseFloat(tokenPrices[local]); - const typicalIgpQuoteUsd = getUsdQuote( - localTokenUsdPrice, - localExchangeRateScale, + + if (localProtocolType === ProtocolType.Sealevel) { + // On Sealevel, the exchange rate doesn't consider decimals. + // We therefore explicitly convert decimals to remote decimals. + newGasPrice = convertDecimals( localNativeTokenDecimals, - localProtocolType, - gasOracleConfig, - typicalRemoteGasAmount, + gasOracleConfig.tokenDecimals, + newGasPrice.toString(), ); - - const minUsdCost = getMinUsdCost(local, remote); - if (typicalIgpQuoteUsd < minUsdCost) { - // Adjust the gasPrice to meet the minimum cost - const minIgpQuoteWei = toWei( - new BigNumberJs(minUsdCost).div(localTokenUsdPrice), - localNativeTokenDecimals, - ); - console.log('minIgpQuoteWei', minIgpQuoteWei); - let newGasPrice = new BigNumberJs(minIgpQuoteWei) - .times(localExchangeRateScale.toString()) - .div( - new BigNumberJs(gasOracleConfig.tokenExchangeRate).times( - typicalRemoteGasAmount, - ), - ); - console.log('newGasPrice', newGasPrice); - if (localProtocolType === ProtocolType.Sealevel) { - // On Sealevel, the exchange rate doesn't consider decimals. - // We therefor explicitly convert decimals to remote decimals. - newGasPrice = convertDecimals( - localNativeTokenDecimals, - gasOracleConfig.tokenDecimals, - newGasPrice.toString(), - ); - // assert(newGasPrice.gt(0), 'newGasPrice must be greater than 0'); - } - return newGasPrice; - } } - return gasOracleConfig.gasPrice; + return newGasPrice; }; return getLocalStorageGasOracleConfig({ @@ -232,7 +246,8 @@ export function getAllStorageGasOracleConfigs( chainNames: ChainName[], tokenPrices: ChainMap, gasPrices: ChainMap, - getOverhead?: (local: ChainName, remote: ChainName) => number, + getOverhead: (local: ChainName, remote: ChainName) => number, + applyMinUsdCost: boolean = true, ): AllStorageGasOracleConfigs { // return chainNames.filter(isEthereumProtocolChain). @@ -257,6 +272,7 @@ export function getAllStorageGasOracleConfigs( tokenPrices, gasPrices, getOverhead, + applyMinUsdCost, ), }; }, {}) as AllStorageGasOracleConfigs; diff --git a/typescript/sdk/src/consts/igp.ts b/typescript/sdk/src/consts/igp.ts index 351fc7b49f..9dcc662399 100644 --- a/typescript/sdk/src/consts/igp.ts +++ b/typescript/sdk/src/consts/igp.ts @@ -1,4 +1,6 @@ -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; + +import { ProtocolType } from '@hyperlane-xyz/utils'; export const TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM = 10; @@ -20,3 +22,26 @@ export const TOKEN_EXCHANGE_RATE_SCALE_COSMOS = ethers.utils.parseUnits( '1', TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS, ); + +// Gets the number of decimals for the exchange rate on a particular origin protocol. +// Different smart contract implementations require different levels of precision. +export function getProtocolExchangeRateDecimals( + protocolType: ProtocolType, +): number { + switch (protocolType) { + case ProtocolType.Ethereum: + return TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM; + case ProtocolType.Sealevel: + return TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL; + case ProtocolType.Cosmos: + return TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS; + default: + throw new Error(`Unsupported protocol type: ${protocolType}`); + } +} + +export function getProtocolExchangeRateScale( + protocolType: ProtocolType, +): BigNumber { + return BigNumber.from(10).pow(getProtocolExchangeRateDecimals(protocolType)); +} diff --git a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts index 0e05db8b25..77ae185db8 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts @@ -132,7 +132,11 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< !actual.tokenExchangeRate.eq(desired.tokenExchangeRate) ) { this.logger.info( - `${chain} -> ${remote}: ${serializeDifference(actual, desiredData)}`, + `${chain} -> ${remote}: ${serializeDifference( + this.multiProvider.getProtocol(chain), + actual, + desiredData, + )}`, ); configsToSet.push({ remoteDomain, diff --git a/typescript/sdk/src/gas/oracle/types.ts b/typescript/sdk/src/gas/oracle/types.ts index 3117c37216..c48a8729b7 100644 --- a/typescript/sdk/src/gas/oracle/types.ts +++ b/typescript/sdk/src/gas/oracle/types.ts @@ -1,7 +1,9 @@ import { ethers } from 'ethers'; import { z } from 'zod'; -import { TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM } from '../../consts/igp.js'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { getProtocolExchangeRateDecimals } from '../../consts/igp.js'; export const StorageGasOracleConfigSchema = z.object({ gasPrice: z.string(), @@ -30,6 +32,7 @@ export type OracleData = { }; export const formatGasOracleConfig = ( + localChainProtocol: ProtocolType, config: OracleData, ): { tokenExchangeRate: string; @@ -37,7 +40,7 @@ export const formatGasOracleConfig = ( } => ({ tokenExchangeRate: ethers.utils.formatUnits( config.tokenExchangeRate, - TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM, + getProtocolExchangeRateDecimals(localChainProtocol), ), gasPrice: ethers.utils.formatUnits(config.gasPrice, 'gwei'), }); @@ -67,6 +70,7 @@ export const oracleConfigToOracleData = ( }); export const serializeDifference = ( + localChainProtocol: ProtocolType, actual: OracleData, expected: OracleData, ): string => { @@ -84,6 +88,6 @@ export const serializeDifference = ( expected.tokenExchangeRate.mul(expected.gasPrice), ); - const formatted = formatGasOracleConfig(expected); + const formatted = formatGasOracleConfig(localChainProtocol, expected); return `Exchange rate: ${formatted.tokenExchangeRate} (${tokenExchangeRateDiff}), Gas price: ${formatted.gasPrice} gwei (${gasPriceDiff}), Product diff: ${productDiff}`; }; diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index 67b17715c3..20772c916f 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -5,11 +5,7 @@ import { BigNumber, ethers } from 'ethers'; import { ProtocolType, convertDecimals, objMap } from '@hyperlane-xyz/utils'; -import { - TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS, - TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM, - TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL, -} from '../consts/igp.js'; +import { getProtocolExchangeRateDecimals } from '../consts/igp.js'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; import { AgentCosmosGasPrice } from '../metadata/agentConfig.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; @@ -105,18 +101,16 @@ export async function getCosmosChainGasPrice( } // Gets the exchange rate of the remote quoted in local tokens, not accounting for decimals. -function getTokenExchangeRateFromValues({ +function getTokenExchangeRate({ local, remote, tokenPrices, exchangeRateMarginPct, -}: // decimals, -{ +}: { local: ChainName; remote: ChainName; tokenPrices: ChainMap; exchangeRateMarginPct: number; - // decimals: { local: number; remote: number }; }): BigNumberJs { // Workaround for chicken-egg dependency problem. // We need to provide some default value here to satisfy the config on initial load, @@ -125,26 +119,11 @@ function getTokenExchangeRateFromValues({ const localValue = new BigNumberJs(tokenPrices[local] ?? defaultValue); const remoteValue = new BigNumberJs(tokenPrices[remote] ?? defaultValue); - console.log( - 'yeet 1', - local, - remote, - 'localValue', - localValue.toString(), - tokenPrices[local], - 'remoteValue', - remoteValue.toString(), - tokenPrices[remote], - ); - // This does not yet account for decimals! + // Note this does not account for decimals! let exchangeRate = remoteValue.div(localValue); - console.log('yeet 2', local, remote, 'exchangeRate', exchangeRate.toString()); // Apply the premium exchangeRate = exchangeRate.times(100 + exchangeRateMarginPct).div(100); - console.log('yeet 3', local, remote, 'exchangeRate', exchangeRate.toString()); - // const value = convertDecimals(decimals.remote, decimals.local, exchangeRate); - // console.log('yeet 4', local, remote, 'value', value.toString()); assert( exchangeRate.isGreaterThan(0), 'Exchange rate must be greater than 0, possible loss of precision', @@ -152,35 +131,11 @@ function getTokenExchangeRateFromValues({ return exchangeRate; } -function getProtocolSpecificExchangeRateDecimals( - protocolType: ProtocolType, -): number { - switch (protocolType) { - case ProtocolType.Ethereum: - return TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM; - case ProtocolType.Sealevel: - return TOKEN_EXCHANGE_RATE_DECIMALS_SEALEVEL; - case ProtocolType.Cosmos: - return TOKEN_EXCHANGE_RATE_DECIMALS_COSMOS; - default: - throw new Error(`Unsupported protocol type: ${protocolType}`); - } -} - -export function getProtocolSpecificExchangeRateScale( - protocolType: ProtocolType, -): BigNumber { - return BigNumber.from(10).pow( - getProtocolSpecificExchangeRateDecimals(protocolType), - ); -} - -function getProtocolSpecificExchangeRate( +function getProtocolExchangeRate( + localProtocolType: ProtocolType, exchangeRate: BigNumberJs, - protocolType: ProtocolType, ): BigNumber { - const multiplierDecimals = - getProtocolSpecificExchangeRateDecimals(protocolType); + const multiplierDecimals = getProtocolExchangeRateDecimals(localProtocolType); const multiplier = new BigNumberJs(10).pow(multiplierDecimals); const integer = exchangeRate .times(multiplier) @@ -189,11 +144,15 @@ function getProtocolSpecificExchangeRate( return BigNumber.from(integer); } -// The move is to somehow scale up the gas price and scale down the exchange rate by the same factor. - // Gets the StorageGasOracleConfig for each remote chain for a particular local chain. // Accommodates small non-integer gas prices by scaling up the gas price // and scaling down the exchange rate by the same factor. +// A gasPriceModifier can be supplied to adjust the gas price based on a prospective +// gasOracleConfig. +// Values take into consideration the local chain's needs depending on the protocol type, +// e.g. the expected decimals of the token exchange rate, or whether to account for +// a native token decimal difference in the exchange rate. +// Therefore the values here can be applied directly to the chain's gas oracle. export function getLocalStorageGasOracleConfig({ local, localProtocolType, @@ -221,24 +180,17 @@ export function getLocalStorageGasOracleConfig({ const localDecimals = gasOracleParams[local].nativeToken.decimals; return remotes.reduce((agg, remote) => { const remoteDecimals = gasOracleParams[remote].nativeToken.decimals; - let exchangeRateFloat = getTokenExchangeRateFromValues({ + // The exchange rate, not yet accounting for decimals, and potentially + // floating point. + let exchangeRateFloat = getTokenExchangeRate({ local, remote, tokenPrices, exchangeRateMarginPct, - // decimals: { local: localDecimals, remote: remoteDecimals }, }); if (localProtocolType !== ProtocolType.Sealevel) { - // On all chains other than Sealevel, we need to adjust the exchange rate for decimals - console.log( - 'exchangeRateFloat before convertDecimals', - exchangeRateFloat.toString(), - 'localDecimals', - localDecimals, - 'remoteDecimals', - remoteDecimals, - ); + // On all chains other than Sealevel, we need to adjust the exchange rate for decimals. exchangeRateFloat = convertDecimals( remoteDecimals, localDecimals, @@ -246,48 +198,35 @@ export function getLocalStorageGasOracleConfig({ ); } - console.log( - 'exchangeRateFloat before protocol specific', - exchangeRateFloat.toString(), - ); - let exchangeRate = getProtocolSpecificExchangeRate( - exchangeRateFloat, - // TODO need to get this + // Make the exchange rate an integer by scaling it up by the appropriate factor for the protocol. + let exchangeRate = getProtocolExchangeRate( localProtocolType, - ); - console.log( - 'exchangeRate after protocol specific', - exchangeRate.toString(), + exchangeRateFloat, ); - // First parse as a number, so we have floating point precision. + // First parse the gas price as a number, so we have floating point precision. // Recall it's possible to have gas prices that are not integers, even // after converting to the "wei" version of the token. - let gasPrice = - parseFloat(gasOracleParams[remote].gasPrice.amount) * - Math.pow(10, gasOracleParams[remote].gasPrice.decimals); - if (isNaN(gasPrice)) { + let gasPrice = new BigNumberJs( + gasOracleParams[remote].gasPrice.amount, + ).times(new BigNumberJs(10).pow(gasOracleParams[remote].gasPrice.decimals)); + if (gasPrice.isNaN()) { throw new Error( `Invalid gas price for chain ${remote}: ${gasOracleParams[remote].gasPrice.amount}`, ); } - // Our integer gas price. - // let gasPriceBn = BigNumber.from(Math.ceil(gasPrice)); - - // let gasOracleConfig: ProtocolAgnositicGasOracleConfig = { - // gasPrice: gasPriceBn.toString(), - // tokenExchangeRate: exchangeRate.toString(), - // tokenDecimals: remoteDecimals, - // }; - + // Get a prospective gasOracleConfig, adjusting the gas price and exchange rate + // as needed to account for precision loss (e.g. if the gas price is super small). let gasOracleConfig = adjustForPrecisionLoss( gasPrice, exchangeRate, remoteDecimals, ); + // Apply the modifier if provided. if (gasPriceModifier) { + // Once again adjust for precision loss after applying the modifier. gasOracleConfig = adjustForPrecisionLoss( gasPriceModifier(local, remote, gasOracleConfig), BigNumber.from(gasOracleConfig.tokenExchangeRate), @@ -303,17 +242,18 @@ export function getLocalStorageGasOracleConfig({ } function adjustForPrecisionLoss( - gasPriceValue: BigNumberJs.Value, + gasPrice: BigNumberJs.Value, exchangeRate: BigNumber, remoteDecimals: number, ): ProtocolAgnositicGasOracleConfig { - let newGasPrice = new BigNumberJs(gasPriceValue); + let newGasPrice = new BigNumberJs(gasPrice); let newExchangeRate = exchangeRate; - // We have very little precision and ultimately need an integer value for - // the gas price that will be set on-chain. We scale up the gas price and - // scale down the exchange rate by the same factor. + + // We may have very little precision, and ultimately need an integer value for + // the gas price that will be set on-chain. If this is the case, we scale up the + // gas price and scale down the exchange rate by the same factor. if (newGasPrice.lt(10) && newGasPrice.mod(1) !== new BigNumberJs(0)) { - // Scale up the gas price by 1e4 + // Scale up the gas price by 1e4 (arbirtary choice) const gasPriceScalingFactor = 1e4; // Check that there's no significant underflow when applying @@ -326,14 +266,6 @@ function adjustForPrecisionLoss( throw new Error('Too much underflow when downscaling exchange rate'); } - console.log( - 'gasPriceValue', - gasPriceValue, - 'newGasPrice', - newGasPrice.toString(), - 'gasPriceScalingFactor', - gasPriceScalingFactor.toString(), - ); newGasPrice = newGasPrice.times(gasPriceScalingFactor); newExchangeRate = adjustedExchangeRate; } @@ -350,11 +282,3 @@ function adjustForPrecisionLoss( tokenDecimals: remoteDecimals, }; } - -// class ProtocolAgnosticGasOracleConfigClass { -// constructor(readonly config: ProtocolAgnositicGasOracleConfig) {} - -// getProtocolSpecificConfig(protocol: ProtocolType): any { - -// } -// } diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index e6fed8323a..0ec8e740b3 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -9,8 +9,10 @@ export { export { S3Config, S3Receipt, S3Wrapper } from './aws/s3.js'; export { S3Validator } from './aws/validator.js'; export { - TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM as TOKEN_EXCHANGE_RATE_DECIMALS, - TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM as TOKEN_EXCHANGE_RATE_SCALE, + TOKEN_EXCHANGE_RATE_DECIMALS_ETHEREUM, + TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM, + getProtocolExchangeRateDecimals, + getProtocolExchangeRateScale, } from './consts/igp.js'; export { MAILBOX_VERSION } from './consts/mailbox.js'; export { @@ -424,7 +426,6 @@ export { getCosmosChainGasPrice, getGasPrice, getLocalStorageGasOracleConfig, - getProtocolSpecificExchangeRateScale, NativeTokenPriceConfig, } from './gas/utils.js'; export { GcpValidator } from './gcp/validator.js'; From 1ba713099b2a81280eeaa9a318d0bf5676ec0e57 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 14:26:33 +0000 Subject: [PATCH 11/23] cleaning up --- typescript/infra/config/registry.ts | 4 +- .../sealevel-helpers/print-gas-oracles.ts | 84 +++++++++++++++++-- typescript/infra/src/config/gas-oracle.ts | 8 ++ .../configure-gas-oracles.hardhat-test.ts | 1 + typescript/sdk/src/gas/oracle/types.ts | 3 +- .../src/hook/EvmHookModule.hardhat-test.ts | 16 ++++ typescript/sdk/src/hook/EvmHookReader.ts | 4 +- typescript/sdk/src/hook/types.ts | 4 +- typescript/sdk/src/test/testUtils.ts | 1 + 9 files changed, 112 insertions(+), 13 deletions(-) diff --git a/typescript/infra/config/registry.ts b/typescript/infra/config/registry.ts index 8b9ea5edb7..526c23c14b 100644 --- a/typescript/infra/config/registry.ts +++ b/typescript/infra/config/registry.ts @@ -104,7 +104,9 @@ export function getWarpCoreConfig(warpRouteId: string): WarpCoreConfig { return warpRouteConfig; } -export function getWarpAddresses(warpRouteId: string) { +export function getWarpAddresses( + warpRouteId: string, +): ChainMap { const warpCoreConfig = getWarpCoreConfig(warpRouteId); return warpConfigToWarpAddresses(warpCoreConfig); } diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index b05ec3cc02..36bc40f25a 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -1,30 +1,98 @@ -import { objMap, stringifyObject } from '@hyperlane-xyz/utils'; +import { + ChainMap, + ChainName, + ProtocolAgnositicGasOracleConfig, +} from '@hyperlane-xyz/sdk'; +import { + ProtocolType, + objFilter, + objMap, + stringifyObject, +} from '@hyperlane-xyz/utils'; +import { WarpRouteIds } from '../../config/environments/mainnet3/warp/warpIds.js'; +import { getChain, getWarpAddresses } from '../../config/registry.js'; import { writeJsonAtPath } from '../../src/utils/utils.js'; import { getArgs, withOutFile } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; // This script exists to print the gas oracle configs for a given environment // so they can easily be copied into the Sealevel tooling. :'( +interface GasOracleConfigWithOverhead { + oracleConfig: ProtocolAgnositicGasOracleConfig; + overhead?: number; +} async function main() { const { environment, outFile } = await withOutFile(getArgs()).argv; const environmentConfig = getEnvironmentConfig(environment); + // Because there is a limit to how many chains we want to figure in an SVM IGP, + // we limit the chains to only those that are connected via warp routes. + // Returns a record of origin chain -> set of chains that are connected via warp routes. + const allConnectedChains = Object.values(WarpRouteIds).reduce( + (agg, warpRouteId) => { + const warpRouteAddresses = getWarpAddresses(warpRouteId); + const warpRouteChains = Object.keys(warpRouteAddresses); + // Make sure each chain is connected to every other chain + warpRouteChains.forEach((chainA) => { + warpRouteChains.forEach((chainB) => { + if (chainA === chainB) { + return; + } + if (agg[chainA] === undefined) { + agg[chainA] = new Set(); + } + agg[chainA].add(chainB as ChainName); + }); + }); + return agg; + }, + {} as ChainMap>, + ); + // Construct a nested map of origin -> destination -> { oracleConfig, overhead } - const gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { + let gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { + // Only SVM origins for now + if (getChain(origin).protocol !== ProtocolType.Sealevel) { + return undefined; + } + // If there's no oracle config, don't do anything for this origin if (!igpConfig.oracleConfig) { - return {}; + return undefined; } - return objMap(igpConfig.oracleConfig, (destination, oracleConfig) => ({ - oracleConfig, - overhead: igpConfig?.overhead?.[destination], - })); + // Get the set of chains that are connected to this origin via warp routes + const connectedChainsSet = allConnectedChains[origin]; + if (!connectedChainsSet) { + return undefined; + } + const connectedChains = [...connectedChainsSet]; + + return connectedChains.reduce((agg, destination) => { + const oracleConfig = igpConfig.oracleConfig[destination]; + if (oracleConfig.tokenDecimals === undefined) { + throw new Error( + `Token decimals not defined for ${origin} -> ${destination}`, + ); + } + agg[destination] = { + oracleConfig, + overhead: igpConfig?.overhead?.[destination], + }; + return agg; + }, {} as ChainMap); }); - console.log(stringifyObject(gasOracles, 'yaml')); + // Filter out undefined values + gasOracles = objFilter( + gasOracles, + (_, value): value is ChainMap | undefined => + value !== undefined, + ); + + console.log(stringifyObject(gasOracles, 'json', 2)); if (outFile) { console.log(`Writing config to ${outFile}`); diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 704b691a23..d4b2a9de2d 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -124,6 +124,10 @@ function getLocalStorageGasOracleConfigOverride( ); if (localProtocolType === ProtocolType.Sealevel) { + assert( + gasOracleConfig.tokenDecimals, + 'Token decimals must be defined for use by local Sealevel chains', + ); // On Sealevel, the exchange rate doesn't consider decimals. // We therefore explicitly convert decimals to remote decimals. newGasPrice = convertDecimals( @@ -211,6 +215,10 @@ function getUsdQuote( .div(localExchangeRateScale) .toString(); if (localProtocolType === ProtocolType.Sealevel) { + assert( + gasOracleConfig.tokenDecimals, + 'Token decimals must be defined for use by local Sealevel chains', + ); // Convert decimals to local decimals quote = convertDecimals( gasOracleConfig.tokenDecimals, diff --git a/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts b/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts index 8eb66a6edb..c5279a9f92 100644 --- a/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts +++ b/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts @@ -47,6 +47,7 @@ describe('HyperlaneIgpDeployer', () => { testConfig[local].oracleConfig![remote] = { tokenExchangeRate: utils.parseUnits('2', 'gwei').toString(), gasPrice: utils.parseUnits('3', 'gwei').toString(), + tokenDecimals: 18, }; const localContracts = await deployer.deployContracts( diff --git a/typescript/sdk/src/gas/oracle/types.ts b/typescript/sdk/src/gas/oracle/types.ts index c48a8729b7..d33558f1a0 100644 --- a/typescript/sdk/src/gas/oracle/types.ts +++ b/typescript/sdk/src/gas/oracle/types.ts @@ -18,7 +18,8 @@ export type StorageGasOracleConfig = z.output< export const ProtocolAgnositicGasOracleConfigSchema = StorageGasOracleConfigSchema.extend({ // The number of decimals of the remote native token. - tokenDecimals: z.number(), + // Optional because it's not required by all protocol types. + tokenDecimals: z.number().optional(), }); // Gas data to configure on a single destination chain. diff --git a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts index ae66d7adbf..2b31d62346 100644 --- a/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts +++ b/typescript/sdk/src/hook/EvmHookModule.hardhat-test.ts @@ -29,6 +29,7 @@ import { } from './types.js'; const hookTypes = Object.values(HookType); +const DEFAULT_TOKEN_DECIMALS = 18; function randomHookType(): HookType { // OP_STACK filtering is temporary until we have a way to deploy the required contracts @@ -99,6 +100,7 @@ function randomHookConfig( { tokenExchangeRate: randomInt(1234567891234).toString(), gasPrice: randomInt(1234567891234).toString(), + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, ]), ), @@ -347,18 +349,22 @@ describe('EvmHookModule', async () => { test1: { tokenExchangeRate: '1032586497157', gasPrice: '1026942205817', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test2: { tokenExchangeRate: '81451154935', gasPrice: '1231220057593', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test3: { tokenExchangeRate: '31347320275', gasPrice: '21944956734', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test4: { tokenExchangeRate: '1018619796544', gasPrice: '1124484183261', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, }, }, @@ -384,18 +390,22 @@ describe('EvmHookModule', async () => { test1: { tokenExchangeRate: '1132883204938', gasPrice: '1219466305935', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test2: { tokenExchangeRate: '938422264723', gasPrice: '229134538568', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test3: { tokenExchangeRate: '69699594189', gasPrice: '475781234236', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test4: { tokenExchangeRate: '1027245678936', gasPrice: '502686418976', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, }, }, @@ -417,18 +427,22 @@ describe('EvmHookModule', async () => { test1: { tokenExchangeRate: '443874625350', gasPrice: '799154764503', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test2: { tokenExchangeRate: '915348561750', gasPrice: '1124345797215', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test3: { tokenExchangeRate: '930832717805', gasPrice: '621743941770', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, test4: { tokenExchangeRate: '147394981623', gasPrice: '766494385983', + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, }, }, @@ -478,6 +492,7 @@ describe('EvmHookModule', async () => { { tokenExchangeRate: randomInt(1234567891234).toString(), gasPrice: randomInt(1234567891234).toString(), + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, ]), ), @@ -525,6 +540,7 @@ describe('EvmHookModule', async () => { { tokenExchangeRate: randomInt(987654321).toString(), gasPrice: randomInt(987654321).toString(), + tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, ]), ); diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index dae0f9d589..92b42ac7f1 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -251,7 +251,8 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { this.concurrency, domainIds, async (domainId) => { - const chainName = this.multiProvider.getChainName(domainId); + const chainMetadata = this.multiProvider.getChainMetadata(domainId); + const chainName = chainMetadata.name; try { const { tokenExchangeRate, gasPrice } = await hook.getExchangeRateAndGasPrice(domainId); @@ -261,6 +262,7 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { oracleConfig[chainName] = { tokenExchangeRate: tokenExchangeRate.toString(), gasPrice: gasPrice.toString(), + tokenDecimals: chainMetadata?.nativeToken?.decimals, }; const { gasOracle } = await hook.destinationGasConfigs(domainId); diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index 768e93134b..76e749fd95 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { StorageGasOracleConfigSchema } from '../gas/oracle/types.js'; +import { ProtocolAgnositicGasOracleConfigSchema } from '../gas/oracle/types.js'; import { ZHash } from '../metadata/customZodTypes.js'; import { ChainMap, @@ -115,7 +115,7 @@ export const IgpSchema = OwnableSchema.extend({ beneficiary: z.string(), oracleKey: z.string(), overhead: z.record(z.number()), - oracleConfig: z.record(StorageGasOracleConfigSchema), + oracleConfig: z.record(ProtocolAgnositicGasOracleConfigSchema), }); export const DomainRoutingHookConfigSchema: z.ZodSchema = diff --git a/typescript/sdk/src/test/testUtils.ts b/typescript/sdk/src/test/testUtils.ts index 5a2d4f2baa..57ac9d3ecf 100644 --- a/typescript/sdk/src/test/testUtils.ts +++ b/typescript/sdk/src/test/testUtils.ts @@ -65,6 +65,7 @@ export function testCoreConfig( const TEST_ORACLE_CONFIG = { gasPrice: ethers.utils.parseUnits('1', 'gwei').toString(), tokenExchangeRate: ethers.utils.parseUnits('1', 10).toString(), + tokenDecimals: 18, }; const TEST_OVERHEAD_COST = 60000; From e854ee643e9cde96f9fc144ea65f257b999283c2 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 14:46:47 +0000 Subject: [PATCH 12/23] add hardcoded connections --- .../sealevel-helpers/print-gas-oracles.ts | 56 +++++++++++-------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index 36bc40f25a..b247a15cb6 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -28,29 +28,7 @@ async function main() { const environmentConfig = getEnvironmentConfig(environment); - // Because there is a limit to how many chains we want to figure in an SVM IGP, - // we limit the chains to only those that are connected via warp routes. - // Returns a record of origin chain -> set of chains that are connected via warp routes. - const allConnectedChains = Object.values(WarpRouteIds).reduce( - (agg, warpRouteId) => { - const warpRouteAddresses = getWarpAddresses(warpRouteId); - const warpRouteChains = Object.keys(warpRouteAddresses); - // Make sure each chain is connected to every other chain - warpRouteChains.forEach((chainA) => { - warpRouteChains.forEach((chainB) => { - if (chainA === chainB) { - return; - } - if (agg[chainA] === undefined) { - agg[chainA] = new Set(); - } - agg[chainA].add(chainB as ChainName); - }); - }); - return agg; - }, - {} as ChainMap>, - ); + const allConnectedChains = getChainConnections(); // Construct a nested map of origin -> destination -> { oracleConfig, overhead } let gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { @@ -100,6 +78,38 @@ async function main() { } } +// Because there is a limit to how many chains we want to figure in an SVM IGP, +// we limit the chains to only those that are connected via warp routes. +// Returns a record of origin chain -> set of chains that are connected via warp routes. +function getChainConnections(): ChainMap> { + // A list of connected chains + const connectedChains = [ + // Hardcoded connections + ['sonicsvmtestnet', 'solanatestnet'], + // All known warp routes + ...Object.values(WarpRouteIds).map((warpRouteId) => { + const warpRouteAddresses = getWarpAddresses(warpRouteId); + return Object.keys(warpRouteAddresses); + }), + ]; + + return connectedChains.reduce((agg, chains) => { + // Make sure each chain is connected to every other chain + chains.forEach((chainA) => { + chains.forEach((chainB) => { + if (chainA === chainB) { + return; + } + if (agg[chainA] === undefined) { + agg[chainA] = new Set(); + } + agg[chainA].add(chainB as ChainName); + }); + }); + return agg; + }, {} as ChainMap>); +} + main().catch((err) => { console.error(err); process.exit(1); From 782db5307cbbbb5f528f3950019595a202161401 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 15:25:53 +0000 Subject: [PATCH 13/23] testnet svm setting --- .../testnet4/gas-oracle-configs.json | 58 +++++++------------ .../environments/testnet4/gasPrices.json | 4 +- .../environments/testnet4/tokenPrices.json | 1 + 3 files changed, 24 insertions(+), 39 deletions(-) diff --git a/rust/sealevel/environments/testnet4/gas-oracle-configs.json b/rust/sealevel/environments/testnet4/gas-oracle-configs.json index cadbe1ad78..c56c311b79 100644 --- a/rust/sealevel/environments/testnet4/gas-oracle-configs.json +++ b/rust/sealevel/environments/testnet4/gas-oracle-configs.json @@ -1,38 +1,22 @@ -[ - { - "domain": 11155111, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "10000000000000000000", - "gasPrice": "15000000000", - "tokenDecimals": 18 - } - }, - { - "domain": 1399811150, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "10000000000000000000", - "gasPrice": "28", - "tokenDecimals": 9 - } - }, - { - "domain": 239092742, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "10000000000000000000", - "gasPrice": "28", - "tokenDecimals": 9 - } - }, - { - "domain": 15153042, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "10000000000000000000", - "gasPrice": "28", - "tokenDecimals": 9 - } +{ + "solanatestnet": { + "sonicsvmtestnet": { + "oracleConfig": { + "tokenExchangeRate": "1500000000000000", + "gasPrice": "1000", + "tokenDecimals": 9 + }, + "overhead": 600000 } -] \ No newline at end of file + }, + "sonicsvmtestnet": { + "solanatestnet": { + "oracleConfig": { + "tokenExchangeRate": "1500000000000000", + "gasPrice": "1000", + "tokenDecimals": 9 + }, + "overhead": 600000 + } + } +} diff --git a/typescript/infra/config/environments/testnet4/gasPrices.json b/typescript/infra/config/environments/testnet4/gasPrices.json index ebb9736d5b..71acca4f18 100644 --- a/typescript/infra/config/environments/testnet4/gasPrices.json +++ b/typescript/infra/config/environments/testnet4/gasPrices.json @@ -88,7 +88,7 @@ "decimals": 9 }, "solanatestnet": { - "amount": "0.0001", + "amount": "0.01", "decimals": 1 }, "soneiumtestnet": { @@ -96,7 +96,7 @@ "decimals": 9 }, "sonicsvmtestnet": { - "amount": "0.0001", + "amount": "0.01", "decimals": 1 }, "sonictestnet": { diff --git a/typescript/infra/config/environments/testnet4/tokenPrices.json b/typescript/infra/config/environments/testnet4/tokenPrices.json index 24670e98ea..c39149fd9a 100644 --- a/typescript/infra/config/environments/testnet4/tokenPrices.json +++ b/typescript/infra/config/environments/testnet4/tokenPrices.json @@ -24,6 +24,7 @@ "solanatestnet": "10", "soneiumtestnet": "10", "sonictestnet": "10", + "sonicsvmtestnet": "10", "suavetoliman": "10", "superpositiontestnet": "10", "treasuretopaz": "10", From ad01cabefb734ab6a4b7253ae12a9e5d66c6dc0d Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 15:32:44 +0000 Subject: [PATCH 14/23] starting to set mainnets --- .../mainnet3/gas-oracle-configs.json | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 rust/sealevel/environments/mainnet3/gas-oracle-configs.json diff --git a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json new file mode 100644 index 0000000000..7cbf932c9a --- /dev/null +++ b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json @@ -0,0 +1,72 @@ +{ + "solanamainnet": { + "base": { + "oracleConfig": { + "tokenExchangeRate": "263105000000000000000", + "gasPrice": "486782274", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, + "ethereum": { + "oracleConfig": { + "tokenExchangeRate": "263105000000000000000", + "gasPrice": "20047740244", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, + "eclipsemainnet": { + "oracleConfig": { + "tokenExchangeRate": "26310500000000000", + "gasPrice": "1625", + "tokenDecimals": 9 + }, + "overhead": 600000 + }, + "soon": { + "oracleConfig": { + "tokenExchangeRate": "26310500000000000", + "gasPrice": "1625", + "tokenDecimals": 9 + }, + "overhead": 600000 + } + }, + "eclipsemainnet": { + "ethereum": { + "oracleConfig": { + "tokenExchangeRate": "15000000000000000000", + "gasPrice": "20047740244", + "tokenDecimals": 18 + }, + "overhead": 166460 + }, + "solanamainnet": { + "oracleConfig": { + "tokenExchangeRate": "85517188954979", + "gasPrice": "85470", + "tokenDecimals": 9 + }, + "overhead": 600000 + }, + "stride": { + "oracleConfig": { + "tokenExchangeRate": "235829801790", + "gasPrice": "4133", + "tokenDecimals": 6 + }, + "overhead": 600000 + } + }, + "soon": { + "solanamainnet": { + "oracleConfig": { + "tokenExchangeRate": "85517188954979", + "gasPrice": "85470", + "tokenDecimals": 9 + }, + "overhead": 600000 + } + } +} From ca207249c0511ba72ab076d4ee583a076801a81e Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 15:45:01 +0000 Subject: [PATCH 15/23] gas oracle config --- .../environments/mainnet3/gas-oracle-configs.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json index 7cbf932c9a..5525e6e74f 100644 --- a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json +++ b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json @@ -1,5 +1,13 @@ { "solanamainnet": { + "artela": { + "oracleConfig": { + "tokenExchangeRate": "83333333333333333", + "gasPrice": "614759390835", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, "base": { "oracleConfig": { "tokenExchangeRate": "263105000000000000000", From d4227abd00f4d3a89c661dae3117abf6c5437a03 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 15:47:00 +0000 Subject: [PATCH 16/23] rust nit --- rust/sealevel/client/src/igp.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index bf333ff038..6847d81d36 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -9,11 +9,7 @@ use crate::{ Context, GasOverheadSubCmd, GetSetCmd, IgpCmd, IgpSubCmd, }; -use std::{ - fs::File, - path::{Path, PathBuf}, - str::FromStr, -}; +use std::{path::Path, str::FromStr}; use solana_sdk::{ pubkey::Pubkey, From 0a97475347fea6b6e8fa4bb8bac9cac1fadbe993 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 17:02:37 +0000 Subject: [PATCH 17/23] e2e passes --- rust/main/utils/run-locally/src/solana.rs | 28 +++++++++++++++++++ .../local-e2e/gas-oracle-configs.json | 4 +-- typescript/cli/src/config/hooks.ts | 2 ++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/rust/main/utils/run-locally/src/solana.rs b/rust/main/utils/run-locally/src/solana.rs index a2ca137546..b6d0506082 100644 --- a/rust/main/utils/run-locally/src/solana.rs +++ b/rust/main/utils/run-locally/src/solana.rs @@ -234,6 +234,7 @@ pub fn start_solana_test_validator( .arg("environments-dir", SOLANA_ENVS_DIR) .arg("built-so-dir", SBF_OUT_PATH); + // Deploy sealeveltest1 core sealevel_client_deploy_core .clone() .arg("local-domain", SOLANA_LOCAL_CHAIN_ID) @@ -241,12 +242,38 @@ pub fn start_solana_test_validator( .run() .join(); + // Deploy sealeveltest2 core sealevel_client_deploy_core .arg("local-domain", SOLANA_REMOTE_CHAIN_ID) .arg("chain", "sealeveltest2") .run() .join(); + const SEALEVETEST1_IGP_PROGRAM_ID: &str = "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U"; + const SEALEVETEST2_IGP_PROGRAM_ID: &str = "FArd4tEikwz2fk3MB7S9kC82NGhkgT6f9aXi3C5cw1E5"; + + let sealevel_client_deploy_core = sealevel_client + .clone() + .cmd("igp") + .cmd("configure") + .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) + .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE); + + // Configure sealeveltest1 IGP + sealevel_client_deploy_core + .clone() + .arg("program-id", SEALEVETEST1_IGP_PROGRAM_ID) + .arg("chain", "sealeveltest1") + .run() + .join(); + + // Configure sealeveltest2 IGP + sealevel_client_deploy_core + .arg("program-id", SEALEVETEST2_IGP_PROGRAM_ID) + .arg("chain", "sealeveltest2") + .run() + .join(); + sealevel_client .clone() .arg("compute-budget", "200000") @@ -326,6 +353,7 @@ pub fn start_solana_test_validator( .cmd("configure") .arg("program-id", IGP_PROGRAM_ID) .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) + .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE) .arg("chain", "sealeveltest1") .arg("account-salt", ALTERNATIVE_SALT) .run() diff --git a/rust/sealevel/environments/local-e2e/gas-oracle-configs.json b/rust/sealevel/environments/local-e2e/gas-oracle-configs.json index e916230498..896508f142 100644 --- a/rust/sealevel/environments/local-e2e/gas-oracle-configs.json +++ b/rust/sealevel/environments/local-e2e/gas-oracle-configs.json @@ -1,7 +1,7 @@ { "sealeveltest1": { "sealeveltest2": { - "gasOracle": { + "oracleConfig": { "tokenExchangeRate": "1000000000000000000", "gasPrice": "1", "tokenDecimals": 9 @@ -11,7 +11,7 @@ }, "sealeveltest2": { "sealeveltest1": { - "gasOracle": { + "oracleConfig": { "tokenExchangeRate": "1000000000000000000", "gasPrice": "1", "tokenDecimals": 9 diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 1ca07a9a70..a7be3acd68 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -219,8 +219,10 @@ export const createIGPConfig = callWithConfigCreationLogs( ); // Calculate storage gas oracle config + // TODO come back here const oracleConfig = getLocalStorageGasOracleConfig({ local: localChain, + localProtocolType: context.multiProvider.getProtocol(localChain), gasOracleParams: prices, exchangeRateMarginPct, }); From f348e2abfe0750332ba9fd5b227fbf25769cff3f Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 17:03:24 +0000 Subject: [PATCH 18/23] rm old configs --- .../gas-oracle-configs-eclipsemainnet.json | 29 ------------------- .../gas-oracle-configs-solanamainnet.json | 20 ------------- .../soon/gas-oracle-configs-soon.json | 20 ------------- 3 files changed, 69 deletions(-) delete mode 100644 rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json delete mode 100644 rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json delete mode 100644 rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json diff --git a/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json b/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json deleted file mode 100644 index 22fb0da678..0000000000 --- a/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - { - "domain": 1, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "15000000000000000000", - "gasPrice": "10000000000", - "tokenDecimals": 18 - } - }, - { - "domain": 1399811149, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "887000000000000000", - "gasPrice": "20", - "tokenDecimals": 9 - } - }, - { - "domain": 745, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "435246388284187", - "gasPrice": "7", - "tokenDecimals": 6 - } - } -] diff --git a/rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json b/rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json deleted file mode 100644 index 463990b543..0000000000 --- a/rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "domain": 1408864445, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "25036363636360000000", - "gasPrice": "2", - "tokenDecimals": 9 - } - }, - { - "domain": 50075007, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "25036363636360000000", - "gasPrice": "2", - "tokenDecimals": 9 - } - } -] diff --git a/rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json b/rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json deleted file mode 100644 index 4e0dad08ce..0000000000 --- a/rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "domain": 1, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "15000000000000000000", - "gasPrice": "10000000000", - "tokenDecimals": 18 - } - }, - { - "domain": 1399811149, - "gasOracle": { - "type": "remoteGasData", - "tokenExchangeRate": "887000000000000000", - "gasPrice": "20", - "tokenDecimals": 9 - } - } -] From 5ad7621a465d9b41d7e62f270185f8fd2d8a9f9d Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 17:04:41 +0000 Subject: [PATCH 19/23] rm the rust changes for now --- rust/main/utils/run-locally/src/solana.rs | 78 ++--- rust/sealevel/client/src/core.rs | 93 +++++- rust/sealevel/client/src/igp.rs | 268 +++++++----------- rust/sealevel/client/src/main.rs | 23 +- .../local-e2e/gas-oracle-configs.json | 34 ++- .../environments/local-e2e/overheads.json | 10 + .../environments/mainnet3/chain-config.json | 236 +-------------- .../gas-oracle-configs-eclipsemainnet.json | 29 ++ .../gas-oracle-configs-solanamainnet.json | 20 ++ .../soon/gas-oracle-configs-soon.json | 20 ++ .../testnet4/gas-oracle-configs.json | 58 ++-- 11 files changed, 361 insertions(+), 508 deletions(-) create mode 100644 rust/sealevel/environments/local-e2e/overheads.json create mode 100644 rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json create mode 100644 rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json create mode 100644 rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json diff --git a/rust/main/utils/run-locally/src/solana.rs b/rust/main/utils/run-locally/src/solana.rs index b6d0506082..3e32524cdd 100644 --- a/rust/main/utils/run-locally/src/solana.rs +++ b/rust/main/utils/run-locally/src/solana.rs @@ -75,6 +75,7 @@ pub const SOLANA_CHECKPOINT_LOCATION: &str = const SOLANA_GAS_ORACLE_CONFIG_FILE: &str = "../sealevel/environments/local-e2e/gas-oracle-configs.json"; +const SOLANA_OVERHEAD_CONFIG_FILE: &str = "../sealevel/environments/local-e2e/overheads.json"; // Install the CLI tools and return the path to the bin dir. #[apply(as_task)] @@ -232,44 +233,23 @@ pub fn start_solana_test_validator( .cmd("deploy") .arg("environment", SOLANA_ENV_NAME) .arg("environments-dir", SOLANA_ENVS_DIR) - .arg("built-so-dir", SBF_OUT_PATH); + .arg("built-so-dir", SBF_OUT_PATH) + .arg("overhead-config-file", SOLANA_OVERHEAD_CONFIG_FILE); - // Deploy sealeveltest1 core sealevel_client_deploy_core .clone() .arg("local-domain", SOLANA_LOCAL_CHAIN_ID) + .arg( + "remote-domains", + [SOLANA_REMOTE_CHAIN_ID, "9913371", "9913372", "9913373"].join(","), + ) .arg("chain", "sealeveltest1") .run() .join(); - // Deploy sealeveltest2 core sealevel_client_deploy_core .arg("local-domain", SOLANA_REMOTE_CHAIN_ID) - .arg("chain", "sealeveltest2") - .run() - .join(); - - const SEALEVETEST1_IGP_PROGRAM_ID: &str = "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U"; - const SEALEVETEST2_IGP_PROGRAM_ID: &str = "FArd4tEikwz2fk3MB7S9kC82NGhkgT6f9aXi3C5cw1E5"; - - let sealevel_client_deploy_core = sealevel_client - .clone() - .cmd("igp") - .cmd("configure") - .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) - .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE); - - // Configure sealeveltest1 IGP - sealevel_client_deploy_core - .clone() - .arg("program-id", SEALEVETEST1_IGP_PROGRAM_ID) - .arg("chain", "sealeveltest1") - .run() - .join(); - - // Configure sealeveltest2 IGP - sealevel_client_deploy_core - .arg("program-id", SEALEVETEST2_IGP_PROGRAM_ID) + .arg("remote-domains", SOLANA_LOCAL_CHAIN_ID) .arg("chain", "sealeveltest2") .run() .join(); @@ -314,48 +294,40 @@ pub fn start_solana_test_validator( .run() .join(); - // Deterministic due to checked-in keys for e2e - const IGP_PROGRAM_ID: &str = "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U"; - // So we can test paying for gas with a different IGP account - const ALTERNATIVE_SALT: &str = - "0x0000000000000000000000000000000000000000000000000000000000000001"; - const ALTERNATIVE_IGP_ACCOUNT: &str = "8EniU8dQaGQ3HWWtT77V7hrksheygvEu6TtzJ3pX1nKM"; - sealevel_client .clone() .cmd("igp") .cmd("init-igp-account") - .arg("program-id", IGP_PROGRAM_ID) + .arg("program-id", "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U") .arg("environment", SOLANA_ENV_NAME) .arg("environments-dir", SOLANA_ENVS_DIR) .arg("chain", "sealeveltest1") - // .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE) - // .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) - .arg("account-salt", ALTERNATIVE_SALT) + .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE) + .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) + .arg( + "account-salt", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) .run() .join(); sealevel_client - .clone() .cmd("igp") .cmd("init-overhead-igp-account") - .arg("program-id", IGP_PROGRAM_ID) + .arg("program-id", "GwHaw8ewMyzZn9vvrZEnTEAAYpLdkGYs195XWcLDCN4U") .arg("environment", SOLANA_ENV_NAME) .arg("environments-dir", SOLANA_ENVS_DIR) .arg("chain", "sealeveltest1") - .arg("inner-igp-account", ALTERNATIVE_IGP_ACCOUNT) - .arg("account-salt", ALTERNATIVE_SALT) - .run() - .join(); - - sealevel_client - .cmd("igp") - .cmd("configure") - .arg("program-id", IGP_PROGRAM_ID) - .arg("gas-oracle-config-file", SOLANA_GAS_ORACLE_CONFIG_FILE) .arg("chain-config-file", SOLANA_CHAIN_CONFIG_FILE) - .arg("chain", "sealeveltest1") - .arg("account-salt", ALTERNATIVE_SALT) + .arg("overhead-config-file", SOLANA_OVERHEAD_CONFIG_FILE) + .arg( + "inner-igp-account", + "8EniU8dQaGQ3HWWtT77V7hrksheygvEu6TtzJ3pX1nKM", + ) + .arg( + "account-salt", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) .run() .join(); diff --git a/rust/sealevel/client/src/core.rs b/rust/sealevel/client/src/core.rs index 44e615ba89..72ecfcb5ca 100644 --- a/rust/sealevel/client/src/core.rs +++ b/rust/sealevel/client/src/core.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; use solana_sdk::{compute_budget, compute_budget::ComputeBudgetInstruction}; +use std::collections::HashMap; use std::{fs::File, path::Path}; use crate::cmd_utils::get_compute_unit_price_micro_lamports_for_chain_name; @@ -16,6 +17,7 @@ use crate::{ Context, CoreCmd, CoreDeploy, CoreSubCmd, }; use hyperlane_core::H256; +use hyperlane_sealevel_igp::accounts::{SOL_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE}; pub(crate) fn adjust_gas_price_if_needed(chain_name: &str, ctx: &mut Context) { if chain_name.eq("solanamainnet") { @@ -204,10 +206,56 @@ fn deploy_validator_announce( program_id } -// Deploys the IGP program and initializes the zero salt IGP and overhead IGP accounts. -// Configuration of gas oracles is expected to be done separately. #[allow(clippy::too_many_arguments)] fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, Pubkey, Pubkey) { + use hyperlane_sealevel_igp::{ + accounts::{GasOracle, RemoteGasData}, + instruction::{GasOracleConfig, GasOverheadConfig}, + }; + + let mut gas_oracle_configs = core + .gas_oracle_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open oracle config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse oracle config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.domain != core.local_domain) + .map(|c| (c.domain, c)) + .collect::>(); + for &remote in &core.remote_domains { + gas_oracle_configs + .entry(remote) + .or_insert_with(|| GasOracleConfig { + domain: remote, + gas_oracle: Some(GasOracle::RemoteGasData(RemoteGasData { + token_exchange_rate: TOKEN_EXCHANGE_RATE_SCALE, + gas_price: 1, + token_decimals: SOL_DECIMALS, + })), + }); + } + let gas_oracle_configs = gas_oracle_configs.into_values().collect::>(); + + let overhead_configs = core + .overhead_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open overhead config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse overhead config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.destination_domain != core.local_domain) + .map(|c| (c.destination_domain, c)) + .collect::>() // dedup + .into_values() + .collect::>(); + let program_id = deploy_program( ctx.payer_keypair_path(), key_dir, @@ -271,6 +319,47 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, println!("Initialized overhead IGP account {}", overhead_igp_account); + if !gas_oracle_configs.is_empty() { + let domains = gas_oracle_configs + .iter() + .map(|c| c.domain) + .collect::>(); + let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + program_id, + igp_account, + ctx.payer_pubkey, + gas_oracle_configs, + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas oracle for remote domains {domains:?}",); + } else { + println!("Skipping settings gas oracle config"); + } + + if !overhead_configs.is_empty() { + let domains = overhead_configs + .iter() + .map(|c| c.destination_domain) + .collect::>(); + + let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( + program_id, + overhead_igp_account, + ctx.payer_pubkey, + overhead_configs, + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas overheads for remote domains {domains:?}",) + } else { + println!("Skipping setting gas overheads"); + } + (program_id, overhead_igp_account, igp_account) } diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index 6847d81d36..60f51479d9 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -9,7 +9,11 @@ use crate::{ Context, GasOverheadSubCmd, GetSetCmd, IgpCmd, IgpSubCmd, }; -use std::{path::Path, str::FromStr}; +use std::{ + fs::File, + path::{Path, PathBuf}, + str::FromStr, +}; use solana_sdk::{ pubkey::Pubkey, @@ -27,14 +31,6 @@ use hyperlane_sealevel_igp::{ instruction::{GasOracleConfig, GasOverheadConfig}, }; -#[derive(Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -/// Compatible with the format of our TS-generated configs. -struct GasOracleConfigWithOverhead { - oracle_config: RemoteGasData, - overhead: Option, -} - #[derive(Debug, Serialize, Deserialize, Default)] struct IgpAccountsArtifacts { salt: H256, @@ -113,7 +109,16 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { }) .unwrap_or_else(|| get_context_salt(init.context.as_ref())); - let igp_account = init_igp_account(&mut ctx, init.program_id, salt); + let chain_configs = + read_json::>(&init.chain_config_file); + + let igp_account = init_and_configure_igp_account( + &mut ctx, + init.program_id, + chain_configs.get(&init.chain).unwrap().domain_id(), + salt, + init.gas_oracle_config_file, + ); let artifacts = IgpAccountsArtifacts { salt, @@ -150,8 +155,17 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { }) .unwrap_or_else(|| get_context_salt(init.context.as_ref())); - let overhead_igp_account = - init_overhead_igp_account(&mut ctx, init.program_id, init.inner_igp_account, salt); + let chain_configs = + read_json::>(&init.chain_config_file); + + let overhead_igp_account = init_and_configure_overhead_igp_account( + &mut ctx, + init.program_id, + init.inner_igp_account, + chain_configs.get(&init.chain).unwrap().domain_id(), + salt, + init.overhead_config_file, + ); let artifacts = IgpAccountsArtifacts { salt, @@ -426,16 +440,6 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { ) .send_with_payer(); } - IgpSubCmd::Configure(args) => { - configure_igp_and_overhead_igp( - &mut ctx, - args.program_id, - args.chain, - &args.gas_oracle_config_file, - &args.chain_config_file, - args.account_salt, - ); - } } } @@ -481,7 +485,25 @@ fn deploy_igp_program( program_id } -fn init_igp_account(ctx: &mut Context, program_id: Pubkey, salt: H256) -> Pubkey { +fn init_and_configure_igp_account( + ctx: &mut Context, + program_id: Pubkey, + local_domain: u32, + salt: H256, + gas_oracle_config_file: Option, +) -> Pubkey { + let gas_oracle_configs = gas_oracle_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open oracle config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse oracle config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.domain != local_domain) + .collect::>(); + // Initialize IGP with the given salt let (igp_account_pda, _igp_account_bump) = Pubkey::find_program_address(hyperlane_sealevel_igp::igp_pda_seeds!(salt), &program_id); @@ -515,15 +537,54 @@ fn init_igp_account(ctx: &mut Context, program_id: Pubkey, salt: H256) -> Pubkey ); } + if !gas_oracle_configs.is_empty() { + // TODO: idempotency + + let domains = gas_oracle_configs + .iter() + .map(|c| c.domain) + .collect::>(); + let instruction = hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( + program_id, + igp_account_pda, + ctx.payer_pubkey, + gas_oracle_configs, + ) + .unwrap(); + + ctx.new_txn().add(instruction).send_with_payer(); + + println!("Set gas oracle for remote domains {domains:?}",); + } else { + println!("Skipping settings gas oracle config"); + } + igp_account_pda } -fn init_overhead_igp_account( +fn init_and_configure_overhead_igp_account( ctx: &mut Context, program_id: Pubkey, inner_igp_account: Pubkey, + local_domain: u32, salt: H256, + overhead_config_file: Option, ) -> Pubkey { + let overhead_configs = overhead_config_file + .as_deref() + .map(|p| { + let file = File::open(p).expect("Failed to open overhead config file"); + serde_json::from_reader::<_, Vec>(file) + .expect("Failed to parse overhead config file") + }) + .unwrap_or_default() + .into_iter() + .filter(|c| c.destination_domain != local_domain) + .map(|c| (c.destination_domain, c)) + .collect::>() // dedup + .into_values() + .collect::>(); + let (overhead_igp_account, _) = Pubkey::find_program_address( hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), &program_id, @@ -558,151 +619,28 @@ fn init_overhead_igp_account( ); } - overhead_igp_account -} - -/// Idempotently applies gas oracles to the IGP account and overheads to the Overhead IGP -/// account relating to the IGP account salt. -fn configure_igp_and_overhead_igp( - ctx: &mut Context, - program_id: Pubkey, - local_chain: String, - gas_oracle_config_file: &Path, - chain_config_path: &Path, - account_salt: Option, -) { - let chain_configs = read_json::>(chain_config_path); - - let gas_oracle_configs = read_json::< - HashMap>, - >(gas_oracle_config_file); - let gas_oracle_config = gas_oracle_configs.get(&local_chain).unwrap(); + if !overhead_configs.is_empty() { + // TODO: idempotency - let salt = account_salt.unwrap_or_else(H256::zero); + let domains = overhead_configs + .iter() + .map(|c| c.destination_domain) + .collect::>(); - let (igp_account_pubkey, _bump) = - Pubkey::find_program_address(hyperlane_sealevel_igp::igp_pda_seeds!(salt), &program_id); - let igp_account = ctx - .client - .get_account_with_commitment(&igp_account_pubkey, ctx.commitment) - .unwrap() - .value - .expect("IGP account not found. Make sure you are connected to the right RPC."); - let igp_account = IgpAccount::fetch(&mut &igp_account.data[..]) - .unwrap() - .into_inner(); - - let (overhead_igp_account_pubkey, _bump) = Pubkey::find_program_address( - hyperlane_sealevel_igp::overhead_igp_pda_seeds!(salt), - &program_id, - ); - let overhead_igp_account = ctx - .client - .get_account_with_commitment(&overhead_igp_account_pubkey, ctx.commitment) - .unwrap() - .value - .expect("Overhead IGP account not found. Make sure you are connected to the right RPC."); - let overhead_igp_account = OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) - .unwrap() - .into_inner(); - - // Set IGP configurations - println!( - "Setting IGP configurations for IGP account {} and overhead IGP account {}", - igp_account_pubkey, overhead_igp_account_pubkey - ); - - for (remote, config) in gas_oracle_config.iter() { - let remote_domain = chain_configs.get(remote).unwrap().domain_id(); - let gas_oracle_config = GasOracleConfig { - domain: remote_domain, - gas_oracle: Some(GasOracle::RemoteGasData(config.oracle_config.clone())), - }; - - // Gas oracle on the IGP account - if !map_configuration_matches( - &igp_account.gas_oracles, - &remote, - remote_domain, - gas_oracle_config.gas_oracle.as_ref(), - ) { - println!( - "Setting gas oracle for remote domain {:?} ({:?}) with config {:?}", - remote, remote_domain, gas_oracle_config - ); - // For simplicity and to always be well within max tx sizes, just send one config at a time - let instruction = - hyperlane_sealevel_igp::instruction::set_gas_oracle_configs_instruction( - program_id, - igp_account_pubkey, - ctx.payer_pubkey, - vec![gas_oracle_config], - ) - .unwrap(); - - ctx.new_txn().add(instruction).send_with_payer(); - - println!( - "Set gas oracle for remote domain {:?} ({:?})", - remote, remote_domain - ); - } - - // Overhead on the Overhead IGP account - if !map_configuration_matches( - &overhead_igp_account.gas_overheads, - &remote, - remote_domain, - config.overhead.as_ref(), - ) { - let overhead_config = GasOverheadConfig { - destination_domain: remote_domain, - gas_overhead: config.overhead, - }; - println!( - "Setting gas overhead for remote domain {:?} ({:?}) with config {:?}", - remote, remote_domain, overhead_config - ); - // For simplicity and to always be well within max tx sizes, just send one config at a time - let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( - program_id, - overhead_igp_account_pubkey, - ctx.payer_pubkey, - vec![overhead_config], - ) - .unwrap(); - - ctx.new_txn().add(instruction).send_with_payer(); + let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( + program_id, + overhead_igp_account, + ctx.payer_pubkey, + overhead_configs, + ) + .unwrap(); - println!( - "Set gas overhead for remote domain {:?} ({:?})", - remote, remote_domain - ); - } - } -} + ctx.new_txn().add(instruction).send_with_payer(); -fn map_configuration_matches( - existing_map: &HashMap, - remote: &String, - remote_domain: u32, - new_config: Option<&T>, -) -> bool -where - T: PartialEq + std::fmt::Debug, -{ - let existing_config = existing_map.get(&remote_domain); - if existing_config == new_config { - println!( - "Configuration for remote domain {:?} ({:?}) matches expected config: {:?}", - remote, remote_domain, new_config - ); - true + println!("Set gas overheads for remote domains {domains:?}",) } else { - println!( - "Configuration for remote domain {:?} ({:?}) does not match expected config. Current value: {:?}, expected value: {:?}", - remote, remote_domain, existing_config, new_config - ); - false + println!("Skipping setting gas overheads"); } + + overhead_igp_account } diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index eda64e9cb1..c9ea55b548 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -404,7 +404,6 @@ enum IgpSubCmd { DestinationGasOverhead(DestinationGasOverheadArgs), TransferIgpOwnership(TransferIgpOwnership), TransferOverheadIgpOwnership(TransferIgpOwnership), - Configure(ConfigureIgpArgs), } #[derive(Args)] @@ -426,8 +425,12 @@ struct InitIgpAccountArgs { #[arg(long)] chain: String, #[arg(long)] + chain_config_file: PathBuf, + #[arg(long)] context: Option, #[arg(long)] + gas_oracle_config_file: Option, + #[arg(long)] account_salt: Option, // optional salt for deterministic account creation } @@ -440,10 +443,14 @@ struct InitOverheadIgpAccountArgs { #[arg(long)] chain: String, #[arg(long)] + chain_config_file: PathBuf, + #[arg(long)] inner_igp_account: Pubkey, #[arg(long)] context: Option, #[arg(long)] + overhead_config_file: Option, + #[arg(long)] account_salt: Option, // optional salt for deterministic account creation } @@ -548,20 +555,6 @@ struct SetGasOverheadArgs { gas_overhead: u64, } -#[derive(Args)] -struct ConfigureIgpArgs { - #[arg(long)] - program_id: Pubkey, - #[arg(long)] - chain: String, - #[arg(long)] - gas_oracle_config_file: PathBuf, - #[arg(long)] - chain_config_file: PathBuf, - #[arg(long)] - account_salt: Option, -} - #[derive(Args)] struct ValidatorAnnounceCmd { #[command(subcommand)] diff --git a/rust/sealevel/environments/local-e2e/gas-oracle-configs.json b/rust/sealevel/environments/local-e2e/gas-oracle-configs.json index 896508f142..1d69e4c3bf 100644 --- a/rust/sealevel/environments/local-e2e/gas-oracle-configs.json +++ b/rust/sealevel/environments/local-e2e/gas-oracle-configs.json @@ -1,22 +1,20 @@ -{ - "sealeveltest1": { - "sealeveltest2": { - "oracleConfig": { - "tokenExchangeRate": "1000000000000000000", - "gasPrice": "1", - "tokenDecimals": 9 - }, - "overhead": 100000 +[ + { + "domain": 13375, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "0", + "tokenDecimals": 18 } }, - "sealeveltest2": { - "sealeveltest1": { - "oracleConfig": { - "tokenExchangeRate": "1000000000000000000", - "gasPrice": "1", - "tokenDecimals": 9 - }, - "overhead": 100000 + { + "domain": 13376, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "0", + "tokenDecimals": 18 } } -} +] diff --git a/rust/sealevel/environments/local-e2e/overheads.json b/rust/sealevel/environments/local-e2e/overheads.json new file mode 100644 index 0000000000..653fbf55f2 --- /dev/null +++ b/rust/sealevel/environments/local-e2e/overheads.json @@ -0,0 +1,10 @@ +[ + { + "destinationDomain": 13375, + "gasOverhead": 10000 + }, + { + "destinationDomain": 13376, + "gasOverhead": 10000 + } +] diff --git a/rust/sealevel/environments/mainnet3/chain-config.json b/rust/sealevel/environments/mainnet3/chain-config.json index e3abbd89c6..0688d72632 100644 --- a/rust/sealevel/environments/mainnet3/chain-config.json +++ b/rust/sealevel/environments/mainnet3/chain-config.json @@ -243,45 +243,6 @@ ], "technicalStack": "arbitrumnitro" }, - "artela": { - "blockExplorers": [ - { - "apiUrl": "https://artscan.artela.network/api", - "family": "blockscout", - "name": "Artela Explorer", - "url": "https://artscan.artela.network" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 5 - }, - "chainId": 11820, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Artela", - "domainId": 11820, - "gasCurrencyCoinGeckoId": "artela", - "name": "artela", - "nativeToken": { - "decimals": 18, - "name": "Artela", - "symbol": "ART" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://node-euro.artela.network/rpc" - }, - { - "http": "https://node-hongkong.artela.network/rpc" - } - ], - "technicalStack": "other" - }, "arthera": { "blockExplorers": [ { @@ -1660,7 +1621,6 @@ "displayName": "Form", "domainId": 478, "gasCurrencyCoinGeckoId": "ethereum", - "gnosisSafeTransactionServiceUrl": "https://prod.form.keypersafe.xyz/", "name": "form", "nativeToken": { "decimals": 18, @@ -1842,42 +1802,6 @@ ], "technicalStack": "arbitrumnitro" }, - "guru": { - "blockExplorers": [ - { - "apiUrl": "https://blockscout.gurunetwork.ai/api", - "family": "blockscout", - "name": "Guru Explorer", - "url": "https://blockscout.gurunetwork.ai" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 5 - }, - "chainId": 260, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Guru Network", - "domainId": 260, - "gasCurrencyCoinGeckoId": "guru-network", - "name": "guru", - "nativeToken": { - "decimals": 18, - "name": "Guru Network", - "symbol": "GURU" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.gurunetwork.ai/archive/260" - } - ], - "technicalStack": "opstack" - }, "harmony": { "blockExplorers": [ { @@ -1923,42 +1847,6 @@ ], "technicalStack": "other" }, - "hemi": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.hemi.xyz/api", - "family": "blockscout", - "name": "Hemi Explorer", - "url": "https://explorer.hemi.xyz" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 12, - "reorgPeriod": 5 - }, - "chainId": 43111, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Hemi Network", - "domainId": 43111, - "gasCurrencyCoinGeckoId": "ethereum", - "name": "hemi", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.hemi.network/rpc" - } - ], - "technicalStack": "other" - }, "immutablezkevmmainnet": { "blockExplorers": [ { @@ -2067,7 +1955,6 @@ "displayName": "Ink", "domainId": 57073, "gasCurrencyCoinGeckoId": "ethereum", - "gnosisSafeTransactionServiceUrl": "https://safe-transaction-ink.safe.global/", "name": "ink", "nativeToken": { "decimals": 18, @@ -2134,9 +2021,6 @@ } ], "rpcUrls": [ - { - "http": "https://injective-rpc.publicnode.com:443" - }, { "http": "https://sentry.tm.injective.network:443" } @@ -2808,47 +2692,6 @@ ], "technicalStack": "other" }, - "nero": { - "blockExplorers": [ - { - "apiUrl": "https://api.neroscan.io/api", - "family": "etherscan", - "name": "Neroscan", - "url": "https://www.neroscan.io" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 3, - "reorgPeriod": 5 - }, - "chainId": 1689, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Nero", - "domainId": 1689, - "gasCurrencyCoinGeckoId": "nerochain", - "gnosisSafeTransactionServiceUrl": "https://multisign.nerochain.io/txs/", - "name": "nero", - "nativeToken": { - "decimals": 18, - "name": "Nero", - "symbol": "NERO" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.nerochain.io" - } - ], - "technicalStack": "other", - "transactionOverrides": { - "maxFeePerGas": 10000000000, - "maxPriorityFeePerGas": 1000000000 - } - }, "neutron": { "bech32Prefix": "neutron", "blockExplorers": [ @@ -3731,10 +3574,10 @@ "soneium": { "blockExplorers": [ { - "apiUrl": "https://soneium.blockscout.com/api", + "apiUrl": "https://explorer.soneium.org/api", "family": "blockscout", "name": "Soneium Explorer", - "url": "https://soneium.blockscout.com" + "url": "https://explorer.soneium.org" } ], "blocks": { @@ -4122,45 +3965,6 @@ ], "technicalStack": "other" }, - "torus": { - "blockExplorers": [ - { - "apiUrl": "https://api.blockscout.torus.network/api", - "family": "blockscout", - "name": "Torus Explorer", - "url": "https://blockscout.torus.network" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 8, - "reorgPeriod": "finalized" - }, - "chainId": 21000, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Torus", - "domainId": 21000, - "gasCurrencyCoinGeckoId": "torus", - "name": "torus", - "nativeToken": { - "decimals": 18, - "name": "Torus", - "symbol": "TORUS" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://api-hyperlane.nodes.torus.network" - }, - { - "http": "https://api.torus.network" - } - ], - "technicalStack": "polkadotsubstrate" - }, "treasure": { "blockExplorers": [ { @@ -4424,42 +4228,6 @@ ], "technicalStack": "polygoncdk" }, - "xpla": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.xpla.io/mainnet/api", - "family": "other", - "name": "XPLA Explorer", - "url": "https://explorer.xpla.io/mainnet" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 6, - "reorgPeriod": 5 - }, - "chainId": 37, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "XPLA", - "domainId": 37, - "gasCurrencyCoinGeckoId": "xpla", - "name": "xpla", - "nativeToken": { - "decimals": 18, - "name": "XPLA", - "symbol": "XPLA" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://dimension-evm-rpc.xpla.dev" - } - ], - "technicalStack": "other" - }, "zeronetwork": { "blockExplorers": [ { diff --git a/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json b/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json new file mode 100644 index 0000000000..22fb0da678 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/eclipsemainnet/gas-oracle-configs-eclipsemainnet.json @@ -0,0 +1,29 @@ +[ + { + "domain": 1, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "15000000000000000000", + "gasPrice": "10000000000", + "tokenDecimals": 18 + } + }, + { + "domain": 1399811149, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "887000000000000000", + "gasPrice": "20", + "tokenDecimals": 9 + } + }, + { + "domain": 745, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "435246388284187", + "gasPrice": "7", + "tokenDecimals": 6 + } + } +] diff --git a/rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json b/rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json new file mode 100644 index 0000000000..463990b543 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/solanamainnet/gas-oracle-configs-solanamainnet.json @@ -0,0 +1,20 @@ +[ + { + "domain": 1408864445, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "25036363636360000000", + "gasPrice": "2", + "tokenDecimals": 9 + } + }, + { + "domain": 50075007, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "25036363636360000000", + "gasPrice": "2", + "tokenDecimals": 9 + } + } +] diff --git a/rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json b/rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json new file mode 100644 index 0000000000..4e0dad08ce --- /dev/null +++ b/rust/sealevel/environments/mainnet3/soon/gas-oracle-configs-soon.json @@ -0,0 +1,20 @@ +[ + { + "domain": 1, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "15000000000000000000", + "gasPrice": "10000000000", + "tokenDecimals": 18 + } + }, + { + "domain": 1399811149, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "887000000000000000", + "gasPrice": "20", + "tokenDecimals": 9 + } + } +] diff --git a/rust/sealevel/environments/testnet4/gas-oracle-configs.json b/rust/sealevel/environments/testnet4/gas-oracle-configs.json index c56c311b79..cadbe1ad78 100644 --- a/rust/sealevel/environments/testnet4/gas-oracle-configs.json +++ b/rust/sealevel/environments/testnet4/gas-oracle-configs.json @@ -1,22 +1,38 @@ -{ - "solanatestnet": { - "sonicsvmtestnet": { - "oracleConfig": { - "tokenExchangeRate": "1500000000000000", - "gasPrice": "1000", - "tokenDecimals": 9 - }, - "overhead": 600000 +[ + { + "domain": 11155111, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "15000000000", + "tokenDecimals": 18 + } + }, + { + "domain": 1399811150, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "28", + "tokenDecimals": 9 + } + }, + { + "domain": 239092742, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "28", + "tokenDecimals": 9 + } + }, + { + "domain": 15153042, + "gasOracle": { + "type": "remoteGasData", + "tokenExchangeRate": "10000000000000000000", + "gasPrice": "28", + "tokenDecimals": 9 + } } - }, - "sonicsvmtestnet": { - "solanatestnet": { - "oracleConfig": { - "tokenExchangeRate": "1500000000000000", - "gasPrice": "1000", - "tokenDecimals": 9 - }, - "overhead": 600000 - } - } -} +] \ No newline at end of file From eb531916a1d6d213f444376e580ab017628d51b9 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 17:06:35 +0000 Subject: [PATCH 20/23] rm gas oracle config for now --- .../mainnet3/gas-oracle-configs.json | 80 ------------------- 1 file changed, 80 deletions(-) delete mode 100644 rust/sealevel/environments/mainnet3/gas-oracle-configs.json diff --git a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json deleted file mode 100644 index 5525e6e74f..0000000000 --- a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "solanamainnet": { - "artela": { - "oracleConfig": { - "tokenExchangeRate": "83333333333333333", - "gasPrice": "614759390835", - "tokenDecimals": 18 - }, - "overhead": 166887 - }, - "base": { - "oracleConfig": { - "tokenExchangeRate": "263105000000000000000", - "gasPrice": "486782274", - "tokenDecimals": 18 - }, - "overhead": 166887 - }, - "ethereum": { - "oracleConfig": { - "tokenExchangeRate": "263105000000000000000", - "gasPrice": "20047740244", - "tokenDecimals": 18 - }, - "overhead": 166887 - }, - "eclipsemainnet": { - "oracleConfig": { - "tokenExchangeRate": "26310500000000000", - "gasPrice": "1625", - "tokenDecimals": 9 - }, - "overhead": 600000 - }, - "soon": { - "oracleConfig": { - "tokenExchangeRate": "26310500000000000", - "gasPrice": "1625", - "tokenDecimals": 9 - }, - "overhead": 600000 - } - }, - "eclipsemainnet": { - "ethereum": { - "oracleConfig": { - "tokenExchangeRate": "15000000000000000000", - "gasPrice": "20047740244", - "tokenDecimals": 18 - }, - "overhead": 166460 - }, - "solanamainnet": { - "oracleConfig": { - "tokenExchangeRate": "85517188954979", - "gasPrice": "85470", - "tokenDecimals": 9 - }, - "overhead": 600000 - }, - "stride": { - "oracleConfig": { - "tokenExchangeRate": "235829801790", - "gasPrice": "4133", - "tokenDecimals": 6 - }, - "overhead": 600000 - } - }, - "soon": { - "solanamainnet": { - "oracleConfig": { - "tokenExchangeRate": "85517188954979", - "gasPrice": "85470", - "tokenDecimals": 9 - }, - "overhead": 600000 - } - } -} From f66ca53b26dec8898d4344f3baea88f5d04b5f19 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 17 Jan 2025 17:08:20 +0000 Subject: [PATCH 21/23] nits --- typescript/cli/src/config/hooks.ts | 1 - typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index a7be3acd68..bad95f5c3b 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -219,7 +219,6 @@ export const createIGPConfig = callWithConfigCreationLogs( ); // Calculate storage gas oracle config - // TODO come back here const oracleConfig = getLocalStorageGasOracleConfig({ local: localChain, localProtocolType: context.multiProvider.getProtocol(localChain), diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index b247a15cb6..66ef6eb912 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -18,6 +18,7 @@ import { getEnvironmentConfig } from '../core-utils.js'; // This script exists to print the gas oracle configs for a given environment // so they can easily be copied into the Sealevel tooling. :'( + interface GasOracleConfigWithOverhead { oracleConfig: ProtocolAgnositicGasOracleConfig; overhead?: number; From 6f22ec529c59ff256ed7a565a9a0047defbf9ccf Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Mon, 20 Jan 2025 14:46:41 +0000 Subject: [PATCH 22/23] pr comments --- typescript/infra/scripts/agent-utils.ts | 2 +- .../sealevel-helpers/print-gas-oracles.ts | 40 +++++++++++++------ .../warp-routes/generate-warp-config.ts | 4 +- typescript/infra/src/config/gas-oracle.ts | 4 +- typescript/sdk/src/hook/EvmHookReader.ts | 6 +-- typescript/sdk/src/warp/WarpCore.ts | 6 +-- typescript/utils/src/amount.ts | 2 +- typescript/utils/src/index.ts | 2 +- 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 889d6875ad..2b0a788b2b 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -177,7 +177,7 @@ export function withChainsRequired( return withChains(args, chainOptions).demandOption('chains'); } -export function withOutFile(args: Argv) { +export function withOutputFile(args: Argv) { return args .describe('outFile', 'output file') .string('outFile') diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index 66ef6eb912..212cc5062e 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -12,8 +12,9 @@ import { import { WarpRouteIds } from '../../config/environments/mainnet3/warp/warpIds.js'; import { getChain, getWarpAddresses } from '../../config/registry.js'; +import { DeployEnvironment } from '../../src/config/environment.js'; import { writeJsonAtPath } from '../../src/utils/utils.js'; -import { getArgs, withOutFile } from '../agent-utils.js'; +import { getArgs, withOutputFile } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; // This script exists to print the gas oracle configs for a given environment @@ -25,11 +26,11 @@ interface GasOracleConfigWithOverhead { } async function main() { - const { environment, outFile } = await withOutFile(getArgs()).argv; + const { environment, outFile } = await withOutputFile(getArgs()).argv; const environmentConfig = getEnvironmentConfig(environment); - const allConnectedChains = getChainConnections(); + const allConnectedChains = getChainConnections(environment); // Construct a nested map of origin -> destination -> { oracleConfig, overhead } let gasOracles = objMap(environmentConfig.igp, (origin, igpConfig) => { @@ -79,20 +80,33 @@ async function main() { } } +// Gets the chains in the provided warp route +function getWarpChains(warpRouteId: string): ChainName[] { + const warpRouteAddresses = getWarpAddresses(warpRouteId); + return Object.keys(warpRouteAddresses); +} + // Because there is a limit to how many chains we want to figure in an SVM IGP, // we limit the chains to only those that are connected via warp routes. // Returns a record of origin chain -> set of chains that are connected via warp routes. -function getChainConnections(): ChainMap> { +function getChainConnections( + environment: DeployEnvironment, +): ChainMap> { // A list of connected chains - const connectedChains = [ - // Hardcoded connections - ['sonicsvmtestnet', 'solanatestnet'], - // All known warp routes - ...Object.values(WarpRouteIds).map((warpRouteId) => { - const warpRouteAddresses = getWarpAddresses(warpRouteId); - return Object.keys(warpRouteAddresses); - }), - ]; + let connectedChains = []; + + if (environment === 'mainnet3') { + // All the mainnet3 warp route chains + connectedChains = Object.values(WarpRouteIds).map(getWarpChains); + } else if (environment === 'testnet4') { + connectedChains = [ + // As testnet warp routes are not tracked well, hardcode the connected chains. + // For SOL/solanatestnet-sonicsvmtestnet + ['solanatestnet', 'sonicsvmtestnet'], + ]; + } else { + throw new Error(`Unknown environment: ${environment}`); + } return connectedChains.reduce((agg, chains) => { // Make sure each chain is connected to every other chain diff --git a/typescript/infra/scripts/warp-routes/generate-warp-config.ts b/typescript/infra/scripts/warp-routes/generate-warp-config.ts index 2ecb1b56f9..47a39c16d8 100644 --- a/typescript/infra/scripts/warp-routes/generate-warp-config.ts +++ b/typescript/infra/scripts/warp-routes/generate-warp-config.ts @@ -6,13 +6,13 @@ import { getWarpConfig } from '../../config/warp.js'; import { writeYamlAtPath } from '../../src/utils/utils.js'; import { getArgs, - withOutFile, + withOutputFile, withWarpRouteIdRequired, } from '../agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from '../core-utils.js'; async function main() { - const { warpRouteId, environment, outFile } = await withOutFile( + const { warpRouteId, environment, outFile } = await withOutputFile( withWarpRouteIdRequired(getArgs()), ).argv; diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index d4b2a9de2d..edef2c4ba6 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -18,7 +18,7 @@ import { ProtocolType, assert, convertDecimals, - convertDecimalsIntegerString, + convertDecimalsToIntegerString, fromWei, toWei, } from '@hyperlane-xyz/utils'; @@ -257,8 +257,6 @@ export function getAllStorageGasOracleConfigs( getOverhead: (local: ChainName, remote: ChainName) => number, applyMinUsdCost: boolean = true, ): AllStorageGasOracleConfigs { - // return chainNames.filter(isEthereumProtocolChain). - return chainNames .filter((chain) => { // For now, only support Ethereum and Sealevel chains. diff --git a/typescript/sdk/src/hook/EvmHookReader.ts b/typescript/sdk/src/hook/EvmHookReader.ts index 92b42ac7f1..151d3d6f44 100644 --- a/typescript/sdk/src/hook/EvmHookReader.ts +++ b/typescript/sdk/src/hook/EvmHookReader.ts @@ -251,8 +251,8 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { this.concurrency, domainIds, async (domainId) => { - const chainMetadata = this.multiProvider.getChainMetadata(domainId); - const chainName = chainMetadata.name; + const { name: chainName, nativeToken } = + this.multiProvider.getChainMetadata(domainId); try { const { tokenExchangeRate, gasPrice } = await hook.getExchangeRateAndGasPrice(domainId); @@ -262,7 +262,7 @@ export class EvmHookReader extends HyperlaneReader implements HookReader { oracleConfig[chainName] = { tokenExchangeRate: tokenExchangeRate.toString(), gasPrice: gasPrice.toString(), - tokenDecimals: chainMetadata?.nativeToken?.decimals, + tokenDecimals: nativeToken?.decimals, }; const { gasOracle } = await hook.destinationGasConfigs(domainId); diff --git a/typescript/sdk/src/warp/WarpCore.ts b/typescript/sdk/src/warp/WarpCore.ts index 368db1a22f..544fa24b8c 100644 --- a/typescript/sdk/src/warp/WarpCore.ts +++ b/typescript/sdk/src/warp/WarpCore.ts @@ -5,7 +5,7 @@ import { HexString, ProtocolType, assert, - convertDecimalsIntegerString, + convertDecimalsToIntegerString, convertToProtocolAddress, isValidAddress, isZeroishAddress, @@ -502,7 +502,7 @@ export class WarpCore { ); } - const destinationBalanceInOriginDecimals = convertDecimalsIntegerString( + const destinationBalanceInOriginDecimals = convertDecimalsToIntegerString( destinationToken.decimals, originToken.decimals, destinationBalance.toString(), @@ -679,7 +679,7 @@ export class WarpCore { // Convert the minDestinationTransferAmount to an origin amount const minOriginTransferAmount = destinationToken.amount( - convertDecimalsIntegerString( + convertDecimalsToIntegerString( originToken.decimals, destinationToken.decimals, minDestinationTransferAmount.toString(), diff --git a/typescript/utils/src/amount.ts b/typescript/utils/src/amount.ts index 742dd127c4..2642ef1f73 100644 --- a/typescript/utils/src/amount.ts +++ b/typescript/utils/src/amount.ts @@ -111,7 +111,7 @@ export function eqAmountApproximate( * @param value The value to convert. * @returns `value` represented with `toDecimals` decimals in string type. */ -export function convertDecimalsIntegerString( +export function convertDecimalsToIntegerString( fromDecimals: number, toDecimals: number, value: BigNumber.Value, diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index 4c7f5724db..85b6d406e0 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -43,7 +43,7 @@ export { export { addBufferToGasLimit, convertDecimals, - convertDecimalsIntegerString, + convertDecimalsToIntegerString, eqAmountApproximate, fromWei, fromWeiRounded, From e72bf6cec47ff0b526768464af8649b0fba23467 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Mon, 20 Jan 2025 14:47:27 +0000 Subject: [PATCH 23/23] nit --- typescript/sdk/src/gas/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index 20772c916f..021e9c3d43 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -253,7 +253,7 @@ function adjustForPrecisionLoss( // the gas price that will be set on-chain. If this is the case, we scale up the // gas price and scale down the exchange rate by the same factor. if (newGasPrice.lt(10) && newGasPrice.mod(1) !== new BigNumberJs(0)) { - // Scale up the gas price by 1e4 (arbirtary choice) + // Scale up the gas price by 1e4 (arbitrary choice) const gasPriceScalingFactor = 1e4; // Check that there's no significant underflow when applying