Merge remote-tracking branch 'hl/main' into pb/zksync

pull/4882/head
pbio 2 weeks ago
commit 1a5aae1d6d
  1. 5
      .changeset/cold-dingos-give.md
  2. 6
      .changeset/dirty-swans-drum.md
  3. 5
      .changeset/fresh-pigs-work.md
  4. 11
      .changeset/plenty-pens-peel.md
  5. 2
      .changeset/shy-taxis-suffer.md
  6. 5
      .changeset/silent-berries-attend.md
  7. 5
      .changeset/sixty-eggs-smoke.md
  8. 5
      .changeset/sweet-houses-type.md
  9. 5
      .changeset/thin-tips-explain.md
  10. 5
      .changeset/tidy-meals-add.md
  11. 2
      .github/actions/yarn-build-with-cache/action.yml
  12. 4
      .github/workflows/static-analysis.yml
  13. 4
      .github/workflows/storage-analysis.yml
  14. 6
      .github/workflows/test.yml
  15. 2
      .registryrc
  16. 893
      .yarn/releases/yarn-4.0.2.cjs
  17. 934
      .yarn/releases/yarn-4.5.1.cjs
  18. 2
      .yarnrc.yml
  19. 2
      Dockerfile
  20. 2
      package.json
  21. 1
      rust/main/Cargo.lock
  22. 77
      rust/main/agents/relayer/src/relayer.rs
  23. 4
      rust/main/agents/scraper/migration/bin/generate_entities.rs
  24. 9
      rust/main/agents/scraper/migration/src/m20230309_000003_create_table_transaction.rs
  25. 3
      rust/main/agents/scraper/src/db/generated/transaction.rs
  26. 1
      rust/main/agents/scraper/src/db/txn.rs
  27. 32
      rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs
  28. 4
      rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs
  29. 13
      rust/main/chains/hyperlane-cosmos/src/trait_builder.rs
  30. 7
      rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs
  31. 1
      rust/main/chains/hyperlane-fuel/src/provider.rs
  32. 68
      rust/main/chains/hyperlane-sealevel/src/account.rs
  33. 27
      rust/main/chains/hyperlane-sealevel/src/error.rs
  34. 114
      rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs
  35. 3
      rust/main/chains/hyperlane-sealevel/src/lib.rs
  36. 231
      rust/main/chains/hyperlane-sealevel/src/mailbox.rs
  37. 151
      rust/main/chains/hyperlane-sealevel/src/provider.rs
  38. 40
      rust/main/chains/hyperlane-sealevel/src/rpc/client.rs
  39. 4
      rust/main/chains/hyperlane-sealevel/src/trait_builder.rs
  40. 188
      rust/main/chains/hyperlane-sealevel/src/transaction.rs
  41. 329
      rust/main/chains/hyperlane-sealevel/src/transaction/tests.rs
  42. 33
      rust/main/chains/hyperlane-sealevel/src/utils.rs
  43. 134
      rust/main/config/mainnet_config.json
  44. 1
      rust/main/hyperlane-base/Cargo.toml
  45. 7
      rust/main/hyperlane-base/src/agent.rs
  46. 88
      rust/main/hyperlane-base/src/metrics/agent_metrics.rs
  47. 70
      rust/main/hyperlane-base/src/settings/parser/connection_parser.rs
  48. 154
      rust/main/hyperlane-base/src/types/gcs_storage.rs
  49. 2
      rust/main/hyperlane-core/src/types/chain_data.rs
  50. 2
      rust/main/hyperlane-core/src/types/mod.rs
  51. 8
      rust/main/hyperlane-core/src/types/native_token.rs
  52. 13
      rust/main/hyperlane-core/src/utils.rs
  53. 4
      rust/main/utils/run-locally/src/cosmos/types.rs
  54. 22
      rust/sealevel/client/src/warp_route.rs
  55. 542
      rust/sealevel/environments/mainnet3/chain-config.json
  56. 149
      rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json
  57. 10
      rust/sealevel/environments/mainnet3/warp-routes/ORCA-eclipse-solana/program-ids.json
  58. 17
      rust/sealevel/environments/mainnet3/warp-routes/ORCA-eclipse-solana/token-config.json
  59. 14
      rust/sealevel/environments/mainnet3/warp-routes/USDT-eclipse-ethereum-solana/program-ids.json
  60. 23
      rust/sealevel/environments/mainnet3/warp-routes/USDT-eclipse-ethereum-solana/token-config.json
  61. 10
      rust/sealevel/environments/mainnet3/warp-routes/WBTC-eclipse-ethereum-solana/program-ids.json
  62. 16
      rust/sealevel/environments/mainnet3/warp-routes/WBTC-eclipse-ethereum-solana/token-config.json
  63. 10
      rust/sealevel/environments/mainnet3/warp-routes/weETHs-eclipsemainnet-ethereum/program-ids.json
  64. 17
      rust/sealevel/environments/mainnet3/warp-routes/weETHs-eclipsemainnet-ethereum/token-config.json
  65. 23
      solidity/CHANGELOG.md
  66. 1
      solidity/contracts/Mailbox.sol
  67. 2
      solidity/contracts/PackageVersioned.sol
  68. 35
      solidity/contracts/hooks/ArbL2ToL1Hook.sol
  69. 35
      solidity/contracts/hooks/OPL2ToL1Hook.sol
  70. 15
      solidity/contracts/hooks/OPStackHook.sol
  71. 13
      solidity/contracts/hooks/PolygonPosHook.sol
  72. 16
      solidity/contracts/hooks/aggregation/ERC5164Hook.sol
  73. 14
      solidity/contracts/hooks/layer-zero/LayerZeroV2Hook.sol
  74. 30
      solidity/contracts/hooks/libs/AbstractMessageIdAuthHook.sol
  75. 21
      solidity/contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol
  76. 13
      solidity/contracts/isms/hook/ArbL2ToL1Ism.sol
  77. 2
      solidity/contracts/isms/hook/ERC5164Ism.sol
  78. 2
      solidity/contracts/isms/hook/OPStackIsm.sol
  79. 2
      solidity/contracts/isms/hook/PolygonPosIsm.sol
  80. 21
      solidity/contracts/isms/hook/layer-zero/LayerZeroV2Ism.sol
  81. 10
      solidity/contracts/libs/OPL2ToL1Metadata.sol
  82. 4
      solidity/contracts/mock/MockMailbox.sol
  83. 4
      solidity/package.json
  84. 84
      solidity/script/DeployArbHook.s.sol
  85. 10
      solidity/test/hooks/layerzero/LayerZeroV2Hook.t.sol
  86. 25
      solidity/test/isms/ArbL2ToL1Ism.t.sol
  87. 10
      solidity/test/isms/ERC5164ISM.t.sol
  88. 87
      solidity/test/isms/ExternalBridgeTest.sol
  89. 37
      solidity/test/isms/OPL2ToL1Ism.t.sol
  90. 20
      solidity/test/isms/OPStackIsm.t.sol
  91. 26
      solidity/test/isms/PolygonPosIsm.t.sol
  92. 6
      solidity/test/isms/layer-zero/LayerZeroV2Ism.t.sol
  93. 4
      typescript/ccip-server/CHANGELOG.md
  94. 2
      typescript/ccip-server/package.json
  95. 41
      typescript/cli/CHANGELOG.md
  96. 1
      typescript/cli/examples/submit/strategy/impersonated-account-strategy.yaml
  97. 2
      typescript/cli/examples/submit/strategy/json-rpc-chain-strategy.yaml
  98. 1
      typescript/cli/examples/submit/strategy/json-rpc-strategy.yaml
  99. 6
      typescript/cli/package.json
  100. 25
      typescript/cli/src/commands/warp.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': patch
---
Optimize HyperlaneRelayer routing config derivation

@ -1,6 +0,0 @@
---
'@hyperlane-xyz/utils': patch
'@hyperlane-xyz/sdk': patch
---
Dedupe internals of hook and ISM module deploy code

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to apechain, arbitrumnova, b3, fantom, gravity, harmony, kaia, morph, orderly, snaxchain, zeronetwork, zksync. Update default metadata in `HyperlaneCore` to `0x00001` to ensure empty metadata does not break on zksync.

@ -1,11 +0,0 @@
---
'@hyperlane-xyz/widgets': minor
---
Update widgets with components from explorer and warp ui
- Add icons: Discord, Docs, Github, History, LinkedIn, Medium, Twitter, Wallet and Web
- Add animation component: Fade component
- Add components: DatetimeField and SelectField
- New stories: IconList and Fade
- Add "Icon" suffix for icons that did not have it

@ -2,4 +2,4 @@
'@hyperlane-xyz/utils': patch
---
fix median utils func + add test
Added a mustGet helper

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/core': minor
---
disabled the ICARouter's ability to change hook given that the user doesn't expect the hook to change after they deploy their ICA account. Hook is not part of the derivation like ism on the destination chain and hence, cannot be configured custom by the user.

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Enable configuration of IGP hooks in the CLI

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': patch
---
Fix ICA ISM self relay

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Introduce utils that can be reused by the CLI and Infra for fetching token prices from Coingecko and gas prices from EVM/Cosmos chains.

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/utils': patch
---
Filter undefined/null values in invertKeysAndValues function

@ -16,7 +16,7 @@ runs:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
# Typically, the cache will be hit, but if there's a network error when
# restoring the cache, let's run the install step ourselves.

@ -29,9 +29,9 @@ jobs:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-cache-
${{ runner.os }}-yarn-4.5.1.-cache-
- name: yarn-install
run: yarn install

@ -29,9 +29,9 @@ jobs:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-cache-
${{ runner.os }}-yarn-4.5.1-cache-
- name: yarn-install
run: yarn install

@ -40,9 +40,9 @@ jobs:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-cache-
${{ runner.os }}-yarn-4.5.1-cache-
- name: yarn-install
run: |
@ -70,7 +70,7 @@ jobs:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
key: ${{ runner.os }}-yarn-4.5.1-cache-${{ hashFiles('./yarn.lock') }}
fail-on-cache-miss: true
- name: lint

@ -1 +1 @@
302be4817c063629cec70c0b02322b250df71122
1ea2849e2fc1e750bac67e35827c9d682c7fd4bf

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -10,4 +10,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
spec: "https://mskelton.dev/yarn-outdated/v3"
yarnPath: .yarn/releases/yarn-4.0.2.cjs
yarnPath: .yarn/releases/yarn-4.5.1.cjs

@ -4,7 +4,7 @@ WORKDIR /hyperlane-monorepo
RUN apk add --update --no-cache git g++ make py3-pip jq
RUN yarn set version 4.0.2
RUN yarn set version 4.5.1
# Copy package.json and friends
COPY package.json yarn.lock .yarnrc.yml ./

@ -18,7 +18,7 @@
"dependencies": {
"@changesets/cli": "^2.26.2"
},
"packageManager": "yarn@4.0.2",
"packageManager": "yarn@4.5.1",
"private": true,
"scripts": {
"agent-configs": "yarn --cwd typescript/infra/ update-agent-config:mainnet3 && yarn --cwd typescript/infra/ update-agent-config:testnet4 && yarn prettier",

@ -4417,7 +4417,6 @@ dependencies = [
"console-subscriber",
"convert_case 0.6.0",
"derive-new",
"derive_builder",
"ed25519-dalek 1.0.1",
"ethers",
"ethers-prometheus",

@ -12,11 +12,12 @@ use hyperlane_base::{
broadcast::BroadcastMpscSender,
db::{HyperlaneRocksDB, DB},
metrics::{AgentMetrics, MetricsUpdater},
settings::ChainConf,
settings::{ChainConf, IndexSettings},
AgentMetadata, BaseAgent, ChainMetrics, ContractSyncMetrics, ContractSyncer, CoreMetrics,
HyperlaneAgentCore, SyncOptions,
};
use hyperlane_core::{
rpc_clients::call_and_retry_n_times, ChainCommunicationError, ContractSyncCursor,
HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, MerkleTreeInsertion, QueueOperation,
H512, U256,
};
@ -50,6 +51,9 @@ use crate::{
};
use crate::{processor::Processor, server::ENDPOINT_MESSAGES_QUEUE_SIZE};
const CURSOR_BUILDING_ERROR: &str = "Error building cursor for origin";
const CURSOR_INSTANTIATION_ATTEMPTS: usize = 10;
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
struct ContextKey {
origin: u32,
@ -354,6 +358,7 @@ impl BaseAgent for Relayer {
}
for origin in &self.origin_chains {
self.chain_metrics.set_critical_error(origin.name(), false);
let maybe_broadcaster = self
.message_syncs
.get(origin)
@ -412,6 +417,34 @@ impl BaseAgent for Relayer {
}
impl Relayer {
fn record_critical_error(
&self,
origin: &HyperlaneDomain,
err: ChainCommunicationError,
message: &str,
) {
error!(?err, origin=?origin, "{message}");
self.chain_metrics.set_critical_error(origin.name(), true);
}
async fn instantiate_cursor_with_retries<T: 'static>(
contract_sync: Arc<dyn ContractSyncer<T>>,
index_settings: IndexSettings,
) -> Result<Box<dyn ContractSyncCursor<T>>, ChainCommunicationError> {
call_and_retry_n_times(
|| {
let contract_sync = contract_sync.clone();
let index_settings = index_settings.clone();
Box::pin(async move {
let cursor = contract_sync.cursor(index_settings).await?;
Ok(cursor)
})
},
CURSOR_INSTANTIATION_ATTEMPTS,
)
.await
}
async fn run_message_sync(
&self,
origin: &HyperlaneDomain,
@ -419,10 +452,16 @@ impl Relayer {
) -> Instrumented<JoinHandle<()>> {
let index_settings = self.as_ref().settings.chains[origin.name()].index_settings();
let contract_sync = self.message_syncs.get(origin).unwrap().clone();
let cursor = contract_sync
.cursor(index_settings)
.await
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}"));
let cursor_instantiation_result =
Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await;
let cursor = match cursor_instantiation_result {
Ok(cursor) => cursor,
Err(err) => {
self.record_critical_error(origin, err, CURSOR_BUILDING_ERROR);
return tokio::spawn(async {}).instrument(info_span!("MessageSync"));
}
};
tokio::spawn(TaskMonitor::instrument(&task_monitor, async move {
contract_sync
.clone()
@ -444,10 +483,16 @@ impl Relayer {
.get(origin)
.unwrap()
.clone();
let cursor = contract_sync
.cursor(index_settings)
.await
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}"));
let cursor_instantiation_result =
Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await;
let cursor = match cursor_instantiation_result {
Ok(cursor) => cursor,
Err(err) => {
self.record_critical_error(origin, err, CURSOR_BUILDING_ERROR);
return tokio::spawn(async {}).instrument(info_span!("IgpSync"));
}
};
tokio::spawn(TaskMonitor::instrument(&task_monitor, async move {
contract_sync
.clone()
@ -468,10 +513,16 @@ impl Relayer {
) -> Instrumented<JoinHandle<()>> {
let index_settings = self.as_ref().settings.chains[origin.name()].index.clone();
let contract_sync = self.merkle_tree_hook_syncs.get(origin).unwrap().clone();
let cursor = contract_sync
.cursor(index_settings)
.await
.unwrap_or_else(|err| panic!("Error getting cursor for origin {origin}: {err}"));
let cursor_instantiation_result =
Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone())
.await;
let cursor = match cursor_instantiation_result {
Ok(cursor) => cursor,
Err(err) => {
self.record_critical_error(origin, err, CURSOR_BUILDING_ERROR);
return tokio::spawn(async {}).instrument(info_span!("MerkleTreeHookSync"));
}
};
tokio::spawn(TaskMonitor::instrument(&task_monitor, async move {
contract_sync
.clone()

@ -57,8 +57,8 @@ impl Drop for PostgresDockerContainer {
async fn main() -> Result<(), DbErr> {
assert_eq!(
std::env::current_dir().unwrap().file_name().unwrap(),
"rust",
"Must run from the rust dir"
"main",
"Must run from the rust/main dir"
);
let postgres = PostgresDockerContainer::start();

@ -52,6 +52,13 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new_with_type(Transaction::Recipient, Address).borrow_mut())
.col(ColumnDef::new_with_type(Transaction::GasUsed, Wei).not_null())
.col(ColumnDef::new_with_type(Transaction::CumulativeGasUsed, Wei).not_null())
.col(
ColumnDef::new_with_type(
Transaction::RawInputData,
ColumnType::Binary(BlobSize::Blob(None)),
)
.borrow_mut(),
)
.foreign_key(
ForeignKey::create()
.from_col(Transaction::BlockId)
@ -128,4 +135,6 @@ pub enum Transaction {
GasUsed,
/// Cumulative gas used within the block after this was executed
CumulativeGasUsed,
/// Raw input data from Ethereum transaction
RawInputData,
}

@ -27,6 +27,7 @@ pub struct Model {
pub recipient: Option<Vec<u8>>,
pub gas_used: BigDecimal,
pub cumulative_gas_used: BigDecimal,
pub raw_input_data: Option<Vec<u8>>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
@ -45,6 +46,7 @@ pub enum Column {
Recipient,
GasUsed,
CumulativeGasUsed,
RawInputData,
}
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
@ -85,6 +87,7 @@ impl ColumnTrait for Column {
Self::Recipient => ColumnType::Binary(BlobSize::Blob(None)).def().null(),
Self::GasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(),
Self::CumulativeGasUsed => ColumnType::Decimal(Some((78u32, 0u32))).def(),
Self::RawInputData => ColumnType::Binary(BlobSize::Blob(None)).def().null(),
}
}
}

@ -94,6 +94,7 @@ impl ScraperDb {
recipient: Set(txn.recipient.as_ref().map(address_to_bytes)),
max_fee_per_gas: Set(txn.max_fee_per_gas.map(u256_to_decimal)),
cumulative_gas_used: Set(u256_to_decimal(receipt.cumulative_gas_used)),
raw_input_data: Set(txn.raw_input_data.clone()),
})
})
.collect::<Result<Vec<_>>>()?;

@ -18,9 +18,10 @@ use tracing::{error, warn};
use crypto::decompress_public_key;
use hyperlane_core::{
bytes_to_h512, h512_to_bytes, AccountAddressType, BlockInfo, ChainCommunicationError,
ChainInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneProvider,
HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, H512, U256,
bytes_to_h512, h512_to_bytes, utils::to_atto, AccountAddressType, BlockInfo,
ChainCommunicationError, ChainInfo, ChainResult, ContractLocator, HyperlaneChain,
HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256,
H512, U256,
};
use crate::grpc::{WasmGrpcProvider, WasmProvider};
@ -33,9 +34,6 @@ use crate::{
mod parse;
/// Exponent value for atto units (10^-18).
const ATTO_EXPONENT: u32 = 18;
/// Injective public key type URL for protobuf Any
const INJECTIVE_PUBLIC_KEY_TYPE_URL: &str = "/injective.crypto.v1beta1.ethsecp256k1.PubKey";
@ -320,26 +318,25 @@ impl CosmosProvider {
/// `OSMO` and it will keep fees expressed in `inj` as is.
///
/// If fees are expressed in an unsupported denomination, they will be ignored.
fn convert_fee(&self, coin: &Coin) -> U256 {
fn convert_fee(&self, coin: &Coin) -> ChainResult<U256> {
let native_token = self.connection_conf.get_native_token();
if coin.denom.as_ref() != native_token.denom {
return U256::zero();
return Ok(U256::zero());
}
let exponent = ATTO_EXPONENT - native_token.decimals;
let coefficient = U256::from(10u128.pow(exponent));
let amount_in_native_denom = U256::from(coin.amount);
amount_in_native_denom * coefficient
to_atto(amount_in_native_denom, native_token.decimals).ok_or(
ChainCommunicationError::CustomError("Overflow in calculating fees".to_owned()),
)
}
fn calculate_gas_price(&self, hash: &H256, tx: &Tx) -> U256 {
fn calculate_gas_price(&self, hash: &H256, tx: &Tx) -> ChainResult<U256> {
// TODO support multiple denominations for amount
let supported = self.report_unsupported_denominations(tx, hash);
if supported.is_err() {
return U256::max_value();
return Ok(U256::max_value());
}
let gas_limit = U256::from(tx.auth_info.fee.gas_limit);
@ -349,13 +346,13 @@ impl CosmosProvider {
.amount
.iter()
.map(|c| self.convert_fee(c))
.fold(U256::zero(), |acc, v| acc + v);
.fold_ok(U256::zero(), |acc, v| acc + v)?;
if fee < gas_limit {
warn!(tx_hash = ?hash, ?fee, ?gas_limit, "calculated fee is less than gas limit. it will result in zero gas price");
}
fee / gas_limit
Ok(fee / gas_limit)
}
}
@ -425,7 +422,7 @@ impl HyperlaneProvider for CosmosProvider {
let contract = Self::contract(&tx, &hash)?;
let (sender, nonce) = self.sender_and_nonce(&tx)?;
let gas_price = self.calculate_gas_price(&hash, &tx);
let gas_price = self.calculate_gas_price(&hash, &tx)?;
let tx_info = TxnInfo {
hash: hash.into(),
@ -441,6 +438,7 @@ impl HyperlaneProvider for CosmosProvider {
cumulative_gas_used: U256::from(response.tx_result.gas_used),
effective_gas_price: Some(gas_price),
}),
raw_input_data: None,
};
Ok(tx_info)

@ -3,10 +3,10 @@ use std::str::FromStr;
use url::Url;
use hyperlane_core::config::OperationBatchConfig;
use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain};
use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain, NativeToken};
use crate::grpc::{WasmGrpcProvider, WasmProvider};
use crate::{ConnectionConf, CosmosAddress, CosmosAmount, NativeToken, RawCosmosAmount};
use crate::{ConnectionConf, CosmosAddress, CosmosAmount, RawCosmosAmount};
#[ignore]
#[tokio::test]

@ -3,7 +3,9 @@ use std::str::FromStr;
use derive_new::new;
use url::Url;
use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, FixedPointNumber};
use hyperlane_core::{
config::OperationBatchConfig, ChainCommunicationError, FixedPointNumber, NativeToken,
};
/// Cosmos connection configuration
#[derive(Debug, Clone)]
@ -60,15 +62,6 @@ impl TryFrom<RawCosmosAmount> for CosmosAmount {
}
}
/// Chain native token denomination and number of decimal places
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct NativeToken {
/// The number of decimal places in token which can be expressed by denomination
pub decimals: u32,
/// Denomination of the token
pub denom: String,
}
/// An error type when parsing a connection configuration.
#[derive(thiserror::Error, Debug)]
pub enum ConnectionConfError {

@ -106,7 +106,7 @@ where
})
.transpose()?;
Ok(TxnInfo {
let txn_info = TxnInfo {
hash: *hash,
max_fee_per_gas: txn.max_fee_per_gas.map(Into::into),
max_priority_fee_per_gas: txn.max_priority_fee_per_gas.map(Into::into),
@ -116,7 +116,10 @@ where
sender: txn.from.into(),
recipient: txn.to.map(Into::into),
receipt,
})
raw_input_data: Some(txn.input.to_vec()),
};
Ok(txn_info)
}
#[instrument(err, skip(self))]

@ -381,6 +381,7 @@ impl HyperlaneProvider for FuelProvider {
gas_price: Some(gas_price.into()),
recipient,
receipt: None,
raw_input_data: None,
})
}
None => Err(ChainCommunicationError::CustomError(format!(

@ -0,0 +1,68 @@
use base64::{engine::general_purpose::STANDARD as Base64, Engine};
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
use solana_client::{
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
};
use solana_sdk::{account::Account, commitment_config::CommitmentConfig, pubkey::Pubkey};
use hyperlane_core::{ChainCommunicationError, ChainResult};
use crate::rpc::SealevelRpcClient;
pub async fn search_accounts_by_discriminator(
client: &SealevelRpcClient,
program_id: &Pubkey,
discriminator: &[u8; 8],
nonce_bytes: &[u8],
offset: usize,
length: usize,
) -> ChainResult<Vec<(Pubkey, Account)>> {
let target_message_account_bytes = &[discriminator, nonce_bytes].concat();
let target_message_account_bytes = Base64.encode(target_message_account_bytes);
// First, find all accounts with the matching account data.
// To keep responses small in case there is ever more than 1
// match, we don't request the full account data, and just request
// the field which was used to generate account id
#[allow(deprecated)]
let memcmp = RpcFilterType::Memcmp(Memcmp {
// Ignore the first byte, which is the `initialized` bool flag.
offset: 1,
bytes: MemcmpEncodedBytes::Base64(target_message_account_bytes),
encoding: None,
});
let config = RpcProgramAccountsConfig {
filters: Some(vec![memcmp]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: Some(UiDataSliceConfig { offset, length }),
commitment: Some(CommitmentConfig::finalized()),
min_context_slot: None,
},
with_context: Some(false),
};
let accounts = client
.get_program_accounts_with_config(program_id, config)
.await?;
Ok(accounts)
}
pub fn search_and_validate_account<F>(
accounts: Vec<(Pubkey, Account)>,
message_account: F,
) -> ChainResult<Pubkey>
where
F: Fn(&Account) -> ChainResult<Pubkey>,
{
for (pubkey, account) in accounts {
let expected_pubkey = message_account(&account)?;
if expected_pubkey == pubkey {
return Ok(pubkey);
}
}
Err(ChainCommunicationError::from_other_str(
"Could not find valid storage PDA pubkey",
))
}

@ -1,6 +1,7 @@
use hyperlane_core::ChainCommunicationError;
use hyperlane_core::{ChainCommunicationError, H512};
use solana_client::client_error::ClientError;
use solana_sdk::pubkey::ParsePubkeyError;
use solana_transaction_status::{EncodedTransaction, UiMessage};
/// Errors from the crates specific to the hyperlane-sealevel
/// implementation.
@ -17,6 +18,30 @@ pub enum HyperlaneSealevelError {
/// Decoding error
#[error("{0}")]
Decoding(#[from] solana_sdk::bs58::decode::Error),
/// No transaction in block error
#[error("{0}")]
NoTransactions(String),
/// Too many transactions of particular content in block
#[error("{0}")]
TooManyTransactions(String),
/// Unsupported transaction encoding
#[error("{0:?}")]
UnsupportedTransactionEncoding(EncodedTransaction),
/// Unsupported message encoding
#[error("{0:?}")]
UnsupportedMessageEncoding(UiMessage),
/// Unsigned transaction
#[error("{0}")]
UnsignedTransaction(H512),
/// Incorrect transaction
#[error("received incorrect transaction, expected hash: {0:?}, received hash: {1:?}")]
IncorrectTransaction(Box<H512>, Box<H512>),
/// Empty metadata
#[error("received empty metadata in transaction")]
EmptyMetadata,
/// Empty compute units consumed
#[error("received empty compute units consumed in transaction")]
EmptyComputeUnitsConsumed,
}
impl From<HyperlaneSealevelError> for ChainCommunicationError {

@ -1,25 +1,22 @@
use std::ops::RangeInclusive;
use async_trait::async_trait;
use hyperlane_core::{
config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator,
HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer,
InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512,
};
use derive_new::new;
use hyperlane_sealevel_igp::{
accounts::{GasPaymentAccount, ProgramDataAccount},
igp_gas_payment_pda_seeds, igp_program_data_pda_seeds,
};
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
use solana_client::{
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType},
};
use std::ops::RangeInclusive;
use solana_sdk::{account::Account, pubkey::Pubkey};
use tracing::{info, instrument};
use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient};
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
use hyperlane_core::{
config::StrOrIntParseError, ChainCommunicationError, ChainResult, ContractLocator,
HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer,
InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512,
};
use derive_new::new;
use crate::account::{search_accounts_by_discriminator, search_and_validate_account};
use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient};
/// The offset to get the `unique_gas_payment_pubkey` field from the serialized GasPaymentData.
/// The account data includes prefixes that are accounted for here: a 1 byte initialized flag
@ -121,70 +118,23 @@ impl SealevelInterchainGasPaymasterIndexer {
&self,
sequence_number: u64,
) -> ChainResult<SealevelGasPayment> {
let payment_bytes = &[
&hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR[..],
&sequence_number.to_le_bytes()[..],
]
.concat();
#[allow(deprecated)]
let payment_bytes: String = base64::encode(payment_bytes);
// First, find all accounts with the matching gas payment data.
// To keep responses small in case there is ever more than 1
// match, we don't request the full account data, and just request
// the `unique_gas_payment_pubkey` field.
#[allow(deprecated)]
let memcmp = RpcFilterType::Memcmp(Memcmp {
// Ignore the first byte, which is the `initialized` bool flag.
offset: 1,
bytes: MemcmpEncodedBytes::Base64(payment_bytes),
encoding: None,
});
let config = RpcProgramAccountsConfig {
filters: Some(vec![memcmp]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
// Don't return any data
data_slice: Some(UiDataSliceConfig {
offset: UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET,
length: 32, // the length of the `unique_gas_payment_pubkey` field
}),
commitment: Some(CommitmentConfig::finalized()),
min_context_slot: None,
},
with_context: Some(false),
};
tracing::debug!(config=?config, "Fetching program accounts");
let accounts = self
.rpc_client
.get_program_accounts_with_config(&self.igp.program_id, config)
.await?;
let discriminator = hyperlane_sealevel_igp::accounts::GAS_PAYMENT_DISCRIMINATOR;
let sequence_number_bytes = sequence_number.to_le_bytes();
let unique_gas_payment_pubkey_length = 32; // the length of the `unique_gas_payment_pubkey` field
let accounts = search_accounts_by_discriminator(
&self.rpc_client,
&self.igp.program_id,
discriminator,
&sequence_number_bytes,
UNIQUE_GAS_PAYMENT_PUBKEY_OFFSET,
unique_gas_payment_pubkey_length,
)
.await?;
tracing::debug!(accounts=?accounts, "Fetched program accounts");
// Now loop through matching accounts and find the one with a valid account pubkey
// that proves it's an actual gas payment PDA.
let mut valid_payment_pda_pubkey = Option::<Pubkey>::None;
for (pubkey, account) in accounts {
let unique_gas_payment_pubkey = Pubkey::new(&account.data);
let (expected_pubkey, _bump) = Pubkey::try_find_program_address(
igp_gas_payment_pda_seeds!(unique_gas_payment_pubkey),
&self.igp.program_id,
)
.ok_or_else(|| {
ChainCommunicationError::from_other_str(
"Could not find program address for unique_gas_payment_pubkey",
)
})?;
if expected_pubkey == pubkey {
valid_payment_pda_pubkey = Some(pubkey);
break;
}
}
let valid_payment_pda_pubkey = valid_payment_pda_pubkey.ok_or_else(|| {
ChainCommunicationError::from_other_str("Could not find valid gas payment PDA pubkey")
let valid_payment_pda_pubkey = search_and_validate_account(accounts, |account| {
self.interchain_payment_account(account)
})?;
// Now that we have the valid gas payment PDA pubkey, we can get the full account data.
@ -224,6 +174,20 @@ impl SealevelInterchainGasPaymasterIndexer {
H256::from(gas_payment_account.igp.to_bytes()),
))
}
fn interchain_payment_account(&self, account: &Account) -> ChainResult<Pubkey> {
let unique_gas_payment_pubkey = Pubkey::new(&account.data);
let (expected_pubkey, _bump) = Pubkey::try_find_program_address(
igp_gas_payment_pda_seeds!(unique_gas_payment_pubkey),
&self.igp.program_id,
)
.ok_or_else(|| {
ChainCommunicationError::from_other_str(
"Could not find program address for unique_gas_payment_pubkey",
)
})?;
Ok(expected_pubkey)
}
}
#[async_trait]

@ -15,6 +15,7 @@ pub use solana_sdk::signer::keypair::Keypair;
pub use trait_builder::*;
pub use validator_announce::*;
mod account;
mod error;
mod interchain_gas;
mod interchain_security_module;
@ -24,4 +25,6 @@ mod multisig_ism;
mod provider;
mod rpc;
mod trait_builder;
mod transaction;
mod utils;
mod validator_announce;

@ -4,22 +4,15 @@ use std::{collections::HashMap, num::NonZeroU64, ops::RangeInclusive, str::FromS
use async_trait::async_trait;
use borsh::{BorshDeserialize, BorshSerialize};
use jsonrpc_core::futures_util::TryFutureExt;
use tracing::{debug, info, instrument, warn};
use hyperlane_core::{
accumulator::incremental::IncrementalMerkle, BatchItem, ChainCommunicationError,
ChainCommunicationError::ContractError, ChainResult, Checkpoint, ContractLocator, Decode as _,
Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, KnownHyperlaneDomain,
LogMeta, Mailbox, MerkleTreeHook, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome,
H256, H512, U256,
};
use hyperlane_sealevel_interchain_security_module_interface::{
InterchainSecurityModuleInstruction, VerifyInstruction,
};
use hyperlane_sealevel_mailbox::{
accounts::{DispatchedMessageAccount, InboxAccount, OutboxAccount},
accounts::{
DispatchedMessageAccount, InboxAccount, OutboxAccount, ProcessedMessage,
ProcessedMessageAccount, DISPATCHED_MESSAGE_DISCRIMINATOR, PROCESSED_MESSAGE_DISCRIMINATOR,
},
instruction,
instruction::InboxProcess,
mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds,
mailbox_process_authority_pda_seeds, mailbox_processed_message_pda_seeds,
@ -27,6 +20,7 @@ use hyperlane_sealevel_mailbox::{
use hyperlane_sealevel_message_recipient_interface::{
HandleInstruction, MessageRecipientInstruction,
};
use jsonrpc_core::futures_util::TryFutureExt;
use serializable_account_meta::SimulationReturnData;
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
use solana_client::{
@ -51,10 +45,24 @@ use solana_sdk::{
};
use solana_transaction_status::{
EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, TransactionStatus,
UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction, UiReturnDataEncoding,
UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta,
UiCompiledInstruction, UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction,
UiReturnDataEncoding, UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta,
};
use tracing::{debug, info, instrument, warn};
use hyperlane_core::{
accumulator::incremental::IncrementalMerkle, BatchItem, ChainCommunicationError,
ChainCommunicationError::ContractError, ChainResult, Checkpoint, ContractLocator, Decode as _,
Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, KnownHyperlaneDomain,
LogMeta, Mailbox, MerkleTreeHook, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome,
H256, H512, U256,
};
use crate::account::{search_accounts_by_discriminator, search_and_validate_account};
use crate::error::HyperlaneSealevelError;
use crate::transaction::search_dispatched_message_transactions;
use crate::utils::{decode_h256, decode_h512, from_base58};
use crate::{ConnectionConf, SealevelProvider, SealevelRpcClient};
const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111";
@ -653,73 +661,26 @@ impl SealevelMailboxIndexer {
self.rpc().get_block_height().await
}
async fn get_message_with_nonce(
async fn get_dispatched_message_with_nonce(
&self,
nonce: u32,
) -> ChainResult<(Indexed<HyperlaneMessage>, LogMeta)> {
let target_message_account_bytes = &[
&hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR[..],
&nonce.to_le_bytes()[..],
]
.concat();
let target_message_account_bytes = base64::encode(target_message_account_bytes);
// First, find all accounts with the matching account data.
// To keep responses small in case there is ever more than 1
// match, we don't request the full account data, and just request
// the `unique_message_pubkey` field.
let memcmp = RpcFilterType::Memcmp(Memcmp {
// Ignore the first byte, which is the `initialized` bool flag.
offset: 1,
bytes: MemcmpEncodedBytes::Base64(target_message_account_bytes),
encoding: None,
});
let config = RpcProgramAccountsConfig {
filters: Some(vec![memcmp]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
// Don't return any data
data_slice: Some(UiDataSliceConfig {
offset: 1 + 8 + 4 + 8, // the offset to get the `unique_message_pubkey` field
length: 32, // the length of the `unique_message_pubkey` field
}),
commitment: Some(CommitmentConfig::finalized()),
min_context_slot: None,
},
with_context: Some(false),
};
let accounts = self
.rpc()
.get_program_accounts_with_config(&self.mailbox.program_id, config)
.await?;
// Now loop through matching accounts and find the one with a valid account pubkey
// that proves it's an actual message storage PDA.
let mut valid_message_storage_pda_pubkey = Option::<Pubkey>::None;
for (pubkey, account) in accounts {
let unique_message_pubkey = Pubkey::new(&account.data);
let (expected_pubkey, _bump) = Pubkey::try_find_program_address(
mailbox_dispatched_message_pda_seeds!(unique_message_pubkey),
&self.mailbox.program_id,
)
.ok_or_else(|| {
ChainCommunicationError::from_other_str(
"Could not find program address for unique_message_pubkey",
)
})?;
if expected_pubkey == pubkey {
valid_message_storage_pda_pubkey = Some(pubkey);
break;
}
}
let nonce_bytes = nonce.to_le_bytes();
let unique_dispatched_message_pubkey_offset = 1 + 8 + 4 + 8; // the offset to get the `unique_message_pubkey` field
let unique_dispatch_message_pubkey_length = 32; // the length of the `unique_message_pubkey` field
let accounts = search_accounts_by_discriminator(
self.rpc(),
&self.program_id,
&DISPATCHED_MESSAGE_DISCRIMINATOR,
&nonce_bytes,
unique_dispatched_message_pubkey_offset,
unique_dispatch_message_pubkey_length,
)
.await?;
let valid_message_storage_pda_pubkey =
valid_message_storage_pda_pubkey.ok_or_else(|| {
ChainCommunicationError::from_other_str(
"Could not find valid message storage PDA pubkey",
)
})?;
let valid_message_storage_pda_pubkey = search_and_validate_account(accounts, |account| {
self.dispatched_message_account(&account)
})?;
// Now that we have the valid message storage PDA pubkey, we can get the full account data.
let account = self
@ -733,11 +694,99 @@ impl SealevelMailboxIndexer {
let hyperlane_message =
HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?;
let block = self
.mailbox
.provider
.rpc()
.get_block(dispatched_message_account.slot)
.await?;
let block_hash = decode_h256(&block.blockhash)?;
let transactions =
block.transactions.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any transaction".to_owned()))?;
let transaction_hashes = search_dispatched_message_transactions(
&self.mailbox.program_id,
&valid_message_storage_pda_pubkey,
transactions,
);
// We expect to see that there is only one message dispatch transaction
if transaction_hashes.len() > 1 {
Err(HyperlaneSealevelError::TooManyTransactions("Block contains more than one dispatch message transaction operating on the same dispatch message store PDA".to_owned()))?
}
let (transaction_index, transaction_hash) = transaction_hashes
.into_iter()
.next()
.ok_or(HyperlaneSealevelError::NoTransactions("block which should contain message dispatch transaction does not contain any after filtering".to_owned()))?;
Ok((
hyperlane_message.into(),
LogMeta {
address: self.mailbox.program_id.to_bytes().into(),
block_number: dispatched_message_account.slot,
block_hash,
transaction_id: transaction_hash,
transaction_index: transaction_index as u64,
log_index: U256::from(nonce),
},
))
}
fn dispatched_message_account(&self, account: &Account) -> ChainResult<Pubkey> {
let unique_message_pubkey = Pubkey::new(&account.data);
let (expected_pubkey, _bump) = Pubkey::try_find_program_address(
mailbox_dispatched_message_pda_seeds!(unique_message_pubkey),
&self.mailbox.program_id,
)
.ok_or_else(|| {
ChainCommunicationError::from_other_str(
"Could not find program address for unique message pubkey",
)
})?;
Ok(expected_pubkey)
}
async fn get_delivered_message_with_nonce(
&self,
nonce: u32,
) -> ChainResult<(Indexed<H256>, LogMeta)> {
let nonce_bytes = nonce.to_le_bytes();
let delivered_message_id_offset = 1 + 8 + 8; // the offset to get the `message_id` field
let delivered_message_id_length = 32;
let accounts = search_accounts_by_discriminator(
self.rpc(),
&self.program_id,
&PROCESSED_MESSAGE_DISCRIMINATOR,
&nonce_bytes,
delivered_message_id_offset,
delivered_message_id_length,
)
.await?;
debug!(account_len = ?accounts.len(), "Found accounts with processed message discriminator");
let valid_message_storage_pda_pubkey = search_and_validate_account(accounts, |account| {
self.delivered_message_account(&account)
})?;
// Now that we have the valid delivered message storage PDA pubkey,
// we can get the full account data.
let account = self
.rpc()
.get_account_with_finalized_commitment(&valid_message_storage_pda_pubkey)
.await?;
let delivered_message_account = ProcessedMessageAccount::fetch(&mut account.data.as_ref())
.map_err(ChainCommunicationError::from_other)?
.into_inner();
let message_id = delivered_message_account.message_id;
Ok((
message_id.into(),
LogMeta {
address: self.mailbox.program_id.to_bytes().into(),
block_number: delivered_message_account.slot,
// TODO: get these when building out scraper support.
// It's inconvenient to get these :|
block_hash: H256::zero(),
@ -747,6 +796,18 @@ impl SealevelMailboxIndexer {
},
))
}
fn delivered_message_account(&self, account: &Account) -> ChainResult<Pubkey> {
let message_id = H256::from_slice(&account.data);
let (expected_pubkey, _bump) = Pubkey::try_find_program_address(
mailbox_processed_message_pda_seeds!(message_id),
&self.mailbox.program_id,
)
.ok_or_else(|| {
ChainCommunicationError::from_other_str("Could not find program address for message id")
})?;
Ok(expected_pubkey)
}
}
#[async_trait]
@ -774,7 +835,7 @@ impl Indexer<HyperlaneMessage> for SealevelMailboxIndexer {
let message_capacity = range.end().saturating_sub(*range.start());
let mut messages = Vec::with_capacity(message_capacity as usize);
for nonce in range {
messages.push(self.get_message_with_nonce(nonce).await?);
messages.push(self.get_dispatched_message_with_nonce(nonce).await?);
}
Ok(messages)
}
@ -788,9 +849,19 @@ impl Indexer<HyperlaneMessage> for SealevelMailboxIndexer {
impl Indexer<H256> for SealevelMailboxIndexer {
async fn fetch_logs_in_range(
&self,
_range: RangeInclusive<u32>,
range: RangeInclusive<u32>,
) -> ChainResult<Vec<(Indexed<H256>, LogMeta)>> {
todo!()
info!(
?range,
"Fetching SealevelMailboxIndexer HyperlaneMessage Delivery logs"
);
let message_capacity = range.end().saturating_sub(*range.start());
let mut message_ids = Vec::with_capacity(message_capacity as usize);
for nonce in range {
message_ids.push(self.get_delivered_message_with_nonce(nonce).await?);
}
Ok(message_ids)
}
async fn get_finalized_block_number(&self) -> ChainResult<u32> {

@ -1,20 +1,29 @@
use std::{str::FromStr, sync::Arc};
use std::sync::Arc;
use async_trait::async_trait;
use solana_sdk::signature::Signature;
use solana_transaction_status::{
option_serializer::OptionSerializer, EncodedTransaction, EncodedTransactionWithStatusMeta,
UiMessage, UiTransaction, UiTransactionStatusMeta,
};
use tracing::warn;
use hyperlane_core::{
BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider,
HyperlaneProviderError, TxnInfo, H256, H512, U256,
utils::to_atto, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, HyperlaneChain,
HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, NativeToken, TxnInfo,
TxnReceiptInfo, H256, H512, U256,
};
use solana_sdk::bs58;
use solana_sdk::pubkey::Pubkey;
use crate::{error::HyperlaneSealevelError, ConnectionConf, SealevelRpcClient};
use crate::error::HyperlaneSealevelError;
use crate::utils::{decode_h256, decode_h512, decode_pubkey};
use crate::{ConnectionConf, SealevelRpcClient};
/// A wrapper around a Sealevel provider to get generic blockchain information.
#[derive(Debug)]
pub struct SealevelProvider {
domain: HyperlaneDomain,
rpc_client: Arc<SealevelRpcClient>,
native_token: NativeToken,
}
impl SealevelProvider {
@ -22,14 +31,83 @@ impl SealevelProvider {
pub fn new(domain: HyperlaneDomain, conf: &ConnectionConf) -> Self {
// Set the `processed` commitment at rpc level
let rpc_client = Arc::new(SealevelRpcClient::new(conf.url.to_string()));
let native_token = conf.native_token.clone();
SealevelProvider { domain, rpc_client }
SealevelProvider {
domain,
rpc_client,
native_token,
}
}
/// Get an rpc client
pub fn rpc(&self) -> &SealevelRpcClient {
&self.rpc_client
}
fn validate_transaction(hash: &H512, txn: &UiTransaction) -> ChainResult<()> {
let received_signature = txn
.signatures
.first()
.ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?;
let received_hash = decode_h512(received_signature)?;
if &received_hash != hash {
Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::IncorrectTransaction(
Box::new(*hash),
Box::new(received_hash),
),
))?;
}
Ok(())
}
fn sender(hash: &H512, txn: &UiTransaction) -> ChainResult<H256> {
let message = match &txn.message {
UiMessage::Parsed(m) => m,
m => Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::UnsupportedMessageEncoding(m.clone()),
))?,
};
let signer = message
.account_keys
.first()
.ok_or(HyperlaneSealevelError::UnsignedTransaction(*hash))?;
let pubkey = decode_pubkey(&signer.pubkey)?;
let sender = H256::from_slice(&pubkey.to_bytes());
Ok(sender)
}
fn gas(meta: &UiTransactionStatusMeta) -> ChainResult<U256> {
let OptionSerializer::Some(gas) = meta.compute_units_consumed else {
Err(HyperlaneSealevelError::EmptyComputeUnitsConsumed)?
};
Ok(U256::from(gas))
}
/// Extracts and converts fees into atto (10^-18) units.
///
/// We convert fees into atto units since otherwise a compute unit price (gas price)
/// becomes smaller than 1 lamport (or 1 unit of native token) and the price is rounded
/// to zero. We normalise the gas price for all the chain to be expressed in atto units.
fn fee(&self, meta: &UiTransactionStatusMeta) -> ChainResult<U256> {
let amount_in_native_denom = U256::from(meta.fee);
to_atto(amount_in_native_denom, self.native_token.decimals).ok_or(
ChainCommunicationError::CustomError("Overflow in calculating fees".to_owned()),
)
}
fn meta(txn: &EncodedTransactionWithStatusMeta) -> ChainResult<&UiTransactionStatusMeta> {
let meta = txn
.meta
.as_ref()
.ok_or(HyperlaneSealevelError::EmptyMetadata)?;
Ok(meta)
}
}
impl HyperlaneChain for SealevelProvider {
@ -41,6 +119,7 @@ impl HyperlaneChain for SealevelProvider {
Box::new(SealevelProvider {
domain: self.domain.clone(),
rpc_client: self.rpc_client.clone(),
native_token: self.native_token.clone(),
})
}
}
@ -50,10 +129,7 @@ impl HyperlaneProvider for SealevelProvider {
async fn get_block_by_height(&self, slot: u64) -> ChainResult<BlockInfo> {
let confirmed_block = self.rpc_client.get_block(slot).await?;
let hash_binary = bs58::decode(confirmed_block.blockhash)
.into_vec()
.map_err(HyperlaneSealevelError::Decoding)?;
let block_hash = H256::from_slice(&hash_binary);
let block_hash = decode_h256(&confirmed_block.blockhash)?;
let block_time = confirmed_block
.block_time
@ -68,8 +144,55 @@ impl HyperlaneProvider for SealevelProvider {
Ok(block_info)
}
async fn get_txn_by_hash(&self, _hash: &H512) -> ChainResult<TxnInfo> {
todo!() // FIXME
/// TODO This method is superfluous for Solana.
/// Since we have to request full block to find transaction hash and transaction index
/// for Solana, we have all the data about transaction mach earlier before this
/// method is invoked.
/// We can refactor abstractions so that our chain-agnostic code is more suitable
/// for all chains, not only Ethereum-like chains.
async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult<TxnInfo> {
let signature = Signature::new(hash.as_bytes());
let txn_confirmed = self.rpc_client.get_transaction(&signature).await?;
let txn_with_meta = &txn_confirmed.transaction;
let txn = match &txn_with_meta.transaction {
EncodedTransaction::Json(t) => t,
t => Err(Into::<ChainCommunicationError>::into(
HyperlaneSealevelError::UnsupportedTransactionEncoding(t.clone()),
))?,
};
Self::validate_transaction(hash, txn)?;
let sender = Self::sender(hash, txn)?;
let meta = Self::meta(txn_with_meta)?;
let gas_used = Self::gas(meta)?;
let fee = self.fee(meta)?;
if fee < gas_used {
warn!(tx_hash = ?hash, ?fee, ?gas_used, "calculated fee is less than gas used. it will result in zero gas price");
}
let gas_price = Some(fee / gas_used);
let receipt = TxnReceiptInfo {
gas_used,
cumulative_gas_used: gas_used,
effective_gas_price: gas_price,
};
Ok(TxnInfo {
hash: *hash,
gas_limit: gas_used,
max_priority_fee_per_gas: None,
max_fee_per_gas: None,
gas_price,
nonce: 0,
sender,
recipient: None,
receipt: Some(receipt),
raw_input_data: None,
})
}
async fn is_contract(&self, _address: &H256) -> ChainResult<bool> {
@ -78,7 +201,7 @@ impl HyperlaneProvider for SealevelProvider {
}
async fn get_balance(&self, address: String) -> ChainResult<U256> {
let pubkey = Pubkey::from_str(&address).map_err(Into::<HyperlaneSealevelError>::into)?;
let pubkey = decode_pubkey(&address)?;
self.rpc_client.get_balance(&pubkey).await
}

@ -1,10 +1,9 @@
use base64::Engine;
use borsh::{BorshDeserialize, BorshSerialize};
use hyperlane_core::{ChainCommunicationError, ChainResult, U256};
use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData};
use solana_client::{
nonblocking::rpc_client::RpcClient, rpc_config::RpcBlockConfig,
rpc_config::RpcProgramAccountsConfig, rpc_response::Response,
rpc_config::RpcProgramAccountsConfig, rpc_config::RpcTransactionConfig, rpc_response::Response,
};
use solana_sdk::{
account::Account,
@ -17,9 +16,12 @@ use solana_sdk::{
transaction::Transaction,
};
use solana_transaction_status::{
TransactionStatus, UiConfirmedBlock, UiReturnDataEncoding, UiTransactionReturnData,
EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock,
UiReturnDataEncoding, UiTransactionEncoding, UiTransactionReturnData,
};
use hyperlane_core::{ChainCommunicationError, ChainResult, U256};
use crate::error::HyperlaneSealevelError;
pub struct SealevelRpcClient(RpcClient);
@ -99,6 +101,17 @@ impl SealevelRpcClient {
Ok(account)
}
pub async fn get_balance(&self, pubkey: &Pubkey) -> ChainResult<U256> {
let balance = self
.0
.get_balance(pubkey)
.await
.map_err(Into::<HyperlaneSealevelError>::into)
.map_err(ChainCommunicationError::from)?;
Ok(balance.into())
}
pub async fn get_block(&self, height: u64) -> ChainResult<UiConfirmedBlock> {
let config = RpcBlockConfig {
commitment: Some(CommitmentConfig::finalized()),
@ -170,15 +183,20 @@ impl SealevelRpcClient {
.map_err(ChainCommunicationError::from_other)
}
pub async fn get_balance(&self, pubkey: &Pubkey) -> ChainResult<U256> {
let balance = self
.0
.get_balance(pubkey)
pub async fn get_transaction(
&self,
signature: &Signature,
) -> ChainResult<EncodedConfirmedTransactionWithStatusMeta> {
let config = RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::JsonParsed),
commitment: Some(CommitmentConfig::finalized()),
..Default::default()
};
self.0
.get_transaction_with_config(signature, config)
.await
.map_err(Into::<HyperlaneSealevelError>::into)
.map_err(ChainCommunicationError::from)?;
Ok(balance.into())
.map_err(HyperlaneSealevelError::ClientError)
.map_err(Into::into)
}
pub async fn is_blockhash_valid(&self, hash: &Hash) -> ChainResult<bool> {

@ -1,4 +1,4 @@
use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError};
use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, NativeToken};
use url::Url;
/// Sealevel connection configuration
@ -8,6 +8,8 @@ pub struct ConnectionConf {
pub url: Url,
/// Operation batching configuration
pub operation_batch: OperationBatchConfig,
/// Native token and its denomination
pub native_token: NativeToken,
}
/// An error type when parsing a connection configuration.

@ -0,0 +1,188 @@
use std::collections::HashMap;
use hyperlane_sealevel_mailbox::instruction::Instruction;
use solana_sdk::pubkey::Pubkey;
use solana_transaction_status::option_serializer::OptionSerializer;
use solana_transaction_status::{
EncodedTransaction, EncodedTransactionWithStatusMeta, UiCompiledInstruction, UiInstruction,
UiMessage, UiTransaction, UiTransactionStatusMeta,
};
use tracing::warn;
use hyperlane_core::H512;
use crate::utils::{decode_h512, from_base58};
/// This function searches for a transaction which dispatches Hyperlane message and returns
/// list of hashes of such transactions.
///
/// This function takes the mailbox program identifier and the identifier for PDA for storing
/// a dispatched message and searches a message dispatch transaction in a list of transaction.
/// The list of transaction is usually comes from a block. The function returns list of hashes
/// of such transactions.
///
/// The transaction will be searched with the following criteria:
/// 1. Transaction contains Mailbox program id in the list of accounts.
/// 2. Transaction contains dispatched message PDA in the list of accounts.
/// 3. Transaction is performing message dispatch (OutboxDispatch).
///
/// * `mailbox_program_id` - Identifier of Mailbox program
/// * `message_storage_pda_pubkey` - Identifier for dispatch message store PDA
/// * `transactions` - List of transactions
pub fn search_dispatched_message_transactions(
mailbox_program_id: &Pubkey,
message_storage_pda_pubkey: &Pubkey,
transactions: Vec<EncodedTransactionWithStatusMeta>,
) -> Vec<(usize, H512)> {
transactions
.into_iter()
.enumerate()
.filter_map(|(index, tx)| filter_by_encoding(tx).map(|(tx, meta)| (index, tx, meta)))
.filter_map(|(index, tx, meta)| {
filter_by_validity(tx, meta)
.map(|(hash, account_keys, instructions)| (index, hash, account_keys, instructions))
})
.filter_map(|(index, hash, account_keys, instructions)| {
filter_not_relevant(
mailbox_program_id,
message_storage_pda_pubkey,
hash,
account_keys,
instructions,
)
.map(|hash| (index, hash))
})
.collect::<Vec<(usize, H512)>>()
}
fn filter_not_relevant(
mailbox_program_id: &Pubkey,
message_storage_pda_pubkey: &Pubkey,
hash: H512,
account_keys: Vec<String>,
instructions: Vec<UiCompiledInstruction>,
) -> Option<H512> {
let account_index_map = account_index_map(account_keys);
let mailbox_program_id_str = mailbox_program_id.to_string();
let mailbox_program_index = match account_index_map.get(&mailbox_program_id_str) {
Some(i) => *i as u8,
None => return None, // If account keys do not contain Mailbox program, transaction is not message dispatch.
};
let message_storage_pda_pubkey_str = message_storage_pda_pubkey.to_string();
let dispatch_message_pda_account_index =
match account_index_map.get(&message_storage_pda_pubkey_str) {
Some(i) => *i as u8,
None => return None, // If account keys do not contain dispatch message store PDA account, transaction is not message dispatch.
};
let mailbox_program_maybe = instructions
.into_iter()
.find(|instruction| instruction.program_id_index == mailbox_program_index);
let mailbox_program = match mailbox_program_maybe {
Some(p) => p,
None => return None, // If transaction does not contain call into Mailbox, transaction is not message dispatch.
};
// If Mailbox program does not operate on dispatch message store PDA account, transaction is not message dispatch.
if !mailbox_program
.accounts
.contains(&dispatch_message_pda_account_index)
{
return None;
}
let instruction_data = match from_base58(&mailbox_program.data) {
Ok(d) => d,
Err(_) => return None, // If we cannot decode instruction data, transaction is not message dispatch.
};
let instruction = match Instruction::from_instruction_data(&instruction_data) {
Ok(ii) => ii,
Err(_) => return None, // If we cannot parse instruction data, transaction is not message dispatch.
};
// If the call into Mailbox program is not OutboxDispatch, transaction is not message dispatch.
if !matches!(instruction, Instruction::OutboxDispatch(_)) {
return None;
}
Some(hash)
}
fn filter_by_validity(
tx: UiTransaction,
meta: UiTransactionStatusMeta,
) -> Option<(H512, Vec<String>, Vec<UiCompiledInstruction>)> {
let Some(transaction_hash) = tx
.signatures
.first()
.map(|signature| decode_h512(signature))
.and_then(|r| r.ok())
else {
warn!(
transaction = ?tx,
"transaction does not have any signatures or signatures cannot be decoded",
);
return None;
};
let UiMessage::Raw(message) = tx.message else {
warn!(message = ?tx.message, "we expect messages in Raw format");
return None;
};
let instructions = instructions(message.instructions, meta);
Some((transaction_hash, message.account_keys, instructions))
}
fn filter_by_encoding(
tx: EncodedTransactionWithStatusMeta,
) -> Option<(UiTransaction, UiTransactionStatusMeta)> {
match (tx.transaction, tx.meta) {
// We support only transactions encoded as JSON
// We need none-empty metadata as well
(EncodedTransaction::Json(t), Some(m)) => Some((t, m)),
t => {
warn!(
?t,
"transaction is not encoded as json or metadata is empty"
);
None
}
}
}
fn account_index_map(account_keys: Vec<String>) -> HashMap<String, usize> {
account_keys
.into_iter()
.enumerate()
.map(|(index, key)| (key, index))
.collect::<HashMap<String, usize>>()
}
/// Extract all instructions from transaction
fn instructions(
instruction: Vec<UiCompiledInstruction>,
meta: UiTransactionStatusMeta,
) -> Vec<UiCompiledInstruction> {
let inner_instructions = match meta.inner_instructions {
OptionSerializer::Some(ii) => ii
.into_iter()
.flat_map(|ii| ii.instructions)
.flat_map(|ii| match ii {
UiInstruction::Compiled(ci) => Some(ci),
_ => None,
})
.collect::<Vec<UiCompiledInstruction>>(),
OptionSerializer::None | OptionSerializer::Skip => vec![],
};
[instruction, inner_instructions].concat()
}
#[cfg(test)]
mod tests;

@ -0,0 +1,329 @@
use solana_transaction_status::EncodedTransactionWithStatusMeta;
use crate::transaction::search_dispatched_message_transactions;
use crate::utils::decode_pubkey;
#[test]
pub fn test_search_dispatched_message_transaction() {
// given
let mailbox_program_id = decode_pubkey("E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi").unwrap();
let dispatched_message_pda_account =
decode_pubkey("6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT").unwrap();
let transaction = serde_json::from_str::<EncodedTransactionWithStatusMeta>(JSON).unwrap();
let transactions = vec![transaction];
// when
let transaction_hashes = search_dispatched_message_transactions(
&mailbox_program_id,
&dispatched_message_pda_account,
transactions,
);
// then
assert!(!transaction_hashes.is_empty());
}
const JSON: &str = r#"
{
"blockTime": 1729865514,
"meta": {
"computeUnitsConsumed": 171834,
"err": null,
"fee": 3564950,
"innerInstructions": [
{
"index": 2,
"instructions": [
{
"accounts": [
8,
7,
6,
0
],
"data": "gCzo5F74HA9Pb",
"programIdIndex": 19,
"stackHeight": 2
},
{
"accounts": [
5,
11,
10,
18,
0,
1,
2
],
"data": "2Nsbnwq8JuYnSefHfRznxFtFqdPnbeydtt5kenfF8GR1ZU2XtF8jJDo4SUc2VY52V5C25WsKsQZBLsoCVQNzefgVj2bVznkThjuZuSKXJfZN9ADggiM2soRKVsAjf3xHm3CC3w3iyvK5U9LsjmYtiDNbJCFtEPRTDxsfvMS45Bg3q6EogmBN9JiZNLP",
"programIdIndex": 17,
"stackHeight": 2
},
{
"accounts": [
0,
5
],
"data": "3Bxs3zrfFUZbEPqZ",
"programIdIndex": 10,
"stackHeight": 3
},
{
"accounts": [
0,
2
],
"data": "11114XfZCGKrze4PNou1GXiYCJgiBCGpHks9hxjb8tFwYMjtgVtMzvriDxwYPdRqSoqztL",
"programIdIndex": 10,
"stackHeight": 3
},
{
"accounts": [
10,
0,
3,
1,
4,
9,
14
],
"data": "5MtKiLZhPB3NhS7Gus6CenAEMS2QBtpY9QtuLeVH4CkpUN7599vsYzZXhk8Vu",
"programIdIndex": 15,
"stackHeight": 2
},
{
"accounts": [
0,
9
],
"data": "3Bxs4A3YxXXYy5gj",
"programIdIndex": 10,
"stackHeight": 3
},
{
"accounts": [
0,
4
],
"data": "111158VjdPaAaGVkCbPZoXJqknHXBEqoypfVjf96mwePbKxAkrKfR2gUFyN7wD8ccc9g1z",
"programIdIndex": 10,
"stackHeight": 3
}
]
}
],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program ComputeBudget111111111111111111111111111111 invoke [1]",
"Program ComputeBudget111111111111111111111111111111 success",
"Program ComputeBudget111111111111111111111111111111 invoke [1]",
"Program ComputeBudget111111111111111111111111111111 success",
"Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm invoke [1]",
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
"Program log: Instruction: TransferChecked",
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6200 of 983051 compute units",
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
"Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi invoke [2]",
"Program 11111111111111111111111111111111 invoke [3]",
"Program 11111111111111111111111111111111 success",
"Program log: Protocol fee of 0 paid from FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md to BvZpTuYLAR77mPhH4GtvwEWUTs53GQqkgBNuXpCePVNk",
"Program 11111111111111111111111111111111 invoke [3]",
"Program 11111111111111111111111111111111 success",
"Program log: Dispatched message to 1408864445, ID 0x09c74f3e10d98c112696b72ba1609aae47616f64f28b4cb1ad8a4a710e93ee89",
"Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi consumed 86420 of 972001 compute units",
"Program return: E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi CcdPPhDZjBEmlrcroWCarkdhb2Tyi0yxrYpKcQ6T7ok=",
"Program E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi success",
"Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv invoke [2]",
"Program 11111111111111111111111111111111 invoke [3]",
"Program 11111111111111111111111111111111 success",
"Program 11111111111111111111111111111111 invoke [3]",
"Program 11111111111111111111111111111111 success",
"Program log: Paid IGP JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M for 431000 gas for message 0x09c7…ee89 to 1408864445",
"Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv consumed 42792 of 882552 compute units",
"Program BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv success",
"Program log: Warp route transfer completed to destination: 1408864445, recipient: 0xd41b…f050, remote_amount: 2206478600",
"Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm consumed 171534 of 999700 compute units",
"Program 3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm success"
],
"postBalances": [
12374928,
0,
2241120,
1016160,
1872240,
8679120,
2039280,
319231603414,
2039280,
10172586528,
1,
890880,
1141440,
3361680,
1830480,
1141440,
1,
1141440,
1141440,
934087680
],
"postTokenBalances": [
{
"accountIndex": 6,
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"owner": "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj",
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"uiTokenAmount": {
"amount": "165697511204",
"decimals": 6,
"uiAmount": 165697.511204,
"uiAmountString": "165697.511204"
}
},
{
"accountIndex": 8,
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"owner": "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md",
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"uiTokenAmount": {
"amount": "94",
"decimals": 6,
"uiAmount": 9.4E-5,
"uiAmountString": "0.000094"
}
}
],
"preBalances": [
22211372,
0,
0,
1016160,
0,
8679120,
2039280,
319231603414,
2039280,
10170428394,
1,
890880,
1141440,
3361680,
1830480,
1141440,
1,
1141440,
1141440,
934087680
],
"preTokenBalances": [
{
"accountIndex": 6,
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"owner": "CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj",
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"uiTokenAmount": {
"amount": "163491032604",
"decimals": 6,
"uiAmount": 163491.032604,
"uiAmountString": "163491.032604"
}
},
{
"accountIndex": 8,
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"owner": "FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md",
"programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"uiTokenAmount": {
"amount": "2206478694",
"decimals": 6,
"uiAmount": 2206.478694,
"uiAmountString": "2206.478694"
}
}
],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": 297626301,
"transaction": {
"message": {
"accountKeys": [
"FGyh1FfooV7AtVrYjFGmjMxbELC8RMxNp4xY5WY4L4md",
"8DqWVhEZcg4rDYwe5UFaopmGuEajiPz9L3A1ZnytMcUm",
"6eG8PheL41qLFFUtPjSYMtsp4aoAQsMgcsYwkGCB8kwT",
"8Cv4PHJ6Cf3xY7dse7wYeZKtuQv9SAN6ujt5w22a2uho",
"9yMwrDqHsbmmvYPS9h4MLPbe2biEykcL51W7qJSDL5hF",
"BvZpTuYLAR77mPhH4GtvwEWUTs53GQqkgBNuXpCePVNk",
"CcquFeCYNZM48kLPyG3HWxdwgigmyxPBi6iHwve9Myhj",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"FDDbaNtod9pt7pmR8qtmRZJtEj9NViDA7J6cazqUjXQj",
"JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M",
"11111111111111111111111111111111",
"37N3sbyVAd3KvQsPw42i1LWkLahzL4ninVQ4n1NmnHjS",
"3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm",
"AHX3iiEPFMyygANrp15cyUr63o9qGkwkB6ki1pgpZ7gZ",
"AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF",
"BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv",
"ComputeBudget111111111111111111111111111111",
"E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi",
"noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV",
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
],
"header": {
"numReadonlySignedAccounts": 1,
"numReadonlyUnsignedAccounts": 10,
"numRequiredSignatures": 2
},
"instructions": [
{
"accounts": [],
"data": "FjL4FH",
"programIdIndex": 16,
"stackHeight": null
},
{
"accounts": [],
"data": "3butUEijJrLf",
"programIdIndex": 16,
"stackHeight": null
},
{
"accounts": [
10,
18,
13,
17,
5,
11,
0,
1,
2,
15,
3,
4,
14,
9,
19,
7,
8,
6
],
"data": "RpjV6TtUSvt6UnMXdNo4h1Ze2VGVifo65r2jqRBUq6HJKhskSnwWybXyB4NxgfvedV9vhKdmDPg8sFT64JEZvxF8VfoGdqoAFt4WFLSB",
"programIdIndex": 12,
"stackHeight": null
}
],
"recentBlockhash": "GHQhVUy7Eq3hcps8YoG9DCd1Tb6ccQZ9xhh81ju8ujHJ"
},
"signatures": [
"4nRGgV9tqCuiKUXeBzWdvdk6YC9BsGWUZurAVQLMX1NwNPpysbZNwXu97Sw4aM9REwaRmWS7gaiSKXbwtmw6oLRi",
"hXjvQbAuFH9vAxZMdGqfnSjN7t7Z7NLTzRq1SG8i6fLr9LS6XahTduPWqakiTsLDyWSofvq3MSncUAkbQLEj85f"
]
}
}
"#;

@ -0,0 +1,33 @@
use std::str::FromStr;
use solana_sdk::bs58;
use solana_sdk::pubkey::Pubkey;
use hyperlane_core::{H256, H512};
use crate::error::HyperlaneSealevelError;
pub fn from_base58(base58: &str) -> Result<Vec<u8>, HyperlaneSealevelError> {
let binary = bs58::decode(base58)
.into_vec()
.map_err(HyperlaneSealevelError::Decoding)?;
Ok(binary)
}
pub fn decode_h256(base58: &str) -> Result<H256, HyperlaneSealevelError> {
let binary = from_base58(base58)?;
let hash = H256::from_slice(&binary);
Ok(hash)
}
pub fn decode_h512(base58: &str) -> Result<H512, HyperlaneSealevelError> {
let binary = from_base58(base58)?;
let hash = H512::from_slice(&binary);
Ok(hash)
}
pub fn decode_pubkey(address: &str) -> Result<Pubkey, HyperlaneSealevelError> {
Pubkey::from_str(address).map_err(Into::<HyperlaneSealevelError>::into)
}

@ -34,7 +34,7 @@
"interchainAccountIsm": "0xd766e7C7517f2d0D92754b2fe4aE7AdEf7bDEC3e",
"interchainAccountRouter": "0x25C87e735021F72d8728438C2130b02E3141f2cb",
"interchainGasPaymaster": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA",
"interchainSecurityModule": "0x4e1d2cdB48A2C2912b11801Eb1F1d5007474cA43",
"interchainSecurityModule": "0x259Da872714f84B74B4473fab45e9839f3453a40",
"isTestnet": false,
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162",
@ -100,7 +100,7 @@
"interchainAccountIsm": "0x2A7574358Ec53522CE2452887661AB4c86F7d400",
"interchainAccountRouter": "0x91874Dbed74925dFe6059B90385EEb90DdE0B2E6",
"interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22",
"interchainSecurityModule": "0x50d0b0E27B8B93119618f053A623886116dd3b6d",
"interchainSecurityModule": "0xDeeE785e0b3De36A7F1aF5d0A0a1F86e7c356a58",
"mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9",
"merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930",
"name": "arbitrum",
@ -172,7 +172,7 @@
"interchainAccountIsm": "0x27a3233c05C1Df7c163123301D14bE9349E3Cb48",
"interchainAccountRouter": "0xa82a0227e6d6db53AF4B264A852bfF91C6504a51",
"interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0",
"interchainSecurityModule": "0xbc803Da34A88E5f6B50dfc0CC9D924d9865c91C5",
"interchainSecurityModule": "0x8Ea008E1E39D9770312b9b516D1f0C41DD3feCb3",
"mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6",
"merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A",
"name": "avalanche",
@ -245,7 +245,7 @@
"interchainAccountIsm": "0x223F7D3f27E6272266AE4B5B91Fd5C7A2d798cD8",
"interchainAccountRouter": "0x4767D22117bBeeb295413000B620B93FD8522d53",
"interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94",
"interchainSecurityModule": "0xB7fcb4665ace2B0d36fd92D26b4a8B516c0bFe5F",
"interchainSecurityModule": "0x875a788e4A887848ac0B5d4431125Ecd5cdB3dAe",
"mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D",
"merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"name": "base",
@ -316,7 +316,7 @@
"interchainAccountIsm": "0xe93f2f409ad8B5000431D234472973fe848dcBEC",
"interchainAccountRouter": "0x2f4Eb04189e11Af642237Da62d163Ab714614498",
"interchainGasPaymaster": "0xB3fCcD379ad66CED0c91028520C64226611A48c9",
"interchainSecurityModule": "0xECa4a584E91867a72cd036DB7Db22Ad894a197B7",
"interchainSecurityModule": "0x518C62D6771b7a6b393E429ba5f9025a6F485301",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465",
"name": "blast",
@ -384,7 +384,7 @@
"interchainAccountIsm": "0x451dF8AB0936D85526D816f0b4dCaDD934A034A4",
"interchainAccountRouter": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764",
"interchainGasPaymaster": "0x62B7592C1B6D1E43f4630B8e37f4377097840C05",
"interchainSecurityModule": "0x93E3e6CA295803417212421785606B1F7dDeaD8f",
"interchainSecurityModule": "0x28Df661E61e2f4e7a35F3a554094191e64cd6E5A",
"mailbox": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147",
"merkleTreeHook": "0x781bE492F1232E66990d83a9D3AC3Ec26f56DAfB",
"name": "bob",
@ -450,7 +450,7 @@
"interchainAccountIsm": "0x9e22945bE593946618383B108CC5bce09eBA4C26",
"interchainAccountRouter": "0x32A07c1B7a7fe8D4A0e44B0181873aB9d64C16c1",
"interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451",
"interchainSecurityModule": "0xA0506B5b12770494740A4a7cc86C9A36Dc1Fc6Dc",
"interchainSecurityModule": "0x5BC506C2C04dfDe5b45cbF47f05b4373D65832e4",
"mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4",
"merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26",
"name": "bsc",
@ -531,7 +531,7 @@
"interchainAccountIsm": "0xB732c83aeE29596E3163Da2260710eAB67Bc0B29",
"interchainAccountRouter": "0x27a6cAe33378bB6A6663b382070427A01fc9cB37",
"interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7",
"interchainSecurityModule": "0xa6f4835940dbA46E295076D0CD0411349C33789f",
"interchainSecurityModule": "0xB9aD1ccE862444B5C3bad009ef500AD3680940Da",
"mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb",
"merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366",
"name": "celo",
@ -596,7 +596,7 @@
"interchainAccountIsm": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2",
"interchainAccountRouter": "0xEF9A332Ec1fD233Bf9344A58be56ff9E104B4f60",
"interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503",
"interchainSecurityModule": "0x05f6BAa16F1aCf7b19c4A09E019D856c10ab8355",
"interchainSecurityModule": "0x77e9F81405DBC1eBeDA93f23aB4a339452d2AD9D",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD",
"name": "cheesechain",
@ -659,7 +659,7 @@
"from": 4842212
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0x9A746C4BC2bE7E657A3469f0a0DAA1dE517b8514",
"interchainSecurityModule": "0xcC5534C4665D9BD93B377F56eC4c09Fdee87AD30",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "cyber",
@ -726,7 +726,7 @@
"from": 23783929
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0x168E9C1481F50E66Cc5F5E24b04eBf7071629c4E",
"interchainSecurityModule": "0xF6C78dDeeb0Ef1682bC020787ecA835C5F353701",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "degenchain",
@ -839,7 +839,7 @@
"interchainAccountIsm": "0xCeafc098e5c3c7768b9229Be2FEC275862A81Abd",
"interchainAccountRouter": "0xed9a722c543883FB7e07E78F3879762DE09eA7D5",
"interchainGasPaymaster": "0xB30EAB08aa87138D57168D0e236850A530f49921",
"interchainSecurityModule": "0x094120BaC576aD7D88ec6893C9B220a0e64923E9",
"interchainSecurityModule": "0x702A7e81b3625AA02b365Aa95A7123a9EB31012f",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xC831271c1fB212012811a91Dd43e5926C1020563",
"name": "endurance",
@ -910,7 +910,7 @@
"interchainAccountIsm": "0x292C614ED53DaaDBf971521bc2C652d1ca51cB47",
"interchainAccountRouter": "0x5E532F7B610618eE73C2B462978e94CB1F7995Ce",
"interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611",
"interchainSecurityModule": "0x23d160e4474Ce011829c71Bf1bCaA40F0b5612D5",
"interchainSecurityModule": "0xc7b5BA9EcedcFd7D250Cf7df19558D7dd0d06ECc",
"mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239",
"merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA",
"name": "ethereum",
@ -979,7 +979,7 @@
"interchainAccountIsm": "0x7C012DCA02C42cfA3Fd7Da3B0ED7234B52AE68eF",
"interchainAccountRouter": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9",
"interchainGasPaymaster": "0x2Fca7f6eC3d4A0408900f2BB30004d4616eE985E",
"interchainSecurityModule": "0x8B497dff421844Bb0882E0C495d0851D4461675C",
"interchainSecurityModule": "0xC8e75e117E1dBC43880ac6c85e820844Dd5f527a",
"mailbox": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3",
"merkleTreeHook": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147",
"name": "fraxtal",
@ -1047,7 +1047,7 @@
"interchainAccountIsm": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD",
"interchainAccountRouter": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19",
"interchainGasPaymaster": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainSecurityModule": "0x69b33D67B9C51D45E23d22E727FF186DD6298ECA",
"interchainSecurityModule": "0xf0b1EC4228c0F26B7225851f940c625e4Be12226",
"mailbox": "0x3071D4DA6020C956Fe15Bfd0a9Ca8D4574f16696",
"merkleTreeHook": "0xfBc08389224d23b79cb21cDc16c5d42F0ad0F57f",
"name": "fusemainnet",
@ -1121,7 +1121,7 @@
"interchainAccountIsm": "0x07E2062A1bC66a2C1d05cb5C3870a4AF86e0056E",
"interchainAccountRouter": "0xBE70Ab882D1F7E37e04a70CDd9Ec23b37a234064",
"interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f",
"interchainSecurityModule": "0x00533a5F14B3a0632C86f99E4e20a10b73C4AE0D",
"interchainSecurityModule": "0xcfA4D90A43AAB0dE6a07c33A0614606b5Fbf71eC",
"mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f",
"merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645",
"name": "gnosis",
@ -1192,7 +1192,7 @@
"interchainAccountIsm": "0x708E002637792FDC031E6B62f23DD60014AC976a",
"interchainAccountRouter": "0xfB8cea1c7F45608Da30655b50bbF355D123A4358",
"interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"interchainSecurityModule": "0xB8F85B879775adF156Dd4AFa43e97DeB880d99D4",
"interchainSecurityModule": "0x5f39C3f0d9c74a667C177795b769CC22aE0A200d",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65",
"name": "inevm",
@ -1320,7 +1320,7 @@
"from": 14616307
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0xE2968dAb74541184Ad95651b1e5Cf34Ab1bBEc97",
"interchainSecurityModule": "0xa078A84FCB5d53BCe9529DcF96635794951116Ba",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "kroma",
@ -1393,7 +1393,7 @@
"interchainAccountIsm": "0xdcA646C56E7768DD11654956adE24bfFf9Ba4893",
"interchainAccountRouter": "0xD59dA396F162Ed93a41252Cebb8d5DD4F093238C",
"interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28",
"interchainSecurityModule": "0x0EEF1e64646EE01DeED4850074Cd4B97C0A630a9",
"interchainSecurityModule": "0x3902B990C5DC30D6CeeFf8b8B6Ad0cb6466b7d45",
"mailbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9",
"merkleTreeHook": "0xC077A0Cc408173349b1c9870C667B40FE3C01dd7",
"name": "linea",
@ -1464,7 +1464,7 @@
"from": 4195553
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0x63Bb509b9CA644609B15Ea55E56f0Acbbb9dB02E",
"interchainSecurityModule": "0xcee06f87aC789615B0BF989B54fb36De334af5bC",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "lisk",
@ -1528,7 +1528,7 @@
"from": 3088760
},
"interchainGasPaymaster": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"interchainSecurityModule": "0x609ad94304896607A6D81DB00d882245045B79da",
"interchainSecurityModule": "0x351621b2fbfe92193304A8e4DD97E04108883Ac8",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e",
"name": "lukso",
@ -1602,7 +1602,7 @@
"interchainAccountIsm": "0x8Ea50255C282F89d1A14ad3F159437EE5EF0507f",
"interchainAccountRouter": "0x693A4cE39d99e46B04cb562329e3F0141cA17331",
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"interchainSecurityModule": "0xC012e8E3cBeB6295E1E4837FBA5DB8E077EBc549",
"interchainSecurityModule": "0x4110577c0970e591b3DC6D8FfCbE8FA499b4cD14",
"isTestnet": false,
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112",
@ -1672,7 +1672,7 @@
"interchainAccountIsm": "0xe039DA3A0071BEd087A12660D7b03cf669c7776E",
"interchainAccountRouter": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D",
"interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28",
"interchainSecurityModule": "0x8722328A5Ed815965F9B5eBAA21d04f0F9BFDd35",
"interchainSecurityModule": "0xAb7DdE83cf33e0B305624462A6B9D7D06A32A699",
"mailbox": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA",
"merkleTreeHook": "0x5332D1AC0A626D265298c14ff681c0A8D28dB86d",
"name": "mantle",
@ -1734,7 +1734,7 @@
"from": 13523607
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0xE8176Fc70f129255aA83d3db242C2246Ad77Af7D",
"interchainSecurityModule": "0xc0dc69896334eD2f3Af2666d5EEF70e062a4636e",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "merlin",
@ -1801,7 +1801,7 @@
"from": 17966274
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0xA9309228762699D5c81A4b0BAfd06Da21589746b",
"interchainSecurityModule": "0xA04202E77957f7451c0a2F2CA9eF54FFC334BcF3",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "metis",
@ -1866,7 +1866,7 @@
"from": 3752032
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0xA9309228762699D5c81A4b0BAfd06Da21589746b",
"interchainSecurityModule": "0x120bbFe296f11Dd653Cd6aC4F93106d9e6ADe5Bc",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "mint",
@ -1933,7 +1933,7 @@
"interchainAccountIsm": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82",
"interchainAccountRouter": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8",
"interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d",
"interchainSecurityModule": "0x0777dFffcEd18EE416e35401E0e5e0413b7D43be",
"interchainSecurityModule": "0x0ab4853f2104C8f8e0b4Da4028601D999c7dFCF5",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6",
"name": "mode",
@ -2001,7 +2001,7 @@
"interchainAccountIsm": "0x79b3730CE3685f65802aF1771319992bA960EB9D",
"interchainAccountRouter": "0xc4482f66191754a8629D35289043C4EB0285F10E",
"interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F",
"interchainSecurityModule": "0xad1Ad827035eDe500aFd0ff122c53f6eA607Eb5C",
"interchainSecurityModule": "0x11cC5Ac8F65e337Db6E95a542e87Af630cd0b1CD",
"mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3",
"merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547",
"name": "moonbeam",
@ -2143,7 +2143,7 @@
"interchainAccountIsm": "0x2c46BF14641d00549ECa4779BF5CBf91602C1DEd",
"interchainAccountRouter": "0x03D6cC17d45E9EA27ED757A8214d1F07F7D901aD",
"interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C",
"interchainSecurityModule": "0x3878aB31B2426A92E8a1E0AE758d848879F7F5E8",
"interchainSecurityModule": "0x5E04bA55C08456562f7e1BF9E2b46628b81593c8",
"mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D",
"merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f",
"name": "optimism",
@ -2278,7 +2278,7 @@
"interchainAccountIsm": "0xBAC4529cdfE7CCe9E858BF706e41F8Ed096C1BAd",
"interchainAccountRouter": "0xF163949AD9F88977ebF649D0461398Ca752E64B9",
"interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2",
"interchainSecurityModule": "0x9fFC02BfB5C7260C985b005C0cF40d7EC601aac2",
"interchainSecurityModule": "0x0c3fE398595235A3163b39E5C6443aa1f7e145c4",
"mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB",
"merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6",
"name": "polygon",
@ -2355,7 +2355,7 @@
"interchainAccountIsm": "0xc1198e241DAe48BF5AEDE5DCE49Fe4A6064cF7a7",
"interchainAccountRouter": "0x20a0A32a110362920597F72974E1E0d7e25cA20a",
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"interchainSecurityModule": "0xc6c475184F197FA65f233dFc22FA6bD4cE48B4fE",
"interchainSecurityModule": "0x3351FC009ccb309C367787ff9f3040FDee0Dcf66",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112",
"name": "polygonzkevm",
@ -2423,7 +2423,7 @@
"from": 32018468
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0x70e8beCE806914959c1B5D8F75d2217058D31437",
"interchainSecurityModule": "0x37282B7505DA332Fc57FAb1623C73A4AeDb49165",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "proofofplay",
@ -2487,7 +2487,7 @@
"from": 363159
},
"interchainGasPaymaster": "0x3071D4DA6020C956Fe15Bfd0a9Ca8D4574f16696",
"interchainSecurityModule": "0x43346a54445BBdf8241062904E8A13AA62842a02",
"interchainSecurityModule": "0x14551EfDB6c29c8fC61D469627255E5f2c8Bf981",
"mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D",
"merkleTreeHook": "0x55E4F0bc6b7Bb493D50839A8592e7ad8d5e93cf7",
"name": "real",
@ -2554,7 +2554,7 @@
"interchainAccountIsm": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785",
"interchainAccountRouter": "0x7a4d31a686A36285d68e14EDD53631417eB19603",
"interchainGasPaymaster": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634",
"interchainSecurityModule": "0xd8b6B632526834D8192860e6B6CE47165Fd02a42",
"interchainSecurityModule": "0x5fe5b8f20437336a74fA53e553472f6fF048e73A",
"mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D",
"merkleTreeHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA",
"name": "redstone",
@ -2616,7 +2616,7 @@
"from": 937117
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0x6A2748201F66647ad6D164CB3340A893881A4bb2",
"interchainSecurityModule": "0xcC5534C4665D9BD93B377F56eC4c09Fdee87AD30",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "sanko",
@ -2684,7 +2684,7 @@
"interchainAccountIsm": "0x32af5Df81fEd5E26119F6640FBB13f3d63a94CDe",
"interchainAccountRouter": "0x0B48a744698ba8dFa514742dFEB6728f52fD66f7",
"interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2",
"interchainSecurityModule": "0xAd1a987BfE0D6fbD92089628daC7C7e4bA9a6AAF",
"interchainSecurityModule": "0x41D5dbBe3a6B5d3937f03e82C23d1EDF9D4d0B6C",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76",
"name": "scroll",
@ -2752,7 +2752,7 @@
"interchainAccountIsm": "0xf35dc7B9eE4Ebf0cd3546Bd6EE3b403dE2b9F5D6",
"interchainAccountRouter": "0xBcaedE97a98573A88242B3b0CB0A255F3f90d4d5",
"interchainGasPaymaster": "0xFC62DeF1f08793aBf0E67f69257c6be258194F72",
"interchainSecurityModule": "0x494028EA206642e4c60Ec3d12e96B4549E5e1800",
"interchainSecurityModule": "0x42Aa14207b41A7c2b1692DFC4927a32Caaa52b24",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xca1b69fA4c4a7c7fD839bC50867c589592bcfe49",
"name": "sei",
@ -2867,7 +2867,7 @@
"interchainAccountIsm": "0xAE557e108b3336130370aC74836f1356B4b30Cf2",
"interchainAccountRouter": "0x1F8CF09F060A2AE962c0Bb1F92e209a1E7b0E10B",
"interchainGasPaymaster": "0x273Bc6b01D9E88c064b6E5e409BdF998246AEF42",
"interchainSecurityModule": "0xC93F2796A17Ee4580c039aeB7b0c923b10ce79C2",
"interchainSecurityModule": "0x2faB8c35cd093904F3d9a9f022996103d4bb7A85",
"mailbox": "0x28EFBCadA00A7ed6772b3666F3898d276e88CAe3",
"merkleTreeHook": "0x6A55822cf11f9fcBc4c75BC2638AfE8Eb942cAdd",
"name": "taiko",
@ -2929,7 +2929,7 @@
"from": 1678063
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0x70e8beCE806914959c1B5D8F75d2217058D31437",
"interchainSecurityModule": "0xcC5534C4665D9BD93B377F56eC4c09Fdee87AD30",
"isTestnet": false,
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
@ -2997,7 +2997,7 @@
"interchainAccountIsm": "0x551BbEc45FD665a8C95ca8731CbC32b7653Bc59B",
"interchainAccountRouter": "0xc11f8Cf2343d3788405582F65B8af6A4F7a6FfC8",
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"interchainSecurityModule": "0x3465AccC39AE5e6C344184013a57cDCe546834d6",
"interchainSecurityModule": "0x87Fb0665D25aC674D4d1494a13cA60E378f7536F",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112",
"name": "viction",
@ -3065,7 +3065,7 @@
"interchainAccountIsm": "0xCB9f90EE5d83Ea52ABd922BD70898f0155D54798",
"interchainAccountRouter": "0x473884010F0C1742DA8Ad01E7E295624B931076b",
"interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503",
"interchainSecurityModule": "0x05f6BAa16F1aCf7b19c4A09E019D856c10ab8355",
"interchainSecurityModule": "0xb936B97837C4158DEA65CBaBCcC55bAE24134108",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD",
"name": "worldchain",
@ -3127,7 +3127,7 @@
"from": 24395308
},
"interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF",
"interchainSecurityModule": "0x4886ed96bcdba2ad85Bf518C3171C39e256ac840",
"interchainSecurityModule": "0xf61ee4520B7b316a34cdB31F4B95d8ad1808F919",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"name": "xai",
@ -3195,7 +3195,7 @@
"interchainAccountIsm": "0x29B37088724B745C0ABcE591449Cf042772160C2",
"interchainAccountRouter": "0x03cF708E42C89623bd83B281A56935cB562b9258",
"interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503",
"interchainSecurityModule": "0x59B0ec92522F164b72c9BE473382197c564B92dc",
"interchainSecurityModule": "0x59Dcbb486F91561acC3500cdc378104AaE27e94a",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD",
"name": "xlayer",
@ -3263,7 +3263,7 @@
"interchainAccountIsm": "0x2b6d3F7d28B5EC8C3C028fBCAdcf774D9709Dd29",
"interchainAccountRouter": "0x3AdCBc94ab8C48EC52D06dc65Bb787fD1981E3d5",
"interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d",
"interchainSecurityModule": "0xd32353Ae5719ac4f8f24AeD81A2A6898d2632D26",
"interchainSecurityModule": "0x3DFD05cc42F1B5370e688520e65C8fb909418471",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6",
"name": "zetachain",
@ -3329,7 +3329,7 @@
"from": 1511458
},
"interchainGasPaymaster": "0x03cF708E42C89623bd83B281A56935cB562b9258",
"interchainSecurityModule": "0xFFec270FE3D0e3B9348B3664BE73A5d4906BA620",
"interchainSecurityModule": "0x6C5288BBdFBfED98aE3e2C4201a53A9bBcC24fCE",
"mailbox": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c",
"merkleTreeHook": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28",
"name": "zircuit",
@ -3402,7 +3402,7 @@
"interchainAccountIsm": "0xb2674E213019972f937CCFc5e23BF963D915809e",
"interchainAccountRouter": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b",
"interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb",
"interchainSecurityModule": "0xED5fD1715A0885a3C7B908BAd5c8C64Ba5166265",
"interchainSecurityModule": "0x10455742E8D25b3FC2b19D41A006E91594D05Cd5",
"mailbox": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a",
"merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F",
"name": "zoramainnet",
@ -3473,7 +3473,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674",
"interchainSecurityModule": "0xed2b47397954E0a56ca7C2a862dba4027da0f595",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -3540,7 +3540,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0xa18979d2e5b8A64f62E0f9e9523d28E934F1104c",
"interchainSecurityModule": "0x73b3A13eA8084C9BeAd07eD867cEe1E8F5cceF9e",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -3610,7 +3610,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674",
"interchainSecurityModule": "0x0fFD49C8ae6f5DdC20124B92c4b4048EbeF71830",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -3686,7 +3686,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674",
"interchainSecurityModule": "0x6dCfEB77d5801DCC76C0EDd1F44E32D6dEcAD2A2",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -3750,7 +3750,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674",
"interchainSecurityModule": "0x119b3dFBef304b07BBCb08D5182459f35808bfF2",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -3823,7 +3823,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674",
"interchainSecurityModule": "0x7954Da9003180D64B9Eb7dE5E23fC83Fd7673047",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -3891,7 +3891,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0x8Ad4d573D7EafC4Ca58f1dB704B8Db804814D674",
"interchainSecurityModule": "0xE96E34b0e66A5FAe90Be8f5FE41B6adB86D84A8b",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -3954,7 +3954,7 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB",
"interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainSecurityModule": "0xf08b7F859966ed27286Fe7d924A42b40e2DB80Bd",
"interchainSecurityModule": "0x6CECA24Ebc20183B17fbA17f2856d5B3f97659Db",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575",
"pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27",
@ -4024,7 +4024,7 @@
"interchainAccountIsm": "0xcd9D3744512F07AE844c40E27912092d7c503565",
"interchainAccountRouter": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8",
"interchainGasPaymaster": "0xb58257cc81E47EC72fD38aE16297048de23163b4",
"interchainSecurityModule": "0xcC5C35a6214982d9018B95e9684D5b4dA626237e",
"interchainSecurityModule": "0x6B38f93e9145BF8b09B127e20F0275E92E29E4F5",
"mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39",
"merkleTreeHook": "0xCC3D1659D50461d27a2F025dDb2c9B06B584B7e1",
"pausableHook": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
@ -4084,7 +4084,7 @@
"interchainAccountIsm": "0xc23BaF5Eb5848D19701BbE7f139645e6bd58a319",
"interchainAccountRouter": "0x7c58Cadcc2b60ACF794eE1843488d6f5703f76BE",
"interchainGasPaymaster": "0xb4fc9B5fD57499Ef6FfF3995728a55F7A618ef86",
"interchainSecurityModule": "0x50B5Edd94A1C7ad18Fa2CA667A30Dc051a695aEe",
"interchainSecurityModule": "0x0d242364057129d99Fca35BA8Ed2d9Ae90956F63",
"mailbox": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594",
"merkleTreeHook": "0x3E969bA938E6A993eeCD6F65b0dd8712B07dFe59",
"pausableHook": "0x6Fb36672365C7c797028C400A61c58c0ECc53cD2",
@ -4889,7 +4889,7 @@
"interchainAccountIsm": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1",
"interchainAccountRouter": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47",
"interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb",
"interchainSecurityModule": "0x9FF3f38DED52D74EF4b666A7A09BcB5F38d6D272",
"interchainSecurityModule": "0x70196Ec5e193dC07CFDC7AeC583DF9aeC20f4939",
"mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39",
"merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F",
"pausableHook": "0x2F619Ac5122689180AeBB930ADccdae215d538a9",
@ -4953,7 +4953,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0x8FBFF0cF2920c4deFfd8EAE6a51BB55E1d40eFFc",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5014,7 +5014,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0xCC95B2978F65B2f358dD4A8B428Dd442AB37f478",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5093,7 +5093,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0xeD0fc456143f761614478D170bf12403638DDa1e",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5160,7 +5160,7 @@
"interchainAccountIsm": "0x783EC5e105234a570eB90f314284E5dBe53bdd90",
"interchainAccountRouter": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563",
"interchainGasPaymaster": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a",
"interchainSecurityModule": "0xfa19BfEcB4fed2e0268ee5008a11cD946DcC13c3",
"interchainSecurityModule": "0xFbDfEE404a9DFA413fF8B587aDad4B7979Db28a0",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"merkleTreeHook": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8",
"pausableHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F",
@ -5231,7 +5231,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0x2D92EeB56FbEB258d918485200aC1C2e010547b8",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5296,7 +5296,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0xCC95B2978F65B2f358dD4A8B428Dd442AB37f478",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5360,7 +5360,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0x8FBFF0cF2920c4deFfd8EAE6a51BB55E1d40eFFc",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5427,7 +5427,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0x8FBFF0cF2920c4deFfd8EAE6a51BB55E1d40eFFc",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5491,7 +5491,7 @@
"interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed",
"interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451",
"interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754",
"interchainSecurityModule": "0x92772a801db50044a9D5078CC35CD63CEcD7B424",
"interchainSecurityModule": "0xC7a2a8103dF4bdE8c78C2C5C03389ee9337A9942",
"mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D",
"pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7",
@ -5580,7 +5580,7 @@
}
],
"blocks": {
"confirmations": 1,
"confirmations": 2,
"estimateBlockTime": 1,
"reorgPeriod": 0
},

@ -15,7 +15,6 @@ color-eyre = { workspace = true, optional = true }
config.workspace = true
console-subscriber.workspace = true
convert_case.workspace = true
derive_builder.workspace = true
derive-new.workspace = true
ed25519-dalek.workspace = true
ethers.workspace = true

@ -8,8 +8,7 @@ use hyperlane_core::config::*;
use tracing::info;
use crate::{
create_chain_metrics,
metrics::{create_agent_metrics, AgentMetrics, CoreMetrics},
metrics::{AgentMetrics, CoreMetrics},
settings::Settings,
ChainMetrics,
};
@ -88,8 +87,8 @@ pub async fn agent_main<A: BaseAgent>() -> Result<()> {
let metrics = settings.as_ref().metrics(A::AGENT_NAME)?;
let tokio_server = core_settings.tracing.start_tracing(&metrics)?;
let agent_metrics = create_agent_metrics(&metrics)?;
let chain_metrics = create_chain_metrics(&metrics)?;
let agent_metrics = AgentMetrics::new(&metrics)?;
let chain_metrics = ChainMetrics::new(&metrics)?;
let agent = A::from_settings(
agent_metadata,
settings,

@ -4,7 +4,6 @@
use std::sync::Arc;
use std::time::Duration;
use derive_builder::Builder;
use eyre::Result;
use hyperlane_core::metrics::agent::decimals_by_protocol;
use hyperlane_core::metrics::agent::u256_as_scaled_f64;
@ -45,8 +44,14 @@ pub const GAS_PRICE_LABELS: &[&str] = &["chain"];
pub const GAS_PRICE_HELP: &str =
"Tracks the current gas price of the chain, in the lowest denomination (e.g. wei)";
/// Expected label names for the `critical_error` metric.
pub const CRITICAL_ERROR_LABELS: &[&str] = &["chain"];
/// Help string for the metric.
pub const CRITICAL_ERROR_HELP: &str =
"Boolean marker for critical errors on a chain, signalling loss of liveness";
/// Agent-specific metrics
#[derive(Clone, Builder, Debug)]
#[derive(Clone, Debug)]
pub struct AgentMetrics {
/// Current balance of native tokens for the
/// wallet address.
@ -57,27 +62,28 @@ pub struct AgentMetrics {
/// - `token_address`: Address of the token.
/// - `token_symbol`: Symbol of the token.
/// - `token_name`: Full name of the token.
#[builder(setter(into, strip_option), default)]
wallet_balance: Option<GaugeVec>,
}
pub(crate) fn create_agent_metrics(metrics: &CoreMetrics) -> Result<AgentMetrics> {
Ok(AgentMetricsBuilder::default()
.wallet_balance(metrics.new_gauge(
"wallet_balance",
WALLET_BALANCE_HELP,
WALLET_BALANCE_LABELS,
)?)
.build()?)
impl AgentMetrics {
pub(crate) fn new(metrics: &CoreMetrics) -> Result<AgentMetrics> {
let agent_metrics = AgentMetrics {
wallet_balance: Some(metrics.new_gauge(
"wallet_balance",
WALLET_BALANCE_HELP,
WALLET_BALANCE_LABELS,
)?),
};
Ok(agent_metrics)
}
}
/// Chain-specific metrics
#[derive(Clone, Builder, Debug)]
#[derive(Clone, Debug)]
pub struct ChainMetrics {
/// Tracks the current block height of the chain.
/// - `chain`: the chain name (or ID if the name is unknown) of the chain
/// the block number refers to.
#[builder(setter(into))]
pub block_height: IntGaugeVec,
/// Tracks the current gas price of the chain. Uses the base_fee_per_gas if
@ -85,19 +91,45 @@ pub struct ChainMetrics {
/// TODO: use the median of the transactions.
/// - `chain`: the chain name (or chain ID if the name is unknown) of the
/// chain the gas price refers to.
#[builder(setter(into, strip_option), default)]
pub gas_price: Option<GaugeVec>,
/// Boolean marker for critical errors on a chain, signalling loss of liveness.
critical_error: IntGaugeVec,
}
pub(crate) fn create_chain_metrics(metrics: &CoreMetrics) -> Result<ChainMetrics> {
Ok(ChainMetricsBuilder::default()
.block_height(metrics.new_int_gauge(
"block_height",
BLOCK_HEIGHT_HELP,
BLOCK_HEIGHT_LABELS,
)?)
.gas_price(metrics.new_gauge("gas_price", GAS_PRICE_HELP, GAS_PRICE_LABELS)?)
.build()?)
impl ChainMetrics {
pub(crate) fn new(metrics: &CoreMetrics) -> Result<ChainMetrics> {
let block_height_metrics =
metrics.new_int_gauge("block_height", BLOCK_HEIGHT_HELP, BLOCK_HEIGHT_LABELS)?;
let gas_price_metrics = metrics.new_gauge("gas_price", GAS_PRICE_HELP, GAS_PRICE_LABELS)?;
let critical_error_metrics =
metrics.new_int_gauge("critical_error", CRITICAL_ERROR_HELP, CRITICAL_ERROR_LABELS)?;
let chain_metrics = ChainMetrics {
block_height: block_height_metrics,
gas_price: Some(gas_price_metrics),
critical_error: critical_error_metrics,
};
Ok(chain_metrics)
}
pub(crate) fn set_gas_price(&self, chain: &str, price: f64) {
if let Some(gas_price) = &self.gas_price {
gas_price.with(&hashmap! { "chain" => chain }).set(price);
}
}
pub(crate) fn set_block_height(&self, chain: &str, height: i64) {
self.block_height
.with(&hashmap! { "chain" => chain })
.set(height);
}
/// Flag that a critical error has occurred on the chain
pub fn set_critical_error(&self, chain: &str, is_critical: bool) {
self.critical_error
.with(&hashmap! { "chain" => chain })
.set(is_critical as i64);
}
}
/// Configuration for the prometheus middleware. This can be loaded via serde.
@ -174,8 +206,6 @@ impl MetricsUpdater {
}
async fn update_block_details(&self) {
let block_height = self.chain_metrics.block_height.clone();
let gas_price = self.chain_metrics.gas_price.clone();
if let HyperlaneDomain::Unknown { .. } = self.conf.domain {
return;
};
@ -195,10 +225,8 @@ impl MetricsUpdater {
let height = chain_metrics.latest_block.number as i64;
trace!(chain, height, "Fetched block height for metrics");
block_height
.with(&hashmap! { "chain" => chain })
.set(height);
if let Some(gas_price) = gas_price {
self.chain_metrics.set_block_height(chain, height);
if self.chain_metrics.gas_price.is_some() {
let protocol = self.conf.domain.domain_protocol();
let decimals_scale = 10f64.powf(decimals_by_protocol(protocol).into());
let gas = u256_as_scaled_f64(chain_metrics.min_gas_price.unwrap_or_default(), protocol)
@ -208,7 +236,7 @@ impl MetricsUpdater {
gas = format!("{gas:.2}"),
"Gas price updated for chain (using lowest denomination)"
);
gas_price.with(&hashmap! { "chain" => chain }).set(gas);
self.chain_metrics.set_gas_price(chain, gas);
}
}

@ -2,9 +2,9 @@ use eyre::eyre;
use url::Url;
use h_eth::TransactionOverrides;
use hyperlane_core::config::{ConfigErrResultExt, OperationBatchConfig};
use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol};
use hyperlane_cosmos::NativeToken;
use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol, NativeToken};
use crate::settings::envs::*;
use crate::settings::ChainConnectionConf;
@ -137,24 +137,7 @@ pub fn build_cosmos_connection_conf(
.parse_u64()
.end();
let native_token_decimals = chain
.chain(err)
.get_key("nativeToken")
.get_key("decimals")
.parse_u32()
.unwrap_or(18);
let native_token_denom = chain
.chain(err)
.get_key("nativeToken")
.get_key("denom")
.parse_string()
.unwrap_or("");
let native_token = NativeToken {
decimals: native_token_decimals,
denom: native_token_denom.to_owned(),
};
let native_token = parse_native_token(chain, err, 18);
if !local_err.is_ok() {
err.merge(local_err);
@ -174,6 +157,45 @@ pub fn build_cosmos_connection_conf(
}
}
fn build_sealevel_connection_conf(
url: &Url,
chain: &ValueParser,
err: &mut ConfigParsingError,
operation_batch: OperationBatchConfig,
) -> h_sealevel::ConnectionConf {
let native_token = parse_native_token(chain, err, 9);
h_sealevel::ConnectionConf {
url: url.clone(),
operation_batch,
native_token,
}
}
fn parse_native_token(
chain: &ValueParser,
err: &mut ConfigParsingError,
default_decimals: u32,
) -> NativeToken {
let native_token_decimals = chain
.chain(err)
.get_opt_key("nativeToken")
.get_opt_key("decimals")
.parse_u32()
.unwrap_or(default_decimals);
let native_token_denom = chain
.chain(err)
.get_opt_key("nativeToken")
.get_opt_key("denom")
.parse_string()
.unwrap_or("");
NativeToken {
decimals: native_token_decimals,
denom: native_token_denom.to_owned(),
}
}
pub fn build_connection_conf(
domain_protocol: HyperlaneDomainProtocol,
rpcs: &[Url],
@ -195,10 +217,12 @@ pub fn build_connection_conf(
.next()
.map(|url| ChainConnectionConf::Fuel(h_fuel::ConnectionConf { url: url.clone() })),
HyperlaneDomainProtocol::Sealevel => rpcs.iter().next().map(|url| {
ChainConnectionConf::Sealevel(h_sealevel::ConnectionConf {
url: url.clone(),
ChainConnectionConf::Sealevel(build_sealevel_connection_conf(
url,
chain,
err,
operation_batch,
})
))
}),
HyperlaneDomainProtocol::Cosmos => {
build_cosmos_connection_conf(rpcs, chain, err, operation_batch)

@ -4,6 +4,7 @@ use derive_new::new;
use eyre::{bail, Result};
use hyperlane_core::{ReorgEvent, SignedAnnouncement, SignedCheckpointWithMessageId};
use std::fmt;
use tracing::{error, info, instrument};
use ya_gcp::{
storage::{
api::{error::HttpStatusError, http::StatusCode, Error},
@ -16,6 +17,7 @@ const LATEST_INDEX_KEY: &str = "gcsLatestIndexKey";
const METADATA_KEY: &str = "gcsMetadataKey";
const ANNOUNCEMENT_KEY: &str = "gcsAnnouncementKey";
const REORG_FLAG_KEY: &str = "gcsReorgFlagKey";
/// Path to GCS users_secret file
pub const GCS_USER_SECRET: &str = "GCS_USER_SECRET";
/// Path to GCS Service account key
@ -80,12 +82,14 @@ pub struct GcsStorageClient {
inner: StorageClient,
// bucket name of this client's storage
bucket: String,
// folder name of this client's storage
folder: Option<String>,
}
impl GcsStorageClientBuilder {
/// Instantiates `ya_gcp:StorageClient` based on provided auth method
/// # Param
/// * `baucket_name` - String name of target bucket to work with, will be used by all store and get ops
/// * `bucket_name` - String name of target bucket to work with, will be used by all store and get ops
pub async fn build(
self,
bucket_name: impl Into<String>,
@ -94,21 +98,71 @@ impl GcsStorageClientBuilder {
let inner = ClientBuilder::new(ClientBuilderConfig::new().auth_flow(self.auth))
.await?
.build_storage_client();
let bucket = if let Some(folder) = folder {
format! {"{}/{}", bucket_name.into(), folder}
} else {
bucket_name.into()
};
Ok(GcsStorageClient { inner, bucket })
let bucket = bucket_name.into();
let mut processed_folder = folder;
if let Some(ref mut folder_str) = processed_folder {
if folder_str.ends_with('/') {
folder_str.truncate(folder_str.trim_end_matches('/').len());
info!(
"Trimmed trailing '/' from folder name. New folder: '{}'",
folder_str
);
}
}
GcsStorageClient::validate_bucket_name(&bucket)?;
Ok(GcsStorageClient {
inner,
bucket,
folder: processed_folder,
})
}
}
impl GcsStorageClient {
// convenience formatter
// Convenience formatter
fn get_checkpoint_key(index: u32) -> String {
format!("checkpoint_{index}_with_id.json")
}
fn object_path(&self, object_name: &str) -> String {
if let Some(folder) = &self.folder {
format!("{}/{}", folder, object_name)
} else {
object_name.to_string()
}
}
fn validate_bucket_name(bucket: &str) -> Result<()> {
if bucket.contains('/') {
error!("Bucket name '{}' has an invalid symbol '/'", bucket);
bail!("Bucket name '{}' has an invalid symbol '/'", bucket)
} else {
Ok(())
}
}
/// Uploads data to GCS and logs the result.
#[instrument(skip(self, data))]
async fn upload_and_log(&self, object_name: &str, data: Vec<u8>) -> Result<()> {
match self
.inner
.insert_object(&self.bucket, object_name, data)
.await
{
Ok(_) => {
info!("Successfully uploaded to '{}'", object_name);
Ok(())
}
Err(e) => {
error!("Failed to upload to '{}': {:?}", object_name, e);
Err(e.into())
}
}
}
// #test only method[s]
#[cfg(test)]
pub(crate) async fn get_by_path(&self, path: impl AsRef<str>) -> Result<()> {
@ -117,11 +171,12 @@ impl GcsStorageClient {
}
}
// required by `CheckpointSyncer`
// Required by `CheckpointSyncer`
impl fmt::Debug for GcsStorageClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("S3Storage")
f.debug_struct("GcsStorageClient")
.field("bucket", &self.bucket)
.field("folder", &self.folder)
.finish()
}
}
@ -129,6 +184,7 @@ impl fmt::Debug for GcsStorageClient {
#[async_trait]
impl CheckpointSyncer for GcsStorageClient {
/// Read the highest index of this Syncer
#[instrument(skip(self))]
async fn latest_index(&self) -> Result<Option<u32>> {
match self.inner.get_object(&self.bucket, LATEST_INDEX_KEY).await {
Ok(data) => Ok(Some(serde_json::from_slice(data.as_ref())?)),
@ -144,15 +200,14 @@ impl CheckpointSyncer for GcsStorageClient {
}
/// Writes the highest index of this Syncer
#[instrument(skip(self, index))]
async fn write_latest_index(&self, index: u32) -> Result<()> {
let d = serde_json::to_vec(&index)?;
self.inner
.insert_object(&self.bucket, LATEST_INDEX_KEY, d)
.await?;
Ok(())
let data = serde_json::to_vec(&index)?;
self.upload_and_log(LATEST_INDEX_KEY, data).await
}
/// Update the latest index of this syncer if necessary
#[instrument(skip(self, index))]
async fn update_latest_index(&self, index: u32) -> Result<()> {
let curr = self.latest_index().await?.unwrap_or(0);
if index > curr {
@ -162,6 +217,7 @@ impl CheckpointSyncer for GcsStorageClient {
}
/// Attempt to fetch the signed (checkpoint, messageId) tuple at this index
#[instrument(skip(self, index))]
async fn fetch_checkpoint(&self, index: u32) -> Result<Option<SignedCheckpointWithMessageId>> {
match self
.inner
@ -179,56 +235,64 @@ impl CheckpointSyncer for GcsStorageClient {
}
/// Write the signed (checkpoint, messageId) tuple to this syncer
#[instrument(skip(self, signed_checkpoint))]
async fn write_checkpoint(
&self,
signed_checkpoint: &SignedCheckpointWithMessageId,
) -> Result<()> {
self.inner
.insert_object(
&self.bucket,
GcsStorageClient::get_checkpoint_key(signed_checkpoint.value.index),
serde_json::to_vec(signed_checkpoint)?,
)
.await?;
Ok(())
let object_name = Self::get_checkpoint_key(signed_checkpoint.value.index);
let data = serde_json::to_vec(signed_checkpoint)?;
self.upload_and_log(&object_name, data).await
}
/// Write the agent metadata to this syncer
#[instrument(skip(self, metadata))]
async fn write_metadata(&self, metadata: &AgentMetadata) -> Result<()> {
let serialized_metadata = serde_json::to_string_pretty(metadata)?;
self.inner
.insert_object(&self.bucket, METADATA_KEY, serialized_metadata)
.await?;
Ok(())
let object_name = self.object_path(METADATA_KEY);
let data = serde_json::to_string_pretty(metadata)?.into_bytes();
self.upload_and_log(&object_name, data).await
}
/// Write the signed announcement to this syncer
async fn write_announcement(&self, signed_announcement: &SignedAnnouncement) -> Result<()> {
self.inner
.insert_object(
&self.bucket,
ANNOUNCEMENT_KEY,
serde_json::to_string(signed_announcement)?,
)
.await?;
Ok(())
#[instrument(skip(self, announcement))]
async fn write_announcement(&self, announcement: &SignedAnnouncement) -> Result<()> {
let object_name = self.object_path(ANNOUNCEMENT_KEY);
let data = serde_json::to_string(announcement)?.into_bytes();
self.upload_and_log(&object_name, data).await
}
/// Return the announcement storage location for this syncer
#[instrument(skip(self))]
fn announcement_location(&self) -> String {
format!("gs://{}/{}", &self.bucket, ANNOUNCEMENT_KEY)
let location = format!(
"gs://{}/{}",
&self.bucket,
self.object_path(ANNOUNCEMENT_KEY)
);
info!("Announcement storage location: '{}'", location);
location
}
async fn write_reorg_status(&self, reorged_event: &ReorgEvent) -> Result<()> {
let serialized_metadata = serde_json::to_string_pretty(reorged_event)?;
self.inner
.insert_object(&self.bucket, REORG_FLAG_KEY, serialized_metadata)
.await?;
Ok(())
/// Write the reorg status to this syncer
#[instrument(skip(self, reorg_event))]
async fn write_reorg_status(&self, reorg_event: &ReorgEvent) -> Result<()> {
let object_name = REORG_FLAG_KEY;
let data = serde_json::to_string_pretty(reorg_event)?.into_bytes();
self.upload_and_log(object_name, data).await
}
/// Read the reorg status from this syncer
#[instrument(skip(self))]
async fn reorg_status(&self) -> Result<Option<ReorgEvent>> {
Ok(None)
match self.inner.get_object(&self.bucket, REORG_FLAG_KEY).await {
Ok(data) => Ok(Some(serde_json::from_slice(data.as_ref())?)),
Err(e) => match e {
ObjectError::Failure(Error::HttpStatus(HttpStatusError(StatusCode::NOT_FOUND))) => {
Ok(None)
}
_ => bail!(e),
},
}
}
}

@ -49,6 +49,8 @@ pub struct TxnInfo {
/// If the txn has been processed, we can also report some additional
/// information.
pub receipt: Option<TxnReceiptInfo>,
/// Raw input data of a transaction
pub raw_input_data: Option<Vec<u8>>,
}
/// Information about the execution of a transaction.

@ -17,6 +17,7 @@ pub use indexing::*;
pub use log_metadata::*;
pub use merkle_tree::*;
pub use message::*;
pub use native_token::NativeToken;
pub use reorg::*;
pub use transaction::*;
@ -33,6 +34,7 @@ mod indexing;
mod log_metadata;
mod merkle_tree;
mod message;
mod native_token;
mod reorg;
mod serialize;
mod transaction;

@ -0,0 +1,8 @@
/// Chain native token denomination and number of decimal places
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct NativeToken {
/// The number of decimal places in token which can be expressed by denomination
pub decimals: u32,
/// Denomination of the token
pub denom: String,
}

@ -5,7 +5,7 @@ use std::str::FromStr;
#[cfg(feature = "float")]
use std::time::Duration;
use crate::{KnownHyperlaneDomain, H160, H256};
use crate::{KnownHyperlaneDomain, H160, H256, U256};
/// Converts a hex or base58 string to an H256.
pub fn hex_or_base58_to_h256(string: &str) -> Result<H256> {
@ -62,6 +62,17 @@ pub fn bytes_to_hex(bytes: &[u8]) -> String {
format!("0x{}", hex::encode(bytes))
}
/// Exponent value for atto units (10^-18).
const ATTO_EXPONENT: u32 = 18;
/// Converts `value` expressed with `decimals` into `atto` (`10^-18`) decimals.
pub fn to_atto(value: U256, decimals: u32) -> Option<U256> {
assert!(decimals <= ATTO_EXPONENT);
let exponent = ATTO_EXPONENT - decimals;
let coefficient = U256::from(10u128.pow(exponent));
value.checked_mul(coefficient)
}
/// Format a domain id as a name if it is known or just the number if not.
pub fn fmt_domain(domain: u32) -> String {
#[cfg(feature = "strum")]

@ -1,8 +1,10 @@
use std::{collections::BTreeMap, path::PathBuf};
use hyperlane_cosmos::{NativeToken, RawCosmosAmount};
use hyperlane_cosmwasm_interface::types::bech32_decode;
use hyperlane_core::NativeToken;
use hyperlane_cosmos::RawCosmosAmount;
use super::{cli::OsmosisCLI, CosmosNetwork};
#[derive(serde::Serialize, serde::Deserialize)]

@ -192,19 +192,25 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
app_config: &TokenConfig,
program_id: Pubkey,
) {
if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount {
if matches!(
app_config.token_type,
TokenType::Collateral(_) | TokenType::Synthetic(_)
) {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
let try_fund_ata_payer = |ctx: &mut Context, client: &RpcClient| {
if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount {
if matches!(
app_config.token_type,
TokenType::Collateral(_) | TokenType::Synthetic(_)
) {
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount);
}
}
}
};
let (token_pda, _token_bump) =
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id);
if account_exists(client, &token_pda).unwrap() {
println!("Warp route token already exists, skipping init");
// Fund the ATA payer up to the specified amount.
try_fund_ata_payer(ctx, client);
return;
}
@ -398,6 +404,8 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
println!("Set the {authority} authority to the mint account. Status: {status}");
}
}
try_fund_ata_payer(ctx, client);
}
/// Sets gas router configs on all deployable chains.

@ -80,6 +80,45 @@
],
"technicalStack": "arbitrumnitro"
},
"apechain": {
"blockExplorers": [
{
"apiUrl": "https://apechain.calderaexplorer.xyz/api",
"family": "blockscout",
"name": "ApeChain Explorer",
"url": "https://apechain.calderaexplorer.xyz"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 0.2,
"reorgPeriod": 5
},
"chainId": 33139,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "ApeChain",
"domainId": 33139,
"gasCurrencyCoinGeckoId": "apecoin",
"index": {
"from": 1759561
},
"name": "apechain",
"nativeToken": {
"decimals": 18,
"name": "ApeCoin",
"symbol": "APE"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.apechain.com/http"
}
],
"technicalStack": "arbitrumnitro"
},
"arbitrum": {
"blockExplorers": [
{
@ -126,6 +165,45 @@
],
"technicalStack": "arbitrumnitro"
},
"arbitrumnova": {
"blockExplorers": [
{
"apiUrl": "https://api-nova.arbiscan.io/api",
"family": "etherscan",
"name": "Arbiscan Nova",
"url": "https://nova.arbiscan.io/"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 42170,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Arbitrum Nova",
"domainId": 42170,
"gasCurrencyCoinGeckoId": "ethereum",
"index": {
"from": 78794208
},
"name": "arbitrumnova",
"nativeToken": {
"decimals": 18,
"name": "Ethereum",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://nova.arbitrum.io/rpc"
}
],
"technicalStack": "arbitrumnitro"
},
"astar": {
"blockExplorers": [
{
@ -138,7 +216,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 13,
"reorgPeriod": 32
"reorgPeriod": "finalized"
},
"chainId": 592,
"deployer": {
@ -245,6 +323,42 @@
],
"technicalStack": "other"
},
"b3": {
"blockExplorers": [
{
"apiUrl": "https://explorer.b3.fun/api",
"family": "blockscout",
"name": "B3 Explorer",
"url": "https://explorer.b3.fun"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 5
},
"chainId": 8333,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "B3",
"domainId": 8333,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "b3",
"nativeToken": {
"decimals": 18,
"name": "Ethereum",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://mainnet-rpc.b3.fun"
}
],
"technicalStack": "opstack"
},
"base": {
"blockExplorers": [
{
@ -419,7 +533,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 15
"reorgPeriod": "finalized"
},
"chainId": 56,
"deployer": {
@ -899,6 +1013,57 @@
],
"technicalStack": "arbitrumnitro"
},
"fantom": {
"blockExplorers": [
{
"apiUrl": "https://api.ftmscan.com/api",
"family": "etherscan",
"name": "FTMScan",
"url": "https://ftmscan.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 250,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Fantom Opera",
"domainId": 250,
"gasCurrencyCoinGeckoId": "fantom",
"name": "fantom",
"nativeToken": {
"decimals": 18,
"name": "Fantom",
"symbol": "FTM"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpcapi.fantom.network"
},
{
"http": "https://fantom-rpc.publicnode.com"
},
{
"http": "https://fantom-pokt.nodies.app"
},
{
"http": "https://rpc.fantom.network"
},
{
"http": "https://rpc2.fantom.network"
},
{
"http": "https://rpc3.fantom.network"
}
],
"technicalStack": "other"
},
"flare": {
"blockExplorers": [
{
@ -1111,6 +1276,91 @@
],
"technicalStack": "other"
},
"gravity": {
"blockExplorers": [
{
"apiUrl": "https://explorer.gravity.xyz/api",
"family": "blockscout",
"name": "Gravity Alpha Explorer",
"url": "https://explorer.gravity.xyz"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 5
},
"chainId": 1625,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Gravity Alpha Mainnet",
"displayNameShort": "Gravity",
"domainId": 1625,
"gasCurrencyCoinGeckoId": "g-token",
"index": {
"from": 13374779
},
"name": "gravity",
"nativeToken": {
"decimals": 18,
"name": "Gravity",
"symbol": "G"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.gravity.xyz"
}
],
"technicalStack": "arbitrumnitro"
},
"harmony": {
"blockExplorers": [
{
"apiUrl": "https://explorer.harmony.one/api",
"family": "blockscout",
"name": "Harmony Explorer",
"url": "https://explorer.harmony.one"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 1666600000,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Harmony One",
"domainId": 1666600000,
"gasCurrencyCoinGeckoId": "harmony",
"name": "harmony",
"nativeToken": {
"decimals": 18,
"name": "ONE",
"symbol": "ONE"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://api.harmony.one"
},
{
"http": "https://api.s0.t.hmny.io"
},
{
"http": "https://1rpc.io/one"
},
{
"http": "https://rpc.ankr.com/harmony"
}
],
"technicalStack": "other"
},
"immutablezkevm": {
"blockExplorers": [
{
@ -1199,13 +1449,22 @@
},
"injective": {
"bech32Prefix": "inj",
"blockExplorers": [],
"blockExplorers": [
{
"apiUrl": "https://www.mintscan.io/injective",
"family": "other",
"name": "Mintscan",
"url": "https://www.mintscan.io/injective"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 10
},
"canonicalAsset": "inj",
"chainId": "injective-1",
"contractAddressBytes": 20,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
@ -1213,11 +1472,19 @@
"displayName": "Injective",
"domainId": 6909546,
"gasCurrencyCoinGeckoId": "injective-protocol",
"gasPrice": {
"amount": "700000000",
"denom": "inj"
},
"grpcUrls": [
{
"http": "sentry.chain.grpc.injective.network:443"
"http": "https://injective-grpc.goldenratiostaking.net:443"
}
],
"index": {
"chunk": 25,
"from": 58419500
},
"name": "injective",
"nativeToken": {
"decimals": 18,
@ -1239,6 +1506,42 @@
"slip44": 118,
"technicalStack": "other"
},
"kaia": {
"blockExplorers": [
{
"apiUrl": "https://api-cypress.klaytnscope.com/api",
"family": "etherscan",
"name": "Kaiascope",
"url": "https://kaiascope.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 5
},
"chainId": 8217,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Kaia",
"domainId": 8217,
"gasCurrencyCoinGeckoId": "kaia",
"name": "kaia",
"nativeToken": {
"decimals": 18,
"name": "Kaia",
"symbol": "KLAY"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://public-en.node.kaia.io"
}
],
"technicalStack": "other"
},
"kroma": {
"blockExplorers": [
{
@ -1766,7 +2069,7 @@
"blocks": {
"confirmations": 2,
"estimateBlockTime": 12,
"reorgPeriod": 10
"reorgPeriod": "finalized"
},
"chainId": 1284,
"deployer": {
@ -1795,6 +2098,42 @@
"maxPriorityFeePerGas": 50000000000
}
},
"morph": {
"blockExplorers": [
{
"apiUrl": "https://explorer-api.morphl2.io/api",
"family": "blockscout",
"name": "Morph Explorer",
"url": "https://explorer.morphl2.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 4,
"reorgPeriod": 5
},
"chainId": 2818,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Morph",
"domainId": 2818,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "morph",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.morphl2.io"
}
],
"technicalStack": "other"
},
"neutron": {
"bech32Prefix": "neutron",
"blockExplorers": [
@ -1810,7 +2149,9 @@
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"canonicalAsset": "untrn",
"chainId": "neutron-1",
"contractAddressBytes": 32,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
@ -1818,12 +2159,19 @@
"displayName": "Neutron",
"domainId": 1853125230,
"gasCurrencyCoinGeckoId": "neutron-3",
"gasPrice": {
"amount": "0.0053",
"denom": "untrn"
},
"grpcUrls": [
{
"http": "grpc-kralum.neutron-1.neutron.org:80"
"http": "http://grpc-kralum.neutron-1.neutron.org:80"
}
],
"isTestnet": false,
"index": {
"chunk": 5,
"from": 4000000
},
"name": "neutron",
"nativeToken": {
"decimals": 6,
@ -1924,6 +2272,45 @@
],
"technicalStack": "opstack"
},
"orderly": {
"blockExplorers": [
{
"apiUrl": "https://explorer.orderly.network/api",
"family": "blockscout",
"name": "Orderly L2 Explorer",
"url": "https://explorer.orderly.network"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 291,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Orderly L2",
"domainId": 291,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "orderly",
"nativeToken": {
"decimals": 18,
"name": "Ethereum",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://rpc.orderly.network"
},
{
"http": "https://l2-orderly-mainnet-0.t.conduit.xyz"
}
],
"technicalStack": "opstack"
},
"osmosis": {
"bech32Prefix": "osmo",
"blockExplorers": [
@ -1939,7 +2326,9 @@
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"canonicalAsset": "uosmo",
"chainId": "osmosis-1",
"contractAddressBytes": 32,
"deployer": {
"name": "Mitosis",
"url": "https://mitosis.org"
@ -1947,11 +2336,19 @@
"displayName": "Osmosis",
"domainId": 875,
"gasCurrencyCoinGeckoId": "osmosis",
"gasPrice": {
"amount": "0.025",
"denom": "uosmo"
},
"grpcUrls": [
{
"http": "osmosis-grpc.publicnode.com:443"
"http": "https://osmosis-grpc.publicnode.com:443"
}
],
"index": {
"chunk": 10,
"from": 14389169
},
"isTestnet": false,
"name": "osmosis",
"nativeToken": {
@ -1989,7 +2386,7 @@
"blocks": {
"confirmations": 3,
"estimateBlockTime": 2,
"reorgPeriod": 256
"reorgPeriod": "finalized"
},
"chainId": 137,
"deployer": {
@ -2434,7 +2831,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 5,
"reorgPeriod": 5
"reorgPeriod": "finalized"
},
"chainId": 109,
"deployer": {
@ -2461,6 +2858,42 @@
],
"technicalStack": "other"
},
"snaxchain": {
"blockExplorers": [
{
"apiUrl": "https://explorer.snaxchain.io/api",
"family": "blockscout",
"name": "Snaxchain Mainnet Explorer",
"url": "https://explorer.snaxchain.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 5
},
"chainId": 2192,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "SnaxChain",
"domainId": 2192,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "snaxchain",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://mainnet.snaxchain.io"
}
],
"technicalStack": "opstack"
},
"solanamainnet": {
"blockExplorers": [
{
@ -2512,7 +2945,9 @@
"estimateBlockTime": 5,
"reorgPeriod": 1
},
"canonicalAsset": "ustrd",
"chainId": "stride-1",
"contractAddressBytes": 32,
"deployer": {
"name": "Stride Labs",
"url": "https://www.stride.zone"
@ -2520,12 +2955,19 @@
"displayName": "Stride",
"domainId": 745,
"gasCurrencyCoinGeckoId": "stride",
"gasPrice": {
"amount": "0.025",
"denom": "ustrd"
},
"grpcUrls": [
{
"http": "stride-grpc.polkachu.com:12290"
"http": "https://stride-grpc.publicnode.com:443"
}
],
"isTestnet": false,
"index": {
"chunk": 5,
"from": 9152000
},
"name": "stride",
"nativeToken": {
"decimals": 6,
@ -2637,7 +3079,7 @@
"blocks": {
"confirmations": 1,
"estimateBlockTime": 6,
"reorgPeriod": 10
"reorgPeriod": "finalized"
},
"chainId": 5845,
"deployer": {
@ -2732,7 +3174,7 @@
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://raas-backend.alchemy.com/rpc/worldchain-mainnet/rollup"
"http": "https://worldchain-mainnet.g.alchemy.com/public"
}
],
"technicalStack": "opstack"
@ -2817,6 +3259,42 @@
],
"technicalStack": "polygoncdk"
},
"zeronetwork": {
"blockExplorers": [
{
"apiUrl": "https://zero-network-api.calderaexplorer.xyz/api",
"family": "etherscan",
"name": "Zero Network Explorer",
"url": "https://zerion-explorer.vercel.app"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 0
},
"chainId": 543210,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Zero Network",
"domainId": 543210,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "zeronetwork",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://zero-network.calderachain.xyz"
}
],
"technicalStack": "zksync"
},
"zetachain": {
"blockExplorers": [
{
@ -2900,6 +3378,42 @@
],
"technicalStack": "opstack"
},
"zksync": {
"blockExplorers": [
{
"apiUrl": "https://block-explorer-api.mainnet.zksync.io/api",
"family": "etherscan",
"name": "zkSync Explorer",
"url": "https://explorer.zksync.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
"reorgPeriod": 0
},
"chainId": 324,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "zkSync",
"domainId": 324,
"gasCurrencyCoinGeckoId": "ethereum",
"name": "zksync",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "ethereum",
"rpcUrls": [
{
"http": "https://mainnet.era.zksync.io"
}
],
"technicalStack": "zksync"
},
"zoramainnet": {
"blockExplorers": [
{

@ -17,6 +17,15 @@
],
"type": "messageIdMultisigIsm"
},
"apechain": {
"threshold": 2,
"validators": [
"0x773d7fe6ffb1ba4de814c28044ff9a2d83a48221",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"arbitrum": {
"threshold": 3,
"validators": [
@ -28,6 +37,15 @@
],
"type": "messageIdMultisigIsm"
},
"arbitrumnova": {
"threshold": 2,
"validators": [
"0xd2a5e9123308d187383c87053811a2c21bd8af1f",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"astar": {
"threshold": 2,
"validators": [
@ -55,6 +73,15 @@
],
"type": "messageIdMultisigIsm"
},
"b3": {
"threshold": 2,
"validators": [
"0xd77b516730a836fc41934e7d5864e72c165b934e",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"base": {
"threshold": 3,
"validators": [
@ -118,7 +145,8 @@
"threshold": 2,
"validators": [
"0x478fb53c6860ae8fc35235ba0d38d49b13128226",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f"
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x101cE77261245140A0871f9407d6233C8230Ec47"
],
"type": "messageIdMultisigIsm"
},
@ -198,6 +226,15 @@
],
"type": "messageIdMultisigIsm"
},
"fantom": {
"threshold": 2,
"validators": [
"0xa779572028e634e16f26af5dfd4fa685f619457d",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"flare": {
"threshold": 2,
"validators": [
@ -244,6 +281,24 @@
],
"type": "messageIdMultisigIsm"
},
"gravity": {
"threshold": 2,
"validators": [
"0x23d549bf757a02a6f6068e9363196ecd958c974e",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"harmony": {
"threshold": 2,
"validators": [
"0xd677803a67651974b1c264171b5d7ca8838db8d5",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"immutablezkevm": {
"threshold": 2,
"validators": [
@ -271,6 +326,15 @@
],
"type": "messageIdMultisigIsm"
},
"kaia": {
"threshold": 2,
"validators": [
"0x9de0b3abb221d19719882fa4d61f769fdc2be9a4",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"kroma": {
"threshold": 2,
"validators": [
@ -290,11 +354,14 @@
"type": "messageIdMultisigIsm"
},
"lisk": {
"threshold": 2,
"threshold": 4,
"validators": [
"0xc0b282aa5bac43fee83cf71dc3dd1797c1090ea5",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
"0x0d4c1394a255568ec0ecd11795b28d1bda183ca4",
"0x3DA4ee2801Ec6CC5faD73DBb94B10A203ADb3d9e",
"0x4df6e8878992c300e7bfe98cac6bf7d3408b9cbf",
"0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3",
"0xf0da628f3fb71652d48260bad4691054045832ce"
],
"type": "messageIdMultisigIsm"
},
@ -375,12 +442,14 @@
"type": "messageIdMultisigIsm"
},
"mode": {
"threshold": 3,
"threshold": 4,
"validators": [
"0x7eb2e1920a4166c19d6884c1cec3d2cf356fc9b7",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36",
"0x7e29608c6e5792bbf9128599ca309be0728af7b4",
"0x101cE77261245140A0871f9407d6233C8230Ec47"
"0x0d4c1394a255568ec0ecd11795b28d1bda183ca4",
"0x65C140e3a05F33192384AffEF985696Fe3cDDE42",
"0x20eade18ea2af6dfd54d72b3b5366b40fcb47f4b",
"0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3",
"0x485a4f0009d9afbbf44521016f9b8cdd718e36ea"
],
"type": "messageIdMultisigIsm"
},
@ -403,6 +472,15 @@
],
"type": "messageIdMultisigIsm"
},
"morph": {
"threshold": 2,
"validators": [
"0x4884535f393151ec419add872100d352f71af380",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"neutron": {
"threshold": 4,
"validators": [
@ -426,13 +504,23 @@
"type": "messageIdMultisigIsm"
},
"optimism": {
"threshold": 3,
"threshold": 4,
"validators": [
"0x20349eadc6c72e94ce38268b96692b1a5c20de4f",
"0x5b7d47b76c69740462432f6a5a0ca5005e014157",
"0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8",
"0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b",
"0x5450447aee7b544c462c9352bef7cad049b0c2dc"
"0x0d4c1394a255568ec0ecd11795b28d1bda183ca4",
"0xd8c1cCbfF28413CE6c6ebe11A3e29B0D8384eDbB",
"0x1b9e5f36c4bfdb0e3f0df525ef5c888a4459ef99",
"0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3",
"0xf9dfaa5c20ae1d84da4b2696b8dc80c919e48b12"
],
"type": "messageIdMultisigIsm"
},
"orderly": {
"threshold": 2,
"validators": [
"0xec3dc91f9fa2ad35edf5842aa764d5573b778bb6",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
@ -499,11 +587,12 @@
"type": "messageIdMultisigIsm"
},
"redstone": {
"threshold": 2,
"threshold": 3,
"validators": [
"0x1400b9737007f7978d8b4bbafb4a69c83f0641a7",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36",
"0x101cE77261245140A0871f9407d6233C8230Ec47"
],
"type": "messageIdMultisigIsm"
},
@ -554,6 +643,15 @@
],
"type": "messageIdMultisigIsm"
},
"snaxchain": {
"threshold": 2,
"validators": [
"0x2c25829ae32a772d2a49f6c4b34f8b01fd03ef9e",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"solanamainnet": {
"threshold": 3,
"validators": [
@ -577,7 +675,7 @@
"0x87460dcEd16a75AECdBffD4189111d30B099f5b0",
"0xf54982134e52Eb7253236943FBffE0886C5bde0C",
"0x5937b7cE1029C3Ec4bD8e1AaCc0C0f9422654D7d",
"0x3a446ed2923c08445af06e53f0acb558c0e0413c"
"0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b"
],
"type": "messageIdMultisigIsm"
},
@ -645,6 +743,15 @@
],
"type": "messageIdMultisigIsm"
},
"zeronetwork": {
"threshold": 2,
"validators": [
"0x1bd9e3f8a90ea1a13b0f2838a1858046368aad87",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36"
],
"type": "messageIdMultisigIsm"
},
"zetachain": {
"threshold": 3,
"validators": [
@ -665,6 +772,16 @@
],
"type": "messageIdMultisigIsm"
},
"zksync": {
"threshold": 3,
"validators": [
"0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a",
"0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f",
"0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36",
"0x75237d42ce8ea27349a0254ada265db94157e0c1"
],
"type": "messageIdMultisigIsm"
},
"zoramainnet": {
"threshold": 3,
"validators": [

@ -0,0 +1,10 @@
{
"solanamainnet": {
"hex": "0x709e5cd98def7e0acd1cd2f6b7b56a783f1ec212b34a6903c33fc6e2823a9ee9",
"base58": "8acihSm2QTGswniKgdgr4JBvJihZ1cakfvbqWCPBLoSp"
},
"eclipsemainnet": {
"hex": "0x6b0fb225a14358827459d705f4c98e87a7b243b3638f111779b6fb605c5d8306",
"base58": "8CvWJS7SPtauAXinJUURkBDLsGUXWiiTdENkEFUPjQ9j"
}
}

@ -0,0 +1,17 @@
{
"solanamainnet": {
"type": "collateral",
"decimals": 6,
"interchainGasPaymaster": "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF",
"token": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE",
"splTokenProgram": "token"
},
"eclipsemainnet": {
"type": "synthetic",
"decimals": 6,
"name": "Orca",
"symbol": "ORCA",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/13ef3e3b8447ba446c562b7fb9b769324d35e6c0/deployments/warp_routes/ORCA/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
}
}

@ -0,0 +1,14 @@
{
"solanamainnet": {
"hex": "0x9f9fcd70f9772f30e4cd5646890b640e88c6b82a8417f813aaf81dac0cd468b9",
"base58": "Bk79wMjvpPCh5iQcCEjPWFcG1V2TfgdwaBsWBEYFYSNU"
},
"eclipsemainnet": {
"hex": "0x4571f343c286dc844237b550560b90bf5e748a551d60721599b1ac0408d9c08d",
"base58": "5g5ujyYUNvdydwyDVCpZwPpgYRqH5RYJRi156cxyE3me"
},
"ethereum": {
"hex": "0x000000000000000000000000647c621ceb36853ef6a907e397adf18568e70543",
"base58": "1111111111112QCPXWTcdXUC6jAEEnqQtpRo5TXG"
}
}

@ -0,0 +1,23 @@
{
"solanamainnet": {
"type": "collateral",
"decimals": 6,
"interchainGasPaymaster": "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF",
"token": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
"splTokenProgram": "token"
},
"eclipsemainnet": {
"type": "synthetic",
"decimals": 6,
"name": "Tether USD",
"symbol": "USDT",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/13ef3e3b8447ba446c562b7fb9b769324d35e6c0/deployments/warp_routes/USDT/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
},
"ethereum": {
"type": "collateral",
"decimals": 6,
"token": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"foreignDeployment": "0x647C621CEb36853Ef6A907E397Adf18568E70543"
}
}

@ -0,0 +1,10 @@
{
"eclipsemainnet": {
"hex": "0x8751a99baf21a9cbc78e891d99ad8ca04bca545f5b668671a07cd0ec107923d2",
"base58": "A7EGCDYFw5R7Jfm6cYtKvY8dmkrYMgwRCJFkyQwpHTYu"
},
"ethereum": {
"hex": "0x0000000000000000000000005b4e223de74ef8c3218e66eecc541003cab3121a",
"base58": "1111111111112Gn8JRuL53pFjhHVdUdsv2Rzocp9"
}
}

@ -0,0 +1,16 @@
{
"eclipsemainnet": {
"type": "synthetic",
"decimals": 8,
"name": "Wrapped BTC",
"symbol": "WBTC",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/13ef3e3b8447ba446c562b7fb9b769324d35e6c0/deployments/warp_routes/WBTC/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
},
"ethereum": {
"type": "collateral",
"decimals": 8,
"token": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
"foreignDeployment": "0x5B4e223DE74ef8c3218e66EEcC541003CAB3121A"
}
}

@ -0,0 +1,10 @@
{
"eclipsemainnet": {
"hex": "0x6196f9127d2caa0f4265d0870d077e6bf979c585b87cceb7537fc6d2c81729b7",
"base58": "7Zx4wU1QAw98MfvnPFqRh1oyumek7G5VAX6TKB3U1tcn"
},
"ethereum": {
"hex": "0x000000000000000000000000ef899e92da472e014be795ecce948308958e25a2",
"base58": "1111111111114LZDAaWurQERWF32Wqjijy9NWQRX"
}
}

@ -0,0 +1,17 @@
{
"eclipsemainnet": {
"type": "synthetic",
"decimals": 9,
"remoteDecimals": 18,
"name": "Super Symbiotic LRT",
"symbol": "weETHs",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/6ce62e9330ae1af0786f60d9fe6da8541b6b2988/deployments/warp_routes/weETHs/metadata.json",
"interchainGasPaymaster": "3Wp4qKkgf4tjXz1soGyTSndCgBPLZFSrZkiDZ8Qp9EEj"
},
"ethereum": {
"type": "collateral",
"decimals": 18,
"token": "0x917cee801a67f933f2e6b33fc0cd1ed2d5909d88",
"foreignDeployment": "0xef899e92DA472E014bE795Ecce948308958E25A2"
}
}

@ -1,5 +1,28 @@
# @hyperlane-xyz/core
## 5.7.1
### Patch Changes
- Updated dependencies [e3b97c455]
- @hyperlane-xyz/utils@6.0.0
## 5.7.0
### Minor Changes
- 469f2f340: Checking for sufficient fees in `AbstractMessageIdAuthHook` and refund surplus
- f26453ee5: Added msg.value to preverifyMessage to commit it as part of external hook payload
- 0640f837c: disabled the ICARouter's ability to change hook given that the user doesn't expect the hook to change after they deploy their ICA account. Hook is not part of the derivation like ism on the destination chain and hence, cannot be configured custom by the user.
- a82b4b4cb: Made processInboundMessage payable to send value via mailbox.process
### Patch Changes
- Updated dependencies [e104cf6aa]
- Updated dependencies [04108155d]
- Updated dependencies [39a9b2038]
- @hyperlane-xyz/utils@5.7.0
## 5.6.1
### Patch Changes

@ -56,6 +56,7 @@ contract Mailbox is
address processor;
uint48 blockNumber;
}
mapping(bytes32 => Delivery) internal deliveries;
// ============ Events ============

@ -7,5 +7,5 @@ pragma solidity >=0.6.11;
**/
abstract contract PackageVersioned {
// GENERATED CODE - DO NOT EDIT
string public constant PACKAGE_VERSION = "5.6.1";
string public constant PACKAGE_VERSION = "5.7.1";
}

@ -14,17 +14,16 @@ pragma solidity >=0.8.0;
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {Message} from "../libs/Message.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {AbstractPostDispatchHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {Mailbox} from "../Mailbox.sol";
import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol";
import {Message} from "../libs/Message.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {MailboxClient} from "../client/MailboxClient.sol";
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ArbSys} from "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
/**
@ -35,13 +34,14 @@ import {ArbSys} from "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
*/
contract ArbL2ToL1Hook is AbstractMessageIdAuthHook {
using StandardHookMetadata for bytes;
using Message for bytes;
// ============ Constants ============
// precompile contract on L2 for sending messages to L1
ArbSys public immutable arbSys;
// Immutable quote amount
uint256 public immutable GAS_QUOTE;
// child hook to call first
IPostDispatchHook public immutable childHook;
// ============ Constructor ============
@ -50,21 +50,24 @@ contract ArbL2ToL1Hook is AbstractMessageIdAuthHook {
uint32 _destinationDomain,
bytes32 _ism,
address _arbSys,
uint256 _gasQuote
address _childHook
) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) {
arbSys = ArbSys(_arbSys);
GAS_QUOTE = _gasQuote;
childHook = AbstractPostDispatchHook(_childHook);
}
/// @inheritdoc IPostDispatchHook
function hookType() external pure override returns (uint8) {
return uint8(IPostDispatchHook.Types.ARB_L2_TO_L1);
}
/// @inheritdoc AbstractPostDispatchHook
function _quoteDispatch(
bytes calldata,
bytes calldata
bytes calldata metadata,
bytes calldata message
) internal view override returns (uint256) {
return GAS_QUOTE;
return
metadata.msgValue(0) + childHook.quoteDispatch(metadata, message);
}
// ============ Internal functions ============
@ -72,8 +75,16 @@ contract ArbL2ToL1Hook is AbstractMessageIdAuthHook {
/// @inheritdoc AbstractMessageIdAuthHook
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
bytes calldata message
) internal override {
bytes memory payload = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(message.id(), metadata.msgValue(0))
);
childHook.postDispatch{
value: childHook.quoteDispatch(metadata, message)
}(metadata, message);
arbSys.sendTxToL1{value: metadata.msgValue(0)}(
TypeCasts.bytes32ToAddress(ism),
payload

@ -14,10 +14,13 @@ pragma solidity >=0.8.0;
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {Message} from "../libs/Message.sol";
import {AbstractPostDispatchHook, AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {InterchainGasPaymaster} from "./igp/InterchainGasPaymaster.sol";
// ============ External Imports ============
import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenger.sol";
@ -30,13 +33,16 @@ import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenge
*/
contract OPL2ToL1Hook is AbstractMessageIdAuthHook {
using StandardHookMetadata for bytes;
using Message for bytes;
// ============ Constants ============
// precompile contract on L2 for sending messages to L1
ICrossDomainMessenger public immutable l2Messenger;
// Immutable quote amount
uint32 public immutable GAS_QUOTE;
// child hook to call first
IPostDispatchHook public immutable childHook;
// Minimum gas limit that the message can be executed with - OP specific
uint32 public constant MIN_GAS_LIMIT = 300_000;
// ============ Constructor ============
@ -45,10 +51,10 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook {
uint32 _destinationDomain,
bytes32 _ism,
address _l2Messenger,
uint32 _gasQuote
address _childHook
) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) {
GAS_QUOTE = _gasQuote;
l2Messenger = ICrossDomainMessenger(_l2Messenger);
childHook = AbstractPostDispatchHook(_childHook);
}
/// @inheritdoc IPostDispatchHook
@ -58,10 +64,11 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook {
/// @inheritdoc AbstractPostDispatchHook
function _quoteDispatch(
bytes calldata,
bytes calldata
bytes calldata metadata,
bytes calldata message
) internal view override returns (uint256) {
return GAS_QUOTE;
return
metadata.msgValue(0) + childHook.quoteDispatch(metadata, message);
}
// ============ Internal functions ============
@ -69,16 +76,20 @@ contract OPL2ToL1Hook is AbstractMessageIdAuthHook {
/// @inheritdoc AbstractMessageIdAuthHook
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
bytes calldata message
) internal override {
require(
msg.value >= metadata.msgValue(0) + GAS_QUOTE,
"OPL2ToL1Hook: insufficient msg.value"
bytes memory payload = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(message.id(), metadata.msgValue(0))
);
childHook.postDispatch{
value: childHook.quoteDispatch(metadata, message)
}(metadata, message);
l2Messenger.sendMessage{value: metadata.msgValue(0)}(
TypeCasts.bytes32ToAddress(ism),
payload,
GAS_QUOTE
MIN_GAS_LIMIT
);
}
}

@ -18,6 +18,7 @@ import {AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {Message} from "../libs/Message.sol";
import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
// ============ External Imports ============
@ -32,6 +33,7 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
*/
contract OPStackHook is AbstractMessageIdAuthHook {
using StandardHookMetadata for bytes;
using Message for bytes;
// ============ Constants ============
@ -60,21 +62,22 @@ contract OPStackHook is AbstractMessageIdAuthHook {
// ============ Internal functions ============
function _quoteDispatch(
bytes calldata,
bytes calldata metadata,
bytes calldata
) internal pure override returns (uint256) {
return 0; // gas subsidized by the L2
return metadata.msgValue(0); // gas subsidized by the L2
}
/// @inheritdoc AbstractMessageIdAuthHook
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
bytes calldata message
) internal override {
require(
metadata.msgValue(0) < 2 ** 255,
"OPStackHook: msgValue must be less than 2 ** 255"
bytes memory payload = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(message.id(), metadata.msgValue(0))
);
l1Messenger.sendMessage{value: metadata.msgValue(0)}(
TypeCasts.bytes32ToAddress(ism),
payload,

@ -19,6 +19,7 @@ import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {Message} from "../libs/Message.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol";
// ============ External Imports ============
import {FxBaseRootTunnel} from "fx-portal/contracts/tunnel/FxBaseRootTunnel.sol";
@ -31,6 +32,7 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
*/
contract PolygonPosHook is AbstractMessageIdAuthHook, FxBaseRootTunnel {
using StandardHookMetadata for bytes;
using Message for bytes;
// ============ Constructor ============
@ -56,22 +58,27 @@ contract PolygonPosHook is AbstractMessageIdAuthHook, FxBaseRootTunnel {
// ============ Internal functions ============
function _quoteDispatch(
bytes calldata,
bytes calldata metadata,
bytes calldata
) internal pure override returns (uint256) {
return 0;
return metadata.msgValue(0);
}
/// @inheritdoc AbstractMessageIdAuthHook
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
bytes calldata message
) internal override {
require(
metadata.msgValue(0) == 0,
"PolygonPosHook: does not support msgValue"
);
require(msg.value == 0, "PolygonPosHook: does not support msgValue");
bytes memory payload = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(message.id(), metadata.msgValue(0))
);
_sendMessageToChild(payload);
}

@ -15,9 +15,12 @@ pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {Message} from "../../libs/Message.sol";
import {StandardHookMetadata} from "../libs/StandardHookMetadata.sol";
import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol";
import {IMessageDispatcher} from "../../interfaces/hooks/IMessageDispatcher.sol";
import {AbstractMessageIdAuthHook} from "../libs/AbstractMessageIdAuthHook.sol";
import {AbstractMessageIdAuthorizedIsm} from "../../isms/hook/AbstractMessageIdAuthorizedIsm.sol";
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
@ -28,6 +31,9 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
* any of the 5164 adapters.
*/
contract ERC5164Hook is AbstractMessageIdAuthHook {
using StandardHookMetadata for bytes;
using Message for bytes;
IMessageDispatcher public immutable dispatcher;
constructor(
@ -53,11 +59,15 @@ contract ERC5164Hook is AbstractMessageIdAuthHook {
}
function _sendMessageId(
bytes calldata,
/* metadata */
bytes memory payload
bytes calldata metadata,
bytes calldata message
) internal override {
require(msg.value == 0, "ERC5164Hook: no value allowed");
bytes memory payload = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(message.id(), metadata.msgValue(0))
);
dispatcher.dispatchMessage(
destinationDomain,
TypeCasts.bytes32ToAddress(ism),

@ -18,6 +18,7 @@ import {TypeCasts} from "../../libs/TypeCasts.sol";
import {Indexed} from "../../libs/Indexed.sol";
import {IPostDispatchHook} from "../../interfaces/hooks/IPostDispatchHook.sol";
import {AbstractMessageIdAuthHook} from "../libs/AbstractMessageIdAuthHook.sol";
import {AbstractMessageIdAuthorizedIsm} from "../../isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {StandardHookMetadata} from "../libs/StandardHookMetadata.sol";
struct LayerZeroV2Metadata {
@ -55,8 +56,13 @@ contract LayerZeroV2Hook is AbstractMessageIdAuthHook {
/// @inheritdoc AbstractMessageIdAuthHook
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
bytes calldata message
) internal override {
bytes memory payload = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(message.id(), metadata.msgValue(0))
);
bytes calldata lZMetadata = metadata.getCustomMetadata();
(
uint32 eid,
@ -72,7 +78,9 @@ contract LayerZeroV2Hook is AbstractMessageIdAuthHook {
options,
false // payInLzToken
);
lZEndpoint.send{value: msg.value}(msgParams, refundAddress);
uint256 quote = _quoteDispatch(metadata, message);
lZEndpoint.send{value: quote}(msgParams, refundAddress);
}
/// @dev payInZRO is hardcoded to false because zro tokens should not be directly accepted
@ -96,7 +104,7 @@ contract LayerZeroV2Hook is AbstractMessageIdAuthHook {
message.senderAddress()
);
return msgFee.nativeFee;
return metadata.msgValue(0) + msgFee.nativeFee;
}
/**

@ -22,6 +22,9 @@ import {Message} from "../../libs/Message.sol";
import {StandardHookMetadata} from "./StandardHookMetadata.sol";
import {MailboxClient} from "../../client/MailboxClient.sol";
// ============ External Imports ============
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
/**
* @title AbstractMessageIdAuthHook
* @notice Message hook to inform an Abstract Message ID ISM of messages published through
@ -31,8 +34,10 @@ abstract contract AbstractMessageIdAuthHook is
AbstractPostDispatchHook,
MailboxClient
{
using Address for address payable;
using StandardHookMetadata for bytes;
using Message for bytes;
using TypeCasts for bytes32;
// ============ Constants ============
@ -68,7 +73,7 @@ abstract contract AbstractMessageIdAuthHook is
function _postDispatch(
bytes calldata metadata,
bytes calldata message
) internal override {
) internal virtual override {
bytes32 id = message.id();
require(
_isLatestDispatched(id),
@ -82,20 +87,29 @@ abstract contract AbstractMessageIdAuthHook is
metadata.msgValue(0) < 2 ** 255,
"AbstractMessageIdAuthHook: msgValue must be less than 2 ** 255"
);
bytes memory payload = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
id
);
_sendMessageId(metadata, payload);
_sendMessageId(metadata, message);
uint256 _overpayment = msg.value - _quoteDispatch(metadata, message);
if (_overpayment > 0) {
address _refundAddress = metadata.refundAddress(
message.sender().bytes32ToAddress()
);
require(
_refundAddress != address(0),
"AbstractPostDispatchHook: no refund address"
);
payable(_refundAddress).sendValue(_overpayment);
}
}
/**
* @notice Send a message to the ISM.
* @param metadata The metadata for the hook caller
* @param payload The payload for call to the ISM
* @param message The message to send to the ISM
*/
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
bytes calldata message
) internal virtual;
}

@ -53,7 +53,7 @@ abstract contract AbstractMessageIdAuthorizedIsm is
// ============ Events ============
/// @notice Emitted when a message is received from the external bridge
event ReceivedMessage(bytes32 indexed messageId);
event ReceivedMessage(bytes32 indexed messageId, uint256 msgValue);
// ============ Initializer ============
@ -101,7 +101,7 @@ abstract contract AbstractMessageIdAuthorizedIsm is
}
/**
* @notice Check if a message is verified through verifyMessageId first.
* @notice Check if a message is verified through preVerifyMessage first.
* @param message Message to check.
*/
function isVerified(bytes calldata message) public view returns (bool) {
@ -115,24 +115,31 @@ abstract contract AbstractMessageIdAuthorizedIsm is
* @dev Only callable by the authorized hook.
* @param messageId Hyperlane Id of the message.
*/
function verifyMessageId(bytes32 messageId) public payable virtual {
function preVerifyMessage(
bytes32 messageId,
uint256 msgValue
) public payable virtual {
require(
_isAuthorized(),
"AbstractMessageIdAuthorizedIsm: sender is not the hook"
);
require(
msg.value < 2 ** VERIFIED_MASK_INDEX,
"AbstractMessageIdAuthorizedIsm: msg.value must be less than 2^255"
msg.value < 2 ** VERIFIED_MASK_INDEX && msg.value == msgValue,
"AbstractMessageIdAuthorizedIsm: invalid msg.value"
);
require(
verifiedMessages[messageId] == 0,
"AbstractMessageIdAuthorizedIsm: message already verified"
);
verifiedMessages[messageId] = msg.value.setBit(VERIFIED_MASK_INDEX);
emit ReceivedMessage(messageId);
emit ReceivedMessage(messageId, msgValue);
}
// ============ Internal Functions ============
/**
* @notice Check if sender is authorized to message `verifyMessageId`.
* @notice Check if sender is authorized to message `preVerifyMessage`.
*/
function _isAuthorized() internal view virtual returns (bool);
}

@ -44,6 +44,10 @@ contract ArbL2ToL1Ism is
// arbitrum nitro contract on L1 to forward verification
IOutbox public arbOutbox;
uint256 private constant DATA_LENGTH = 68;
uint256 private constant MESSAGE_ID_END = 36;
// ============ Constructor ============
constructor(address _bridge) CrossChainEnabledArbitrumL1(_bridge) {
@ -110,13 +114,16 @@ contract ArbL2ToL1Ism is
l2Sender == TypeCasts.bytes32ToAddress(authorizedHook),
"ArbL2ToL1Ism: l2Sender != authorizedHook"
);
// this data is an abi encoded call of verifyMessageId(bytes32 messageId)
require(data.length == 36, "ArbL2ToL1Ism: invalid data length");
// this data is an abi encoded call of preVerifyMessage(bytes32 messageId)
require(
data.length == DATA_LENGTH,
"ArbL2ToL1Ism: invalid data length"
);
bytes32 messageId = message.id();
bytes32 convertedBytes;
assembly {
// data = 0x[4 bytes function signature][32 bytes messageId]
convertedBytes := mload(add(data, 36))
convertedBytes := mload(add(data, MESSAGE_ID_END))
}
// check if the parsed message id matches the message id of the message
require(

@ -44,7 +44,7 @@ contract ERC5164Ism is AbstractMessageIdAuthorizedIsm {
}
/**
* @notice Check if sender is authorized to message `verifyMessageId`.
* @notice Check if sender is authorized to message `preVerifyMessage`.
*/
function _isAuthorized() internal view override returns (bool) {
return msg.sender == executor;

@ -49,7 +49,7 @@ contract OPStackIsm is
// ============ Internal function ============
/**
* @notice Check if sender is authorized to message `verifyMessageId`.
* @notice Check if sender is authorized to message `preVerifyMessage`.
*/
function _isAuthorized() internal view override returns (bool) {
return

@ -49,7 +49,7 @@ contract PolygonPosIsm is
// ============ Internal function ============
/**
* @notice Check if sender is authorized to message `verifyMessageId`.
* @notice Check if sender is authorized to message `preVerifyMessage`.
*/
function _isAuthorized() internal view override returns (bool) {
return

@ -57,7 +57,7 @@ contract LayerZeroV2Ism is AbstractMessageIdAuthorizedIsm {
/**
* @notice Entry point for receiving msg/packet from the LayerZero endpoint.
* @param _lzMessage The payload of the received message.
* @dev Authorization verification is done within verifyMessageId() -> _isAuthorized()
* @dev Authorization verification is done within preVerifyMessage() -> _isAuthorized()
*/
function lzReceive(
Origin calldata,
@ -66,25 +66,36 @@ contract LayerZeroV2Ism is AbstractMessageIdAuthorizedIsm {
address,
bytes calldata
) external payable {
verifyMessageId(_messageId(_lzMessage));
preVerifyMessage(_messageId(_lzMessage), _msgValue(_lzMessage));
}
// ============ Internal function ============
/**
* @notice Slices the messageId from the message delivered from LayerZeroV2Hook
* @dev message is created as abi.encodeCall(AbstractMessageIdAuthorizedIsm.verifyMessageId, id)
* @dev message is created as abi.encodeCall(AbstractMessageIdAuthorizedIsm.preVerifyMessage, id)
* @dev _message will be 36 bytes (4 bytes for function selector, and 32 bytes for messageId)
*/
function _messageId(
bytes calldata _message
) internal pure returns (bytes32) {
return bytes32(_message[FUNC_SELECTOR_OFFSET:]);
return bytes32(_message[FUNC_SELECTOR_OFFSET:ORIGIN_SENDER_OFFSET]);
}
/**
* @notice Slices the msgValue from the message delivered from LayerZeroV2Hook
* @dev message is created as abi.encodeCall(AbstractMessageIdAuthorizedIsm.preVerifyMessage, (id,msgValue))
* @dev _message will be 68 bytes (4 bytes for function selector, and 32 bytes for messageId, another 32 for msgValue)
*/
function _msgValue(
bytes calldata _message
) internal pure returns (uint256) {
return uint256(bytes32(_message[ORIGIN_SENDER_OFFSET:]));
}
/**
* @notice Validates criteria to verify a message
* @dev this is called by AbstractMessageIdAuthorizedIsm.verifyMessageId
* @dev this is called by AbstractMessageIdAuthorizedIsm.preVerifyMessage
* @dev parses msg.value to get parameters from lzReceive()
*/
function _isAuthorized() internal view override returns (bool) {

@ -7,7 +7,7 @@ pragma solidity >=0.8.0;
*/
library OPL2ToL1Metadata {
// bottom offset to the start of message id in the metadata
uint256 private constant MESSAGE_ID_OFFSET = 88;
uint256 private constant MESSAGE_ID_OFFSET = 120;
// from IOptimismPortal.WithdrawalTransaction
// Σ {
// nonce = 32 bytes
@ -20,7 +20,7 @@ library OPL2ToL1Metadata {
// LENGTH = 32 bytes
// } = 252 bytes
uint256 private constant FIXED_METADATA_LENGTH = 252;
// metadata here is double encoded call relayMessage(..., verifyMessageId)
// metadata here is double encoded call relayMessage(..., preVerifyMessage)
// Σ {
// _selector = 4 bytes
// _nonce = 32 bytes
@ -31,9 +31,9 @@ library OPL2ToL1Metadata {
// _data
// OFFSET = 32 bytes
// LENGTH = 32 bytes
// PADDING + verifyMessageId = 64 bytes
// } = 292 bytes
uint256 private constant MESSENGER_CALLDATA_LENGTH = 292;
// PADDING + preVerifyMessage = 96 bytes
// } = 324 bytes
uint256 private constant MESSENGER_CALLDATA_LENGTH = 324;
/**
* @notice Returns the message ID.

@ -78,8 +78,8 @@ contract MockMailbox is Mailbox {
inboundProcessedNonce++;
}
function processInboundMessage(uint32 _nonce) public {
function processInboundMessage(uint32 _nonce) public payable {
bytes memory _message = inboundMessages[_nonce];
Mailbox(address(this)).process("", _message);
Mailbox(address(this)).process{value: msg.value}("", _message);
}
}

@ -1,11 +1,11 @@
{
"name": "@hyperlane-xyz/core",
"description": "Core solidity contracts for Hyperlane",
"version": "5.6.1",
"version": "5.7.1",
"dependencies": {
"@arbitrum/nitro-contracts": "^1.2.1",
"@eth-optimism/contracts": "^0.6.0",
"@hyperlane-xyz/utils": "5.6.2",
"@hyperlane-xyz/utils": "6.0.0",
"@layerzerolabs/lz-evm-oapp-v2": "2.0.2",
"@openzeppelin/contracts": "^4.9.3",
"@openzeppelin/contracts-upgradeable": "^v4.9.3",

@ -1,84 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
import "forge-std/Script.sol";
import {Mailbox} from "../../contracts/Mailbox.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {ArbL2ToL1Hook} from "../../contracts/hooks/ArbL2ToL1Hook.sol";
import {ArbL2ToL1Ism} from "../../contracts/isms/hook/ArbL2ToL1Ism.sol";
import {TestRecipient} from "../../contracts/test/TestRecipient.sol";
import {TestIsm} from "../../contracts/test/TestIsm.sol";
contract DeployArbHook is Script {
uint256 deployerPrivateKey;
ArbL2ToL1Hook hook;
ArbL2ToL1Ism ism;
uint32 constant L1_DOMAIN = 11155111;
address constant L1_MAILBOX = 0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766;
address constant L1_BRIDGE = 0x38f918D0E9F1b721EDaA41302E399fa1B79333a9;
address constant L1_ISM = 0x096A1c034c7Ad113B6dB786b7BA852cB67025458; // placeholder
bytes32 TEST_RECIPIENT =
0x000000000000000000000000155b1cd2f7cbc58d403b9be341fab6cd77425175; // placeholder
address constant ARBSYS = 0x0000000000000000000000000000000000000064;
address constant L2_MAILBOX = 0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8;
address constant L2_HOOK = 0xd9d99AC1C645563576b8Df22cBebFC23FB60Ec73; // placeholder
function deployIsm() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
ism = new ArbL2ToL1Ism(L1_BRIDGE);
TestRecipient testRecipient = new TestRecipient();
testRecipient.setInterchainSecurityModule(address(ism));
vm.stopBroadcast();
}
function deployHook() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
hook = new ArbL2ToL1Hook(
L2_MAILBOX,
L1_DOMAIN,
TypeCasts.addressToBytes32(L1_ISM),
ARBSYS,
200_000 // estimated gas amount used for verify
);
vm.stopBroadcast();
}
function deployTestRecipient() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
TestIsm noopIsm = new TestIsm();
noopIsm.setVerify(true);
TestRecipient testRecipient = new TestRecipient();
testRecipient.setInterchainSecurityModule(address(noopIsm));
console.log("TestRecipient address: %s", address(testRecipient));
vm.stopBroadcast();
}
function setAuthorizedHook() external {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
ism = ArbL2ToL1Ism(L1_ISM);
ism.setAuthorizedHook(TypeCasts.addressToBytes32(L2_HOOK));
vm.stopBroadcast();
}
}

@ -147,15 +147,7 @@ contract LayerZeroV2HookTest is Test {
vm.assume(balance < nativeFee - 1);
vm.deal(address(this), balance);
vm.expectRevert(
abi.encodeWithSelector(
Errors.InsufficientFee.selector,
100,
balance,
0,
0
)
);
vm.expectRevert(); // OutOfFunds
mailbox.dispatch{value: balance}(
HYPERLANE_DEST_DOMAIN,
address(crossChainCounterApp).addressToBytes32(),

@ -12,6 +12,7 @@ import {ArbL2ToL1Ism} from "../../contracts/isms/hook/ArbL2ToL1Ism.sol";
import {MockArbBridge, MockArbSys} from "../../contracts/mock/MockArbBridge.sol";
import {TestRecipient} from "../../contracts/test/TestRecipient.sol";
import {ExternalBridgeTest} from "./ExternalBridgeTest.sol";
import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol";
contract ArbL2ToL1IsmTest is ExternalBridgeTest {
uint256 internal constant MOCK_LEAF_INDEX = 40160;
@ -22,10 +23,10 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest {
0x0000000000000000000000000000000000000064;
MockArbBridge internal arbBridge;
TestInterchainGasPaymaster internal mockOverheadIgp;
function setUp() public override {
// Arbitrum bridge mock setup
GAS_QUOTE = 120_000;
vm.etch(L2_ARBSYS_ADDRESS, address(new MockArbSys()).code);
deployAll();
@ -38,12 +39,13 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest {
function deployHook() public {
originMailbox = new TestMailbox(ORIGIN_DOMAIN);
mockOverheadIgp = new TestInterchainGasPaymaster();
hook = new ArbL2ToL1Hook(
address(originMailbox),
DESTINATION_DOMAIN,
TypeCasts.addressToBytes32(address(ism)),
L2_ARBSYS_ADDRESS,
GAS_QUOTE
address(mockOverheadIgp)
);
}
@ -60,6 +62,20 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest {
ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook)));
}
function test_postDispatch_childHook() public {
bytes memory encodedHookData = _encodeHookData(messageId, 0);
originMailbox.updateLatestDispatchedId(messageId);
_expectOriginExternalBridgeCall(encodedHookData);
bytes memory igpMetadata = StandardHookMetadata.overrideGasLimit(
78_000
);
uint256 quote = hook.quoteDispatch(igpMetadata, encodedMessage);
assertEq(quote, mockOverheadIgp.quoteGasPayment(ORIGIN_DOMAIN, 78_000));
hook.postDispatch{value: quote}(igpMetadata, encodedMessage);
}
/* ============ helper functions ============ */
function _expectOriginExternalBridgeCall(
@ -115,10 +131,7 @@ contract ArbL2ToL1IsmTest is ExternalBridgeTest {
bytes32 _messageId,
uint256 _value
) internal view returns (bytes memory) {
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(_messageId)
);
bytes memory encodedHookData = _encodeHookData(_messageId, _value);
bytes32[] memory proof = new bytes32[](16);
return

@ -134,7 +134,7 @@ contract ERC5164IsmTest is ExternalBridgeTest {
vm.expectRevert(
"AbstractMessageIdAuthorizedIsm: sender is not the hook"
);
ism.verifyMessageId(messageId);
ism.preVerifyMessage(messageId, 0);
assertFalse(ism.isVerified(encodedMessage));
}
@ -150,6 +150,10 @@ contract ERC5164IsmTest is ExternalBridgeTest {
function test_verify_valueAlreadyClaimed(uint256) public override {}
function test_verify_override_msgValue() public override {}
function testFuzz_postDispatch_refundsExtraValue(uint256) public override {}
function test_verify_false_arbitraryCall() public override {}
/* ============ helper functions ============ */
@ -159,7 +163,7 @@ contract ERC5164IsmTest is ExternalBridgeTest {
uint256 _msgValue
) internal override {
vm.prank(address(executor));
ism.verifyMessageId(messageId);
ism.preVerifyMessage(messageId, 0);
}
function _encodeExternalDestinationBridgeCall(
@ -170,7 +174,7 @@ contract ERC5164IsmTest is ExternalBridgeTest {
) internal override returns (bytes memory) {
if (_from == address(hook)) {
vm.prank(address(executor));
ism.verifyMessageId{value: _msgValue}(messageId);
ism.preVerifyMessage{value: _msgValue}(messageId, 0);
}
}
}

@ -18,6 +18,7 @@ abstract contract ExternalBridgeTest is Test {
uint8 internal constant HYPERLANE_VERSION = 1;
uint32 internal constant ORIGIN_DOMAIN = 1;
uint32 internal constant DESTINATION_DOMAIN = 2;
uint256 internal constant MSG_VALUE = 1 ether;
uint256 internal constant MAX_MSG_VALUE = 2 ** 255 - 1;
uint256 internal GAS_QUOTE;
@ -49,11 +50,13 @@ abstract contract ExternalBridgeTest is Test {
/* ============ Hook.postDispatch ============ */
function test_postDispatch() public {
bytes memory encodedHookData = _encodeHookData(messageId);
bytes memory hookMetadata = testMetadata;
bytes memory encodedHookData = _encodeHookData(messageId, 0);
originMailbox.updateLatestDispatchedId(messageId);
_expectOriginExternalBridgeCall(encodedHookData);
hook.postDispatch{value: GAS_QUOTE}(testMetadata, encodedMessage);
uint256 quote = hook.quoteDispatch(testMetadata, encodedMessage);
hook.postDispatch{value: quote}(testMetadata, encodedMessage);
}
function test_postDispatch_revertWhen_chainIDNotSupported() public {
@ -89,19 +92,47 @@ abstract contract ExternalBridgeTest is Test {
hook.postDispatch(excessValueMetadata, encodedMessage);
}
/* ============ ISM.verifyMessageId ============ */
function testFuzz_postDispatch_refundsExtraValue(
uint256 extraValue
) public virtual {
vm.assume(extraValue < MAX_MSG_VALUE);
vm.deal(address(this), address(this).balance + extraValue);
uint256 valueBefore = address(this).balance;
function test_verifyMessageId_asyncCall() public {
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
bytes memory encodedHookData = _encodeHookData(messageId, 0);
originMailbox.updateLatestDispatchedId(messageId);
_expectOriginExternalBridgeCall(encodedHookData);
uint256 quote = hook.quoteDispatch(testMetadata, encodedMessage);
hook.postDispatch{value: quote + extraValue}(
testMetadata,
encodedMessage
);
assertEq(address(this).balance, valueBefore - quote);
}
function test_postDispatch_revertWhen_insufficientValue() public {
bytes memory encodedHookData = _encodeHookData(messageId, 0);
originMailbox.updateLatestDispatchedId(messageId);
_expectOriginExternalBridgeCall(encodedHookData);
uint256 quote = hook.quoteDispatch(testMetadata, encodedMessage);
vm.expectRevert(); //arithmetic underflow
hook.postDispatch{value: quote - 1}(testMetadata, encodedMessage);
}
/* ============ ISM.preVerifyMessage ============ */
function test_preVerifyMessage_asyncCall() public {
bytes memory encodedHookData = _encodeHookData(messageId, 0);
_externalBridgeDestinationCall(encodedHookData, 0);
assertTrue(ism.isVerified(encodedMessage));
}
function test_verifyMessageId_externalBridgeCall() public virtual {
function test_preVerifyMessage_externalBridgeCall() public virtual {
bytes memory externalCalldata = _encodeExternalDestinationBridgeCall(
address(hook),
address(ism),
@ -121,18 +152,18 @@ abstract contract ExternalBridgeTest is Test {
}
function test_verify_msgValue_asyncCall() public virtual {
bytes memory encodedHookData = _encodeHookData(messageId);
_externalBridgeDestinationCall(encodedHookData, 1 ether);
bytes memory encodedHookData = _encodeHookData(messageId, MSG_VALUE);
_externalBridgeDestinationCall(encodedHookData, MSG_VALUE);
assertTrue(ism.verify(new bytes(0), encodedMessage));
assertEq(address(testRecipient).balance, 1 ether);
assertEq(address(testRecipient).balance, MSG_VALUE);
}
function test_verify_msgValue_externalBridgeCall() public virtual {
bytes memory externalCalldata = _encodeExternalDestinationBridgeCall(
address(hook),
address(ism),
1 ether,
MSG_VALUE,
messageId
);
assertTrue(ism.verify(externalCalldata, encodedMessage));
@ -189,7 +220,7 @@ abstract contract ExternalBridgeTest is Test {
// async call - native bridges might have try catch block to prevent revert
try
this.externalBridgeDestinationCallWrapper(
_encodeHookData(incorrectMessageId),
_encodeHookData(incorrectMessageId, 0),
0
)
{} catch {}
@ -199,7 +230,10 @@ abstract contract ExternalBridgeTest is Test {
/// forge-config: default.fuzz.runs = 10
function test_verify_valueAlreadyClaimed(uint256 _msgValue) public virtual {
_msgValue = bound(_msgValue, 0, MAX_MSG_VALUE);
_externalBridgeDestinationCall(_encodeHookData(messageId), _msgValue);
_externalBridgeDestinationCall(
_encodeHookData(messageId, _msgValue),
_msgValue
);
bool verified = ism.verify(new bytes(0), encodedMessage);
assertTrue(verified);
@ -217,6 +251,18 @@ abstract contract ExternalBridgeTest is Test {
assertEq(address(testRecipient).balance, _msgValue);
}
function test_verify_override_msgValue() public virtual {
bytes memory encodedHookData = _encodeHookData(messageId, MSG_VALUE);
_externalBridgeDestinationCall(encodedHookData, MSG_VALUE);
vm.expectRevert("AbstractMessageIdAuthorizedIsm: invalid msg.value");
_externalBridgeDestinationCall(encodedHookData, 0);
assertTrue(ism.verify(new bytes(0), encodedMessage));
assertEq(address(testRecipient).balance, MSG_VALUE);
}
function test_verify_false_arbitraryCall() public virtual {
bytes memory incorrectCalldata = _encodeExternalDestinationBridgeCall(
address(hook),
@ -242,12 +288,13 @@ abstract contract ExternalBridgeTest is Test {
}
function _encodeHookData(
bytes32 _messageId
bytes32 _messageId,
uint256 _msgValue
) internal pure returns (bytes memory) {
return
abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(_messageId)
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(_messageId, _msgValue)
);
}
@ -279,6 +326,8 @@ abstract contract ExternalBridgeTest is Test {
address _sender
) internal virtual returns (bytes memory) {}
// meant to mock an arbitrary successful call made by the external bridge
function verifyMessageId(bytes32 /*messageId*/) public payable {}
receive() external payable {}
// meant to be mock an arbitrary successful call made by the external bridge
function preVerifyMessage(bytes32 /*messageId*/) public payable {}
}

@ -14,6 +14,7 @@ import {MockOptimismMessenger, MockOptimismPortal} from "../../contracts/mock/Mo
import {OPL2ToL1Hook} from "../../contracts/hooks/OPL2ToL1Hook.sol";
import {OPL2ToL1Ism} from "../../contracts/isms/hook/OPL2ToL1Ism.sol";
import {ExternalBridgeTest} from "./ExternalBridgeTest.sol";
import {TestInterchainGasPaymaster} from "../../contracts/test/TestInterchainGasPaymaster.sol";
contract OPL2ToL1IsmTest is ExternalBridgeTest {
address internal constant L2_MESSENGER_ADDRESS =
@ -21,6 +22,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
uint256 internal constant MOCK_NONCE = 0;
TestInterchainGasPaymaster internal mockOverheadIgp;
MockOptimismPortal internal portal;
MockOptimismMessenger internal l1Messenger;
@ -30,7 +32,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
function setUp() public override {
// Optimism messenger mock setup
GAS_QUOTE = 120_000;
// GAS_QUOTE = 300_000;
vm.etch(
L2_MESSENGER_ADDRESS,
address(new MockOptimismMessenger()).code
@ -42,12 +44,13 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
function deployHook() public {
originMailbox = new TestMailbox(ORIGIN_DOMAIN);
mockOverheadIgp = new TestInterchainGasPaymaster();
hook = new OPL2ToL1Hook(
address(originMailbox),
DESTINATION_DOMAIN,
TypeCasts.addressToBytes32(address(ism)),
L2_MESSENGER_ADDRESS,
uint32(GAS_QUOTE)
address(mockOverheadIgp)
);
}
@ -67,6 +70,20 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook)));
}
function test_postDispatch_childHook() public {
bytes memory encodedHookData = _encodeHookData(messageId, 0);
originMailbox.updateLatestDispatchedId(messageId);
_expectOriginExternalBridgeCall(encodedHookData);
bytes memory igpMetadata = StandardHookMetadata.overrideGasLimit(
78_000
);
uint256 quote = hook.quoteDispatch(igpMetadata, encodedMessage);
assertEq(quote, mockOverheadIgp.quoteGasPayment(ORIGIN_DOMAIN, 78_000));
hook.postDispatch{value: quote}(igpMetadata, encodedMessage);
}
/* ============ helper functions ============ */
function _expectOriginExternalBridgeCall(
@ -76,7 +93,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
L2_MESSENGER_ADDRESS,
abi.encodeCall(
ICrossDomainMessenger.sendMessage,
(address(ism), _encodedHookData, uint32(GAS_QUOTE))
(address(ism), _encodedHookData, uint32(300_000))
)
);
}
@ -100,8 +117,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
}
function _externalBridgeDestinationCall(
bytes memory,
/*_encodedHookData*/
bytes memory _encodedHookData,
uint256 _msgValue
) internal override {
vm.deal(address(portal), _msgValue);
@ -115,7 +131,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
data: _encodeMessengerCalldata(
address(ism),
_msgValue,
messageId
_encodedHookData
)
});
portal.finalizeWithdrawalTransaction(withdrawal);
@ -124,10 +140,8 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
function _encodeMessengerCalldata(
address _ism,
uint256 _value,
bytes32 _messageId
bytes memory _encodedHookData
) internal view returns (bytes memory) {
bytes memory encodedHookData = _encodeHookData(_messageId);
return
abi.encodeCall(
ICrossDomainMessenger.relayMessage,
@ -137,7 +151,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
_ism,
_value,
uint256(GAS_QUOTE),
encodedHookData
_encodedHookData
)
);
}
@ -147,6 +161,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
uint256 _value,
bytes32 _messageId
) internal view returns (bytes memory) {
bytes memory encodedHookData = _encodeHookData(_messageId, _value);
return
abi.encode(
MOCK_NONCE,
@ -154,7 +169,7 @@ contract OPL2ToL1IsmTest is ExternalBridgeTest {
l1Messenger,
_value,
uint256(GAS_QUOTE),
_encodeMessengerCalldata(_ism, _value, _messageId)
_encodeMessengerCalldata(_ism, _value, encodedHookData)
);
}
}

@ -99,7 +99,10 @@ contract OPStackIsmTest is ExternalBridgeTest {
function test_verify_revertsWhen_incorrectMessageId() public override {
bytes32 incorrectMessageId = keccak256("incorrect message id");
_externalBridgeDestinationCall(_encodeHookData(incorrectMessageId), 0);
_externalBridgeDestinationCall(
_encodeHookData(incorrectMessageId, 0),
0
);
assertFalse(ism.isVerified(testMessage));
}
@ -142,7 +145,7 @@ contract OPStackIsmTest is ExternalBridgeTest {
}
// SKIP - no external bridge call
function test_verifyMessageId_externalBridgeCall() public override {}
function test_preVerifyMessage_externalBridgeCall() public override {}
function test_verify_msgValue_externalBridgeCall() public override {}
@ -150,12 +153,12 @@ contract OPStackIsmTest is ExternalBridgeTest {
function test_verify_false_arbitraryCall() public override {}
/* ============ ISM.verifyMessageId ============ */
/* ============ ISM.preVerifyMessage ============ */
function test_verify_revertsWhen_notAuthorizedHook() public override {
// needs to be called by the canonical messenger on Optimism
vm.expectRevert(NotCrossChainCall.selector);
ism.verifyMessageId(messageId);
ism.preVerifyMessage(messageId, 0);
vm.startPrank(L2_MESSENGER_ADDRESS);
_setExternalOriginSender(address(this));
@ -164,7 +167,7 @@ contract OPStackIsmTest is ExternalBridgeTest {
vm.expectRevert(
"AbstractMessageIdAuthorizedIsm: sender is not the hook"
);
ism.verifyMessageId(messageId);
ism.preVerifyMessage(messageId, 0);
}
function _setExternalOriginSender(
@ -179,10 +182,11 @@ contract OPStackIsmTest is ExternalBridgeTest {
function test_verify_tooMuchValue() public {
uint256 _msgValue = 2 ** 255 + 1;
vm.expectRevert(
"AbstractMessageIdAuthorizedIsm: msg.value must be less than 2^255"
vm.expectRevert("AbstractMessageIdAuthorizedIsm: invalid msg.value");
_externalBridgeDestinationCall(
_encodeHookData(messageId, _msgValue),
_msgValue
);
_externalBridgeDestinationCall(_encodeHookData(messageId), _msgValue);
assertFalse(ism.isVerified(encodedMessage));

@ -78,7 +78,7 @@ contract PolygonPosIsmTest is Test {
bytes data
);
event ReceivedMessage(bytes32 indexed messageId);
event ReceivedMessage(bytes32 indexed messageId, uint256 msgValue);
function setUp() public {
// block numbers to fork from, chain data is cached to ../../forge-cache/
@ -155,8 +155,8 @@ contract PolygonPosIsmTest is Test {
vm.selectFork(mainnetFork);
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(messageId, 0)
);
l1Mailbox.updateLatestDispatchedId(messageId);
@ -228,22 +228,22 @@ contract PolygonPosIsmTest is Test {
polygonPosHook.postDispatch(testMetadata, encodedMessage);
}
/* ============ ISM.verifyMessageId ============ */
/* ============ ISM.preVerifyMessage ============ */
function testFork_verifyMessageId() public {
function testFork_preVerifyMessage() public {
deployAll();
vm.selectFork(polygonPosFork);
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(messageId, 0)
);
vm.startPrank(POLYGON_CROSSCHAIN_SYSTEM_ADDR);
vm.expectEmit(true, false, false, false, address(polygonPosISM));
emit ReceivedMessage(messageId);
emit ReceivedMessage(messageId, 0);
// FIX: expect other events
fxChild.onStateReceive(
@ -259,14 +259,14 @@ contract PolygonPosIsmTest is Test {
vm.stopPrank();
}
function testFork_verifyMessageId_RevertWhen_NotAuthorized() public {
function testFork_preVerifyMessage_RevertWhen_NotAuthorized() public {
deployAll();
vm.selectFork(polygonPosFork);
// needs to be called by the fxchild on Polygon
vm.expectRevert(NotCrossChainCall.selector);
polygonPosISM.verifyMessageId(messageId);
polygonPosISM.preVerifyMessage(messageId, 0);
vm.startPrank(MAINNET_FX_CHILD);
@ -274,7 +274,7 @@ contract PolygonPosIsmTest is Test {
vm.expectRevert(
"AbstractMessageIdAuthorizedIsm: sender is not the hook"
);
polygonPosISM.verifyMessageId(messageId);
polygonPosISM.preVerifyMessage(messageId, 0);
}
/* ============ ISM.verify ============ */
@ -349,8 +349,8 @@ contract PolygonPosIsmTest is Test {
vm.selectFork(polygonPosFork);
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(_messageId)
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(_messageId, 0)
);
vm.prank(POLYGON_CROSSCHAIN_SYSTEM_ADDR);

@ -23,8 +23,8 @@ contract LayerZeroV2IsmTest is Test {
) internal pure returns (bytes memory) {
return
abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(_messageId)
AbstractMessageIdAuthorizedIsm.preVerifyMessage,
(_messageId, 0)
);
}
@ -133,7 +133,7 @@ contract LayerZeroV2IsmTest is Test {
vm.stopPrank();
}
function testLzV2Ism_verifyMessageId_SetsCorrectMessageId(
function testLzV2Ism_preVerifyMessage_SetsCorrectMessageId(
bytes32 messageId
) public {
lZIsm.setAuthorizedHook(hook.addressToBytes32());

@ -1,5 +1,9 @@
# @hyperlane-xyz/ccip-server
## 6.0.0
## 5.7.0
## 5.6.2
## 5.6.1

@ -1,6 +1,6 @@
{
"name": "@hyperlane-xyz/ccip-server",
"version": "5.6.2",
"version": "6.0.0",
"description": "CCIP server",
"typings": "dist/index.d.ts",
"typedocMain": "src/index.ts",

@ -1,5 +1,46 @@
# @hyperlane-xyz/cli
## 6.0.0
### Major Changes
- e3b97c455: Detangle assumption that chainId == domainId for EVM chains. Domain IDs and Chain Names are still unique, but chainId is no longer guaranteed to be a unique identifier. Domain ID is no longer an optional field and is now required for all chain metadata.
### Patch Changes
- Updated dependencies [7b3b07900]
- Updated dependencies [30d92c319]
- Updated dependencies [e3b97c455]
- @hyperlane-xyz/sdk@6.0.0
- @hyperlane-xyz/utils@6.0.0
## 5.7.0
### Minor Changes
- db0e73502: re-enable space key for multiselect cli prompt
- 7e9e248be: Add feat to allow updates to destination gas using warp apply
- 4c0605dca: Add optional proxy admin reuse in warp route deployments and admin proxy ownership transfer in warp apply
- db5875cc2: Add `hyperlane warp verify` to allow post-deployment verification.
- 956ff752a: Enable configuration of IGP hooks in the CLI
### Patch Changes
- Updated dependencies [5dabdf388]
- Updated dependencies [469f2f340]
- Updated dependencies [e104cf6aa]
- Updated dependencies [d9505ab58]
- Updated dependencies [04108155d]
- Updated dependencies [7e9e248be]
- Updated dependencies [4c0605dca]
- Updated dependencies [db9196837]
- Updated dependencies [db5875cc2]
- Updated dependencies [56328e6e1]
- Updated dependencies [956ff752a]
- Updated dependencies [39a9b2038]
- @hyperlane-xyz/sdk@5.7.0
- @hyperlane-xyz/utils@5.7.0
## 5.6.2
### Patch Changes

@ -1,3 +1,4 @@
submitter:
chain: anvil2
type: impersonatedAccount
userAddress: '0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb'

@ -1,8 +1,10 @@
anvil2:
submitter:
chain: anvil2
type: jsonRpc
anvil3:
submitter:
chain: anvil3
type: jsonRpc
zksync1:
submitter:

@ -1,2 +1,3 @@
submitter:
chain: anvil2
type: jsonRpc

@ -1,13 +1,13 @@
{
"name": "@hyperlane-xyz/cli",
"version": "5.6.2",
"version": "6.0.0",
"description": "A command-line utility for common Hyperlane operations",
"dependencies": {
"@aws-sdk/client-kms": "^3.577.0",
"@aws-sdk/client-s3": "^3.577.0",
"@hyperlane-xyz/registry": "4.7.0",
"@hyperlane-xyz/sdk": "5.6.2",
"@hyperlane-xyz/utils": "5.6.2",
"@hyperlane-xyz/sdk": "6.0.0",
"@hyperlane-xyz/utils": "6.0.0",
"@inquirer/core": "9.0.10",
"@inquirer/figures": "1.0.5",
"@inquirer/prompts": "^3.0.0",

@ -24,6 +24,8 @@ import {
writeYamlOrJson,
} from '../utils/files.js';
import { getWarpCoreConfigOrExit } from '../utils/input.js';
import { selectRegistryWarpRoute } from '../utils/tokens.js';
import { runVerifyWarpRoute } from '../verify/warp.js';
import {
DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH,
@ -54,6 +56,7 @@ export const warpCommand: CommandModule = {
.command(init)
.command(read)
.command(send)
.command(verify)
.version(false)
.demandCommand(),
@ -334,3 +337,25 @@ export const check: CommandModuleWithContext<{
process.exit(0);
},
};
export const verify: CommandModuleWithWriteContext<{
symbol: string;
}> = {
command: 'verify',
describe: 'Verify deployed contracts on explorers',
builder: {
symbol: {
...symbolCommandOption,
demandOption: false,
},
},
handler: async ({ context, symbol }) => {
logCommandHeader('Hyperlane Warp Verify');
const warpCoreConfig = await selectRegistryWarpRoute(
context.registry,
symbol,
);
return runVerifyWarpRoute({ context, warpCoreConfig });
},
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save