Merge branch 'main' into ltyu/sp1-lightclient-ism

ltyu/sp1-lightclient-ism
Lee 3 months ago committed by GitHub
commit 24ee39f8e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/breezy-turkeys-march.md
  2. 5
      .changeset/curly-mangos-buy.md
  3. 8
      .changeset/dirty-cameras-breathe.md
  4. 5
      .changeset/dirty-items-sparkle.md
  5. 3
      .changeset/dull-days-yell.md
  6. 5
      .changeset/fast-schools-battle.md
  7. 5
      .changeset/fifty-chefs-visit.md
  8. 6
      .changeset/four-years-tease.md
  9. 5
      .changeset/gold-weeks-lay.md
  10. 5
      .changeset/gorgeous-pans-look.md
  11. 5
      .changeset/nine-lamps-boil.md
  12. 5
      .changeset/pink-poets-think.md
  13. 5
      .changeset/pretty-dots-look.md
  14. 5
      .changeset/real-guests-search.md
  15. 6
      .changeset/rich-donkeys-visit.md
  16. 5
      .changeset/short-cobras-wink.md
  17. 5
      .changeset/sour-ladybugs-appear.md
  18. 5
      .changeset/tough-ties-pump.md
  19. 5
      .changeset/two-tigers-sniff.md
  20. 5
      .changeset/warm-foxes-jam.md
  21. 5
      .changeset/warm-grapes-talk.md
  22. 5
      .changeset/warm-zoos-smell.md
  23. 5
      .github/workflows/test.yml
  24. 2
      .registryrc
  25. 1
      rust/Cargo.lock
  26. 2
      rust/agents/relayer/src/msg/pending_message.rs
  27. 80
      rust/chains/hyperlane-cosmos/src/providers/grpc.rs
  28. 77
      rust/chains/hyperlane-cosmos/src/providers/grpc/tests.rs
  29. 12
      rust/chains/hyperlane-cosmos/src/providers/mod.rs
  30. 36
      rust/config/mainnet_config.json
  31. 8
      rust/config/testnet_config.json
  32. 2
      rust/sealevel/client/Cargo.toml
  33. 52
      rust/sealevel/client/src/cmd_utils.rs
  34. 1
      rust/sealevel/client/src/context.rs
  35. 115
      rust/sealevel/client/src/core.rs
  36. 12
      rust/sealevel/client/src/igp.rs
  37. 24
      rust/sealevel/client/src/main.rs
  38. 22
      rust/sealevel/client/src/multisig_ism.rs
  39. 10
      rust/sealevel/client/src/router.rs
  40. 150
      rust/sealevel/client/src/warp_route.rs
  41. 3
      rust/sealevel/environments/mainnet3/multisig-ism-message-id/solana/hyperlane/program-ids.json
  42. 2
      rust/sealevel/environments/mainnet3/solana/core/program-ids.json
  43. 10
      rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/program-ids.json
  44. 15
      rust/sealevel/environments/mainnet3/warp-routes/eclipsesol/token-config.json
  45. 10
      rust/sealevel/environments/mainnet3/warp-routes/pzeth/program-ids.json
  46. 18
      rust/sealevel/environments/mainnet3/warp-routes/pzeth/token-config.json
  47. 25
      rust/sealevel/libraries/multisig-ism/src/test_data.rs
  48. 5
      rust/sealevel/libraries/test-utils/src/lib.rs
  49. 57
      rust/sealevel/programs/hyperlane-sealevel-token-collateral/tests/functional.rs
  50. 57
      rust/sealevel/programs/hyperlane-sealevel-token-native/tests/functional.rs
  51. 10
      rust/sealevel/programs/hyperlane-sealevel-token/src/plugin.rs
  52. 43
      rust/sealevel/programs/hyperlane-sealevel-token/tests/functional.rs
  53. 2
      rust/sealevel/programs/ism/multisig-ism-message-id/src/processor.rs
  54. 554
      rust/sealevel/programs/mailbox-test/src/functional.rs
  55. 2
      rust/sealevel/programs/mailbox/Cargo.toml
  56. 15
      rust/sealevel/programs/mailbox/src/accounts.rs
  57. 17
      rust/sealevel/programs/mailbox/src/instruction.rs
  58. 1
      rust/sealevel/programs/mailbox/src/lib.rs
  59. 102
      rust/sealevel/programs/mailbox/src/processor.rs
  60. 15
      rust/sealevel/programs/mailbox/src/protocol_fee.rs
  61. 7
      rust/utils/run-locally/src/ethereum/mod.rs
  62. 2
      rust/utils/run-locally/src/solana.rs
  63. 15
      solidity/CHANGELOG.md
  64. 84
      solidity/contracts/hooks/OPL2ToL1Hook.sol
  65. 5
      solidity/contracts/interfaces/IInterchainSecurityModule.sol
  66. 3
      solidity/contracts/interfaces/hooks/IPostDispatchHook.sol
  67. 2
      solidity/contracts/interfaces/optimism/ICrossDomainMessenger.sol
  68. 28
      solidity/contracts/interfaces/optimism/IOptimismPortal.sol
  69. 109
      solidity/contracts/isms/hook/OPL2ToL1Ism.sol
  70. 4
      solidity/contracts/isms/multisig/WeightedMultisigIsm.sol
  71. 62
      solidity/contracts/libs/OPL2ToL1Metadata.sol
  72. 4
      solidity/contracts/mock/MockHyperlaneEnvironment.sol
  73. 61
      solidity/contracts/mock/MockOptimism.sol
  74. 4
      solidity/package.json
  75. 173
      solidity/test/InterchainAccountRouter.t.sol
  76. 320
      solidity/test/isms/OPL2ToL1Ism.t.sol
  77. 2
      typescript/ccip-server/CHANGELOG.md
  78. 2
      typescript/ccip-server/package.json
  79. 33
      typescript/cli/CHANGELOG.md
  80. 6
      typescript/cli/package.json
  81. 8
      typescript/cli/src/deploy/warp.ts
  82. 2
      typescript/cli/src/version.ts
  83. 30
      typescript/helloworld/CHANGELOG.md
  84. 6
      typescript/helloworld/package.json
  85. 2
      typescript/helloworld/src/deploy/deploy.ts
  86. 1
      typescript/infra/.gitignore
  87. 28
      typescript/infra/CHANGELOG.md
  88. 24
      typescript/infra/config/environments/mainnet3/agent.ts
  89. 38
      typescript/infra/config/environments/mainnet3/funding.ts
  90. 4
      typescript/infra/config/environments/mainnet3/gasPrices.json
  91. 2
      typescript/infra/config/environments/mainnet3/helloworld.ts
  92. 4
      typescript/infra/config/environments/mainnet3/index.ts
  93. 1
      typescript/infra/config/environments/mainnet3/owners.ts
  94. 26
      typescript/infra/config/environments/mainnet3/warp/EZETH-deployments.yaml
  95. 11
      typescript/infra/config/environments/mainnet3/warp/addresses.json
  96. 34
      typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumSolanaPzETHWarpConfig.ts
  97. 190
      typescript/infra/config/environments/mainnet3/warp/configGetters/getRenzoEZETHWarpConifg.ts
  98. 0
      typescript/infra/config/environments/mainnet3/warp/renzo-ezETH-addresses-v1.json
  99. 32
      typescript/infra/config/environments/mainnet3/warp/renzo-ezETH-addresses-v3.json
  100. 19
      typescript/infra/config/environments/mainnet3/warp/verification.json
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Add ether's error reasoning handling to SmartProvider to show clearer error messages

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Support proxiedFactories in HypERC20App and extend HypERC20Checker with ProxiedRouterChecker

@ -1,8 +0,0 @@
---
'@hyperlane-xyz/helloworld': minor
'@hyperlane-xyz/widgets': minor
'@hyperlane-xyz/infra': minor
'@hyperlane-xyz/cli': minor
---
Update to registry v2.5.0

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to arbitrumsepolia, basesepolia, ecotestnet, optimismsepolia, polygonamoy

@ -1,7 +1,8 @@
---
'@hyperlane-xyz/infra': minor
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---
Added SDK support for ArbL2ToL1Hook/ISM for selfrelay
Added sdk support for Stake weighted ISM

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': patch
---
Require at least 1 chain selection in warp init

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to zircuit

@ -1,6 +0,0 @@
---
"@hyperlane-xyz/cli": patch
"@hyperlane-xyz/sdk": patch
---
feat: Add long-running CLI relayer

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Update cosmos zod schema and enroll new validators for cheesechain, xlayer, zircuit, worldchain.

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': patch
---
Update ProxyAdminViolation interface to include proxyAdmin and proxy contract fields

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Adds CollateralFiat to token mapping which will output the correct standard to the warp deploy artifact.

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to solana + eclipse

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Add output of hyperlane warp read to ./configs/warp-route-deployment.yaml

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/core': patch
---
fix: only evaluate dynamic revert reasons in reverting branch

@ -1,6 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---
Added yield route with yield going to message recipient.

@ -1,5 +0,0 @@
---
"@hyperlane-xyz/core": minor
---
feat: attributable fraud for signers

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Remove registry.getUri() from core read logging to prevent registry error

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Fixes the new chain message to display the correct command

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/core': minor
---
Implement checkpoint fraud proofs for use in slashing

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Supprt passing foreignDeployments to HypERC20App constructor

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': patch
---
Improved check for mailbox initialization

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Add check & confirm for existing mailbox to core deploy to allow users to decide if they want to deploy a new mailbox

@ -246,6 +246,11 @@ jobs:
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq -y libudev-dev pkg-config protobuf-compiler
- name: Checkout registry
uses: ./.github/actions/checkout-registry

@ -1 +1 @@
a000b2a0e64b6cdd27b3edd98cbf2495f2725c64
488c6eb828e46821e3858275c1e6f53bc5721db3

1
rust/Cargo.lock generated

@ -4549,6 +4549,7 @@ dependencies = [
"num-derive 0.4.1",
"num-traits",
"proc-macro-crate 1.2.1",
"serde",
"serializable-account-meta",
"solana-program",
"spl-noop",

@ -170,7 +170,7 @@ impl PendingOperation for PendingMessage {
self.app_context.clone()
}
#[instrument(skip(self), ret, fields(id=?self.id()), level = "debug")]
#[instrument(skip(self), fields(id=?self.id()), level = "debug")]
async fn prepare(&mut self) -> PendingOperationResult {
if !self.is_ready() {
trace!("Message is not ready to be submitted yet");

@ -16,8 +16,8 @@ use cosmrs::{
},
},
cosmwasm::wasm::v1::{
query_client::QueryClient as WasmQueryClient, MsgExecuteContract,
QuerySmartContractStateRequest,
query_client::QueryClient as WasmQueryClient, ContractInfo, MsgExecuteContract,
QueryContractInfoRequest, QuerySmartContractStateRequest,
},
traits::Message,
},
@ -97,13 +97,8 @@ pub trait WasmProvider: Send + Sync {
block_height: Option<u64>,
) -> ChainResult<Vec<u8>>;
/// Perform a wasm query against a specified contract address.
async fn wasm_query_to<T: Serialize + Sync + Send + Clone + Debug>(
&self,
to: String,
payload: T,
block_height: Option<u64>,
) -> ChainResult<Vec<u8>>;
/// Request contract info from the stored contract address.
async fn wasm_contract_info(&self) -> ChainResult<ContractInfo>;
/// Send a wasm tx.
async fn wasm_send<T: Serialize + Sync + Send + Clone + Debug>(
@ -450,6 +445,13 @@ impl WasmGrpcProvider {
sequence: base_account.sequence,
})
}
fn get_contract_address(&self) -> Result<&CosmosAddress, ChainCommunicationError> {
let contract_address = self.contract_address.as_ref().ok_or_else(|| {
ChainCommunicationError::from_other_str("No contract address available")
})?;
Ok(contract_address)
}
}
#[async_trait]
@ -486,27 +488,12 @@ impl WasmProvider for WasmGrpcProvider {
where
T: Serialize + Send + Sync + Clone + Debug,
{
let contract_address = self.contract_address.as_ref().ok_or_else(|| {
ChainCommunicationError::from_other_str("No contract address available")
})?;
self.wasm_query_to(contract_address.address(), payload, block_height)
.await
}
async fn wasm_query_to<T>(
&self,
to: String,
payload: T,
block_height: Option<u64>,
) -> ChainResult<Vec<u8>>
where
T: Serialize + Send + Sync + Clone,
{
let contract_address = self.get_contract_address()?;
let query_data = serde_json::to_string(&payload)?.as_bytes().to_vec();
let response = self
.provider
.call(move |provider| {
let to = to.clone();
let to = contract_address.address().clone();
let query_data = query_data.clone();
let future = async move {
let mut client = WasmQueryClient::new(provider.channel.clone());
@ -534,15 +521,43 @@ impl WasmProvider for WasmGrpcProvider {
Ok(response.data)
}
async fn wasm_contract_info(&self) -> ChainResult<ContractInfo> {
let contract_address = self.get_contract_address()?;
let response = self
.provider
.call(move |provider| {
let to = contract_address.address().clone();
let future = async move {
let mut client = WasmQueryClient::new(provider.channel.clone());
let request = tonic::Request::new(QueryContractInfoRequest { address: to });
let response = client
.contract_info(request)
.await
.map_err(ChainCommunicationError::from_other)?
.into_inner()
.contract_info
.ok_or(ChainCommunicationError::from_other_str(
"empty contract info",
))?;
Ok(response)
};
Box::pin(future)
})
.await?;
Ok(response)
}
#[instrument(skip(self))]
async fn wasm_send<T>(&self, payload: T, gas_limit: Option<U256>) -> ChainResult<TxResponse>
where
T: Serialize + Send + Sync + Clone + Debug,
{
let signer = self.get_signer()?;
let contract_address = self.contract_address.as_ref().ok_or_else(|| {
ChainCommunicationError::from_other_str("No contract address available")
})?;
let contract_address = self.get_contract_address()?;
let msgs = vec![MsgExecuteContract {
sender: signer.address.clone(),
contract: contract_address.address(),
@ -610,9 +625,7 @@ impl WasmProvider for WasmGrpcProvider {
// Estimating gas requires a signer, which we can reasonably expect to have
// since we need one to send a tx with the estimated gas anyways.
let signer = self.get_signer()?;
let contract_address = self.contract_address.as_ref().ok_or_else(|| {
ChainCommunicationError::from_other_str("No contract address available")
})?;
let contract_address = self.get_contract_address()?;
let msg = MsgExecuteContract {
sender: signer.address.clone(),
contract: contract_address.address(),
@ -636,3 +649,6 @@ impl BlockNumberGetter for WasmGrpcProvider {
self.latest_block_height().await
}
}
#[cfg(test)]
mod tests;

@ -0,0 +1,77 @@
use std::str::FromStr;
use url::Url;
use hyperlane_core::config::OperationBatchConfig;
use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain};
use crate::address::CosmosAddress;
use crate::grpc::{WasmGrpcProvider, WasmProvider};
use crate::{ConnectionConf, CosmosAmount, RawCosmosAmount};
#[ignore]
#[tokio::test]
async fn test_wasm_contract_info_success() {
// given
let provider = provider("neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4");
// when
let result = provider.wasm_contract_info().await;
// then
assert!(result.is_ok());
let contract_info = result.unwrap();
assert_eq!(
contract_info.creator,
"neutron1dwnrgwsf5c9vqjxsax04pdm0mx007yrre4yyvm",
);
assert_eq!(
contract_info.admin,
"neutron1fqf5mprg3f5hytvzp3t7spmsum6rjrw80mq8zgkc0h6rxga0dtzqws3uu7",
);
}
#[ignore]
#[tokio::test]
async fn test_wasm_contract_info_no_contract() {
// given
let provider = provider("neutron1dwnrgwsf5c9vqjxsax04pdm0mx007yrre4yyvm");
// when
let result = provider.wasm_contract_info().await;
// then
assert!(result.is_err());
}
fn provider(address: &str) -> WasmGrpcProvider {
let domain = HyperlaneDomain::Known(KnownHyperlaneDomain::Neutron);
let address = CosmosAddress::from_str(address).unwrap();
let locator = Some(ContractLocator::new(&domain, address.digest()));
WasmGrpcProvider::new(
domain.clone(),
ConnectionConf::new(
vec![Url::parse("http://grpc-kralum.neutron-1.neutron.org:80").unwrap()],
"https://rpc-kralum.neutron-1.neutron.org".to_owned(),
"neutron-1".to_owned(),
"neutron".to_owned(),
"untrn".to_owned(),
RawCosmosAmount::new("untrn".to_owned(), "0".to_owned()),
32,
OperationBatchConfig {
batch_contract_address: None,
max_batch_size: 1,
},
),
CosmosAmount {
denom: "untrn".to_owned(),
amount: Default::default(),
},
locator,
None,
)
.unwrap()
}

@ -1,10 +1,12 @@
use async_trait::async_trait;
use tendermint_rpc::{client::CompatMode, HttpClient};
use hyperlane_core::{
BlockInfo, ChainInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain,
HyperlaneProvider, TxnInfo, H256, U256,
};
use tendermint_rpc::{client::CompatMode, HttpClient};
use crate::grpc::WasmProvider;
use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer};
use self::grpc::WasmGrpcProvider;
@ -88,9 +90,11 @@ impl HyperlaneProvider for CosmosProvider {
todo!() // FIXME
}
async fn is_contract(&self, _address: &H256) -> ChainResult<bool> {
// FIXME
Ok(true)
async fn is_contract(&self, address: &H256) -> ChainResult<bool> {
match self.grpc_client.wasm_contract_info().await {
Ok(c) => Ok(true),
Err(e) => Ok(false),
}
}
async fn get_balance(&self, address: String) -> ChainResult<U256> {

@ -520,7 +520,7 @@
"name": "celo",
"nativeToken": {
"decimals": 18,
"name": "CELO",
"name": "Celo",
"symbol": "CELO"
},
"pausableHook": "0x80672c5D9Fd26B235654C24adc1CFcDeb8d15115",
@ -582,7 +582,7 @@
"name": "cheesechain",
"nativeToken": {
"decimals": 18,
"name": "CHEESE",
"name": "Cheese",
"symbol": "CHEESE"
},
"pausableHook": "0x7b75b29caD47e10146e29BBf7BD9025e021a7023",
@ -703,7 +703,7 @@
"name": "degenchain",
"nativeToken": {
"decimals": 18,
"name": "Degen Chain",
"name": "Degen",
"symbol": "DEGEN"
},
"pausableHook": "0x61594D2cA900C44ab51d07776465397FefC643C6",
@ -808,7 +808,7 @@
"name": "endurance",
"nativeToken": {
"decimals": 18,
"name": "ACE",
"name": "Fusionist",
"symbol": "ACE"
},
"pausableHook": "0xaE7BCf37D4541d3CFc46c1459829c1246E11aE08",
@ -1179,8 +1179,7 @@
},
"injective": {
"bech32Prefix": "inj",
"blockExplorers": [
],
"blockExplorers": [],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 1,
@ -1456,7 +1455,7 @@
"name": "lukso",
"nativeToken": {
"decimals": 18,
"name": "LYX",
"name": "LUKSO",
"symbol": "LYX"
},
"pausableHook": "0x232471495586baF1aCc4FD930B18302AEa3B1b35",
@ -1651,7 +1650,7 @@
"name": "merlin",
"nativeToken": {
"decimals": 18,
"name": "BTC",
"name": "Bitcoin",
"symbol": "BTC"
},
"pausableHook": "0xDd1CddEd982e4d271d4D6Bc4cdE8d4F3338733B5",
@ -1899,7 +1898,7 @@
"name": "moonbeam",
"nativeToken": {
"decimals": 18,
"name": "GLMR",
"name": "Moonbeam",
"symbol": "GLMR"
},
"pausableHook": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f",
@ -2363,7 +2362,7 @@
"name": "real",
"nativeToken": {
"decimals": 18,
"name": "reETH",
"name": "Real Ether",
"symbol": "reETH"
},
"pausableHook": "0xc965292958794f59ec3e9538738dD252873F07CC",
@ -2484,7 +2483,7 @@
"name": "sanko",
"nativeToken": {
"decimals": 18,
"name": "DMT",
"name": "Dream Machine Token",
"symbol": "DMT"
},
"pausableHook": "0x61594D2cA900C44ab51d07776465397FefC643C6",
@ -2671,7 +2670,7 @@
"name": "solana",
"nativeToken": {
"decimals": 9,
"name": "Sol",
"name": "Solana",
"symbol": "SOL"
},
"protocol": "sealevel",
@ -3125,8 +3124,8 @@
"aggregationHook": "0x198e8c938EC00Da143e772628c7958DD97B7c2A6",
"blockExplorers": [
{
"apiUrl": "https://explorer.zircuit.com/api",
"family": "other",
"apiUrl": "https://explorer.zircuit.com/api/contractVerifyHardhat",
"family": "etherscan",
"name": "Zircuit Eplorer",
"url": "https://explorer.zircuit.com"
}
@ -3147,12 +3146,12 @@
"domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"fallbackRoutingHook": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762",
"gasCurrencyCoinGeckoId": "ethereum",
"gnosisSafeTransactionServiceUrl": "https://gateway.safe.zircuit.com/api",
"gnosisSafeTransactionServiceUrl": "https://transaction.safe.zircuit.com",
"index": {
"from": 1511458
},
"interchainGasPaymaster": "0x03cF708E42C89623bd83B281A56935cB562b9258",
"interchainSecurityModule": "0x636bB3fCa3B74B942bA8C258c44109384B252979",
"interchainSecurityModule": "0x817D346Add1D08Af124a90e43123111c9d6813B3",
"mailbox": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c",
"merkleTreeHook": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28",
"name": "zircuit",
@ -3162,7 +3161,7 @@
"symbol": "ETH"
},
"pausableHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2",
"pausableIsm": "0x4848d54987ffc732aD313827cdC25DF2eedD79d8",
"pausableIsm": "0xc2Da384799488B4e1E773d70a83346529145085B",
"protocol": "ethereum",
"protocolFee": "0xe243Fb51d91c5DE62afAbE44F7Ed2D4DC51668C6",
"proxyAdmin": "0xA5580D7Af50F3FD869EbEA51e352e2656F8DD5C2",
@ -3172,6 +3171,9 @@
},
{
"http": "https://zircuit1-mainnet.liquify.com"
},
{
"http": "https://zircuit-mainnet.drpc.org"
}
],
"staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6",

@ -114,10 +114,10 @@
"proxyAdmin": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8",
"rpcUrls": [
{
"http": "https://public.stackup.sh/api/v1/node/arbitrum-sepolia"
"http": "https://arbitrum-sepolia.blockpi.network/v1/rpc/public"
},
{
"http": "https://arbitrum-sepolia.blockpi.network/v1/rpc/public"
"http": "https://sepolia-rollup.arbitrum.io/rpc"
}
],
"staticAggregationHookFactory": "0x0526E47C49742C15F8817ef8cf0d8FFc72139D4F",
@ -693,7 +693,7 @@
"name": "polygonamoy",
"nativeToken": {
"decimals": 18,
"name": "MATIC",
"name": "Matic",
"symbol": "MATIC"
},
"pausableHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8",
@ -903,7 +903,7 @@
"name": "solanatestnet",
"nativeToken": {
"decimals": 9,
"name": "Sol",
"name": "Solana",
"symbol": "SOL"
},
"protocol": "sealevel",

@ -25,7 +25,7 @@ solana-transaction-status.workspace = true
account-utils = { path = "../libraries/account-utils" }
hyperlane-core = { path = "../../hyperlane-core" }
hyperlane-sealevel-connection-client = { path = "../libraries/hyperlane-sealevel-connection-client" }
hyperlane-sealevel-mailbox = { path = "../programs/mailbox", features = ["no-entrypoint"] }
hyperlane-sealevel-mailbox = { path = "../programs/mailbox", features = ["no-entrypoint", "serde"] }
hyperlane-sealevel-multisig-ism-message-id = { path = "../programs/ism/multisig-ism-message-id", features = ["no-entrypoint"] }
hyperlane-sealevel-token = { path = "../programs/hyperlane-sealevel-token", features = ["no-entrypoint"] }
hyperlane-sealevel-igp = { path = "../programs/hyperlane-sealevel-igp", features = ["no-entrypoint", "serde"] }

@ -13,6 +13,8 @@ use solana_sdk::{
signature::{Keypair, Signer},
};
const SOLANA_DOMAIN: u32 = 1399811149;
pub(crate) fn account_exists(client: &RpcClient, account: &Pubkey) -> Result<bool, ClientError> {
// Using `get_account_with_commitment` instead of `get_account` so we get Ok(None) when the account
// doesn't exist, rather than an error
@ -29,10 +31,17 @@ pub(crate) fn deploy_program_idempotent(
program_keypair_path: &str,
program_path: &str,
url: &str,
local_domain: u32,
) -> Result<(), ClientError> {
let client = RpcClient::new(url.to_string());
if !account_exists(&client, &program_keypair.pubkey())? {
deploy_program(payer_keypair_path, program_keypair_path, program_path, url);
deploy_program(
payer_keypair_path,
program_keypair_path,
program_path,
url,
local_domain,
);
} else {
println!("Program {} already deployed", program_keypair.pubkey());
}
@ -45,25 +54,29 @@ pub(crate) fn deploy_program(
program_keypair_path: &str,
program_path: &str,
url: &str,
local_domain: u32,
) {
build_cmd(
&[
"solana",
"--url",
url,
"-k",
payer_keypair_path,
"program",
"deploy",
program_path,
"--upgrade-authority",
payer_keypair_path,
"--program-id",
program_keypair_path,
],
None,
None,
);
let mut command = vec![
"solana",
"--url",
url,
"-k",
payer_keypair_path,
"program",
"deploy",
program_path,
"--upgrade-authority",
payer_keypair_path,
"--program-id",
program_keypair_path,
];
if local_domain.eq(&SOLANA_DOMAIN) {
// May need tweaking depending on gas prices / available balance
command.append(&mut vec!["--with-compute-unit-price", "550000"]);
}
build_cmd(command.as_slice(), None, None);
}
pub(crate) fn create_new_directory(parent_dir: &Path, name: &str) -> PathBuf {
@ -112,6 +125,7 @@ fn build_cmd(cmd: &[&str], wd: Option<&str>, env: Option<&HashMap<&str, &str>>)
if let Some(env) = env {
c.envs(env);
}
println!("Running command: {:?}", c);
let status = c.status().expect("Failed to run command");
assert!(
status.success(),

@ -29,6 +29,7 @@ pub(crate) struct Context {
pub require_tx_approval: bool,
}
#[derive(Debug)]
pub(crate) struct InstructionWithDescription {
pub instruction: Instruction,
pub description: Option<String>,

@ -1,7 +1,10 @@
use borsh::BorshDeserialize;
use hyperlane_sealevel_mailbox::protocol_fee::ProtocolFee;
use serde::{Deserialize, Serialize};
use solana_program::pubkey::Pubkey;
use solana_sdk::signature::Signer;
use solana_sdk::{compute_budget, compute_budget::ComputeBudgetInstruction};
use std::collections::HashMap;
use std::{fs::File, path::Path};
@ -12,12 +15,77 @@ use crate::{
multisig_ism::deploy_multisig_ism_message_id,
Context, CoreCmd, CoreDeploy, CoreSubCmd,
};
use crate::{DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, ONE_SOL_IN_LAMPORTS};
use hyperlane_core::H256;
use hyperlane_sealevel_igp::accounts::{SOL_DECIMALS, TOKEN_EXCHANGE_RATE_SCALE};
pub(crate) fn adjust_gas_price_if_needed(chain_name: &str, ctx: &mut Context) {
if chain_name.eq("solana") {
let mut initial_instructions = ctx.initial_instructions.borrow_mut();
const PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX: u64 = 50_000_000;
const MICRO_LAMPORT_FEE_PER_LIMIT: u64 =
// Convert to micro-lamports
(PROCESS_DESIRED_PRIORITIZATION_FEE_LAMPORTS_PER_TX * 1_000_000)
// Divide by the max compute units
/ DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64;
for i in initial_instructions.iter_mut() {
if i.instruction.program_id != compute_budget::id() {
continue;
}
if let Ok(compute_budget_instruction) =
ComputeBudgetInstruction::try_from_slice(&i.instruction.data)
{
if matches!(
compute_budget_instruction,
ComputeBudgetInstruction::SetComputeUnitPrice { .. }
) {
// The compute unit price has already been set, so we override it and return early
i.instruction = ComputeBudgetInstruction::set_compute_unit_price(
MICRO_LAMPORT_FEE_PER_LIMIT,
);
return;
}
}
}
initial_instructions.push(
(
ComputeBudgetInstruction::set_compute_unit_price(MICRO_LAMPORT_FEE_PER_LIMIT),
Some(format!(
"Set compute unit price to {}",
MICRO_LAMPORT_FEE_PER_LIMIT
)),
)
.into(),
);
}
}
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
struct ProtocolFeeConfig {
max_protocol_fee: u64,
fee: u64,
#[serde(with = "crate::serde::serde_option_pubkey")]
beneficiary: Option<Pubkey>,
}
impl Default for ProtocolFeeConfig {
fn default() -> Self {
Self {
max_protocol_fee: ONE_SOL_IN_LAMPORTS,
fee: 0,
beneficiary: None,
}
}
}
pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) {
match cmd.cmd {
CoreSubCmd::Deploy(core) => {
adjust_gas_price_if_needed(core.chain.as_str(), &mut ctx);
let environments_dir =
create_new_directory(&core.env_args.environments_dir, &core.env_args.environment);
let chain_dir = create_new_directory(&environments_dir, &core.chain);
@ -29,9 +97,11 @@ pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) {
&core.built_so_dir,
core.use_existing_keys,
&key_dir,
core.local_domain,
);
let mailbox_program_id = deploy_mailbox(&mut ctx, &core, &key_dir, ism_program_id);
let mailbox_program_id =
deploy_mailbox(&mut ctx, &core, &key_dir, ism_program_id, core.local_domain);
let validator_announce_program_id =
deploy_validator_announce(&mut ctx, &core, &key_dir, mailbox_program_id);
@ -57,6 +127,7 @@ fn deploy_mailbox(
core: &CoreDeploy,
key_dir: &Path,
default_ism: Pubkey,
local_domain: u32,
) -> Pubkey {
let (keypair, keypair_path) = create_and_write_keypair(
key_dir,
@ -73,15 +144,33 @@ fn deploy_mailbox(
.to_str()
.unwrap(),
&ctx.client.url(),
local_domain,
);
println!("Deployed Mailbox at program ID {}", program_id);
let protocol_fee_config = core
.protocol_fee_config_file
.as_deref()
.map(|p| {
let file = File::open(p).expect("Failed to open oracle config file");
serde_json::from_reader::<_, ProtocolFeeConfig>(file)
.expect("Failed to parse oracle config file")
})
.unwrap_or_default();
let protocol_fee_beneficiary = protocol_fee_config.beneficiary.unwrap_or(ctx.payer_pubkey);
// Initialize
let instruction = hyperlane_sealevel_mailbox::instruction::init_instruction(
program_id,
core.local_domain,
default_ism,
protocol_fee_config.max_protocol_fee,
ProtocolFee {
fee: protocol_fee_config.fee,
beneficiary: protocol_fee_beneficiary,
},
ctx.payer_pubkey,
)
.unwrap();
@ -114,6 +203,7 @@ fn deploy_validator_announce(
.to_str()
.unwrap(),
&ctx.client.url(),
core.local_domain,
);
println!("Deployed ValidatorAnnounce at program ID {}", program_id);
@ -199,6 +289,7 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey,
.to_str()
.unwrap(),
&ctx.client.url(),
core.local_domain,
);
println!("Deployed IGP at program ID {}", program_id);
@ -327,3 +418,25 @@ pub(crate) fn read_core_program_ids(
.join("program-ids.json");
read_json(&path)
}
#[cfg(test)]
mod test {
use solana_program::pubkey::Pubkey;
#[test]
fn test_protocol_fee_serialization() {
let protocol_fee_config = super::ProtocolFeeConfig {
max_protocol_fee: 100,
fee: 10,
beneficiary: Some(Pubkey::new_unique()),
};
let json_serialized = serde_json::to_string(&protocol_fee_config).unwrap();
assert_eq!(
json_serialized,
r#"{"maxProtocolFee":100,"fee":10,"beneficiary":"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM"}"#
);
let deserialized: super::ProtocolFeeConfig =
serde_json::from_str(&json_serialized).unwrap();
assert_eq!(deserialized, protocol_fee_config);
}
}

@ -20,7 +20,7 @@ use solana_sdk::{
signature::{Keypair, Signer as _},
};
use hyperlane_core::H256;
use hyperlane_core::{KnownHyperlaneDomain, H256};
use hyperlane_sealevel_igp::{
accounts::{
@ -68,8 +68,14 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) {
let ism_dir = create_new_directory(&environments_dir, "igp");
let chain_dir = create_new_directory(&ism_dir, &deploy.chain);
let key_dir = create_new_directory(&chain_dir, "keys");
let local_domain = deploy
.chain
.parse::<KnownHyperlaneDomain>()
.map(|v| v as u32)
.expect("Invalid chain name");
let program_id = deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir);
let program_id =
deploy_igp_program(&mut ctx, &deploy.built_so_dir, true, &key_dir, local_domain);
write_json::<SingularProgramIdArtifact>(
&chain_dir.join("program-ids.json"),
@ -409,6 +415,7 @@ fn deploy_igp_program(
built_so_dir: &Path,
use_existing_keys: bool,
key_dir: &Path,
local_domain: u32,
) -> Pubkey {
let (keypair, keypair_path) = create_and_write_keypair(
key_dir,
@ -425,6 +432,7 @@ fn deploy_igp_program(
.to_str()
.unwrap(),
&ctx.client.url(),
local_domain,
);
println!("Deployed IGP at program ID {}", program_id);

@ -31,7 +31,9 @@ use hyperlane_sealevel_mailbox::{
instruction::{Instruction as MailboxInstruction, OutboxDispatch},
mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds,
mailbox_message_dispatch_authority_pda_seeds, mailbox_outbox_pda_seeds,
mailbox_processed_message_pda_seeds, spl_noop,
mailbox_processed_message_pda_seeds,
protocol_fee::ProtocolFee,
spl_noop,
};
use hyperlane_sealevel_token::{
@ -175,6 +177,8 @@ struct CoreDeploy {
#[command(flatten)]
env_args: EnvironmentArgs,
#[arg(long)]
protocol_fee_config_file: Option<PathBuf>,
#[arg(long)]
gas_oracle_config_file: Option<PathBuf>,
#[arg(long)]
overhead_config_file: Option<PathBuf>,
@ -209,6 +213,8 @@ const HYPERLANE_TOKEN_PROG_ID: Pubkey = pubkey!("3MzUPjP5LEkiHH82nEAe28Xtz9ztuMq
const MULTISIG_ISM_MESSAGE_ID_PROG_ID: Pubkey =
pubkey!("2YjtZDiUoptoSsA5eVrDCcX6wxNK6YoEVW7y82x5Z2fw");
const VALIDATOR_ANNOUNCE_PROG_ID: Pubkey = pubkey!("DH43ae1LwemXAboWwSh8zc9pG8j72gKUEXNi57w8fEnn");
pub const DEFAULT_PROTOCOL_FEE: u64 = 0;
pub const ONE_SOL_IN_LAMPORTS: u64 = 1_000_000_000;
#[derive(Args)]
struct Init {
@ -218,6 +224,12 @@ struct Init {
local_domain: u32,
#[arg(long, short, default_value_t = MULTISIG_ISM_MESSAGE_ID_PROG_ID)]
default_ism: Pubkey,
#[arg(long, short, default_value_t = ONE_SOL_IN_LAMPORTS)]
max_protocol_fee: u64,
#[arg(long, short, default_value_t = DEFAULT_PROTOCOL_FEE)]
protocol_fee: u64,
#[arg(long, short, default_value = None)]
protocol_fee_beneficiary: Option<Pubkey>,
}
#[derive(Args)]
@ -698,7 +710,7 @@ fn main() {
payer_keypair.pubkey(),
Some(PayerKeypair {
keypair: payer_keypair,
keypair_path,
keypair_path: keypair_path.clone(),
}),
)
} else {
@ -712,6 +724,7 @@ fn main() {
let commitment = CommitmentConfig::confirmed();
let mut instructions = vec![];
if cli.compute_budget != DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT {
assert!(cli.compute_budget <= MAX_COMPUTE_UNIT_LIMIT);
instructions.push(
@ -758,10 +771,17 @@ fn main() {
fn process_mailbox_cmd(ctx: Context, cmd: MailboxCmd) {
match cmd.cmd {
MailboxSubCmd::Init(init) => {
let protocol_fee_beneficiary =
init.protocol_fee_beneficiary.unwrap_or(ctx.payer_pubkey);
let instruction = hyperlane_sealevel_mailbox::instruction::init_instruction(
init.program_id,
init.local_domain,
init.default_ism,
init.max_protocol_fee,
ProtocolFee {
fee: init.protocol_fee,
beneficiary: protocol_fee_beneficiary,
},
ctx.payer_pubkey,
)
.unwrap();

@ -12,7 +12,7 @@ use crate::{
Context, MultisigIsmMessageIdCmd, MultisigIsmMessageIdSubCmd,
};
use hyperlane_core::H160;
use hyperlane_core::{KnownHyperlaneDomain, H160};
use hyperlane_sealevel_multisig_ism_message_id::{
access_control_pda_seeds,
@ -53,9 +53,19 @@ pub(crate) fn process_multisig_ism_message_id_cmd(mut ctx: Context, cmd: Multisi
let chain_dir = create_new_directory(&ism_dir, &deploy.chain);
let context_dir = create_new_directory(&chain_dir, &deploy.context);
let key_dir = create_new_directory(&context_dir, "keys");
let local_domain = deploy
.chain
.parse::<KnownHyperlaneDomain>()
.map(|v| v as u32)
.expect("Invalid chain name");
let ism_program_id =
deploy_multisig_ism_message_id(&mut ctx, &deploy.built_so_dir, true, &key_dir);
let ism_program_id = deploy_multisig_ism_message_id(
&mut ctx,
&deploy.built_so_dir,
true,
&key_dir,
local_domain,
);
write_json::<SingularProgramIdArtifact>(
&context_dir.join("program-ids.json"),
@ -158,6 +168,7 @@ pub(crate) fn deploy_multisig_ism_message_id(
built_so_dir: &Path,
use_existing_keys: bool,
key_dir: &Path,
local_domain: u32,
) -> Pubkey {
let (keypair, keypair_path) = create_and_write_keypair(
key_dir,
@ -174,6 +185,7 @@ pub(crate) fn deploy_multisig_ism_message_id(
.to_str()
.unwrap(),
&ctx.client.url(),
local_domain,
);
println!(
@ -197,6 +209,10 @@ pub(crate) fn deploy_multisig_ism_message_id(
),
)
.send_with_payer();
println!(
"initialized Multisig ISM Message ID at program ID {}",
program_id
);
program_id
}

@ -15,9 +15,10 @@ use hyperlane_sealevel_connection_client::router::RemoteRouterConfig;
use hyperlane_sealevel_igp::accounts::{Igp, InterchainGasPaymasterType, OverheadIgp};
use crate::{
adjust_gas_price_if_needed,
artifacts::{write_json, HexAndBase58ProgramIdArtifact},
cmd_utils::{create_and_write_keypair, create_new_directory, deploy_program_idempotent},
read_core_program_ids, Context, CoreProgramIds,
read_core_program_ids, warp_route, Context, CoreProgramIds,
};
/// Optional connection client configuration.
@ -185,6 +186,7 @@ pub(crate) trait RouterDeployer<Config: RouterConfigGetter + std::fmt::Debug>:
.to_str()
.unwrap(),
&chain_config.rpc_urls[0].http,
chain_config.domain_id(),
)
.unwrap();
@ -348,6 +350,8 @@ pub(crate) fn deploy_routers<
.filter(|(_, app_config)| app_config.router_config().foreign_deployment.is_none())
.collect::<HashMap<_, _>>();
warp_route::install_spl_token_cli();
// Now we deploy to chains that don't have a foreign deployment
for (chain_name, app_config) in app_configs_to_deploy.iter() {
let chain_config = chain_configs
@ -360,6 +364,8 @@ pub(crate) fn deploy_routers<
}
}
adjust_gas_price_if_needed(chain_name.as_str(), ctx);
// Deploy - this is idempotent.
let program_id = deployer.deploy(
ctx,
@ -537,6 +543,8 @@ fn enroll_all_remote_routers<
routers: &HashMap<u32, H256>,
) {
for (chain_name, _) in app_configs_to_deploy.iter() {
adjust_gas_price_if_needed(chain_name.as_str(), ctx);
let chain_config = chain_configs
.get(*chain_name)
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name));

@ -3,7 +3,11 @@ use hyperlane_core::H256;
use hyperlane_sealevel_token_collateral::plugin::CollateralPlugin;
use hyperlane_sealevel_token_native::plugin::NativePlugin;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt::Debug};
use std::{
collections::HashMap,
fmt::Debug,
process::{Command, Stdio},
};
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
@ -78,6 +82,7 @@ struct TokenMetadata {
name: String,
symbol: String,
total_supply: Option<String>,
uri: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
@ -243,6 +248,9 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
remote_decimals: app_config.decimal_metadata.remote_decimals(),
};
let home_path = std::env::var("HOME").unwrap();
let spl_token_binary_path = format!("{home_path}/.cargo/bin/spl-token");
match &app_config.token_type {
TokenType::Native => ctx.new_txn().add(
hyperlane_sealevel_token_native::instruction::init_instruction(
@ -255,23 +263,51 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
TokenType::Synthetic(_token_metadata) => {
let decimals = init.decimals;
let init_txn = ctx.new_txn().add(
hyperlane_sealevel_token::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
ctx.new_txn()
.add(
hyperlane_sealevel_token::instruction::init_instruction(
program_id,
ctx.payer_pubkey,
init,
)
.unwrap(),
)
.unwrap(),
);
.with_client(client)
.send_with_payer();
let (mint_account, _mint_bump) =
Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id);
// TODO: Also set Metaplex metadata?
init_txn.add(
let mut cmd = Command::new(spl_token_binary_path.clone());
cmd.args([
"create-token",
mint_account.to_string().as_str(),
"--enable-metadata",
"-p",
spl_token_2022::id().to_string().as_str(),
"--url",
client.url().as_str(),
"--with-compute-unit-limit",
"500000",
"--mint-authority",
&ctx.payer_pubkey.to_string(),
"--fee-payer",
ctx.payer_keypair_path(),
]);
println!("running command: {:?}", cmd);
let status = cmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("Failed to run command");
println!("initialized metadata pointer. Status: {status}");
ctx.new_txn().add(
spl_token_2022::instruction::initialize_mint2(
&spl_token_2022::id(),
&mint_account,
&mint_account,
&ctx.payer_pubkey,
None,
decimals,
)
@ -295,6 +331,71 @@ impl RouterDeployer<TokenConfig> for WarpRouteDeployer {
}
.with_client(client)
.send_with_payer();
if let TokenType::Synthetic(token_metadata) = &app_config.token_type {
let (mint_account, _mint_bump) =
Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id);
let mut cmd = Command::new(spl_token_binary_path.clone());
cmd.args([
"initialize-metadata",
mint_account.to_string().as_str(),
token_metadata.name.as_str(),
token_metadata.symbol.as_str(),
token_metadata.uri.as_deref().unwrap_or(""),
"-p",
spl_token_2022::id().to_string().as_str(),
"--url",
client.url().as_str(),
"--with-compute-unit-limit",
"500000",
"--mint-authority",
ctx.payer_keypair_path(),
"--fee-payer",
ctx.payer_keypair_path(),
"--update-authority",
&ctx.payer_pubkey.to_string(),
]);
println!("running command: {:?}", cmd);
let status = cmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("Failed to run command");
println!("initialized metadata. Status: {status}");
// Burn the metadata pointer, metadata, and mint authorities by moving them to the mint
let authorities_to_transfer = &["metadata-pointer", "metadata", "mint"];
for authority in authorities_to_transfer {
println!("Transferring authority: {authority} to the mint account {mint_account}");
let mut cmd = Command::new(spl_token_binary_path.clone());
cmd.args([
"authorize",
mint_account.to_string().as_str(),
authority,
mint_account.to_string().as_str(),
"-p",
spl_token_2022::id().to_string().as_str(),
"--url",
client.url().as_str(),
"--with-compute-unit-limit",
"500000",
"--fee-payer",
ctx.payer_keypair_path(),
"--authority",
ctx.payer_keypair_path(),
]);
println!("Running command: {:?}", cmd);
let status = cmd
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("Failed to run command");
println!("Set the {authority} authority to the mint account. Status: {status}");
}
}
}
/// Sets gas router configs on all deployable chains.
@ -539,3 +640,30 @@ pub fn parse_token_account_data(token_type: FlatTokenType, data: &mut &[u8]) {
}
}
}
pub fn install_spl_token_cli() {
println!("Installing cargo 1.76.0 (required by spl-token-cli)");
Command::new("rustup")
.args(["toolchain", "install", "1.76.0"])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("Failed to run command");
println!("Installing the spl token cli");
Command::new("cargo")
.args([
"+1.76.0",
"install",
"spl-token-cli",
"--git",
"https://github.com/hyperlane-xyz/solana-program-library",
"--branch",
"dan/create-token-for-mint",
"--rev",
"c1278a3f1",
])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("Failed to run command");
}

@ -0,0 +1,3 @@
{
"program_id": "HDXcKGggF4aLRE1Q6ApF2rLwriSQjpjrnE811cwH21mr"
}

@ -1,7 +1,7 @@
{
"mailbox": "4rRZgaC1DaCqtWYLzg14ftuXKPuHe1nGEM6ZtNpim3Wz",
"validator_announce": "bn63TQYrzzK9H3XQwZ1gzGdNS91xkt5YaTinFPWyahR",
"multisig_ism_message_id": "2abS919HMBQ39GviSgjMQQf5L1G7cfdbAiNq61wQ8CvJ",
"multisig_ism_message_id": "HDXcKGggF4aLRE1Q6ApF2rLwriSQjpjrnE811cwH21mr",
"igp_program_id": "8cPZ6Lv49h1cYBb6q29E2pxn4xbWjJGxfarNE8LqP1Ks",
"overhead_igp_account": "5FG1TUuhXGKdMbbH8uHEnUghazD4aVfEPAgKLNGNx3SL",
"igp_account": "HkqbGqRX7Fi5pwqi8HkDaUhHK6mGWsy7Rt17fpgBrbP5"

@ -0,0 +1,10 @@
{
"eclipse": {
"hex": "0x6d8f3a8e68449b4c5fa5f09f9f9bf82607f6b2b1b6052260bf7c5c66aa5a5684",
"base58": "8Ng7TLE223sWcRjH7rEdUPoR3seaugqPw1djdQRRZ6Gj"
},
"solana": {
"hex": "0x6903def7c07b2844eb549e7037651c07f508884e0c962345c8bb2b9834633e15",
"base58": "84wESfpyisKVYwkpaJpkHS6XxkLPC5bLZrTD1jQw1TrL"
}
}

@ -0,0 +1,15 @@
{
"solana": {
"type": "native",
"decimals": 9,
"interchainGasPaymaster": "HkqbGqRX7Fi5pwqi8HkDaUhHK6mGWsy7Rt17fpgBrbP5"
},
"eclipse": {
"type": "synthetic",
"decimals": 9,
"name": "Solana",
"symbol": "SOL",
"uri": "https://github.com/hyperlane-xyz/hyperlane-registry/blob/b661127dd3dce5ea98b78ae0051fbd10c384b173/deployments/warp_routes/SOL/eclipse/metadata.json",
"interchainGasPaymaster": "AgjedtgQKTWGR77ULJ9j9AhLjNDk1D3BTtuxKmcZrJqE"
}
}

@ -0,0 +1,10 @@
{
"solana": {
"hex": "0xe9792265ec273ffc17731af890d3e9963e9d744e7b99f02491911ce1bb75b8cb",
"base58": "GiP8GwN1GsscVJvmKSD4muDEihRzZRa9mxnS1Toi64pa"
},
"ethereum": {
"hex": "0x0000000000000000000000001d622da2ce4c4d9d4b0611718cb3bcdcad008dd4",
"base58": "111111111111Qk6NeeudaXPNPB2XfB1yQ9furMh"
}
}

@ -0,0 +1,18 @@
{
"solana": {
"type": "synthetic",
"decimals": 9,
"name": "Renzo Restaked LST",
"symbol": "pzETH",
"uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/12660fd34d30e960a748d87408a8d88f634f7454/deployments/warp_routes/pzETH/ethereum-solana-metadata.json",
"interchainGasPaymaster": "5FG1TUuhXGKdMbbH8uHEnUghazD4aVfEPAgKLNGNx3SL",
"remoteDecimals": 18
},
"ethereum": {
"type": "collateral",
"decimals": 18,
"token": "0x8c9532a60e0e7c6bbd2b2c1303f63ace1c3e9811",
"foreignDeployment": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4",
"destinationGas": 200000
}
}

@ -43,37 +43,40 @@ pub fn get_multisig_ism_test_data() -> MultisigIsmTestData {
"0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
)
.unwrap(),
index: 69,
// Intentionally set to a different value than the message's nonce to test
// that the checkpoint in the ISM is constructed correctly using the metadata's
// merkle index.
index: message.nonce + 1,
},
message_id: message.id(),
};
// checkpoint.signing_hash() is equal to:
// 0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a
// 0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257
// Validator 0:
// Address: 0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D
// Private Key: 0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091
let validator_0 = H160::from_str("0xE3DCDBbc248cE191bDc271f3FCcd0d95911BFC5D").unwrap();
// > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a'))
// '0xb8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b'
let signature_0 = hex::decode("b8875fb75adf471e43a943b78eb422ffe86a4291fa6324f9f875241605ca831d2b4225358936deee7501cf305aafc1677e3dc9bcfea4caec54f4cde49d416bd91b").unwrap();
// > await (new ethers.Wallet('0x788aa7213bd92ff92017d767fde0d75601425818c8e4b21e87314c2a4dcd6091')).signMessage(ethers.utils.arrayify('0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257'))
// '0x081d398e1452ae12267f63f224d3037b4bb3f496cb55c14a2076c5e27ed944ad6d8e10d3164bc13b5820846a3f19e013e1c551b67a3c863882f7b951acdab96d1c'
let signature_0 = hex::decode("081d398e1452ae12267f63f224d3037b4bb3f496cb55c14a2076c5e27ed944ad6d8e10d3164bc13b5820846a3f19e013e1c551b67a3c863882f7b951acdab96d1c").unwrap();
// Validator 1:
// Address: 0xb25206874C24733F05CC0dD11924724A8E7175bd
// Private Key: 0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e
let validator_1 = H160::from_str("0xb25206874C24733F05CC0dD11924724A8E7175bd").unwrap();
// > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a'))
// '0xfa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c'
let signature_1 = hex::decode("fa8d61da2e0ac6f32c8432e75770d90483613efe96b442fbca9ca8d200447bf979f46529c7341879333a9c24a7d3fba08b53d13447618b71cf2ee4734e82f96e1c").unwrap();
// > await (new ethers.Wallet('0x4a599de3915f404d84a2ebe522bfe7032ebb1ca76a65b55d6eb212b129043a0e')).signMessage(ethers.utils.arrayify('0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257'))
// '0x0c189e25dea6bb93292af16fd0516f3adc8a19556714c0b8d624016175bebcba7a5fe8218dad6fc86faeb8104fad8390ccdec989d992e852553ea6b61fbb2eda1b'
let signature_1 = hex::decode("0c189e25dea6bb93292af16fd0516f3adc8a19556714c0b8d624016175bebcba7a5fe8218dad6fc86faeb8104fad8390ccdec989d992e852553ea6b61fbb2eda1b").unwrap();
// Validator 2:
// Address: 0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3
// Private Key: 0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515
let validator_2 = H160::from_str("0x28b8d0E2bBfeDe9071F8Ff3DaC9CcE3d3176DBd3").unwrap();
// > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x5c2b4cbafdd7319d9a3dea4aa078748ae53368e1521494a80cf4259b9c6dba6a'))
// '0x6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b'
let signature_2 = hex::decode("6c60c3744f6bf779017b8ab9aa8eed60bf53c317cf4d74d765015cc0a7dcad783dee39334bfc7e4baab944355914e7431e4286df8c0557a0c1f6ba867677da421b").unwrap();
// > await (new ethers.Wallet('0x2cc76d56db9924ddc3388164454dfea9edd2d5f5da81102fd3594fc7c5281515')).signMessage(ethers.utils.arrayify('0x3fd308215a20af20b137372f8a69fd336ebf93d57d4076a7c46e13f315255257'))
// '0x5493449e8a09c1105195ecf913997de51bd50926a075ad98fe3e845e0a11126b5212a2cd1afdd35a44322146d31f8fa3d179d8a9822637d8db0e2fa8b3d292421b'
let signature_2 = hex::decode("5493449e8a09c1105195ecf913997de51bd50926a075ad98fe3e845e0a11126b5212a2cd1afdd35a44322146d31f8fa3d179d8a9822637d8db0e2fa8b3d292421b").unwrap();
MultisigIsmTestData {
message,

@ -24,6 +24,7 @@ use hyperlane_sealevel_mailbox::{
instruction::{InboxProcess, Init as InitMailbox, Instruction as MailboxInstruction},
mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds, mailbox_process_authority_pda_seeds,
mailbox_processed_message_pda_seeds,
protocol_fee::ProtocolFee,
};
use hyperlane_sealevel_message_recipient_interface::{
HandleInstruction, MessageRecipientInstruction, HANDLE_ACCOUNT_METAS_PDA_SEEDS,
@ -55,6 +56,8 @@ pub async fn initialize_mailbox(
mailbox_program_id: &Pubkey,
payer: &Keypair,
local_domain: u32,
max_protocol_fee: u64,
protocol_fee: ProtocolFee,
) -> Result<MailboxAccounts, BanksClientError> {
let (inbox_account, inbox_bump) =
Pubkey::find_program_address(mailbox_inbox_pda_seeds!(), mailbox_program_id);
@ -66,6 +69,8 @@ pub async fn initialize_mailbox(
let ixn = MailboxInstruction::Init(InitMailbox {
local_domain,
default_ism,
max_protocol_fee,
protocol_fee,
});
let init_instruction = Instruction {
program_id: *mailbox_program_id,

@ -25,6 +25,7 @@ use hyperlane_sealevel_mailbox::{
accounts::{DispatchedMessage, DispatchedMessageAccount},
mailbox_dispatched_message_pda_seeds, mailbox_message_dispatch_authority_pda_seeds,
mailbox_process_authority_pda_seeds,
protocol_fee::ProtocolFee,
};
use hyperlane_sealevel_message_recipient_interface::{
HandleInstruction, MessageRecipientInstruction,
@ -425,10 +426,16 @@ async fn test_initialize() {
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -558,10 +565,16 @@ async fn test_transfer_remote(spl_token_program_id: Pubkey) {
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -796,10 +809,16 @@ async fn transfer_from_remote(
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -1023,10 +1042,16 @@ async fn test_transfer_from_remote_errors_if_process_authority_not_signer() {
let (mut banks_client, payer) = setup_client().await;
let _mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let _mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let (mint, _mint_authority) = initialize_mint(
&mut banks_client,

@ -22,6 +22,7 @@ use hyperlane_sealevel_mailbox::{
accounts::{DispatchedMessage, DispatchedMessageAccount},
mailbox_dispatched_message_pda_seeds, mailbox_message_dispatch_authority_pda_seeds,
mailbox_process_authority_pda_seeds,
protocol_fee::ProtocolFee,
};
use hyperlane_sealevel_message_recipient_interface::{
HandleInstruction, MessageRecipientInstruction,
@ -263,10 +264,16 @@ async fn test_initialize() {
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -349,10 +356,16 @@ async fn test_transfer_remote() {
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -568,10 +581,16 @@ async fn transfer_from_remote(
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -722,10 +741,16 @@ async fn test_transfer_from_remote_errors_if_process_authority_not_signer() {
let (mut banks_client, payer) = setup_client().await;
let _mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let _mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let hyperlane_token_accounts =
initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None)

@ -8,12 +8,13 @@ use hyperlane_sealevel_token_lib::{
accounts::HyperlaneToken, message::TokenMessage, processor::HyperlaneSealevelTokenPlugin,
};
use serializable_account_meta::SerializableAccountMeta;
#[cfg(not(target_arch = "sbf"))]
use solana_program::program_pack::Pack as _;
use solana_program::{
account_info::{next_account_info, AccountInfo},
instruction::AccountMeta,
program::{invoke, invoke_signed},
program_error::ProgramError,
program_pack::Pack as _,
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
@ -74,6 +75,13 @@ impl SizedData for SyntheticPlugin {
}
impl SyntheticPlugin {
/// The size of the mint account.
// Need to hardcode this value because our `spl_token_2022` version doesn't include it.
// It was calculated by calling `ExtensionType::try_calculate_account_len::<Mint>(vec![ExtensionType::MetadataPointer]).unwrap()`
#[cfg(target_arch = "sbf")]
const MINT_ACCOUNT_SIZE: usize = 234;
/// The size of the mint account.
#[cfg(not(target_arch = "sbf"))]
const MINT_ACCOUNT_SIZE: usize = spl_token_2022::state::Mint::LEN;
/// Returns Ok(()) if the mint account info is valid.

@ -15,6 +15,7 @@ use hyperlane_sealevel_mailbox::{
accounts::{DispatchedMessage, DispatchedMessageAccount},
mailbox_dispatched_message_pda_seeds, mailbox_message_dispatch_authority_pda_seeds,
mailbox_process_authority_pda_seeds,
protocol_fee::ProtocolFee,
};
use hyperlane_sealevel_message_recipient_interface::{
HandleInstruction, MessageRecipientInstruction,
@ -293,10 +294,16 @@ async fn test_initialize() {
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -404,10 +411,16 @@ async fn transfer_from_remote(
let (mut banks_client, payer) = setup_client().await;
let mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let igp_accounts =
initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN)
@ -545,10 +558,16 @@ async fn test_transfer_from_remote_errors_if_process_authority_not_signer() {
let (mut banks_client, payer) = setup_client().await;
let _mailbox_accounts =
initialize_mailbox(&mut banks_client, &mailbox_program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let _mailbox_accounts = initialize_mailbox(
&mut banks_client,
&mailbox_program_id,
&payer,
LOCAL_DOMAIN,
ONE_SOL_IN_LAMPORTS,
ProtocolFee::default(),
)
.await
.unwrap();
let hyperlane_token_accounts =
initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None)

@ -249,7 +249,7 @@ fn verify(
merkle_tree_hook_address: metadata.origin_merkle_tree_hook,
mailbox_domain: message.origin,
root: metadata.merkle_root,
index: message.nonce,
index: metadata.merkle_index,
},
message_id: message.id(),
},

@ -7,6 +7,7 @@ use hyperlane_sealevel_mailbox::{
error::Error as MailboxError,
instruction::{Instruction as MailboxInstruction, OutboxDispatch},
mailbox_dispatched_message_pda_seeds,
protocol_fee::ProtocolFee,
};
use hyperlane_sealevel_test_ism::{program::TestIsmError, test_client::TestIsmTestClient};
use hyperlane_sealevel_test_send_receiver::{
@ -21,6 +22,7 @@ use hyperlane_test_utils::{
use solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
rent::Rent,
system_program,
};
use solana_program_test::*;
@ -39,6 +41,8 @@ use crate::utils::{
const LOCAL_DOMAIN: u32 = 13775;
const REMOTE_DOMAIN: u32 = 69420;
const PROTOCOL_FEE: u64 = 1_000_000_000;
const MAX_PROTOCOL_FEE: u64 = 1_000_000_001;
async fn setup_client() -> (
BanksClient,
@ -92,14 +96,28 @@ async fn setup_client() -> (
(banks_client, payer, test_send_receiver, test_ism)
}
fn test_protocol_fee_config() -> ProtocolFee {
ProtocolFee {
fee: PROTOCOL_FEE,
beneficiary: Pubkey::new_unique(),
}
}
#[tokio::test]
async fn test_initialize() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let protocol_fee_config = test_protocol_fee_config();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
protocol_fee_config.clone(),
)
.await
.unwrap();
// Make sure the outbox account was created.
assert_outbox(
@ -110,6 +128,8 @@ async fn test_initialize() {
outbox_bump_seed: mailbox_accounts.outbox_bump_seed,
owner: Some(payer.pubkey()),
tree: MerkleTree::default(),
max_protocol_fee: MAX_PROTOCOL_FEE,
protocol_fee: protocol_fee_config,
},
)
.await;
@ -141,13 +161,28 @@ async fn test_initialize_errors_if_called_twice() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
// Different local domain to force a different transaction signature,
// otherwise we'll get a (silent) duplicate transaction error.
let result = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN + 1).await;
let result = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN + 1,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await;
assert_transaction_error(
result,
TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized),
@ -158,10 +193,18 @@ async fn test_initialize_errors_if_called_twice() {
async fn test_dispatch_from_eoa() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let protocol_fee_config = test_protocol_fee_config();
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
protocol_fee_config.clone(),
)
.await
.unwrap();
let recipient = H256::random();
let message_body = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
@ -213,6 +256,8 @@ async fn test_dispatch_from_eoa() {
outbox_bump_seed: mailbox_accounts.outbox_bump_seed,
owner: Some(payer.pubkey()),
tree: expected_tree.clone(),
max_protocol_fee: MAX_PROTOCOL_FEE,
protocol_fee: protocol_fee_config.clone(),
},
)
.await;
@ -266,21 +311,270 @@ async fn test_dispatch_from_eoa() {
local_domain: LOCAL_DOMAIN,
outbox_bump_seed: mailbox_accounts.outbox_bump_seed,
owner: Some(payer.pubkey()),
tree: expected_tree,
tree: expected_tree.clone(),
max_protocol_fee: MAX_PROTOCOL_FEE,
protocol_fee: protocol_fee_config,
},
)
.await;
}
#[tokio::test]
async fn test_protocol_fee() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let protocol_fee_config = test_protocol_fee_config();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
protocol_fee_config.clone(),
)
.await
.unwrap();
let recipient = H256::random();
let message_body = vec![];
let outbox_dispatch = OutboxDispatch {
sender: payer.pubkey(),
destination_domain: REMOTE_DOMAIN,
recipient,
message_body: message_body.clone(),
};
// ---- Test protocol fee payment ----
let outbox_balance_before = banks_client
.get_balance(mailbox_accounts.outbox)
.await
.unwrap();
dispatch_from_payer(
&mut banks_client,
&payer,
&mailbox_accounts,
outbox_dispatch,
)
.await
.unwrap();
let outbox_balance_after = banks_client
.get_balance(mailbox_accounts.outbox)
.await
.unwrap();
// The outbox pda has been paid `protocol_fee.fee`
assert_eq!(
outbox_balance_after - outbox_balance_before,
protocol_fee_config.fee,
);
// ---- Test protocol fee claiming ----
let non_beneficiary = new_funded_keypair(&mut banks_client, &payer, 1000000000).await;
let beneficiary_balance_before = banks_client
.get_balance(protocol_fee_config.beneficiary)
.await
.unwrap();
// Accounts:
// 0. `[writeable]` The outbox account.
// 1. `[writeable]` The protocol fees beneficiary.
process_instruction(
&mut banks_client,
Instruction::new_with_borsh(
program_id,
&MailboxInstruction::ClaimProtocolFees,
vec![
AccountMeta::new(mailbox_accounts.outbox, false),
AccountMeta::new(protocol_fee_config.beneficiary, false),
],
),
&non_beneficiary,
&[&non_beneficiary],
)
.await
.unwrap();
let beneficiary_balance_after = banks_client
.get_balance(protocol_fee_config.beneficiary)
.await
.unwrap();
assert_eq!(
beneficiary_balance_after - beneficiary_balance_before,
protocol_fee_config.fee,
);
// Make sure the outbox account is still rent exempt
let outbox_account = banks_client
.get_account(mailbox_accounts.outbox)
.await
.unwrap()
.unwrap();
let rent_exempt_balance = Rent::default().minimum_balance(outbox_account.data.len());
assert_eq!(outbox_account.lamports, rent_exempt_balance);
}
#[tokio::test]
async fn test_setting_valid_protocol_fee_config_works() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let protocol_fee_config = test_protocol_fee_config();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
protocol_fee_config.clone(),
)
.await
.unwrap();
let new_protocol_fee = ProtocolFee {
fee: protocol_fee_config.fee + 1,
beneficiary: Pubkey::new_unique(),
};
process_instruction(
&mut banks_client,
Instruction::new_with_borsh(
program_id,
&MailboxInstruction::SetProtocolFeeConfig(new_protocol_fee.clone()),
vec![
AccountMeta::new(mailbox_accounts.outbox, false),
AccountMeta::new_readonly(payer.pubkey(), true),
],
),
&payer,
&[&payer],
)
.await
.unwrap();
// Make sure the outbox account was updated.
assert_outbox(
&mut banks_client,
mailbox_accounts.outbox,
Outbox {
local_domain: LOCAL_DOMAIN,
outbox_bump_seed: mailbox_accounts.outbox_bump_seed,
owner: Some(payer.pubkey()),
tree: MerkleTree::default(),
max_protocol_fee: MAX_PROTOCOL_FEE,
protocol_fee: new_protocol_fee,
},
)
.await;
}
#[tokio::test]
async fn test_setting_invalid_protocol_fee_config_fails() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let protocol_fee_config = test_protocol_fee_config();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
protocol_fee_config.clone(),
)
.await
.unwrap();
let new_protocol_fee = ProtocolFee {
fee: MAX_PROTOCOL_FEE + 1,
beneficiary: Pubkey::new_unique(),
};
let result = process_instruction(
&mut banks_client,
Instruction::new_with_borsh(
program_id,
&MailboxInstruction::SetProtocolFeeConfig(new_protocol_fee.clone()),
vec![
AccountMeta::new(mailbox_accounts.outbox, false),
AccountMeta::new_readonly(payer.pubkey(), true),
],
),
&payer,
&[&payer],
)
.await;
assert_transaction_error(
result,
TransactionError::InstructionError(0, InstructionError::InvalidArgument),
)
}
#[tokio::test]
async fn test_setting_protocol_fee_config_from_unauthorized_account_fails() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let protocol_fee_config = test_protocol_fee_config();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
protocol_fee_config.clone(),
)
.await
.unwrap();
let new_protocol_fee = ProtocolFee {
fee: MAX_PROTOCOL_FEE + 1,
beneficiary: Pubkey::new_unique(),
};
let unauthorized_account = new_funded_keypair(&mut banks_client, &payer, 1000000000).await;
let result = process_instruction(
&mut banks_client,
Instruction::new_with_borsh(
program_id,
&MailboxInstruction::SetProtocolFeeConfig(new_protocol_fee.clone()),
vec![
AccountMeta::new(mailbox_accounts.outbox, false),
AccountMeta::new_readonly(unauthorized_account.pubkey(), true),
],
),
&unauthorized_account,
&[&unauthorized_account],
)
.await;
assert_transaction_error(
result,
TransactionError::InstructionError(0, InstructionError::InvalidArgument),
)
}
#[tokio::test]
async fn test_dispatch_from_program() {
let program_id = mailbox_id();
let (mut banks_client, payer, mut test_send_receiver, _) = setup_client().await;
let test_sender_receiver_program_id = test_send_receiver.id();
let protocol_fee_config = test_protocol_fee_config();
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
protocol_fee_config.clone(),
)
.await
.unwrap();
let recipient = H256::random();
let message_body = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
@ -330,55 +624,29 @@ async fn test_dispatch_from_program() {
local_domain: LOCAL_DOMAIN,
outbox_bump_seed: mailbox_accounts.outbox_bump_seed,
owner: Some(payer.pubkey()),
tree: expected_tree,
tree: expected_tree.clone(),
max_protocol_fee: MAX_PROTOCOL_FEE,
protocol_fee: protocol_fee_config,
},
)
.await;
}
#[tokio::test]
async fn test_dispatch_errors_if_message_too_large() {
async fn test_dispatch_returns_message_id() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let recipient = H256::random();
let message_body = vec![1; 2049];
let outbox_dispatch = OutboxDispatch {
sender: payer.pubkey(),
destination_domain: REMOTE_DOMAIN,
recipient,
message_body,
};
let result = dispatch_from_payer(
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
&mailbox_accounts,
outbox_dispatch,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await;
assert_transaction_error(
result,
TransactionError::InstructionError(
0,
InstructionError::Custom(MailboxError::MaxMessageSizeExceeded as u32),
),
);
}
#[tokio::test]
async fn test_dispatch_returns_message_id() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
.await
.unwrap();
let recipient = H256::random();
let message_body = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
@ -453,9 +721,16 @@ async fn test_get_recipient_ism_when_specified() {
let program_id = mailbox_id();
let (mut banks_client, payer, mut test_send_receiver, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = test_send_receiver.id();
@ -478,9 +753,16 @@ async fn test_get_recipient_ism_when_option_none_returned() {
let program_id = mailbox_id();
let (mut banks_client, payer, mut test_send_receiver, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = test_send_receiver.id();
@ -504,9 +786,16 @@ async fn test_get_recipient_ism_when_no_return_data() {
let program_id = mailbox_id();
let (mut banks_client, payer, mut test_send_receiver, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = test_send_receiver.id();
@ -534,9 +823,16 @@ async fn test_get_recipient_ism_errors_with_malformatted_recipient_ism_return_da
let program_id = mailbox_id();
let (mut banks_client, payer, mut test_send_receiver, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = test_send_receiver.id();
@ -567,9 +863,16 @@ async fn test_process_successful_verify_and_handle() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
@ -640,9 +943,16 @@ async fn test_process_errors_if_message_already_processed() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
@ -688,9 +998,16 @@ async fn test_process_errors_if_ism_verify_fails() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, mut test_ism) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
@ -731,9 +1048,16 @@ async fn test_process_errors_if_recipient_handle_fails() {
let program_id = mailbox_id();
let (mut banks_client, payer, mut test_send_receiver, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
@ -777,9 +1101,16 @@ async fn test_process_errors_if_incorrect_destination_domain() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
@ -819,9 +1150,16 @@ async fn test_process_errors_if_wrong_message_version() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let recipient_id = hyperlane_sealevel_test_send_receiver::id();
@ -860,9 +1198,16 @@ async fn test_process_errors_if_recipient_not_a_program() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let message = HyperlaneMessage {
version: 1,
@ -893,9 +1238,16 @@ async fn test_process_errors_if_reentrant() {
let program_id = mailbox_id();
let (mut banks_client, payer, mut test_send_receiver, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
test_send_receiver
.set_handle_mode(HandleMode::ReenterProcess)
@ -951,9 +1303,16 @@ async fn test_inbox_set_default_ism() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let new_default_ism = Pubkey::new_unique();
@ -996,9 +1355,16 @@ async fn test_inbox_set_default_ism_errors_if_owner_not_signer() {
let program_id = mailbox_id();
let (mut banks_client, payer, _, _) = setup_client().await;
let mailbox_accounts = initialize_mailbox(&mut banks_client, &program_id, &payer, LOCAL_DOMAIN)
.await
.unwrap();
let mailbox_accounts = initialize_mailbox(
&mut banks_client,
&program_id,
&payer,
LOCAL_DOMAIN,
MAX_PROTOCOL_FEE,
test_protocol_fee_config(),
)
.await
.unwrap();
let new_default_ism = Pubkey::new_unique();

@ -8,6 +8,7 @@ edition = "2021"
[features]
no-entrypoint = []
no-spl-noop = []
serde = ["dep:serde"]
[dependencies]
borsh.workspace = true
@ -29,6 +30,7 @@ hyperlane-core = { path = "../../../hyperlane-core" }
hyperlane-sealevel-interchain-security-module-interface = { path = "../../libraries/interchain-security-module-interface" }
hyperlane-sealevel-message-recipient-interface = { path = "../../libraries/message-recipient-interface" }
serializable-account-meta = { path = "../../libraries/serializable-account-meta" }
serde = { workspace = true, optional = true }
[dev-dependencies]
base64.workspace = true

@ -11,7 +11,7 @@ use solana_program::{
account_info::AccountInfo, clock::Slot, program_error::ProgramError, pubkey::Pubkey,
};
use crate::{mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds};
use crate::{mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds, protocol_fee::ProtocolFee};
/// The Inbox account.
pub type InboxAccount = AccountData<Inbox>;
@ -100,6 +100,10 @@ pub struct Outbox {
pub owner: Option<Pubkey>,
/// The merkle tree of dispatched messages.
pub tree: MerkleTree,
/// Max protocol fee that can be set.
pub max_protocol_fee: u64,
/// The protocol fee configuration.
pub protocol_fee: ProtocolFee,
}
impl SizedData for Outbox {
@ -108,7 +112,9 @@ impl SizedData for Outbox {
// 1 byte outbox_bump_seed
// 33 byte owner (1 byte enum variant, 32 byte pubkey)
// 1032 byte tree (32 * 32 = 1024 byte branch, 8 byte count)
4 + 1 + 33 + 1032
// 8 byte max_protocol_fee
// 40 byte protocol_fee (8 byte fee, 32 byte beneficiary)
4 + 1 + 33 + 1032 + 8 + 40
}
}
@ -328,6 +334,11 @@ mod test {
outbox_bump_seed: 69,
owner: Some(Pubkey::new_unique()),
tree: MerkleTree::default(),
max_protocol_fee: 100000000,
protocol_fee: ProtocolFee {
fee: 69696969,
beneficiary: Pubkey::new_unique(),
},
};
let mut serialized = vec![];

@ -8,14 +8,11 @@ use solana_program::{
pubkey::Pubkey,
};
use crate::{mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds};
use crate::{mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds, protocol_fee::ProtocolFee};
/// The current message version.
pub const VERSION: u8 = 3;
/// Maximum bytes per message = 2 KiB (somewhat arbitrarily set to begin).
pub const MAX_MESSAGE_BODY_BYTES: usize = 2 * 2_usize.pow(10);
/// Instructions supported by the Mailbox program.
#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq)]
pub enum Instruction {
@ -39,6 +36,10 @@ pub enum Instruction {
GetOwner,
/// Transfers ownership of the Mailbox.
TransferOwnership(Option<Pubkey>),
/// Transfers accumulated protocol fees to the beneficiary.
ClaimProtocolFees,
/// Sets the protocol fee configuration.
SetProtocolFeeConfig(ProtocolFee),
}
impl Instruction {
@ -61,6 +62,10 @@ pub struct Init {
pub local_domain: u32,
/// The default ISM.
pub default_ism: Pubkey,
/// The maximum protocol fee that can be charged.
pub max_protocol_fee: u64,
/// The protocol fee configuration.
pub protocol_fee: ProtocolFee,
}
/// Instruction data for the OutboxDispatch instruction.
@ -94,6 +99,8 @@ pub fn init_instruction(
program_id: Pubkey,
local_domain: u32,
default_ism: Pubkey,
max_protocol_fee: u64,
protocol_fee: ProtocolFee,
payer: Pubkey,
) -> Result<SolanaInstruction, ProgramError> {
let (inbox_account, _inbox_bump) =
@ -108,6 +115,8 @@ pub fn init_instruction(
data: Instruction::Init(Init {
local_domain,
default_ism,
max_protocol_fee,
protocol_fee,
})
.into_instruction_data()?,
accounts: vec![

@ -9,5 +9,6 @@ pub mod error;
pub mod instruction;
pub mod pda_seeds;
pub mod processor;
pub mod protocol_fee;
pub use spl_noop;

@ -1,7 +1,7 @@
//! Entrypoint, dispatch, and execution for the Hyperlane Sealevel mailbox instruction.
use access_control::AccessControl;
use account_utils::SizedData;
use account_utils::{verify_rent_exempt, SizedData};
use borsh::{BorshDeserialize, BorshSerialize};
use hyperlane_core::{
accumulator::incremental::IncrementalMerkle as MerkleTree, Decode, Encode, HyperlaneMessage,
@ -10,14 +10,14 @@ use hyperlane_core::{
#[cfg(not(feature = "no-entrypoint"))]
use solana_program::entrypoint;
use solana_program::{
account_info::next_account_info,
account_info::AccountInfo,
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
instruction::{AccountMeta, Instruction},
msg,
program::{get_return_data, invoke, invoke_signed, set_return_data},
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
sysvar::{clock::Clock, rent::Rent, Sysvar},
};
@ -36,13 +36,11 @@ use crate::{
ProcessedMessage, ProcessedMessageAccount,
},
error::Error,
instruction::{
InboxProcess, Init, Instruction as MailboxIxn, OutboxDispatch, MAX_MESSAGE_BODY_BYTES,
VERSION,
},
instruction::{InboxProcess, Init, Instruction as MailboxIxn, OutboxDispatch, VERSION},
mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds,
mailbox_message_dispatch_authority_pda_seeds, mailbox_outbox_pda_seeds,
mailbox_process_authority_pda_seeds, mailbox_processed_message_pda_seeds,
protocol_fee::ProtocolFee,
};
#[cfg(not(feature = "no-entrypoint"))]
@ -69,6 +67,10 @@ pub fn process_instruction(
MailboxIxn::TransferOwnership(new_owner) => {
transfer_ownership(program_id, accounts, new_owner)
}
MailboxIxn::ClaimProtocolFees => claim_protocol_fees(program_id, accounts),
MailboxIxn::SetProtocolFeeConfig(new_protocol_fee_config) => {
set_protocol_fee_config(program_id, accounts, new_protocol_fee_config)
}
}
.map_err(|err| {
msg!("{}", err);
@ -115,6 +117,10 @@ fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> Prog
default_ism: init.default_ism,
processed_count: 0,
});
if init.protocol_fee.fee > init.max_protocol_fee {
msg!("Invalid initialization config: Protocol fee is greater than max protocol fee",);
return Err(ProgramError::InvalidArgument);
}
// Create the inbox PDA account.
create_pda_account(
@ -143,6 +149,8 @@ fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> Prog
outbox_bump_seed: outbox_bump,
owner: Some(*payer_info.key),
tree: MerkleTree::default(),
max_protocol_fee: init.max_protocol_fee,
protocol_fee: init.protocol_fee,
});
// Create the outbox PDA account.
@ -637,15 +645,24 @@ fn outbox_dispatch(
return Err(ProgramError::from(Error::ExtraneousAccount));
}
if dispatch.message_body.len() > MAX_MESSAGE_BODY_BYTES {
return Err(ProgramError::from(Error::MaxMessageSizeExceeded));
}
let count = outbox
.tree
.count()
.try_into()
.expect("Too many messages in outbox tree");
let protocol_fee = outbox.protocol_fee.fee;
invoke(
&system_instruction::transfer(payer_info.key, outbox_info.key, protocol_fee),
&[payer_info.clone(), outbox_info.clone()],
)?;
msg!(
"Protocol fee of {} paid from {} to {}",
protocol_fee,
payer_info.key,
outbox_info.key
);
let message = HyperlaneMessage {
version: VERSION,
nonce: count,
@ -852,3 +869,66 @@ fn transfer_ownership(
Ok(())
}
/// Claims protocol fees
///
/// Accounts:
/// 0. `[writeable]` The Outbox PDA account.
/// 1. `[]` The beneficiary.
fn claim_protocol_fees(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
// Account 0: Outbox PDA.
let outbox_info = next_account_info(accounts_iter)?;
let outbox = Outbox::verify_account_and_fetch_inner(program_id, outbox_info)?;
// Account 1: Beneficiary
let beneficiary_info = next_account_info(accounts_iter)?;
if beneficiary_info.key != &outbox.protocol_fee.beneficiary {
return Err(ProgramError::InvalidArgument);
}
let rent = Rent::get()?;
let required_outbox_rent = rent.minimum_balance(outbox_info.data_len());
let claimable_protocol_fees = outbox_info.lamports().saturating_sub(required_outbox_rent);
**outbox_info.try_borrow_mut_lamports()? -= claimable_protocol_fees;
**beneficiary_info.try_borrow_mut_lamports()? += claimable_protocol_fees;
// For good measure...
verify_rent_exempt(outbox_info, &rent)?;
Ok(())
}
/// Sets the protocol fee configuration.
///
/// Accounts:
/// 0. `[writeable]` The Outbox PDA account.
/// 1. `[signer]` The current owner.
fn set_protocol_fee_config(
program_id: &Pubkey,
accounts: &[AccountInfo],
new_protocol_fee_config: ProtocolFee,
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
// Account 0: Outbox PDA.
let outbox_info = next_account_info(accounts_iter)?;
let mut outbox = Outbox::verify_account_and_fetch_inner(program_id, outbox_info)?;
// Account 1: Owner
let owner_info = next_account_info(accounts_iter)?;
outbox.ensure_owner_signer(owner_info)?;
if new_protocol_fee_config.fee > outbox.max_protocol_fee {
msg!("Invalid protocol fee config: Fee is greater than max protocol fee",);
return Err(ProgramError::InvalidArgument);
}
outbox.protocol_fee = new_protocol_fee_config;
// Store the updated outbox.
OutboxAccount::from(outbox).store(outbox_info, false)?;
Ok(())
}

@ -0,0 +1,15 @@
//! Data structures for the protocol fee configuration.
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::pubkey::Pubkey;
/// The Protocol Fee configuration.
#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct ProtocolFee {
/// The current protocol fee, expressed in the lowest denomination.
pub fee: u64,
/// The beneficiary of protocol fees.
pub beneficiary: Pubkey,
}

@ -48,6 +48,13 @@ pub fn start_anvil(config: Arc<Config>) -> AgentHandles {
log!("Deploying hyperlane core contracts...");
yarn_infra.clone().cmd("deploy-core").run().join();
log!("Updating agent config...");
yarn_infra
.clone()
.cmd("update-agent-config:test")
.run()
.join();
log!("Deploying multicall contract...");
tokio::runtime::Builder::new_current_thread()
.enable_all()

@ -16,7 +16,7 @@ use crate::AGENT_BIN_PATH;
/// The Solana CLI tool version to download and use.
const SOLANA_CLI_VERSION: &str = "1.14.20";
const SOLANA_PROGRAM_LIBRARY_ARCHIVE: &str =
"https://github.com/hyperlane-xyz/solana-program-library/releases/download/2023-07-27-01/spl.tar.gz";
"https://github.com/hyperlane-xyz/solana-program-library/releases/download/2024-08-23/spl.tar.gz";
// Solana program tuples of:
// 0: Solana address or keypair for the bpf program

@ -1,5 +1,20 @@
# @hyperlane-xyz/core
## 5.1.0
### Minor Changes
- 013f19c64: Added SDK support for ArbL2ToL1Hook/ISM for selfrelay
- 013f19c64: Added hook/ism for using the Optimism native bridge for L2->L1 calls
- 013f19c64: Added yield route with yield going to message recipient.
- 013f19c64: feat: attributable fraud for signers
- 013f19c64: Implement checkpoint fraud proofs for use in slashing
### Patch Changes
- 013f19c64: fix: only evaluate dynamic revert reasons in reverting branch
- @hyperlane-xyz/utils@5.1.0
## 5.0.0
### Patch Changes

@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {AbstractPostDispatchHook, AbstractMessageIdAuthHook} from "./libs/AbstractMessageIdAuthHook.sol";
import {StandardHookMetadata} from "./libs/StandardHookMetadata.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
// ============ External Imports ============
import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenger.sol";
/**
* @title OPL2ToL1Hook
* @notice Message hook to inform the OPL2ToL1Ism of messages published through
* the native Optimism bridge.
* @notice This works only for L2 -> L1 messages and has the 7 day delay as specified by the OptimismPortal contract.
*/
contract OPL2ToL1Hook is AbstractMessageIdAuthHook {
using StandardHookMetadata for bytes;
// ============ Constants ============
// precompile contract on L2 for sending messages to L1
ICrossDomainMessenger public immutable l2Messenger;
// Immutable quote amount
uint32 public immutable GAS_QUOTE;
// ============ Constructor ============
constructor(
address _mailbox,
uint32 _destinationDomain,
bytes32 _ism,
address _l2Messenger,
uint32 _gasQuote
) AbstractMessageIdAuthHook(_mailbox, _destinationDomain, _ism) {
GAS_QUOTE = _gasQuote;
l2Messenger = ICrossDomainMessenger(_l2Messenger);
}
/// @inheritdoc IPostDispatchHook
function hookType() external pure override returns (uint8) {
return uint8(IPostDispatchHook.Types.OP_L2_TO_L1);
}
/// @inheritdoc AbstractPostDispatchHook
function _quoteDispatch(
bytes calldata,
bytes calldata
) internal view override returns (uint256) {
return GAS_QUOTE;
}
// ============ Internal functions ============
/// @inheritdoc AbstractMessageIdAuthHook
function _sendMessageId(
bytes calldata metadata,
bytes memory payload
) internal override {
require(
msg.value >= metadata.msgValue(0) + GAS_QUOTE,
"OPL2ToL1Hook: insufficient msg.value"
);
l2Messenger.sendMessage{value: metadata.msgValue(0)}(
TypeCasts.bytes32ToAddress(ism),
payload,
GAS_QUOTE
);
}
}

@ -12,8 +12,9 @@ interface IInterchainSecurityModule {
NULL, // used with relayer carrying no metadata
CCIP_READ,
ARB_L2_TO_L1,
WEIGHT_MERKLE_ROOT_MULTISIG,
WEIGHT_MESSAGE_ID_MULTISIG
WEIGHTED_MERKLE_ROOT_MULTISIG,
WEIGHTED_MESSAGE_ID_MULTISIG,
OP_L2_TO_L1
}
/**

@ -27,7 +27,8 @@ interface IPostDispatchHook {
LAYER_ZERO_V1,
RATE_LIMITED,
ARB_L2_TO_L1,
DISPATCHED
OP_L2_TO_L1,
DISPATCHED,
}
/**

@ -30,6 +30,8 @@ interface ICrossDomainMessenger {
function xDomainMessageSender() external view returns (address);
function OTHER_MESSENGER() external view returns (address);
function PORTAL() external view returns (address);
}
interface IL1CrossDomainMessenger is ICrossDomainMessenger {}

@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// author: OP Labs
// copied from https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock
interface IOptimismPortal {
/// @notice Struct representing a withdrawal transaction.
/// @custom:field nonce Nonce of the withdrawal transaction
/// @custom:field sender Address of the sender of the transaction.
/// @custom:field target Address of the recipient of the transaction.
/// @custom:field value Value to send to the recipient.
/// @custom:field gasLimit Gas limit of the transaction.
/// @custom:field data Data of the transaction.
struct WithdrawalTransaction {
uint256 nonce;
address sender;
address target;
uint256 value;
uint256 gasLimit;
bytes data;
}
/// @notice Finalizes a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
function finalizeWithdrawalTransaction(
WithdrawalTransaction memory _tx
) external;
}

@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/*@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@ HYPERLANE @@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@@
@@@@@@@@@ @@@@@@@@*/
// ============ Internal Imports ============
import {IInterchainSecurityModule} from "../../interfaces/IInterchainSecurityModule.sol";
import {TypeCasts} from "../../libs/TypeCasts.sol";
import {Message} from "../../libs/Message.sol";
import {OPL2ToL1Metadata} from "../../libs/OPL2ToL1Metadata.sol";
import {AbstractMessageIdAuthorizedIsm} from "./AbstractMessageIdAuthorizedIsm.sol";
// ============ External Imports ============
import {ICrossDomainMessenger} from "../../interfaces/optimism/ICrossDomainMessenger.sol";
import {IOptimismPortal} from "../../interfaces/optimism/IOptimismPortal.sol";
import {CrossChainEnabledOptimism} from "@openzeppelin/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
/**
* @title OPL2ToL1Ism
* @notice Uses the native Optimism bridge to verify interchain messages from L2 to L1.
*/
contract OPL2ToL1Ism is
CrossChainEnabledOptimism,
AbstractMessageIdAuthorizedIsm
{
using TypeCasts for address;
using Message for bytes;
using OPL2ToL1Metadata for bytes;
// ============ Constants ============
// module type for the ISM
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.OP_L2_TO_L1);
// OptimismPortal contract on L1 to finalize withdrawal from L1
IOptimismPortal public immutable portal;
// ============ Constructor ============
constructor(address _messenger) CrossChainEnabledOptimism(_messenger) {
address _portal = ICrossDomainMessenger(_messenger).PORTAL();
require(
Address.isContract(_portal),
"OPL2ToL1Ism: invalid OptimismPortal contract"
);
portal = IOptimismPortal(_portal);
}
// ============ External Functions ============
/// @inheritdoc IInterchainSecurityModule
function verify(
bytes calldata metadata,
bytes calldata message
) external override returns (bool) {
bool verified = isVerified(message);
if (!verified) {
_verifyWithPortalCall(metadata, message);
}
releaseValueToRecipient(message);
return true;
}
// ============ Internal function ============
/**
* @notice Verify message directly using the portal.finalizeWithdrawal function.
* @dev This is a fallback in case the message is not verified by the stateful verify function first.
*/
function _verifyWithPortalCall(
bytes calldata metadata,
bytes calldata message
) internal {
require(
metadata.checkCalldataLength(),
"OPL2ToL1Ism: invalid data length"
);
require(
metadata.messageId() == message.id(),
"OPL2ToL1Ism: invalid message id"
);
IOptimismPortal.WithdrawalTransaction memory withdrawal = abi.decode(
metadata,
(IOptimismPortal.WithdrawalTransaction)
);
// if the finalizeWithdrawalTransaction call is successful, the message is verified
portal.finalizeWithdrawalTransaction(withdrawal);
}
/// @inheritdoc AbstractMessageIdAuthorizedIsm
function _isAuthorized() internal view override returns (bool) {
return
_crossChainSender() == TypeCasts.bytes32ToAddress(authorizedHook);
}
}

@ -39,7 +39,7 @@ contract StaticMerkleRootWeightedMultisigIsm is
AbstractMetaProxyWeightedMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.WEIGHT_MERKLE_ROOT_MULTISIG);
uint8(IInterchainSecurityModule.Types.WEIGHTED_MERKLE_ROOT_MULTISIG);
}
contract StaticMessageIdWeightedMultisigIsm is
@ -47,7 +47,7 @@ contract StaticMessageIdWeightedMultisigIsm is
AbstractMetaProxyWeightedMultisigIsm
{
uint8 public constant moduleType =
uint8(IInterchainSecurityModule.Types.WEIGHT_MESSAGE_ID_MULTISIG);
uint8(IInterchainSecurityModule.Types.WEIGHTED_MESSAGE_ID_MULTISIG);
}
contract StaticMerkleRootWeightedMultisigIsmFactory is

@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
/**
* @title Hyperlane OPL2ToL1Metadata Library
* @notice Library for formatted metadata used by OPL2ToL1Ism
*/
library OPL2ToL1Metadata {
// bottom offset to the start of message id in the metadata
uint256 private constant MESSAGE_ID_OFFSET = 88;
// from IOptimismPortal.WithdrawalTransaction
// Σ {
// nonce = 32 bytes
// PADDING + sender = 32 bytes
// PADDING + target = 32 bytes
// value = 32 bytes
// gasLimit = 32 bytes
// _data
// OFFSET = 32 bytes
// LENGTH = 32 bytes
// } = 252 bytes
uint256 private constant FIXED_METADATA_LENGTH = 252;
// metadata here is double encoded call relayMessage(..., verifyMessageId)
// Σ {
// _selector = 4 bytes
// _nonce = 32 bytes
// PADDING + _sender = 32 bytes
// PADDING + _target = 32 bytes
// _value = 32 bytes
// _minGasLimit = 32 bytes
// _data
// OFFSET = 32 bytes
// LENGTH = 32 bytes
// PADDING + verifyMessageId = 64 bytes
// } = 292 bytes
uint256 private constant MESSENGER_CALLDATA_LENGTH = 292;
/**
* @notice Returns the message ID.
* @param _metadata OptimismPortal.WithdrawalTransaction encoded calldata
* @return ID of `_metadata`
*/
function messageId(
bytes calldata _metadata
) internal pure returns (bytes32) {
uint256 metadataLength = _metadata.length;
return
bytes32(
_metadata[metadataLength - MESSAGE_ID_OFFSET:metadataLength -
MESSAGE_ID_OFFSET +
32]
);
}
function checkCalldataLength(
bytes calldata _metadata
) internal pure returns (bool) {
return
_metadata.length ==
MESSENGER_CALLDATA_LENGTH + FIXED_METADATA_LENGTH;
}
}

@ -8,8 +8,8 @@ import "../test/TestIsm.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
contract MockHyperlaneEnvironment {
uint32 originDomain;
uint32 destinationDomain;
uint32 public originDomain;
uint32 public destinationDomain;
mapping(uint32 => MockMailbox) public mailboxes;
mapping(uint32 => TestInterchainGasPaymaster) public igps;

@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CallLib} from "../middleware/libs/Call.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {ICrossDomainMessenger} from "../interfaces/optimism/ICrossDomainMessenger.sol";
import {IOptimismPortal} from "../interfaces/optimism/IOptimismPortal.sol";
// for both L1 and L2
contract MockOptimismMessenger is ICrossDomainMessenger {
address public xDomainMessageSender;
address public PORTAL;
function sendMessage(
address _target,
bytes calldata _message,
uint32 _gasLimit
) external payable {}
function relayMessage(
uint256 /*_nonce*/,
address /*_sender*/,
address _target,
uint256 _value,
uint256 /*_minGasLimit*/,
bytes calldata _message
) external payable {
CallLib.Call memory call = CallLib.Call(
TypeCasts.addressToBytes32(_target),
_value,
_message
);
CallLib.call(call);
}
function OTHER_MESSENGER() external view returns (address) {}
function setXDomainMessageSender(address _sender) external {
xDomainMessageSender = _sender;
}
function setPORTAL(address _portal) external {
PORTAL = _portal;
}
}
// mock deployment on L1
contract MockOptimismPortal is IOptimismPortal {
error WithdrawalTransactionFailed();
function finalizeWithdrawalTransaction(
WithdrawalTransaction memory _tx
) external {
CallLib.Call memory call = CallLib.Call(
TypeCasts.addressToBytes32(_tx.target),
_tx.value,
_tx.data
);
CallLib.call(call);
}
}

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

@ -38,7 +38,7 @@ contract FailingIsm is IInterchainSecurityModule {
}
}
contract InterchainAccountRouterTest is Test {
contract InterchainAccountRouterTestBase is Test {
using TypeCasts for address;
event InterchainAccountCreated(
@ -55,8 +55,8 @@ contract InterchainAccountRouterTest is Test {
TestInterchainGasPaymaster internal igp;
InterchainAccountIsm internal icaIsm;
InterchainAccountRouter internal originRouter;
InterchainAccountRouter internal destinationRouter;
InterchainAccountRouter internal originIcaRouter;
InterchainAccountRouter internal destinationIcaRouter;
bytes32 internal ismOverride;
bytes32 internal routerOverride;
uint256 gasPaymentQuote;
@ -90,7 +90,7 @@ contract InterchainAccountRouterTest is Test {
return InterchainAccountRouter(address(proxy));
}
function setUp() public {
function setUp() public virtual {
environment = new MockHyperlaneEnvironment(origin, destination);
igp = new TestInterchainGasPaymaster();
@ -104,13 +104,13 @@ contract InterchainAccountRouterTest is Test {
);
address owner = address(this);
originRouter = deployProxiedIcaRouter(
originIcaRouter = deployProxiedIcaRouter(
environment.mailboxes(origin),
environment.igps(destination),
icaIsm,
owner
);
destinationRouter = deployProxiedIcaRouter(
destinationIcaRouter = deployProxiedIcaRouter(
environment.mailboxes(destination),
environment.igps(destination),
icaIsm,
@ -119,47 +119,55 @@ contract InterchainAccountRouterTest is Test {
environment.mailboxes(origin).setDefaultHook(address(igp));
routerOverride = TypeCasts.addressToBytes32(address(destinationRouter));
routerOverride = TypeCasts.addressToBytes32(
address(destinationIcaRouter)
);
ismOverride = TypeCasts.addressToBytes32(
address(environment.isms(destination))
);
ica = destinationRouter.getLocalInterchainAccount(
ica = destinationIcaRouter.getLocalInterchainAccount(
origin,
address(this),
address(originRouter),
address(originIcaRouter),
address(environment.isms(destination))
);
target = new Callable();
}
receive() external payable {}
}
contract InterchainAccountRouterTest is InterchainAccountRouterTestBase {
using TypeCasts for address;
function testFuzz_constructor(address _localOwner) public {
OwnableMulticall _account = destinationRouter
OwnableMulticall _account = destinationIcaRouter
.getDeployedInterchainAccount(
origin,
_localOwner,
address(originRouter),
address(originIcaRouter),
address(environment.isms(destination))
);
assertEq(_account.owner(), address(destinationRouter));
assertEq(_account.owner(), address(destinationIcaRouter));
}
function testFuzz_getRemoteInterchainAccount(
address _localOwner,
address _ism
) public {
address _account = originRouter.getRemoteInterchainAccount(
address _account = originIcaRouter.getRemoteInterchainAccount(
address(_localOwner),
address(destinationRouter),
address(destinationIcaRouter),
_ism
);
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
TypeCasts.addressToBytes32(_ism)
);
assertEq(
originRouter.getRemoteInterchainAccount(
originIcaRouter.getRemoteInterchainAccount(
destination,
address(_localOwner)
),
@ -184,16 +192,16 @@ contract InterchainAccountRouterTest is Test {
}
// act
originRouter.enrollRemoteRouters(domains, routers);
originIcaRouter.enrollRemoteRouters(domains, routers);
// assert
uint32[] memory actualDomains = originRouter.domains();
uint32[] memory actualDomains = originIcaRouter.domains();
assertEq(actualDomains.length, domains.length);
assertEq(abi.encode(originRouter.domains()), abi.encode(domains));
assertEq(abi.encode(originIcaRouter.domains()), abi.encode(domains));
for (uint256 i = 0; i < count; i++) {
bytes32 actualRouter = originRouter.routers(domains[i]);
bytes32 actualIsm = originRouter.isms(domains[i]);
bytes32 actualRouter = originIcaRouter.routers(domains[i]);
bytes32 actualIsm = originIcaRouter.isms(domains[i]);
assertEq(actualRouter, routers[i]);
assertEq(actualIsm, bytes32(0));
@ -208,17 +216,17 @@ contract InterchainAccountRouterTest is Test {
vm.assume(router != bytes32(0));
// arrange pre-condition
bytes32 actualRouter = originRouter.routers(destination);
bytes32 actualIsm = originRouter.isms(destination);
bytes32 actualRouter = originIcaRouter.routers(destination);
bytes32 actualIsm = originIcaRouter.isms(destination);
assertEq(actualRouter, bytes32(0));
assertEq(actualIsm, bytes32(0));
// act
originRouter.enrollRemoteRouterAndIsm(destination, router, ism);
originIcaRouter.enrollRemoteRouterAndIsm(destination, router, ism);
// assert
actualRouter = originRouter.routers(destination);
actualIsm = originRouter.isms(destination);
actualRouter = originIcaRouter.routers(destination);
actualIsm = originIcaRouter.isms(destination);
assertEq(actualRouter, router);
assertEq(actualIsm, ism);
}
@ -234,17 +242,21 @@ contract InterchainAccountRouterTest is Test {
destinations.length != isms.length
) {
vm.expectRevert(bytes("length mismatch"));
originRouter.enrollRemoteRouterAndIsms(destinations, routers, isms);
originIcaRouter.enrollRemoteRouterAndIsms(
destinations,
routers,
isms
);
return;
}
// act
originRouter.enrollRemoteRouterAndIsms(destinations, routers, isms);
originIcaRouter.enrollRemoteRouterAndIsms(destinations, routers, isms);
// assert
for (uint256 i = 0; i < destinations.length; i++) {
bytes32 actualRouter = originRouter.routers(destinations[i]);
bytes32 actualIsm = originRouter.isms(destinations[i]);
bytes32 actualRouter = originIcaRouter.routers(destinations[i]);
bytes32 actualIsm = originIcaRouter.isms(destinations[i]);
assertEq(actualRouter, routers[i]);
assertEq(actualIsm, isms[i]);
}
@ -259,13 +271,13 @@ contract InterchainAccountRouterTest is Test {
vm.assume(routerA != bytes32(0) && routerB != bytes32(0));
// act
originRouter.enrollRemoteRouterAndIsm(destination, routerA, ismA);
originIcaRouter.enrollRemoteRouterAndIsm(destination, routerA, ismA);
// assert
vm.expectRevert(
bytes("router and ISM defaults are immutable once set")
);
originRouter.enrollRemoteRouterAndIsm(destination, routerB, ismB);
originIcaRouter.enrollRemoteRouterAndIsm(destination, routerB, ismB);
}
function testFuzz_enrollRemoteRouterAndIsmNonOwner(
@ -273,14 +285,16 @@ contract InterchainAccountRouterTest is Test {
bytes32 router,
bytes32 ism
) public {
vm.assume(newOwner != address(0) && newOwner != originRouter.owner());
vm.assume(
newOwner != address(0) && newOwner != originIcaRouter.owner()
);
// act
originRouter.transferOwnership(newOwner);
originIcaRouter.transferOwnership(newOwner);
// assert
vm.expectRevert(bytes("Ownable: caller is not the owner"));
originRouter.enrollRemoteRouterAndIsm(destination, router, ism);
originIcaRouter.enrollRemoteRouterAndIsm(destination, router, ism);
}
function getCalls(
@ -300,7 +314,7 @@ contract InterchainAccountRouterTest is Test {
function assertRemoteCallReceived(bytes32 data) private {
assertEq(target.data(address(this)), bytes32(0));
vm.expectEmit(true, true, false, true, address(destinationRouter));
vm.expectEmit(true, true, false, true, address(destinationIcaRouter));
emit InterchainAccountCreated(
origin,
address(this).addressToBytes32(),
@ -325,16 +339,15 @@ contract InterchainAccountRouterTest is Test {
address owner
) public {
// act
ica = destinationRouter.getDeployedInterchainAccount(
ica = destinationIcaRouter.getDeployedInterchainAccount(
origin,
owner,
address(originRouter),
address(originIcaRouter),
address(environment.isms(destination))
);
(uint32 domain, bytes32 ownerBytes) = destinationRouter.accountOwners(
address(ica)
);
(uint32 domain, bytes32 ownerBytes) = destinationIcaRouter
.accountOwners(address(ica));
// assert
assertEq(domain, origin);
assertEq(ownerBytes, owner.addressToBytes32());
@ -342,19 +355,19 @@ contract InterchainAccountRouterTest is Test {
function test_quoteGasPayment() public {
// arrange
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
ismOverride
);
// assert
assertEq(originRouter.quoteGasPayment(destination), gasPaymentQuote);
assertEq(originIcaRouter.quoteGasPayment(destination), gasPaymentQuote);
}
function test_quoteGasPayment_gasLimitOverride() public {
// arrange
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
ismOverride
@ -362,14 +375,18 @@ contract InterchainAccountRouterTest is Test {
// assert
assertEq(
originRouter.quoteGasPayment(destination, "", GAS_LIMIT_OVERRIDE),
originIcaRouter.quoteGasPayment(
destination,
"",
GAS_LIMIT_OVERRIDE
),
igp.quoteGasPayment(destination, GAS_LIMIT_OVERRIDE)
);
}
function testFuzz_singleCallRemoteWithDefault(bytes32 data) public {
// arrange
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
ismOverride
@ -378,7 +395,7 @@ contract InterchainAccountRouterTest is Test {
// act
CallLib.Call[] memory calls = getCalls(data);
originRouter.callRemote{value: gasPaymentQuote}(
originIcaRouter.callRemote{value: gasPaymentQuote}(
destination,
TypeCasts.bytes32ToAddress(calls[0].to),
calls[0].value,
@ -393,7 +410,7 @@ contract InterchainAccountRouterTest is Test {
function testFuzz_callRemoteWithDefault(bytes32 data) public {
// arrange
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
ismOverride
@ -401,7 +418,7 @@ contract InterchainAccountRouterTest is Test {
uint256 balanceBefore = address(this).balance;
// act
originRouter.callRemote{value: gasPaymentQuote}(
originIcaRouter.callRemote{value: gasPaymentQuote}(
destination,
getCalls(data)
);
@ -414,7 +431,7 @@ contract InterchainAccountRouterTest is Test {
function testFuzz_overrideAndCallRemote(bytes32 data) public {
// arrange
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
ismOverride
@ -422,7 +439,7 @@ contract InterchainAccountRouterTest is Test {
uint256 balanceBefore = address(this).balance;
// act
originRouter.callRemote{value: gasPaymentQuote}(
originIcaRouter.callRemote{value: gasPaymentQuote}(
destination,
getCalls(data)
);
@ -439,7 +456,7 @@ contract InterchainAccountRouterTest is Test {
// assert error
CallLib.Call[] memory calls = getCalls(data);
vm.expectRevert(bytes("no router specified for destination"));
originRouter.callRemote(destination, calls);
originIcaRouter.callRemote(destination, calls);
}
function testFuzz_customMetadata_forIgp(
@ -454,7 +471,7 @@ contract InterchainAccountRouterTest is Test {
address(this),
""
);
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
ismOverride
@ -462,11 +479,9 @@ contract InterchainAccountRouterTest is Test {
uint256 balanceBefore = address(this).balance;
// act
originRouter.callRemote{value: gasLimit * igp.gasPrice() + overpayment}(
destination,
getCalls(data),
metadata
);
originIcaRouter.callRemote{
value: gasLimit * igp.gasPrice() + overpayment
}(destination, getCalls(data), metadata);
// assert
uint256 balanceAfter = address(this).balance;
@ -488,7 +503,7 @@ contract InterchainAccountRouterTest is Test {
address(this),
""
);
originRouter.enrollRemoteRouterAndIsm(
originIcaRouter.enrollRemoteRouterAndIsm(
destination,
routerOverride,
ismOverride
@ -496,7 +511,11 @@ contract InterchainAccountRouterTest is Test {
// act
vm.expectRevert("IGP: insufficient interchain gas payment");
originRouter.callRemote{value: payment}(destination, calls, metadata);
originIcaRouter.callRemote{value: payment}(
destination,
calls,
metadata
);
}
function testFuzz_callRemoteWithOverrides_default(bytes32 data) public {
@ -504,7 +523,7 @@ contract InterchainAccountRouterTest is Test {
uint256 balanceBefore = address(this).balance;
// act
originRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
originIcaRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
destination,
routerOverride,
ismOverride,
@ -531,13 +550,9 @@ contract InterchainAccountRouterTest is Test {
uint256 balanceBefore = address(this).balance;
// act
originRouter.callRemoteWithOverrides{value: gasLimit * igp.gasPrice()}(
destination,
routerOverride,
ismOverride,
getCalls(data),
metadata
);
originIcaRouter.callRemoteWithOverrides{
value: gasLimit * igp.gasPrice()
}(destination, routerOverride, ismOverride, getCalls(data), metadata);
// assert
uint256 balanceAfter = address(this).balance;
@ -553,7 +568,7 @@ contract InterchainAccountRouterTest is Test {
);
// act
originRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
originIcaRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
destination,
routerOverride,
failingIsm,
@ -573,7 +588,7 @@ contract InterchainAccountRouterTest is Test {
// act
environment.mailboxes(destination).setDefaultIsm(address(failingIsm));
originRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
originIcaRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
destination,
routerOverride,
bytes32(0),
@ -588,20 +603,20 @@ contract InterchainAccountRouterTest is Test {
function testFuzz_getLocalInterchainAccount(bytes32 data) public {
// check
OwnableMulticall destinationIca = destinationRouter
OwnableMulticall destinationIca = destinationIcaRouter
.getLocalInterchainAccount(
origin,
address(this),
address(originRouter),
address(originIcaRouter),
address(environment.isms(destination))
);
assertEq(
address(destinationIca),
address(
destinationRouter.getLocalInterchainAccount(
destinationIcaRouter.getLocalInterchainAccount(
origin,
TypeCasts.addressToBytes32(address(this)),
TypeCasts.addressToBytes32(address(originRouter)),
TypeCasts.addressToBytes32(address(originIcaRouter)),
address(environment.isms(destination))
)
)
@ -609,7 +624,7 @@ contract InterchainAccountRouterTest is Test {
assertEq(address(destinationIca).code.length, 0);
// act
originRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
originIcaRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
destination,
routerOverride,
ismOverride,
@ -631,10 +646,10 @@ contract InterchainAccountRouterTest is Test {
require(success, "transfer before deploy failed");
// receive value after deployed
destinationRouter.getDeployedInterchainAccount(
destinationIcaRouter.getDeployedInterchainAccount(
origin,
address(this),
address(originRouter),
address(originIcaRouter),
address(environment.isms(destination))
);
assert(address(ica).code.length > 0);
@ -658,7 +673,7 @@ contract InterchainAccountRouterTest is Test {
CallLib.Call[] memory calls = new CallLib.Call[](1);
calls[0] = call;
originRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
originIcaRouter.callRemoteWithOverrides{value: gasPaymentQuote}(
destination,
routerOverride,
ismOverride,
@ -668,6 +683,4 @@ contract InterchainAccountRouterTest is Test {
vm.expectCall(address(this), value, data);
environment.processNextPendingMessage();
}
receive() external payable {}
}

@ -0,0 +1,320 @@
// SPDX-License-Identifier: MIT or Apache-2.0
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol";
import {Message} from "../../contracts/libs/Message.sol";
import {MessageUtils} from "./IsmTestUtils.sol";
import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol";
import {IOptimismPortal} from "../../contracts/interfaces/optimism/IOptimismPortal.sol";
import {ICrossDomainMessenger} from "../../contracts/interfaces/optimism/ICrossDomainMessenger.sol";
import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol";
import {TestMailbox} from "../../contracts/test/TestMailbox.sol";
import {TestRecipient} from "../../contracts/test/TestRecipient.sol";
import {MockOptimismMessenger, MockOptimismPortal} from "../../contracts/mock/MockOptimism.sol";
import {OPL2ToL1Hook} from "../../contracts/hooks/OPL2ToL1Hook.sol";
import {OPL2ToL1Ism} from "../../contracts/isms/hook/OPL2ToL1Ism.sol";
contract OPL2ToL1IsmTest is Test {
uint8 internal constant HYPERLANE_VERSION = 1;
uint32 internal constant MAINNET_DOMAIN = 1;
uint32 internal constant OPTIMISM_DOMAIN = 10;
uint32 internal constant GAS_QUOTE = 120_000;
address internal constant L2_MESSENGER_ADDRESS =
0x4200000000000000000000000000000000000007;
uint256 internal constant MOCK_NONCE = 0;
TestMailbox public l2Mailbox;
TestRecipient internal testRecipient;
bytes internal testMessage =
abi.encodePacked("Hello from the other chain!");
bytes internal encodedMessage;
bytes internal testMetadata =
StandardHookMetadata.overrideRefundAddress(address(this));
bytes32 internal messageId;
MockOptimismPortal internal portal;
MockOptimismMessenger internal l1Messenger;
OPL2ToL1Hook public hook;
OPL2ToL1Ism public ism;
///////////////////////////////////////////////////////////////////
/// SETUP ///
///////////////////////////////////////////////////////////////////
function setUp() public {
// Optimism messenger mock setup
vm.etch(
L2_MESSENGER_ADDRESS,
address(new MockOptimismMessenger()).code
);
testRecipient = new TestRecipient();
encodedMessage = _encodeTestMessage();
messageId = Message.id(encodedMessage);
}
function deployHook() public {
l2Mailbox = new TestMailbox(OPTIMISM_DOMAIN);
hook = new OPL2ToL1Hook(
address(l2Mailbox),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(ism)),
L2_MESSENGER_ADDRESS,
GAS_QUOTE
);
}
function deployIsm() public {
l1Messenger = new MockOptimismMessenger();
portal = new MockOptimismPortal();
l1Messenger.setPORTAL(address(portal));
ism = new OPL2ToL1Ism(address(l1Messenger));
}
function deployAll() public {
deployIsm();
deployHook();
l1Messenger.setXDomainMessageSender(address(hook));
ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook)));
}
function test_postDispatch() public {
deployAll();
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(messageId)
);
l2Mailbox.updateLatestDispatchedId(messageId);
vm.expectCall(
L2_MESSENGER_ADDRESS,
abi.encodeCall(
ICrossDomainMessenger.sendMessage,
(address(ism), encodedHookData, GAS_QUOTE)
)
);
hook.postDispatch{value: GAS_QUOTE}(testMetadata, encodedMessage);
}
function testFork_postDispatch_revertWhen_chainIDNotSupported() public {
deployAll();
bytes memory message = MessageUtils.formatMessage(
0,
uint32(0),
OPTIMISM_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
2, // wrong domain
TypeCasts.addressToBytes32(address(testRecipient)),
testMessage
);
l2Mailbox.updateLatestDispatchedId(Message.id(message));
vm.expectRevert(
"AbstractMessageIdAuthHook: invalid destination domain"
);
hook.postDispatch(testMetadata, message);
}
function test_postDispatch_revertWhen_notLastDispatchedMessage() public {
deployAll();
vm.expectRevert(
"AbstractMessageIdAuthHook: message not latest dispatched"
);
hook.postDispatch(testMetadata, encodedMessage);
}
function test_verify_directWithdrawalCall() public {
deployAll();
bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx(
address(ism),
0,
messageId
);
assertTrue(ism.verify(encodedWithdrawalTx, encodedMessage));
}
function test_verify_directWithdrawalCall_revertsWhen_invalidSender()
public
{
deployAll();
l1Messenger.setXDomainMessageSender(address(this));
bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx(
address(ism),
0,
messageId
);
vm.expectRevert(); // evmRevert in MockOptimismPortal
ism.verify(encodedWithdrawalTx, encodedMessage);
}
function test_verify_statefulVerify() public {
deployAll();
vm.deal(address(portal), 1 ether);
IOptimismPortal.WithdrawalTransaction
memory withdrawal = IOptimismPortal.WithdrawalTransaction({
nonce: MOCK_NONCE,
sender: L2_MESSENGER_ADDRESS,
target: address(l1Messenger),
value: 1 ether,
gasLimit: uint256(GAS_QUOTE),
data: _encodeMessengerCalldata(address(ism), 1 ether, messageId)
});
portal.finalizeWithdrawalTransaction(withdrawal);
vm.etch(address(portal), new bytes(0)); // this is a way to test that the portal isn't called again
assertTrue(ism.verify(new bytes(0), encodedMessage));
assertEq(address(testRecipient).balance, 1 ether); // testing msg.value
}
function test_verify_statefulAndDirectWithdrawal() public {
deployAll();
IOptimismPortal.WithdrawalTransaction
memory withdrawal = IOptimismPortal.WithdrawalTransaction({
nonce: MOCK_NONCE,
sender: L2_MESSENGER_ADDRESS,
target: address(l1Messenger),
value: 0,
gasLimit: uint256(GAS_QUOTE),
data: _encodeMessengerCalldata(address(ism), 0, messageId)
});
portal.finalizeWithdrawalTransaction(withdrawal);
bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx(
address(ism),
0,
messageId
);
vm.etch(address(portal), new bytes(0)); // this is a way to test that the portal isn't called again
assertTrue(ism.verify(encodedWithdrawalTx, encodedMessage));
}
function test_verify_revertsWhen_noStatefulAndDirectWithdrawal() public {
deployAll();
vm.expectRevert();
ism.verify(new bytes(0), encodedMessage);
}
function test_verify_revertsWhen_invalidIsm() public {
deployAll();
bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx(
address(this),
0,
messageId
);
vm.expectRevert(); // evmRevert in MockOptimismPortal
ism.verify(encodedWithdrawalTx, encodedMessage);
}
function test_verify_revertsWhen_incorrectMessageId() public {
deployAll();
bytes32 incorrectMessageId = keccak256("incorrect message id");
bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx(
address(this),
0,
incorrectMessageId
);
// through portal call
vm.expectRevert("OPL2ToL1Ism: invalid message id");
ism.verify(encodedWithdrawalTx, encodedMessage);
// through statefulVerify
IOptimismPortal.WithdrawalTransaction
memory withdrawal = IOptimismPortal.WithdrawalTransaction({
nonce: MOCK_NONCE,
sender: L2_MESSENGER_ADDRESS,
target: address(l1Messenger),
value: 0,
gasLimit: uint256(GAS_QUOTE),
data: _encodeMessengerCalldata(
address(ism),
0,
incorrectMessageId
)
});
portal.finalizeWithdrawalTransaction(withdrawal);
vm.etch(address(portal), new bytes(0)); // to stop the portal route
vm.expectRevert(); // evmRevert()
assertFalse(ism.verify(new bytes(0), encodedMessage));
}
/* ============ helper functions ============ */
function _encodeTestMessage() internal view returns (bytes memory) {
return
MessageUtils.formatMessage(
HYPERLANE_VERSION,
uint32(0),
OPTIMISM_DOMAIN,
TypeCasts.addressToBytes32(address(this)),
MAINNET_DOMAIN,
TypeCasts.addressToBytes32(address(testRecipient)),
testMessage
);
}
function _encodeMessengerCalldata(
address _ism,
uint256 _value,
bytes32 _messageId
) internal view returns (bytes memory) {
bytes memory encodedHookData = abi.encodeCall(
AbstractMessageIdAuthorizedIsm.verifyMessageId,
(_messageId)
);
return
abi.encodeCall(
ICrossDomainMessenger.relayMessage,
(
MOCK_NONCE,
address(hook),
_ism,
_value,
uint256(GAS_QUOTE),
encodedHookData
)
);
}
function _encodeFinalizeWithdrawalTx(
address _ism,
uint256 _value,
bytes32 _messageId
) internal view returns (bytes memory) {
return
abi.encode(
MOCK_NONCE,
L2_MESSENGER_ADDRESS,
l1Messenger,
_value,
uint256(GAS_QUOTE),
_encodeMessengerCalldata(_ism, _value, _messageId)
);
}
}

@ -1,5 +1,7 @@
# @hyperlane-xyz/ccip-server
## 5.1.0
## 5.0.0
## 4.1.0

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

@ -1,5 +1,38 @@
# @hyperlane-xyz/cli
## 5.1.0
### Minor Changes
- 013f19c64: Update to registry v2.5.0
- 013f19c64: Added SDK support for ArbL2ToL1Hook/ISM for selfrelay
- 013f19c64: Add output of hyperlane warp read to ./configs/warp-route-deployment.yaml
- 013f19c64: Remove registry.getUri() from core read logging to prevent registry error
- 013f19c64: Fixes the new chain message to display the correct command
- 013f19c64: Add check & confirm for existing mailbox to core deploy to allow users to decide if they want to deploy a new mailbox
### Patch Changes
- 013f19c64: Require at least 1 chain selection in warp init
- 013f19c64: feat: Add long-running CLI relayer
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [19f7d4fd9]
- @hyperlane-xyz/sdk@5.1.0
- @hyperlane-xyz/utils@5.1.0
## 5.0.0
### Major Changes

@ -1,13 +1,13 @@
{
"name": "@hyperlane-xyz/cli",
"version": "5.0.0",
"version": "5.1.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": "2.5.0",
"@hyperlane-xyz/sdk": "5.0.0",
"@hyperlane-xyz/utils": "5.0.0",
"@hyperlane-xyz/sdk": "5.1.0",
"@hyperlane-xyz/utils": "5.1.0",
"@inquirer/prompts": "^3.0.0",
"asn1.js": "^5.4.1",
"bignumber.js": "^9.1.1",

@ -278,6 +278,10 @@ async function deployAndResolveWarpIsm(
chainAddresses.staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory:
chainAddresses.staticMessageIdMultisigIsmFactory,
staticMerkleRootWeightedMultisigIsmFactory:
chainAddresses.staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory:
chainAddresses.staticMessageIdWeightedMultisigIsmFactory,
},
contractVerifier,
);
@ -308,6 +312,8 @@ async function createWarpIsm(
staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory,
staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory,
} = factoryAddresses;
const evmIsmModule = await EvmIsmModule.create({
chain,
@ -319,6 +325,8 @@ async function createWarpIsm(
staticAggregationIsmFactory,
staticMerkleRootMultisigIsmFactory,
staticMessageIdMultisigIsmFactory,
staticMerkleRootWeightedMultisigIsmFactory,
staticMessageIdWeightedMultisigIsmFactory,
},
config: warpConfig[chain].interchainSecurityModule!,
contractVerifier,

@ -1 +1 @@
export const VERSION = '5.0.0';
export const VERSION = '5.1.0';

@ -1,5 +1,35 @@
# @hyperlane-xyz/helloworld
## 5.1.0
### Minor Changes
- 013f19c64: Update to registry v2.5.0
### Patch Changes
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [19f7d4fd9]
- @hyperlane-xyz/sdk@5.1.0
- @hyperlane-xyz/core@5.1.0
## 5.0.0
### Patch Changes

@ -1,11 +1,11 @@
{
"name": "@hyperlane-xyz/helloworld",
"description": "A basic skeleton of an Hyperlane app",
"version": "5.0.0",
"version": "5.1.0",
"dependencies": {
"@hyperlane-xyz/core": "5.0.0",
"@hyperlane-xyz/core": "5.1.0",
"@hyperlane-xyz/registry": "2.5.0",
"@hyperlane-xyz/sdk": "5.0.0",
"@hyperlane-xyz/sdk": "5.1.0",
"@openzeppelin/contracts-upgradeable": "^4.9.3",
"ethers": "^5.7.2"
},

@ -22,10 +22,12 @@ export class HelloWorldDeployer extends HyperlaneRouterDeployer<
multiProvider: MultiProvider,
readonly ismFactory?: HyperlaneIsmFactory,
readonly contractVerifier?: ContractVerifier,
concurrentDeploy = false,
) {
super(multiProvider, helloWorldFactories, {
ismFactory,
contractVerifier,
concurrentDeploy,
});
}

@ -3,6 +3,7 @@ tmp.ts
dist/
.env*
cache/
deployment-plan.yaml
test/outputs
config/environments/test/core/
config/environments/test/igp/

@ -1,5 +1,33 @@
# @hyperlane-xyz/infra
## 5.1.0
### Minor Changes
- 013f19c64: Update to registry v2.5.0
### Patch Changes
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [013f19c64]
- Updated dependencies [19f7d4fd9]
- @hyperlane-xyz/sdk@5.1.0
- @hyperlane-xyz/helloworld@5.1.0
- @hyperlane-xyz/utils@5.1.0
## 5.0.0
### Minor Changes

@ -3,7 +3,6 @@ import {
GasPaymentEnforcementPolicyType,
RpcConsensusType,
} from '@hyperlane-xyz/sdk';
import { addressToBytes32 } from '@hyperlane-xyz/utils';
import {
AgentChainConfig,
@ -33,7 +32,8 @@ import inevmEthereumUsdtAddresses from './warp/inevm-USDT-addresses.json';
import injectiveInevmInjAddresses from './warp/injective-inevm-addresses.json';
import mantaTIAAddresses from './warp/manta-TIA-addresses.json';
import merklyEthAddresses from './warp/merkly-eth-addresses.json';
import renzoEzEthAddresses from './warp/renzo-ezETH-addresses.json';
import renzoEzEthAddressesV1 from './warp/renzo-ezETH-addresses-v1.json';
import renzoEzEthAddressesV3 from './warp/renzo-ezETH-addresses-v3.json';
import victionEthereumEthAddresses from './warp/viction-ETH-addresses.json';
import victionEthereumUsdcAddresses from './warp/viction-USDC-addresses.json';
import victionEthereumUsdtAddresses from './warp/viction-USDT-addresses.json';
@ -86,8 +86,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig<
moonbeam: true,
neutron: true,
optimism: true,
// Experiencing some issues with RPCs
osmosis: false,
osmosis: true,
polygon: true,
polygonzkevm: true,
proofofplay: true,
@ -140,8 +139,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig<
moonbeam: true,
// At the moment, we only relay between Neutron and Manta Pacific on the neutron context.
neutron: false,
// Experiencing some issues with RPCs
optimism: false,
optimism: true,
osmosis: true,
polygon: true,
polygonzkevm: true,
@ -297,20 +295,12 @@ const metricAppContexts = [
},
{
name: 'renzo_ezeth',
matchingList: routerMatchingList(renzoEzEthAddresses),
matchingList: matchingList(renzoEzEthAddressesV3),
},
{
// preserving old addresses in case any transactions are still in flight and need to be processed
name: 'renzo_ezeth_old',
// There's an old message to Base that's stuck around, we
// just care about this one for now.
matchingList: [
{
recipientAddress: addressToBytes32(
'0x584BA77ec804f8B6A559D196661C0242C6844F49',
),
destinationDomain: getDomainId('base'),
},
],
matchingList: matchingList(renzoEzEthAddressesV1),
},
// Hitting max env var size limits, see https://stackoverflow.com/questions/28865473/setting-environment-variable-to-a-large-value-argument-list-too-long#answer-28865503
// {

@ -10,7 +10,7 @@ export const keyFunderConfig: KeyFunderConfig<
> = {
docker: {
repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo',
tag: '78b596e-20240813-123401',
tag: '06180b0-20240816-152200',
},
// We're currently using the same deployer/key funder key as mainnet2.
// To minimize nonce clobbering we offset the key funder cron
@ -35,7 +35,7 @@ export const keyFunderConfig: KeyFunderConfig<
bsc: '5',
celo: '3',
cheesechain: '50',
cyber: '0.1',
cyber: '0.05',
degenchain: '100',
endurance: '20',
ethereum: '0.5',
@ -43,34 +43,34 @@ export const keyFunderConfig: KeyFunderConfig<
fusemainnet: '20',
gnosis: '5',
inevm: '3',
kroma: '0.1',
kroma: '0.05',
linea: '0.2',
lisk: '0.1',
lisk: '0.05',
lukso: '20',
mantapacific: '0.2',
mantle: '20',
merlin: '0.002',
metis: '10',
mint: '0.1',
metis: '3',
mint: '0.05',
mode: '0.2',
moonbeam: '5',
optimism: '0.5',
polygon: '20',
polygonzkevm: '0.5',
proofofplay: '0.1',
proofofplay: '0.05',
real: '0.1',
redstone: '0.2',
sanko: '2',
scroll: '0.5',
sei: '10',
sei: '50',
taiko: '0.2',
tangle: '10',
tangle: '2',
viction: '3',
worldchain: '0.2',
xai: '20',
xlayer: '0.5',
zetachain: '20',
zircuit: '0.1',
zircuit: '0.02',
zoramainnet: '0.2',
// ignore non-evm chains
injective: '0',
@ -143,7 +143,7 @@ export const keyFunderConfig: KeyFunderConfig<
bsc: '0.3',
celo: '5',
cheesechain: '25',
cyber: '0.05',
cyber: '0.025',
degenchain: '50',
endurance: '10',
ethereum: '0.2',
@ -151,34 +151,34 @@ export const keyFunderConfig: KeyFunderConfig<
fusemainnet: '10',
gnosis: '5',
inevm: '3',
kroma: '0.05',
kroma: '0.025',
linea: '0.1',
lisk: '0.05',
lisk: '0.025',
lukso: '10',
mantapacific: '0.1',
mantle: '10',
merlin: '0.001',
metis: '5',
mint: '0.05',
metis: '1',
mint: '0.025',
mode: '0.1',
moonbeam: '5',
optimism: '0.1',
polygon: '20',
polygonzkevm: '0.1',
proofofplay: '0.05',
proofofplay: '0.025',
real: '0.05',
redstone: '0.1',
sanko: '1',
scroll: '0.1',
sei: '2',
sei: '5',
taiko: '0.1',
tangle: '5',
tangle: '1',
viction: '2',
worldchain: '0.1',
xai: '10',
xlayer: '0.25',
zetachain: '20',
zircuit: '0.05',
zircuit: '0.01',
zoramainnet: '0.1',
// ignore non-evm chains
injective: '0',

@ -164,8 +164,8 @@
"decimals": 9
},
"solana": {
"amount": "0.001",
"decimals": 9
"amount": "0.5",
"decimals": 1
},
"taiko": {
"amount": "0.050000001",

@ -4,7 +4,7 @@ import {
} from '../../../src/config/helloworld/types.js';
import { Contexts } from '../../contexts.js';
import { environment, ethereumChainNames } from './chains.js';
import { environment } from './chains.js';
import hyperlaneAddresses from './helloworld/hyperlane/addresses.json';
import rcAddresses from './helloworld/rc/addresses.json';

@ -1,3 +1,5 @@
import { ChainName } from '@hyperlane-xyz/sdk';
import {
getKeysForRole,
getMultiProtocolProvider,
@ -28,9 +30,11 @@ export const environment: EnvironmentConfig = {
context: Contexts = Contexts.Hyperlane,
role: Role = Role.Deployer,
useSecrets?: boolean,
chains?: ChainName[],
) =>
getMultiProviderForRole(
environmentName,
chains && chains.length > 0 ? chains : supportedChainNames,
await getRegistry(useSecrets),
context,
role,

@ -53,6 +53,7 @@ export const safes: ChainMap<Address> = {
zoramainnet: '0xF87018025575552889062De4b05bBC3DAe35Cd96',
fusemainnet: '0x29a526227CB864C90Cf078d03872da913B473139',
endurance: '0xaCD1865B262C89Fb0b50dcc8fB095330ae8F35b5',
zircuit: '0x9e2fe7723b018d02cDE4f5cC1A9bC9C65b922Fc8',
};
export const icaOwnerChain = 'ethereum';

@ -8,7 +8,7 @@ data:
type: xERC20Lockbox
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0x0B386cAe2851E954b52D10Aee4ba8CCDC11463E1'
hypAddress: '0xC59336D8edDa9722B4f1Ec104007191Ec16f7087'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
bsc:
@ -16,7 +16,7 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0x9Ff085A556A498395B9edA690666E3BD34751024'
hypAddress: '0xE00C6185a5c19219F1FFeD213b4406a254968c26'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
arbitrum:
@ -24,7 +24,7 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0x1217441e6E8E960D065698317FE13594a5704f69'
hypAddress: '0xB26bBfC6d1F469C821Ea25099017862e7368F4E8'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
optimism:
@ -32,7 +32,7 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0xEF90AF9FCC831c5E2266285C4A1787201f96736a'
hypAddress: '0xacEB607CdF59EB8022Cc0699eEF3eCF246d149e2'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
base:
@ -40,7 +40,7 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0x4b36617B3D2cAb714a056090306A88Dd6DD4cCcf'
hypAddress: '0x2552516453368e42705D791F674b312b8b87CD9e'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
blast:
@ -48,7 +48,7 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0xB951c9b16603825C285bf59b14e983047d421Af5'
hypAddress: '0x486b39378f99f073A3043C6Aabe8666876A8F3C5'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
mode:
@ -56,7 +56,7 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0x477614191C3ccF8e10a6D1291dBAf098D17497BE'
hypAddress: '0xC59336D8edDa9722B4f1Ec104007191Ec16f7087'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
linea:
@ -64,7 +64,7 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0x477614191C3ccF8e10a6D1291dBAf098D17497BE'
hypAddress: '0xC59336D8edDa9722B4f1Ec104007191Ec16f7087'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
fraxtal:
@ -72,6 +72,14 @@ data:
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0xdFf621F952c23972dFD3A9E5d7B9f6339e9c078B'
hypAddress: '0x3aE8635A4D581d40a6Edfb3f2ED480f9532994F5'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18
zircuit:
protocolType: ethereum
type: xERC20
name: Renzo Restaked ETH
symbol: ezETH
hypAddress: '0x2552516453368e42705D791F674b312b8b87CD9e'
tokenAddress: '0x2416092f143378750bb29b79eD961ab195CcEea5'
decimals: 18

@ -1,11 +1,8 @@
{
"ancient8": {
"HypERC20": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5",
"synthetic": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5"
},
"ethereum": {
"HypERC20Collateral": "0x8b4192B9Ad1fCa440A5808641261e5289e6de95D",
"collateral": "0x8b4192B9Ad1fCa440A5808641261e5289e6de95D",
"proxyAdmin": "0x7d0c8b23c5b35091972023ccc689cfedcd881c7d"
"HypERC20Collateral": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4",
"collateral": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4",
"proxyAdmin": "0xe41a3270875f28A03312877cD95A01e9a53664b1",
"timelockController": "0x0000000000000000000000000000000000000000"
}
}

@ -0,0 +1,34 @@
import { ethers } from 'ethers';
import {
ChainMap,
RouterConfig,
TokenRouterConfig,
TokenType,
} from '@hyperlane-xyz/sdk';
import { DEPLOYER } from '../../owners.js';
export const getEthereumSolanaPzETHWarpConfig = async (
routerConfig: ChainMap<RouterConfig>,
): Promise<ChainMap<TokenRouterConfig>> => {
// @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig
const solana: TokenRouterConfig = {
type: TokenType.synthetic,
foreignDeployment: 'GiP8GwN1GsscVJvmKSD4muDEihRzZRa9mxnS1Toi64pa',
gas: 300_000,
};
const ethereum: TokenRouterConfig = {
...routerConfig.ethereum,
type: TokenType.collateral,
interchainSecurityModule: ethers.constants.AddressZero,
token: '0x8c9532a60e0e7c6bbd2b2c1303f63ace1c3e9811',
owner: DEPLOYER,
};
return {
solana,
ethereum,
};
};

@ -0,0 +1,190 @@
import {
ChainMap,
IsmType,
RouterConfig,
TokenRouterConfig,
TokenType,
buildAggregationIsmConfigs,
} from '@hyperlane-xyz/sdk';
import { symmetricDifference } from '@hyperlane-xyz/utils';
import { getRegistry } from '../../chains.js';
const lockbox = '0xC8140dA31E6bCa19b287cC35531c2212763C2059';
const xERC20 = '0x2416092f143378750bb29b79eD961ab195CcEea5';
const lockboxChain = 'ethereum';
// over the default 100k to account for xerc20 gas + ISM overhead over the default ISM https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/49f41d9759fd515bfd89e6e22e799c41b27b4119/typescript/sdk/src/router/GasRouterDeployer.ts#L14
const warpRouteOverheadGas = 200_000;
const chainsToDeploy = [
'arbitrum',
'optimism',
'base',
'blast',
'bsc',
'mode',
'linea',
'ethereum',
'fraxtal',
'zircuit',
];
const ezEthValidators = {
arbitrum: {
threshold: 1,
validators: [
'0x9bccfad3bd12ef0ee8ae839dd9ed7835bccadc9d', // Everclear
'0xc27032c6bbd48c20005f552af3aaa0dbf14260f3', // Renzo
],
},
optimism: {
threshold: 1,
validators: [
'0x6f4cb8e96db5d44422a4495faa73fffb9d30e9e2', // Everclear
'0xe2593d205f5e7f74a50fa900824501084e092ebd', // Renzo
],
},
base: {
threshold: 1,
validators: [
'0x25ba4ee5268cbfb8d69bac531aa10368778702bd', // Renzo
'0x9ec803b503e9c7d2611e231521ef3fde73f7a21c', // Everclear
],
},
blast: {
threshold: 1,
validators: [
'0x1652d8ba766821cf01aeea34306dfc1cab964a32', // Everclear
'0x54bb0036f777202371429e062fe6aee0d59442f9', // Renzo
],
},
bsc: {
threshold: 1,
validators: [
'0x3156db97a3b3e2dcc3d69fddfd3e12dc7c937b6d', // Renzo
'0x9a0326c43e4713ae2477f09e0f28ffedc24d8266', // Everclear
],
},
mode: {
threshold: 1,
validators: [
'0x456fbbe05484fc9f2f38ea09648424f54d6872be', // Everclear
'0x7e29608c6e5792bbf9128599ca309be0728af7b4', // Renzo
],
},
linea: {
threshold: 1,
validators: [
'0x06a5a2a429560034d38bf62ca6d470942535947e', // Everclear
'0xcb3e44edd2229860bdbaa58ba2c3817d111bee9a', // Renzo
],
},
ethereum: {
threshold: 1,
validators: [
'0x1fd889337f60986aa57166bc5ac121efd13e4fdd', // Everclear
'0xc7f7b94a6baf2fffa54dfe1dde6e5fcbb749e04f', // Renzo
],
},
fraxtal: {
threshold: 1,
validators: [
'0x25b3a88f7cfd3c9f7d7e32b295673a16a6ddbd91', // luganodes
'0xe986f457965227a05dcf984c8d0c29e01253c44d', // Renzo
],
},
zircuit: {
threshold: 1,
validators: [
'0x1da9176c2ce5cc7115340496fa7d1800a98911ce', // Renzo
'0x7ac6584c068eb2a72d4db82a7b7cd5ab34044061', // luganodes
],
},
};
const ezEthSafes: Record<string, string> = {
arbitrum: '0x0e60fd361fF5b90088e1782e6b21A7D177d462C5',
optimism: '0x8410927C286A38883BC23721e640F31D3E3E79F8',
base: '0x8410927C286A38883BC23721e640F31D3E3E79F8',
blast: '0xda7dBF0DB81882372B598a715F86eD5254A01b0a',
bsc: '0x0e60fd361fF5b90088e1782e6b21A7D177d462C5',
mode: '0x7791eeA3484Ba4E5860B7a2293840767619c2B58',
linea: '0xb7092685571B49786F1248c6205B5ac3A691c65E',
ethereum: '0xD1e6626310fD54Eceb5b9a51dA2eC329D6D4B68A',
fraxtal: '0x8410927C286A38883BC23721e640F31D3E3E79F8',
zircuit: '0x8410927C286A38883BC23721e640F31D3E3E79F8',
};
export const getRenzoEZETHWarpConfig = async (): Promise<
ChainMap<TokenRouterConfig>
> => {
const registry = await getRegistry();
const validatorDiff = symmetricDifference(
new Set(chainsToDeploy),
new Set(Object.keys(ezEthValidators)),
);
const safeDiff = symmetricDifference(
new Set(chainsToDeploy),
new Set(Object.keys(ezEthSafes)),
);
if (validatorDiff.size > 0) {
throw new Error(
`chainsToDeploy !== validatorConfig, diff is ${Array.from(
validatorDiff,
).join(', ')}`,
);
}
if (safeDiff.size > 0) {
throw new Error(
`chainsToDeploy !== safeDiff, diff is ${Array.from(safeDiff).join(', ')}`,
);
}
const tokenConfig = Object.fromEntries<TokenRouterConfig>(
await Promise.all(
chainsToDeploy.map(
async (chain): Promise<[string, TokenRouterConfig]> => {
const ret: [string, TokenRouterConfig] = [
chain,
{
isNft: false,
type:
chain === lockboxChain
? TokenType.XERC20Lockbox
: TokenType.XERC20,
token: chain === lockboxChain ? lockbox : xERC20,
owner: ezEthSafes[chain],
gas: warpRouteOverheadGas,
mailbox: (await registry.getChainAddresses(chain))!.mailbox,
interchainSecurityModule: {
type: IsmType.AGGREGATION,
threshold: 2,
modules: [
{
type: IsmType.ROUTING,
owner: ezEthSafes[chain],
domains: buildAggregationIsmConfigs(
chain,
chainsToDeploy,
ezEthValidators,
),
},
{
type: IsmType.FALLBACK_ROUTING,
domains: {},
owner: ezEthSafes[chain],
},
],
},
},
];
return ret;
},
),
),
);
return tokenConfig;
};

@ -0,0 +1,32 @@
{
"arbitrum": {
"xERC20": "0xB26bBfC6d1F469C821Ea25099017862e7368F4E8"
},
"optimism": {
"xERC20": "0xacEB607CdF59EB8022Cc0699eEF3eCF246d149e2"
},
"base": {
"xERC20": "0x2552516453368e42705D791F674b312b8b87CD9e"
},
"blast": {
"xERC20": "0x486b39378f99f073A3043C6Aabe8666876A8F3C5"
},
"bsc": {
"xERC20": "0xE00C6185a5c19219F1FFeD213b4406a254968c26"
},
"mode": {
"xERC20": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087"
},
"linea": {
"xERC20": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087"
},
"ethereum": {
"xERC20Lockbox": "0xC59336D8edDa9722B4f1Ec104007191Ec16f7087"
},
"fraxtal": {
"xERC20": "0x3aE8635A4D581d40a6Edfb3f2ED480f9532994F5"
},
"zircuit": {
"xERC20": "0x2552516453368e42705D791F674b312b8b87CD9e"
}
}

@ -111,6 +111,25 @@
"constructorArguments": "0000000000000000000000007d0c8b23c5b35091972023ccc689cfedcd881c7d000000000000000000000000ed96482bea3c51a33b4c1ada8b438e33a236741300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000019b2cf952b70b217c90fc408714fbc1acd29a6a8000000000000000000000000d17b4100cc66a2f1b9a452007ff26365aaeb7ec3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000",
"isProxy": true,
"name": "TransparentUpgradeableProxy"
},
{
"address": "0xe41a3270875f28A03312877cD95A01e9a53664b1",
"constructorArguments": "",
"isProxy": false,
"name": "ProxyAdmin"
},
{
"address": "0xbE6501A4E68a3463A217eC0dEc862b1593C0A47D",
"constructorArguments": "0000000000000000000000008c9532a60e0e7c6bbd2b2c1303f63ace1c3e9811000000000000000000000000c005dc82818d67af737725bd4bf75435d065d239",
"isProxy": false,
"name": "HypERC20Collateral"
},
{
"address": "0x1D622da2ce4C4D9D4B0611718cb3BcDcAd008DD4",
"constructorArguments": "000000000000000000000000be6501a4e68a3463a217ec0dec862b1593c0a47d000000000000000000000000e41a3270875f28a03312877cd95a01e9a53664b100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000",
"expectedimplementation": "0xbE6501A4E68a3463A217eC0dEc862b1593C0A47D",
"isProxy": true,
"name": "TransparentUpgradeableProxy"
}
],
"inevm": [

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

Loading…
Cancel
Save