Sealevel (#2404)
### Description Opening for that sweet sweet agent image ### Drive-by changes _Are there any minor or drive-by changes also included?_ ### Related issues - Fixes #[issue number here] ### Backward compatibility _Are these changes backward compatible?_ Yes No _Are there any infrastructure implications, e.g. changes that would prohibit deploying older commits using this infra tooling?_ None Yes ### Testing _What kind of testing have these changes undergone?_ None Manual Unit Tests --------- Co-authored-by: Steven Sloboda <steven@eclipse.builders> Co-authored-by: Nam Chu Hoai <nambrot@googlemail.com> Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz> Co-authored-by: J M Rossy <jm.rossy@gmail.com> Co-authored-by: Mattie Conover <git@mconover.dev> Co-authored-by: Asa Oines <asaoines@gmail.com> Co-authored-by: Sergei Patrikeev <serejke.best@gmail.com> Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com> Co-authored-by: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Co-authored-by: yorhodes <yorkerhodesiv@gmail.com> Co-authored-by: Yorke Rhodes <yorke@useabacus.network> Co-authored-by: Yorke Rhodes <email@yorke.dev> Co-authored-by: Asa Oines <asaoines@Asas-MacBook-Pro.local> Co-authored-by: Kunal Arora <kuarora@ucsd.edu> Co-authored-by: Anett <44020788+anettrolikova@users.noreply.github.com> Co-authored-by: Yaqub Mahmoud <yaqub320@gmail.com> Co-authored-by: Alex <alex@alexbh.dev>trevor/last-agent-release agents-2023-07-25
parent
04bdce0877
commit
8c5983933b
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,33 @@ |
|||||||
|
cargo-features = ["workspace-inheritance"] |
||||||
|
|
||||||
|
[package] |
||||||
|
name = "hyperlane-sealevel" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
anyhow.workspace = true |
||||||
|
async-trait.workspace = true |
||||||
|
base64.workspace = true |
||||||
|
borsh.workspace = true |
||||||
|
jsonrpc-core.workspace = true |
||||||
|
num-traits.workspace = true |
||||||
|
serde.workspace = true |
||||||
|
solana-account-decoder.workspace = true |
||||||
|
solana-client.workspace = true |
||||||
|
solana-sdk.workspace = true |
||||||
|
solana-transaction-status.workspace = true |
||||||
|
thiserror.workspace = true |
||||||
|
tracing-futures.workspace = true |
||||||
|
tracing.workspace = true |
||||||
|
url.workspace = true |
||||||
|
|
||||||
|
hyperlane-core = { path = "../../hyperlane-core" } |
||||||
|
hyperlane-sealevel-mailbox = { path = "../../sealevel/programs/mailbox", features = ["no-entrypoint"] } |
||||||
|
hyperlane-sealevel-interchain-security-module-interface = { path = "../../sealevel/libraries/interchain-security-module-interface" } |
||||||
|
hyperlane-sealevel-message-recipient-interface = { path = "../../sealevel/libraries/message-recipient-interface" } |
||||||
|
serializable-account-meta = { path = "../../sealevel/libraries/serializable-account-meta" } |
||||||
|
account-utils = { path = "../../sealevel/libraries/account-utils" } |
||||||
|
multisig-ism = { path = "../../sealevel/libraries/multisig-ism" } |
||||||
|
hyperlane-sealevel-multisig-ism-message-id = { path = "../../sealevel/programs/ism/multisig-ism-message-id", features = ["no-entrypoint"] } |
||||||
|
hyperlane-sealevel-validator-announce = { path = "../../sealevel/programs/validator-announce", features = ["no-entrypoint"] } |
@ -0,0 +1,24 @@ |
|||||||
|
use solana_client::nonblocking::rpc_client::RpcClient; |
||||||
|
|
||||||
|
/// Kludge to implement Debug for RpcClient.
|
||||||
|
pub(crate) struct RpcClientWithDebug(RpcClient); |
||||||
|
|
||||||
|
impl RpcClientWithDebug { |
||||||
|
pub fn new(rpc_endpoint: String) -> Self { |
||||||
|
Self(RpcClient::new(rpc_endpoint)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl std::fmt::Debug for RpcClientWithDebug { |
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||||
|
f.write_str("RpcClient { ... }") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl std::ops::Deref for RpcClientWithDebug { |
||||||
|
type Target = RpcClient; |
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
&self.0 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
use async_trait::async_trait; |
||||||
|
use hyperlane_core::{ |
||||||
|
ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, |
||||||
|
HyperlaneProvider, IndexRange, Indexer, InterchainGasPaymaster, InterchainGasPayment, LogMeta, |
||||||
|
H256, |
||||||
|
}; |
||||||
|
use tracing::{info, instrument}; |
||||||
|
|
||||||
|
use crate::{ConnectionConf, SealevelProvider}; |
||||||
|
use solana_sdk::pubkey::Pubkey; |
||||||
|
|
||||||
|
/// A reference to an IGP contract on some Sealevel chain
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct SealevelInterchainGasPaymaster { |
||||||
|
program_id: Pubkey, |
||||||
|
domain: HyperlaneDomain, |
||||||
|
} |
||||||
|
|
||||||
|
impl SealevelInterchainGasPaymaster { |
||||||
|
/// Create a new Sealevel IGP.
|
||||||
|
pub fn new(_conf: &ConnectionConf, locator: ContractLocator) -> Self { |
||||||
|
let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); |
||||||
|
Self { |
||||||
|
program_id, |
||||||
|
domain: locator.domain.clone(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneContract for SealevelInterchainGasPaymaster { |
||||||
|
fn address(&self) -> H256 { |
||||||
|
self.program_id.to_bytes().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneChain for SealevelInterchainGasPaymaster { |
||||||
|
fn domain(&self) -> &HyperlaneDomain { |
||||||
|
&self.domain |
||||||
|
} |
||||||
|
|
||||||
|
fn provider(&self) -> Box<dyn HyperlaneProvider> { |
||||||
|
Box::new(SealevelProvider::new(self.domain.clone())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl InterchainGasPaymaster for SealevelInterchainGasPaymaster {} |
||||||
|
|
||||||
|
/// Struct that retrieves event data for a Sealevel IGP contract
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct SealevelInterchainGasPaymasterIndexer {} |
||||||
|
|
||||||
|
impl SealevelInterchainGasPaymasterIndexer { |
||||||
|
/// Create a new Sealevel IGP indexer.
|
||||||
|
pub fn new(_conf: &ConnectionConf, _locator: ContractLocator) -> Self { |
||||||
|
Self {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl Indexer<InterchainGasPayment> for SealevelInterchainGasPaymasterIndexer { |
||||||
|
#[instrument(err, skip(self))] |
||||||
|
async fn fetch_logs( |
||||||
|
&self, |
||||||
|
_range: IndexRange, |
||||||
|
) -> ChainResult<Vec<(InterchainGasPayment, LogMeta)>> { |
||||||
|
info!("Gas payment indexing not implemented for Sealevel"); |
||||||
|
Ok(vec![]) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(level = "debug", err, ret, skip(self))] |
||||||
|
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||||
|
// As a workaround to avoid gas payment indexing on Sealevel,
|
||||||
|
// we pretend the block number is 1.
|
||||||
|
Ok(1) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
use async_trait::async_trait; |
||||||
|
use num_traits::cast::FromPrimitive; |
||||||
|
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair}; |
||||||
|
use tracing::warn; |
||||||
|
|
||||||
|
use hyperlane_core::{ |
||||||
|
ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, |
||||||
|
HyperlaneDomain, HyperlaneMessage, InterchainSecurityModule, ModuleType, H256, U256, |
||||||
|
}; |
||||||
|
use hyperlane_sealevel_interchain_security_module_interface::InterchainSecurityModuleInstruction; |
||||||
|
use serializable_account_meta::SimulationReturnData; |
||||||
|
|
||||||
|
use crate::{utils::simulate_instruction, ConnectionConf, RpcClientWithDebug}; |
||||||
|
|
||||||
|
/// A reference to an InterchainSecurityModule contract on some Sealevel chain
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct SealevelInterchainSecurityModule { |
||||||
|
rpc_client: RpcClientWithDebug, |
||||||
|
payer: Option<Keypair>, |
||||||
|
program_id: Pubkey, |
||||||
|
domain: HyperlaneDomain, |
||||||
|
} |
||||||
|
|
||||||
|
impl SealevelInterchainSecurityModule { |
||||||
|
/// Create a new sealevel InterchainSecurityModule
|
||||||
|
pub fn new(conf: &ConnectionConf, locator: ContractLocator, payer: Option<Keypair>) -> Self { |
||||||
|
let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); |
||||||
|
let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); |
||||||
|
Self { |
||||||
|
rpc_client, |
||||||
|
payer, |
||||||
|
program_id, |
||||||
|
domain: locator.domain.clone(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneContract for SealevelInterchainSecurityModule { |
||||||
|
fn address(&self) -> H256 { |
||||||
|
self.program_id.to_bytes().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneChain for SealevelInterchainSecurityModule { |
||||||
|
fn domain(&self) -> &HyperlaneDomain { |
||||||
|
&self.domain |
||||||
|
} |
||||||
|
|
||||||
|
fn provider(&self) -> Box<dyn hyperlane_core::HyperlaneProvider> { |
||||||
|
Box::new(crate::SealevelProvider::new(self.domain.clone())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl InterchainSecurityModule for SealevelInterchainSecurityModule { |
||||||
|
async fn module_type(&self) -> ChainResult<ModuleType> { |
||||||
|
let instruction = Instruction::new_with_bytes( |
||||||
|
self.program_id, |
||||||
|
&InterchainSecurityModuleInstruction::Type |
||||||
|
.encode() |
||||||
|
.map_err(ChainCommunicationError::from_other)?[..], |
||||||
|
vec![], |
||||||
|
); |
||||||
|
|
||||||
|
let module = simulate_instruction::<SimulationReturnData<u32>>( |
||||||
|
&self.rpc_client, |
||||||
|
self.payer |
||||||
|
.as_ref() |
||||||
|
.ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, |
||||||
|
instruction, |
||||||
|
) |
||||||
|
.await? |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str("No return data was returned from the ISM") |
||||||
|
})? |
||||||
|
.return_data; |
||||||
|
|
||||||
|
if let Some(module_type) = ModuleType::from_u32(module) { |
||||||
|
Ok(module_type) |
||||||
|
} else { |
||||||
|
warn!(%module, "Unknown module type"); |
||||||
|
Ok(ModuleType::Unused) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async fn dry_run_verify( |
||||||
|
&self, |
||||||
|
_message: &HyperlaneMessage, |
||||||
|
_metadata: &[u8], |
||||||
|
) -> ChainResult<Option<U256>> { |
||||||
|
// TODO: Implement this once we have aggregation ISM support in Sealevel
|
||||||
|
Ok(Some(U256::zero())) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
//! Implementation of hyperlane for Sealevel.
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)] |
||||||
|
#![warn(missing_docs)] |
||||||
|
#![deny(warnings)] |
||||||
|
|
||||||
|
pub use crate::multisig_ism::*; |
||||||
|
pub(crate) use client::RpcClientWithDebug; |
||||||
|
pub use interchain_gas::*; |
||||||
|
pub use interchain_security_module::*; |
||||||
|
pub use mailbox::*; |
||||||
|
pub use provider::*; |
||||||
|
pub use solana_sdk::signer::keypair::Keypair; |
||||||
|
pub use trait_builder::*; |
||||||
|
pub use validator_announce::*; |
||||||
|
|
||||||
|
mod interchain_gas; |
||||||
|
mod interchain_security_module; |
||||||
|
mod mailbox; |
||||||
|
mod multisig_ism; |
||||||
|
mod provider; |
||||||
|
mod trait_builder; |
||||||
|
mod utils; |
||||||
|
|
||||||
|
mod client; |
||||||
|
mod validator_announce; |
@ -0,0 +1,748 @@ |
|||||||
|
#![allow(warnings)] // FIXME remove
|
||||||
|
|
||||||
|
use std::{collections::HashMap, num::NonZeroU64, str::FromStr as _}; |
||||||
|
|
||||||
|
use async_trait::async_trait; |
||||||
|
use borsh::{BorshDeserialize, BorshSerialize}; |
||||||
|
use jsonrpc_core::futures_util::TryFutureExt; |
||||||
|
use tracing::{debug, info, instrument, warn}; |
||||||
|
|
||||||
|
use hyperlane_core::{ |
||||||
|
accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, |
||||||
|
ContractLocator, Decode as _, Encode as _, HyperlaneAbi, HyperlaneChain, HyperlaneContract, |
||||||
|
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, IndexRange, Indexer, LogMeta, Mailbox, |
||||||
|
MessageIndexer, SequenceRange, TxCostEstimate, TxOutcome, H256, U256, |
||||||
|
}; |
||||||
|
use hyperlane_sealevel_interchain_security_module_interface::{ |
||||||
|
InterchainSecurityModuleInstruction, VerifyInstruction, |
||||||
|
}; |
||||||
|
use hyperlane_sealevel_mailbox::{ |
||||||
|
accounts::{DispatchedMessageAccount, InboxAccount, OutboxAccount}, |
||||||
|
instruction::InboxProcess, |
||||||
|
mailbox_dispatched_message_pda_seeds, mailbox_inbox_pda_seeds, mailbox_outbox_pda_seeds, |
||||||
|
mailbox_process_authority_pda_seeds, mailbox_processed_message_pda_seeds, |
||||||
|
}; |
||||||
|
use hyperlane_sealevel_message_recipient_interface::{ |
||||||
|
HandleInstruction, MessageRecipientInstruction, |
||||||
|
}; |
||||||
|
use serializable_account_meta::SimulationReturnData; |
||||||
|
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}; |
||||||
|
use solana_client::{ |
||||||
|
nonblocking::rpc_client::RpcClient, |
||||||
|
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig}, |
||||||
|
rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, |
||||||
|
}; |
||||||
|
use solana_sdk::{ |
||||||
|
account::Account, |
||||||
|
commitment_config::CommitmentConfig, |
||||||
|
compute_budget::ComputeBudgetInstruction, |
||||||
|
hash::Hash, |
||||||
|
instruction::AccountMeta, |
||||||
|
instruction::Instruction, |
||||||
|
message::Message, |
||||||
|
pubkey::Pubkey, |
||||||
|
signature::Signature, |
||||||
|
signer::{keypair::Keypair, Signer as _}, |
||||||
|
transaction::{Transaction, VersionedTransaction}, |
||||||
|
}; |
||||||
|
use solana_transaction_status::{ |
||||||
|
EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, |
||||||
|
UiInnerInstructions, UiInstruction, UiMessage, UiParsedInstruction, UiReturnDataEncoding, |
||||||
|
UiTransaction, UiTransactionReturnData, UiTransactionStatusMeta, |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::RpcClientWithDebug; |
||||||
|
use crate::{ |
||||||
|
utils::{get_account_metas, simulate_instruction}, |
||||||
|
ConnectionConf, SealevelProvider, |
||||||
|
}; |
||||||
|
|
||||||
|
const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111"; |
||||||
|
const SPL_NOOP: &str = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"; |
||||||
|
|
||||||
|
// FIXME solana uses the first 64 byte signature of a transaction to uniquely identify the
|
||||||
|
// transaction rather than a 32 byte transaction hash like ethereum. Hash it here to reduce
|
||||||
|
// size - requires more thought to ensure this makes sense to do...
|
||||||
|
fn signature_to_txn_hash(signature: &Signature) -> H256 { |
||||||
|
H256::from(solana_sdk::hash::hash(signature.as_ref()).to_bytes()) |
||||||
|
} |
||||||
|
|
||||||
|
// The max amount of compute units for a transaction.
|
||||||
|
// TODO: consider a more sane value and/or use IGP gas payments instead.
|
||||||
|
const PROCESS_COMPUTE_UNITS: u32 = 1_400_000; |
||||||
|
|
||||||
|
/// A reference to a Mailbox contract on some Sealevel chain
|
||||||
|
pub struct SealevelMailbox { |
||||||
|
program_id: Pubkey, |
||||||
|
inbox: (Pubkey, u8), |
||||||
|
outbox: (Pubkey, u8), |
||||||
|
rpc_client: RpcClient, |
||||||
|
domain: HyperlaneDomain, |
||||||
|
payer: Option<Keypair>, |
||||||
|
} |
||||||
|
|
||||||
|
impl SealevelMailbox { |
||||||
|
/// Create a new sealevel mailbox
|
||||||
|
pub fn new( |
||||||
|
conf: &ConnectionConf, |
||||||
|
locator: ContractLocator, |
||||||
|
payer: Option<Keypair>, |
||||||
|
) -> ChainResult<Self> { |
||||||
|
// Set the `processed` commitment at rpc level
|
||||||
|
let rpc_client = |
||||||
|
RpcClient::new_with_commitment(conf.url.to_string(), CommitmentConfig::processed()); |
||||||
|
|
||||||
|
let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); |
||||||
|
let domain = locator.domain.id(); |
||||||
|
let inbox = Pubkey::find_program_address(mailbox_inbox_pda_seeds!(), &program_id); |
||||||
|
let outbox = Pubkey::find_program_address(mailbox_outbox_pda_seeds!(), &program_id); |
||||||
|
|
||||||
|
debug!( |
||||||
|
"domain={}\nmailbox={}\ninbox=({}, {})\noutbox=({}, {})", |
||||||
|
domain, program_id, inbox.0, inbox.1, outbox.0, outbox.1, |
||||||
|
); |
||||||
|
|
||||||
|
Ok(SealevelMailbox { |
||||||
|
program_id, |
||||||
|
inbox, |
||||||
|
outbox, |
||||||
|
rpc_client, |
||||||
|
domain: locator.domain.clone(), |
||||||
|
payer, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn inbox(&self) -> (Pubkey, u8) { |
||||||
|
self.inbox |
||||||
|
} |
||||||
|
pub fn outbox(&self) -> (Pubkey, u8) { |
||||||
|
self.outbox |
||||||
|
} |
||||||
|
|
||||||
|
/// Simulates an instruction, and attempts to deserialize it into a T.
|
||||||
|
/// If no return data at all was returned, returns Ok(None).
|
||||||
|
/// If some return data was returned but deserialization was unsuccesful,
|
||||||
|
/// an Err is returned.
|
||||||
|
pub async fn simulate_instruction<T: BorshDeserialize + BorshSerialize>( |
||||||
|
&self, |
||||||
|
instruction: Instruction, |
||||||
|
) -> ChainResult<Option<T>> { |
||||||
|
simulate_instruction( |
||||||
|
&self.rpc_client, |
||||||
|
self.payer |
||||||
|
.as_ref() |
||||||
|
.ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, |
||||||
|
instruction, |
||||||
|
) |
||||||
|
.await |
||||||
|
} |
||||||
|
|
||||||
|
/// Simulates an Instruction that will return a list of AccountMetas.
|
||||||
|
pub async fn get_account_metas( |
||||||
|
&self, |
||||||
|
instruction: Instruction, |
||||||
|
) -> ChainResult<Vec<AccountMeta>> { |
||||||
|
get_account_metas( |
||||||
|
&self.rpc_client, |
||||||
|
self.payer |
||||||
|
.as_ref() |
||||||
|
.ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, |
||||||
|
instruction, |
||||||
|
) |
||||||
|
.await |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the recipient ISM given a recipient program id and the ISM getter account metas.
|
||||||
|
pub async fn get_recipient_ism( |
||||||
|
&self, |
||||||
|
recipient_program_id: Pubkey, |
||||||
|
ism_getter_account_metas: Vec<AccountMeta>, |
||||||
|
) -> ChainResult<Pubkey> { |
||||||
|
let mut accounts = vec![ |
||||||
|
// Inbox PDA
|
||||||
|
AccountMeta::new_readonly(self.inbox.0, false), |
||||||
|
// The recipient program.
|
||||||
|
AccountMeta::new_readonly(recipient_program_id, false), |
||||||
|
]; |
||||||
|
accounts.extend(ism_getter_account_metas); |
||||||
|
|
||||||
|
let instruction = Instruction::new_with_borsh( |
||||||
|
self.program_id, |
||||||
|
&hyperlane_sealevel_mailbox::instruction::Instruction::InboxGetRecipientIsm( |
||||||
|
recipient_program_id, |
||||||
|
), |
||||||
|
accounts, |
||||||
|
); |
||||||
|
let ism = self |
||||||
|
.simulate_instruction::<SimulationReturnData<Pubkey>>(instruction) |
||||||
|
.await? |
||||||
|
.ok_or(ChainCommunicationError::from_other_str( |
||||||
|
"No return data from InboxGetRecipientIsm instruction", |
||||||
|
))? |
||||||
|
.return_data; |
||||||
|
Ok(ism) |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the account metas required for the recipient's
|
||||||
|
/// `MessageRecipientInstruction::InterchainSecurityModule` instruction.
|
||||||
|
pub async fn get_ism_getter_account_metas( |
||||||
|
&self, |
||||||
|
recipient_program_id: Pubkey, |
||||||
|
) -> ChainResult<Vec<AccountMeta>> { |
||||||
|
let instruction = |
||||||
|
hyperlane_sealevel_message_recipient_interface::MessageRecipientInstruction::InterchainSecurityModuleAccountMetas; |
||||||
|
self.get_account_metas_with_instruction_bytes( |
||||||
|
recipient_program_id, |
||||||
|
&instruction |
||||||
|
.encode() |
||||||
|
.map_err(ChainCommunicationError::from_other)?, |
||||||
|
hyperlane_sealevel_message_recipient_interface::INTERCHAIN_SECURITY_MODULE_ACCOUNT_METAS_PDA_SEEDS, |
||||||
|
).await |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the account metas required for the ISM's `Verify` instruction.
|
||||||
|
pub async fn get_ism_verify_account_metas( |
||||||
|
&self, |
||||||
|
ism: Pubkey, |
||||||
|
metadata: Vec<u8>, |
||||||
|
message: Vec<u8>, |
||||||
|
) -> ChainResult<Vec<AccountMeta>> { |
||||||
|
let instruction = |
||||||
|
InterchainSecurityModuleInstruction::VerifyAccountMetas(VerifyInstruction { |
||||||
|
metadata, |
||||||
|
message, |
||||||
|
}); |
||||||
|
self.get_account_metas_with_instruction_bytes( |
||||||
|
ism, |
||||||
|
&instruction |
||||||
|
.encode() |
||||||
|
.map_err(ChainCommunicationError::from_other)?, |
||||||
|
hyperlane_sealevel_interchain_security_module_interface::VERIFY_ACCOUNT_METAS_PDA_SEEDS, |
||||||
|
) |
||||||
|
.await |
||||||
|
} |
||||||
|
|
||||||
|
/// Gets the account metas required for the recipient's `MessageRecipientInstruction::Handle` instruction.
|
||||||
|
pub async fn get_handle_account_metas( |
||||||
|
&self, |
||||||
|
message: &HyperlaneMessage, |
||||||
|
) -> ChainResult<Vec<AccountMeta>> { |
||||||
|
let recipient_program_id = Pubkey::new_from_array(message.recipient.into()); |
||||||
|
let instruction = MessageRecipientInstruction::HandleAccountMetas(HandleInstruction { |
||||||
|
sender: message.sender, |
||||||
|
origin: message.origin, |
||||||
|
message: message.body.clone(), |
||||||
|
}); |
||||||
|
|
||||||
|
self.get_account_metas_with_instruction_bytes( |
||||||
|
recipient_program_id, |
||||||
|
&instruction |
||||||
|
.encode() |
||||||
|
.map_err(ChainCommunicationError::from_other)?, |
||||||
|
hyperlane_sealevel_message_recipient_interface::HANDLE_ACCOUNT_METAS_PDA_SEEDS, |
||||||
|
) |
||||||
|
.await |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_account_metas_with_instruction_bytes( |
||||||
|
&self, |
||||||
|
program_id: Pubkey, |
||||||
|
instruction_data: &[u8], |
||||||
|
account_metas_pda_seeds: &[&[u8]], |
||||||
|
) -> ChainResult<Vec<AccountMeta>> { |
||||||
|
let (account_metas_pda_key, _) = |
||||||
|
Pubkey::find_program_address(account_metas_pda_seeds, &program_id); |
||||||
|
let instruction = Instruction::new_with_bytes( |
||||||
|
program_id, |
||||||
|
instruction_data, |
||||||
|
vec![AccountMeta::new(account_metas_pda_key, false)], |
||||||
|
); |
||||||
|
|
||||||
|
self.get_account_metas(instruction).await |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneContract for SealevelMailbox { |
||||||
|
fn address(&self) -> H256 { |
||||||
|
self.program_id.to_bytes().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneChain for SealevelMailbox { |
||||||
|
fn domain(&self) -> &HyperlaneDomain { |
||||||
|
&self.domain |
||||||
|
} |
||||||
|
|
||||||
|
fn provider(&self) -> Box<dyn HyperlaneProvider> { |
||||||
|
Box::new(SealevelProvider::new(self.domain.clone())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl std::fmt::Debug for SealevelMailbox { |
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||||
|
write!(f, "{:?}", self as &dyn HyperlaneContract) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO refactor the sealevel client into a lib and bin, pull in and use the lib here rather than
|
||||||
|
// duplicating.
|
||||||
|
#[async_trait] |
||||||
|
impl Mailbox for SealevelMailbox { |
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn count(&self, _maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> { |
||||||
|
let tree = self.tree(_maybe_lag).await?; |
||||||
|
|
||||||
|
tree.count() |
||||||
|
.try_into() |
||||||
|
.map_err(ChainCommunicationError::from_other) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn delivered(&self, id: H256) -> ChainResult<bool> { |
||||||
|
let (processed_message_account_key, _processed_message_account_bump) = |
||||||
|
Pubkey::find_program_address( |
||||||
|
mailbox_processed_message_pda_seeds!(id), |
||||||
|
&self.program_id, |
||||||
|
); |
||||||
|
|
||||||
|
let account = self |
||||||
|
.rpc_client |
||||||
|
.get_account_with_commitment( |
||||||
|
&processed_message_account_key, |
||||||
|
CommitmentConfig::finalized(), |
||||||
|
) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
|
||||||
|
Ok(account.value.is_some()) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn tree(&self, lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle> { |
||||||
|
assert!( |
||||||
|
lag.is_none(), |
||||||
|
"Sealevel does not support querying point-in-time" |
||||||
|
); |
||||||
|
|
||||||
|
let outbox_account = self |
||||||
|
.rpc_client |
||||||
|
.get_account_with_commitment(&self.outbox.0, CommitmentConfig::finalized()) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)? |
||||||
|
.value |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str("Could not find account data") |
||||||
|
})?; |
||||||
|
let outbox = OutboxAccount::fetch(&mut outbox_account.data.as_ref()) |
||||||
|
.map_err(ChainCommunicationError::from_other)? |
||||||
|
.into_inner(); |
||||||
|
|
||||||
|
Ok(outbox.tree) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn latest_checkpoint(&self, lag: Option<NonZeroU64>) -> ChainResult<Checkpoint> { |
||||||
|
assert!( |
||||||
|
lag.is_none(), |
||||||
|
"Sealevel does not support querying point-in-time" |
||||||
|
); |
||||||
|
|
||||||
|
let tree = self.tree(lag).await?; |
||||||
|
|
||||||
|
let root = tree.root(); |
||||||
|
let count: u32 = tree |
||||||
|
.count() |
||||||
|
.try_into() |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
let index = count.checked_sub(1).ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_contract_error_str( |
||||||
|
"Outbox is empty, cannot compute checkpoint", |
||||||
|
) |
||||||
|
})?; |
||||||
|
let checkpoint = Checkpoint { |
||||||
|
mailbox_address: self.program_id.to_bytes().into(), |
||||||
|
mailbox_domain: self.domain.id(), |
||||||
|
root, |
||||||
|
index, |
||||||
|
}; |
||||||
|
Ok(checkpoint) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn default_ism(&self) -> ChainResult<H256> { |
||||||
|
let inbox_account = self |
||||||
|
.rpc_client |
||||||
|
.get_account(&self.inbox.0) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
let inbox = InboxAccount::fetch(&mut inbox_account.data.as_ref()) |
||||||
|
.map_err(ChainCommunicationError::from_other)? |
||||||
|
.into_inner(); |
||||||
|
|
||||||
|
Ok(inbox.default_ism.to_bytes().into()) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn recipient_ism(&self, recipient: H256) -> ChainResult<H256> { |
||||||
|
let recipient_program_id = Pubkey::new_from_array(recipient.0); |
||||||
|
|
||||||
|
// Get the account metas required for the recipient.InterchainSecurityModule instruction.
|
||||||
|
let ism_getter_account_metas = self |
||||||
|
.get_ism_getter_account_metas(recipient_program_id) |
||||||
|
.await?; |
||||||
|
|
||||||
|
// Get the ISM to use.
|
||||||
|
let ism_pubkey = self |
||||||
|
.get_recipient_ism(recipient_program_id, ism_getter_account_metas) |
||||||
|
.await?; |
||||||
|
|
||||||
|
Ok(ism_pubkey.to_bytes().into()) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn process( |
||||||
|
&self, |
||||||
|
message: &HyperlaneMessage, |
||||||
|
metadata: &[u8], |
||||||
|
_tx_gas_limit: Option<U256>, |
||||||
|
) -> ChainResult<TxOutcome> { |
||||||
|
let recipient: Pubkey = message.recipient.0.into(); |
||||||
|
let mut encoded_message = vec![]; |
||||||
|
message.write_to(&mut encoded_message).unwrap(); |
||||||
|
|
||||||
|
let payer = self |
||||||
|
.payer |
||||||
|
.as_ref() |
||||||
|
.ok_or_else(|| ChainCommunicationError::SignerUnavailable)?; |
||||||
|
|
||||||
|
let mut instructions = Vec::with_capacity(2); |
||||||
|
// Set the compute unit limit.
|
||||||
|
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( |
||||||
|
PROCESS_COMPUTE_UNITS, |
||||||
|
)); |
||||||
|
|
||||||
|
// "processed" level commitment does not guarantee finality.
|
||||||
|
// roughly 5% of blocks end up on a dropped fork.
|
||||||
|
// However we don't want this function to be a bottleneck and there already
|
||||||
|
// is retry logic in the agents.
|
||||||
|
let commitment = CommitmentConfig::processed(); |
||||||
|
|
||||||
|
let (process_authority_key, _process_authority_bump) = Pubkey::try_find_program_address( |
||||||
|
mailbox_process_authority_pda_seeds!(&recipient), |
||||||
|
&self.program_id, |
||||||
|
) |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str( |
||||||
|
"Could not find program address for process authority", |
||||||
|
) |
||||||
|
})?; |
||||||
|
let (processed_message_account_key, _processed_message_account_bump) = |
||||||
|
Pubkey::try_find_program_address( |
||||||
|
mailbox_processed_message_pda_seeds!(message.id()), |
||||||
|
&self.program_id, |
||||||
|
) |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str( |
||||||
|
"Could not find program address for processed message account", |
||||||
|
) |
||||||
|
})?; |
||||||
|
|
||||||
|
// Get the account metas required for the recipient.InterchainSecurityModule instruction.
|
||||||
|
let ism_getter_account_metas = self.get_ism_getter_account_metas(recipient).await?; |
||||||
|
|
||||||
|
// Get the recipient ISM.
|
||||||
|
let ism = self |
||||||
|
.get_recipient_ism(recipient, ism_getter_account_metas.clone()) |
||||||
|
.await?; |
||||||
|
|
||||||
|
let ixn = |
||||||
|
hyperlane_sealevel_mailbox::instruction::Instruction::InboxProcess(InboxProcess { |
||||||
|
metadata: metadata.to_vec(), |
||||||
|
message: encoded_message.clone(), |
||||||
|
}); |
||||||
|
let ixn_data = ixn |
||||||
|
.into_instruction_data() |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
|
||||||
|
// Craft the accounts for the transaction.
|
||||||
|
let mut accounts: Vec<AccountMeta> = vec![ |
||||||
|
AccountMeta::new_readonly(payer.pubkey(), true), |
||||||
|
AccountMeta::new_readonly(Pubkey::from_str(SYSTEM_PROGRAM).unwrap(), false), |
||||||
|
AccountMeta::new(self.inbox.0, false), |
||||||
|
AccountMeta::new_readonly(process_authority_key, false), |
||||||
|
AccountMeta::new(processed_message_account_key, false), |
||||||
|
]; |
||||||
|
accounts.extend(ism_getter_account_metas); |
||||||
|
accounts.extend([ |
||||||
|
AccountMeta::new_readonly(Pubkey::from_str(SPL_NOOP).unwrap(), false), |
||||||
|
AccountMeta::new_readonly(ism, false), |
||||||
|
]); |
||||||
|
|
||||||
|
// Get the account metas required for the ISM.Verify instruction.
|
||||||
|
let ism_verify_account_metas = self |
||||||
|
.get_ism_verify_account_metas(ism, metadata.into(), encoded_message) |
||||||
|
.await?; |
||||||
|
accounts.extend(ism_verify_account_metas); |
||||||
|
|
||||||
|
// The recipient.
|
||||||
|
accounts.extend([AccountMeta::new_readonly(recipient, false)]); |
||||||
|
|
||||||
|
// Get account metas required for the Handle instruction
|
||||||
|
let handle_account_metas = self.get_handle_account_metas(message).await?; |
||||||
|
accounts.extend(handle_account_metas); |
||||||
|
|
||||||
|
let inbox_instruction = Instruction { |
||||||
|
program_id: self.program_id, |
||||||
|
data: ixn_data, |
||||||
|
accounts, |
||||||
|
}; |
||||||
|
tracing::info!("accounts={:#?}", inbox_instruction.accounts); |
||||||
|
instructions.push(inbox_instruction); |
||||||
|
let (recent_blockhash, _) = self |
||||||
|
.rpc_client |
||||||
|
.get_latest_blockhash_with_commitment(commitment) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
let txn = Transaction::new_signed_with_payer( |
||||||
|
&instructions, |
||||||
|
Some(&payer.pubkey()), |
||||||
|
&[payer], |
||||||
|
recent_blockhash, |
||||||
|
); |
||||||
|
|
||||||
|
let signature = self |
||||||
|
.rpc_client |
||||||
|
.send_and_confirm_transaction(&txn) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
tracing::info!("signature={}", signature); |
||||||
|
tracing::info!("txn={:?}", txn); |
||||||
|
let executed = self |
||||||
|
.rpc_client |
||||||
|
.confirm_transaction_with_commitment(&signature, commitment) |
||||||
|
.await |
||||||
|
.map_err(|err| warn!("Failed to confirm inbox process transaction: {}", err)) |
||||||
|
.map(|ctx| ctx.value) |
||||||
|
.unwrap_or(false); |
||||||
|
let txid = signature_to_txn_hash(&signature); |
||||||
|
|
||||||
|
Ok(TxOutcome { |
||||||
|
txid, |
||||||
|
executed, |
||||||
|
// TODO use correct data upon integrating IGP support
|
||||||
|
gas_price: U256::zero(), |
||||||
|
gas_used: U256::zero(), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
#[instrument(err, ret, skip(self))] |
||||||
|
async fn process_estimate_costs( |
||||||
|
&self, |
||||||
|
_message: &HyperlaneMessage, |
||||||
|
_metadata: &[u8], |
||||||
|
) -> ChainResult<TxCostEstimate> { |
||||||
|
// TODO use correct data upon integrating IGP support
|
||||||
|
Ok(TxCostEstimate { |
||||||
|
gas_limit: U256::zero(), |
||||||
|
gas_price: U256::zero(), |
||||||
|
l2_gas_limit: None, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
fn process_calldata(&self, _message: &HyperlaneMessage, _metadata: &[u8]) -> Vec<u8> { |
||||||
|
todo!() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Struct that retrieves event data for a Sealevel Mailbox contract
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct SealevelMailboxIndexer { |
||||||
|
rpc_client: RpcClientWithDebug, |
||||||
|
mailbox: SealevelMailbox, |
||||||
|
program_id: Pubkey, |
||||||
|
} |
||||||
|
|
||||||
|
impl SealevelMailboxIndexer { |
||||||
|
pub fn new(conf: &ConnectionConf, locator: ContractLocator) -> ChainResult<Self> { |
||||||
|
let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); |
||||||
|
let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); |
||||||
|
let mailbox = SealevelMailbox::new(conf, locator, None)?; |
||||||
|
Ok(Self { |
||||||
|
program_id, |
||||||
|
rpc_client, |
||||||
|
mailbox, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||||
|
let height = self |
||||||
|
.rpc_client |
||||||
|
.get_block_height() |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)? |
||||||
|
.try_into() |
||||||
|
// FIXME solana block height is u64...
|
||||||
|
.expect("sealevel block height exceeds u32::MAX"); |
||||||
|
Ok(height) |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_message_with_nonce(&self, nonce: u32) -> ChainResult<(HyperlaneMessage, LogMeta)> { |
||||||
|
let target_message_account_bytes = &[ |
||||||
|
&hyperlane_sealevel_mailbox::accounts::DISPATCHED_MESSAGE_DISCRIMINATOR[..], |
||||||
|
&nonce.to_le_bytes()[..], |
||||||
|
] |
||||||
|
.concat(); |
||||||
|
let target_message_account_bytes = base64::encode(target_message_account_bytes); |
||||||
|
|
||||||
|
// First, find all accounts with the matching account data.
|
||||||
|
// To keep responses small in case there is ever more than 1
|
||||||
|
// match, we don't request the full account data, and just request
|
||||||
|
// the `unique_message_pubkey` field.
|
||||||
|
let memcmp = RpcFilterType::Memcmp(Memcmp { |
||||||
|
// Ignore the first byte, which is the `initialized` bool flag.
|
||||||
|
offset: 1, |
||||||
|
bytes: MemcmpEncodedBytes::Base64(target_message_account_bytes), |
||||||
|
encoding: None, |
||||||
|
}); |
||||||
|
let config = RpcProgramAccountsConfig { |
||||||
|
filters: Some(vec![memcmp]), |
||||||
|
account_config: RpcAccountInfoConfig { |
||||||
|
encoding: Some(UiAccountEncoding::Base64), |
||||||
|
// Don't return any data
|
||||||
|
data_slice: Some(UiDataSliceConfig { |
||||||
|
offset: 1 + 8 + 4 + 8, // the offset to get the `unique_message_pubkey` field
|
||||||
|
length: 32, // the length of the `unique_message_pubkey` field
|
||||||
|
}), |
||||||
|
commitment: Some(CommitmentConfig::finalized()), |
||||||
|
min_context_slot: None, |
||||||
|
}, |
||||||
|
with_context: Some(false), |
||||||
|
}; |
||||||
|
let accounts = self |
||||||
|
.rpc_client |
||||||
|
.get_program_accounts_with_config(&self.mailbox.program_id, config) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
|
||||||
|
// Now loop through matching accounts and find the one with a valid account pubkey
|
||||||
|
// that proves it's an actual message storage PDA.
|
||||||
|
let mut valid_message_storage_pda_pubkey = Option::<Pubkey>::None; |
||||||
|
|
||||||
|
for (pubkey, account) in accounts.iter() { |
||||||
|
let unique_message_pubkey = Pubkey::new(&account.data); |
||||||
|
let (expected_pubkey, _bump) = Pubkey::try_find_program_address( |
||||||
|
mailbox_dispatched_message_pda_seeds!(unique_message_pubkey), |
||||||
|
&self.mailbox.program_id, |
||||||
|
) |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str( |
||||||
|
"Could not find program address for unique_message_pubkey", |
||||||
|
) |
||||||
|
})?; |
||||||
|
if expected_pubkey == *pubkey { |
||||||
|
valid_message_storage_pda_pubkey = Some(*pubkey); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let valid_message_storage_pda_pubkey = |
||||||
|
valid_message_storage_pda_pubkey.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str( |
||||||
|
"Could not find valid message storage PDA pubkey", |
||||||
|
) |
||||||
|
})?; |
||||||
|
|
||||||
|
// Now that we have the valid message storage PDA pubkey, we can get the full account data.
|
||||||
|
let account = self |
||||||
|
.rpc_client |
||||||
|
.get_account_with_commitment( |
||||||
|
&valid_message_storage_pda_pubkey, |
||||||
|
CommitmentConfig::finalized(), |
||||||
|
) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)? |
||||||
|
.value |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str("Could not find account data") |
||||||
|
})?; |
||||||
|
let dispatched_message_account = |
||||||
|
DispatchedMessageAccount::fetch(&mut account.data.as_ref()) |
||||||
|
.map_err(ChainCommunicationError::from_other)? |
||||||
|
.into_inner(); |
||||||
|
let hyperlane_message = |
||||||
|
HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?; |
||||||
|
|
||||||
|
Ok(( |
||||||
|
hyperlane_message, |
||||||
|
LogMeta { |
||||||
|
address: self.mailbox.program_id.to_bytes().into(), |
||||||
|
block_number: dispatched_message_account.slot, |
||||||
|
// TODO: get these when building out scraper support.
|
||||||
|
// It's inconvenient to get these :|
|
||||||
|
block_hash: H256::zero(), |
||||||
|
transaction_hash: H256::zero(), |
||||||
|
transaction_index: 0, |
||||||
|
log_index: U256::zero(), |
||||||
|
}, |
||||||
|
)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl MessageIndexer for SealevelMailboxIndexer { |
||||||
|
#[instrument(err, skip(self))] |
||||||
|
async fn fetch_count_at_tip(&self) -> ChainResult<(u32, u32)> { |
||||||
|
let tip = Indexer::<HyperlaneMessage>::get_finalized_block_number(self as _).await?; |
||||||
|
// TODO: need to make sure the call and tip are at the same height?
|
||||||
|
let count = self.mailbox.count(None).await?; |
||||||
|
Ok((count, tip)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl Indexer<HyperlaneMessage> for SealevelMailboxIndexer { |
||||||
|
async fn fetch_logs(&self, range: IndexRange) -> ChainResult<Vec<(HyperlaneMessage, LogMeta)>> { |
||||||
|
let SequenceRange(range) = range else { |
||||||
|
return Err(ChainCommunicationError::from_other_str( |
||||||
|
"SealevelMailboxIndexer only supports sequence-based indexing", |
||||||
|
)) |
||||||
|
}; |
||||||
|
|
||||||
|
info!( |
||||||
|
?range, |
||||||
|
"Fetching SealevelMailboxIndexer HyperlaneMessage logs" |
||||||
|
); |
||||||
|
|
||||||
|
let mut messages = Vec::with_capacity((range.end() - range.start()) as usize); |
||||||
|
for nonce in range { |
||||||
|
messages.push(self.get_message_with_nonce(nonce).await?); |
||||||
|
} |
||||||
|
Ok(messages) |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||||
|
self.get_finalized_block_number().await |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl Indexer<H256> for SealevelMailboxIndexer { |
||||||
|
async fn fetch_logs(&self, _range: IndexRange) -> ChainResult<Vec<(H256, LogMeta)>> { |
||||||
|
todo!() |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||||
|
self.get_finalized_block_number().await |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct SealevelMailboxAbi; |
||||||
|
|
||||||
|
// TODO figure out how this is used and if we can support it for sealevel.
|
||||||
|
impl HyperlaneAbi for SealevelMailboxAbi { |
||||||
|
const SELECTOR_SIZE_BYTES: usize = 8; |
||||||
|
|
||||||
|
fn fn_map() -> HashMap<Vec<u8>, &'static str> { |
||||||
|
todo!() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,140 @@ |
|||||||
|
use async_trait::async_trait; |
||||||
|
|
||||||
|
use hyperlane_core::{ |
||||||
|
ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, |
||||||
|
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, MultisigIsm, RawHyperlaneMessage, H256, |
||||||
|
}; |
||||||
|
use solana_sdk::{ |
||||||
|
instruction::{AccountMeta, Instruction}, |
||||||
|
pubkey::Pubkey, |
||||||
|
signature::Keypair, |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
utils::{get_account_metas, simulate_instruction}, |
||||||
|
ConnectionConf, RpcClientWithDebug, SealevelProvider, |
||||||
|
}; |
||||||
|
|
||||||
|
use hyperlane_sealevel_multisig_ism_message_id::instruction::ValidatorsAndThreshold; |
||||||
|
use multisig_ism::interface::{ |
||||||
|
MultisigIsmInstruction, VALIDATORS_AND_THRESHOLD_ACCOUNT_METAS_PDA_SEEDS, |
||||||
|
}; |
||||||
|
|
||||||
|
/// A reference to a MultisigIsm contract on some Sealevel chain
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct SealevelMultisigIsm { |
||||||
|
rpc_client: RpcClientWithDebug, |
||||||
|
payer: Option<Keypair>, |
||||||
|
program_id: Pubkey, |
||||||
|
domain: HyperlaneDomain, |
||||||
|
} |
||||||
|
|
||||||
|
impl SealevelMultisigIsm { |
||||||
|
/// Create a new Sealevel MultisigIsm.
|
||||||
|
pub fn new(conf: &ConnectionConf, locator: ContractLocator, payer: Option<Keypair>) -> Self { |
||||||
|
let rpc_client = RpcClientWithDebug::new(conf.url.to_string()); |
||||||
|
let program_id = Pubkey::from(<[u8; 32]>::from(locator.address)); |
||||||
|
|
||||||
|
Self { |
||||||
|
rpc_client, |
||||||
|
payer, |
||||||
|
program_id, |
||||||
|
domain: locator.domain.clone(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneContract for SealevelMultisigIsm { |
||||||
|
fn address(&self) -> H256 { |
||||||
|
self.program_id.to_bytes().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneChain for SealevelMultisigIsm { |
||||||
|
fn domain(&self) -> &HyperlaneDomain { |
||||||
|
&self.domain |
||||||
|
} |
||||||
|
|
||||||
|
fn provider(&self) -> Box<dyn HyperlaneProvider> { |
||||||
|
Box::new(SealevelProvider::new(self.domain.clone())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl MultisigIsm for SealevelMultisigIsm { |
||||||
|
/// Returns the validator and threshold needed to verify message
|
||||||
|
async fn validators_and_threshold( |
||||||
|
&self, |
||||||
|
message: &HyperlaneMessage, |
||||||
|
) -> ChainResult<(Vec<H256>, u8)> { |
||||||
|
let message_bytes = RawHyperlaneMessage::from(message).to_vec(); |
||||||
|
|
||||||
|
let account_metas = self |
||||||
|
.get_validators_and_threshold_account_metas(message_bytes.clone()) |
||||||
|
.await?; |
||||||
|
|
||||||
|
let instruction = Instruction::new_with_bytes( |
||||||
|
self.program_id, |
||||||
|
&MultisigIsmInstruction::ValidatorsAndThreshold(message_bytes) |
||||||
|
.encode() |
||||||
|
.map_err(ChainCommunicationError::from_other)?[..], |
||||||
|
account_metas, |
||||||
|
); |
||||||
|
|
||||||
|
let validators_and_threshold = simulate_instruction::<ValidatorsAndThreshold>( |
||||||
|
&self.rpc_client, |
||||||
|
self.payer |
||||||
|
.as_ref() |
||||||
|
.ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, |
||||||
|
instruction, |
||||||
|
) |
||||||
|
.await? |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str( |
||||||
|
"No return data was returned from the multisig ism", |
||||||
|
) |
||||||
|
})?; |
||||||
|
|
||||||
|
let validators = validators_and_threshold |
||||||
|
.validators |
||||||
|
.into_iter() |
||||||
|
.map(|validator| validator.into()) |
||||||
|
.collect(); |
||||||
|
|
||||||
|
Ok((validators, validators_and_threshold.threshold)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl SealevelMultisigIsm { |
||||||
|
async fn get_validators_and_threshold_account_metas( |
||||||
|
&self, |
||||||
|
message_bytes: Vec<u8>, |
||||||
|
) -> ChainResult<Vec<AccountMeta>> { |
||||||
|
let (account_metas_pda_key, _account_metas_pda_bump) = Pubkey::try_find_program_address( |
||||||
|
VALIDATORS_AND_THRESHOLD_ACCOUNT_METAS_PDA_SEEDS, |
||||||
|
&self.program_id, |
||||||
|
) |
||||||
|
.ok_or_else(|| { |
||||||
|
ChainCommunicationError::from_other_str( |
||||||
|
"Could not find program address for domain data", |
||||||
|
) |
||||||
|
})?; |
||||||
|
|
||||||
|
let instruction = Instruction::new_with_bytes( |
||||||
|
self.program_id, |
||||||
|
&MultisigIsmInstruction::ValidatorsAndThresholdAccountMetas(message_bytes) |
||||||
|
.encode() |
||||||
|
.map_err(ChainCommunicationError::from_other)?[..], |
||||||
|
vec![AccountMeta::new_readonly(account_metas_pda_key, false)], |
||||||
|
); |
||||||
|
|
||||||
|
get_account_metas( |
||||||
|
&self.rpc_client, |
||||||
|
self.payer |
||||||
|
.as_ref() |
||||||
|
.ok_or_else(|| ChainCommunicationError::SignerUnavailable)?, |
||||||
|
instruction, |
||||||
|
) |
||||||
|
.await |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
use async_trait::async_trait; |
||||||
|
|
||||||
|
use hyperlane_core::{ |
||||||
|
BlockInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo, H256, |
||||||
|
}; |
||||||
|
|
||||||
|
/// A wrapper around a Sealevel provider to get generic blockchain information.
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct SealevelProvider { |
||||||
|
domain: HyperlaneDomain, |
||||||
|
} |
||||||
|
|
||||||
|
impl SealevelProvider { |
||||||
|
/// Create a new Sealevel provider.
|
||||||
|
pub fn new(domain: HyperlaneDomain) -> Self { |
||||||
|
SealevelProvider { domain } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl HyperlaneChain for SealevelProvider { |
||||||
|
fn domain(&self) -> &HyperlaneDomain { |
||||||
|
&self.domain |
||||||
|
} |
||||||
|
|
||||||
|
fn provider(&self) -> Box<dyn HyperlaneProvider> { |
||||||
|
Box::new(SealevelProvider { |
||||||
|
domain: self.domain.clone(), |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl HyperlaneProvider for SealevelProvider { |
||||||
|
async fn get_block_by_hash(&self, _hash: &H256) -> ChainResult<BlockInfo> { |
||||||
|
todo!() // FIXME
|
||||||
|
} |
||||||
|
|
||||||
|
async fn get_txn_by_hash(&self, _hash: &H256) -> ChainResult<TxnInfo> { |
||||||
|
todo!() // FIXME
|
||||||
|
} |
||||||
|
|
||||||
|
async fn is_contract(&self, _address: &H256) -> ChainResult<bool> { |
||||||
|
// FIXME
|
||||||
|
Ok(true) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
//! The [ed25519 native program][np].
|
||||||
|
//!
|
||||||
|
//! [np]: https://docs.solana.com/developing/runtime-facilities/programs#ed25519-program
|
||||||
|
use crate::solana::pubkey::Pubkey; |
||||||
|
use solana_sdk_macro::declare_id; |
||||||
|
|
||||||
|
declare_id!("Ed25519SigVerify111111111111111111111111111"); |
@ -0,0 +1,360 @@ |
|||||||
|
//! Calculation of transaction fees.
|
||||||
|
|
||||||
|
#![allow(clippy::integer_arithmetic)] |
||||||
|
|
||||||
|
use serde_derive::{Deserialize, Serialize}; |
||||||
|
|
||||||
|
use super::{ed25519_program, message::Message, secp256k1_program}; |
||||||
|
// use super::
|
||||||
|
use log::*; |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
pub struct FeeCalculator { |
||||||
|
/// The current cost of a signature.
|
||||||
|
///
|
||||||
|
/// This amount may increase/decrease over time based on cluster processing
|
||||||
|
/// load.
|
||||||
|
pub lamports_per_signature: u64, |
||||||
|
} |
||||||
|
|
||||||
|
impl FeeCalculator { |
||||||
|
pub fn new(lamports_per_signature: u64) -> Self { |
||||||
|
Self { |
||||||
|
lamports_per_signature, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[deprecated(
|
||||||
|
since = "1.9.0", |
||||||
|
note = "Please do not use, will no longer be available in the future" |
||||||
|
)] |
||||||
|
pub fn calculate_fee(&self, message: &Message) -> u64 { |
||||||
|
let mut num_signatures: u64 = 0; |
||||||
|
for instruction in &message.instructions { |
||||||
|
let program_index = instruction.program_id_index as usize; |
||||||
|
// Message may not be sanitized here
|
||||||
|
if program_index < message.account_keys.len() { |
||||||
|
let id = message.account_keys[program_index]; |
||||||
|
if (secp256k1_program::check_id(&id) || ed25519_program::check_id(&id)) |
||||||
|
&& !instruction.data.is_empty() |
||||||
|
{ |
||||||
|
num_signatures += instruction.data[0] as u64; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.lamports_per_signature |
||||||
|
* (u64::from(message.header.num_required_signatures) + num_signatures) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
pub struct FeeRateGovernor { |
||||||
|
// The current cost of a signature This amount may increase/decrease over time based on
|
||||||
|
// cluster processing load.
|
||||||
|
#[serde(skip)] |
||||||
|
pub lamports_per_signature: u64, |
||||||
|
|
||||||
|
// The target cost of a signature when the cluster is operating around target_signatures_per_slot
|
||||||
|
// signatures
|
||||||
|
pub target_lamports_per_signature: u64, |
||||||
|
|
||||||
|
// Used to estimate the desired processing capacity of the cluster. As the signatures for
|
||||||
|
// recent slots are fewer/greater than this value, lamports_per_signature will decrease/increase
|
||||||
|
// for the next slot. A value of 0 disables lamports_per_signature fee adjustments
|
||||||
|
pub target_signatures_per_slot: u64, |
||||||
|
|
||||||
|
pub min_lamports_per_signature: u64, |
||||||
|
pub max_lamports_per_signature: u64, |
||||||
|
|
||||||
|
// What portion of collected fees are to be destroyed, as a fraction of std::u8::MAX
|
||||||
|
pub burn_percent: u8, |
||||||
|
} |
||||||
|
|
||||||
|
pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000; |
||||||
|
pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT; |
||||||
|
|
||||||
|
// Percentage of tx fees to burn
|
||||||
|
pub const DEFAULT_BURN_PERCENT: u8 = 50; |
||||||
|
|
||||||
|
impl Default for FeeRateGovernor { |
||||||
|
fn default() -> Self { |
||||||
|
Self { |
||||||
|
lamports_per_signature: 0, |
||||||
|
target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE, |
||||||
|
target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT, |
||||||
|
min_lamports_per_signature: 0, |
||||||
|
max_lamports_per_signature: 0, |
||||||
|
burn_percent: DEFAULT_BURN_PERCENT, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl FeeRateGovernor { |
||||||
|
pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self { |
||||||
|
let base_fee_rate_governor = Self { |
||||||
|
target_lamports_per_signature, |
||||||
|
lamports_per_signature: target_lamports_per_signature, |
||||||
|
target_signatures_per_slot, |
||||||
|
..FeeRateGovernor::default() |
||||||
|
}; |
||||||
|
|
||||||
|
Self::new_derived(&base_fee_rate_governor, 0) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn new_derived( |
||||||
|
base_fee_rate_governor: &FeeRateGovernor, |
||||||
|
latest_signatures_per_slot: u64, |
||||||
|
) -> Self { |
||||||
|
let mut me = base_fee_rate_governor.clone(); |
||||||
|
|
||||||
|
if me.target_signatures_per_slot > 0 { |
||||||
|
// lamports_per_signature can range from 50% to 1000% of
|
||||||
|
// target_lamports_per_signature
|
||||||
|
me.min_lamports_per_signature = std::cmp::max(1, me.target_lamports_per_signature / 2); |
||||||
|
me.max_lamports_per_signature = me.target_lamports_per_signature * 10; |
||||||
|
|
||||||
|
// What the cluster should charge at `latest_signatures_per_slot`
|
||||||
|
let desired_lamports_per_signature = |
||||||
|
me.max_lamports_per_signature |
||||||
|
.min(me.min_lamports_per_signature.max( |
||||||
|
me.target_lamports_per_signature |
||||||
|
* std::cmp::min(latest_signatures_per_slot, std::u32::MAX as u64) |
||||||
|
as u64 |
||||||
|
/ me.target_signatures_per_slot as u64, |
||||||
|
)); |
||||||
|
|
||||||
|
trace!( |
||||||
|
"desired_lamports_per_signature: {}", |
||||||
|
desired_lamports_per_signature |
||||||
|
); |
||||||
|
|
||||||
|
let gap = desired_lamports_per_signature as i64 |
||||||
|
- base_fee_rate_governor.lamports_per_signature as i64; |
||||||
|
|
||||||
|
if gap == 0 { |
||||||
|
me.lamports_per_signature = desired_lamports_per_signature; |
||||||
|
} else { |
||||||
|
// Adjust fee by 5% of target_lamports_per_signature to produce a smooth
|
||||||
|
// increase/decrease in fees over time.
|
||||||
|
let gap_adjust = |
||||||
|
std::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum(); |
||||||
|
|
||||||
|
trace!( |
||||||
|
"lamports_per_signature gap is {}, adjusting by {}", |
||||||
|
gap, |
||||||
|
gap_adjust |
||||||
|
); |
||||||
|
|
||||||
|
me.lamports_per_signature = |
||||||
|
me.max_lamports_per_signature |
||||||
|
.min(me.min_lamports_per_signature.max( |
||||||
|
(base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust) |
||||||
|
as u64, |
||||||
|
)); |
||||||
|
} |
||||||
|
} else { |
||||||
|
me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature; |
||||||
|
me.min_lamports_per_signature = me.target_lamports_per_signature; |
||||||
|
me.max_lamports_per_signature = me.target_lamports_per_signature; |
||||||
|
} |
||||||
|
debug!( |
||||||
|
"new_derived(): lamports_per_signature: {}", |
||||||
|
me.lamports_per_signature |
||||||
|
); |
||||||
|
me |
||||||
|
} |
||||||
|
|
||||||
|
pub fn clone_with_lamports_per_signature(&self, lamports_per_signature: u64) -> Self { |
||||||
|
Self { |
||||||
|
lamports_per_signature, |
||||||
|
..*self |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// calculate unburned fee from a fee total, returns (unburned, burned)
|
||||||
|
pub fn burn(&self, fees: u64) -> (u64, u64) { |
||||||
|
let burned = fees * u64::from(self.burn_percent) / 100; |
||||||
|
(fees - burned, burned) |
||||||
|
} |
||||||
|
|
||||||
|
/// create a FeeCalculator based on current cluster signature throughput
|
||||||
|
pub fn create_fee_calculator(&self) -> FeeCalculator { |
||||||
|
FeeCalculator::new(self.lamports_per_signature) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use { |
||||||
|
super::*, |
||||||
|
crate::{pubkey::Pubkey, system_instruction}, |
||||||
|
}; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_fee_rate_governor_burn() { |
||||||
|
let mut fee_rate_governor = FeeRateGovernor::default(); |
||||||
|
assert_eq!(fee_rate_governor.burn(2), (1, 1)); |
||||||
|
|
||||||
|
fee_rate_governor.burn_percent = 0; |
||||||
|
assert_eq!(fee_rate_governor.burn(2), (2, 0)); |
||||||
|
|
||||||
|
fee_rate_governor.burn_percent = 100; |
||||||
|
assert_eq!(fee_rate_governor.burn(2), (0, 2)); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
#[allow(deprecated)] |
||||||
|
fn test_fee_calculator_calculate_fee() { |
||||||
|
// Default: no fee.
|
||||||
|
let message = Message::default(); |
||||||
|
assert_eq!(FeeCalculator::default().calculate_fee(&message), 0); |
||||||
|
|
||||||
|
// No signature, no fee.
|
||||||
|
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0); |
||||||
|
|
||||||
|
// One signature, a fee.
|
||||||
|
let pubkey0 = Pubkey::new(&[0; 32]); |
||||||
|
let pubkey1 = Pubkey::new(&[1; 32]); |
||||||
|
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); |
||||||
|
let message = Message::new(&[ix0], Some(&pubkey0)); |
||||||
|
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2); |
||||||
|
|
||||||
|
// Two signatures, double the fee.
|
||||||
|
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); |
||||||
|
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1); |
||||||
|
let message = Message::new(&[ix0, ix1], Some(&pubkey0)); |
||||||
|
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
#[allow(deprecated)] |
||||||
|
fn test_fee_calculator_calculate_fee_secp256k1() { |
||||||
|
use crate::instruction::Instruction; |
||||||
|
let pubkey0 = Pubkey::new(&[0; 32]); |
||||||
|
let pubkey1 = Pubkey::new(&[1; 32]); |
||||||
|
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); |
||||||
|
let mut secp_instruction = Instruction { |
||||||
|
program_id: crate::secp256k1_program::id(), |
||||||
|
accounts: vec![], |
||||||
|
data: vec![], |
||||||
|
}; |
||||||
|
let mut secp_instruction2 = Instruction { |
||||||
|
program_id: crate::secp256k1_program::id(), |
||||||
|
accounts: vec![], |
||||||
|
data: vec![1], |
||||||
|
}; |
||||||
|
|
||||||
|
let message = Message::new( |
||||||
|
&[ |
||||||
|
ix0.clone(), |
||||||
|
secp_instruction.clone(), |
||||||
|
secp_instruction2.clone(), |
||||||
|
], |
||||||
|
Some(&pubkey0), |
||||||
|
); |
||||||
|
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2); |
||||||
|
|
||||||
|
secp_instruction.data = vec![0]; |
||||||
|
secp_instruction2.data = vec![10]; |
||||||
|
let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0)); |
||||||
|
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_fee_rate_governor_derived_default() { |
||||||
|
solana_logger::setup(); |
||||||
|
|
||||||
|
let f0 = FeeRateGovernor::default(); |
||||||
|
assert_eq!( |
||||||
|
f0.target_signatures_per_slot, |
||||||
|
DEFAULT_TARGET_SIGNATURES_PER_SLOT |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
f0.target_lamports_per_signature, |
||||||
|
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE |
||||||
|
); |
||||||
|
assert_eq!(f0.lamports_per_signature, 0); |
||||||
|
|
||||||
|
let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT); |
||||||
|
assert_eq!( |
||||||
|
f1.target_signatures_per_slot, |
||||||
|
DEFAULT_TARGET_SIGNATURES_PER_SLOT |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
f1.target_lamports_per_signature, |
||||||
|
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
f1.lamports_per_signature, |
||||||
|
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2 |
||||||
|
); // min
|
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_fee_rate_governor_derived_adjust() { |
||||||
|
solana_logger::setup(); |
||||||
|
|
||||||
|
let mut f = FeeRateGovernor { |
||||||
|
target_lamports_per_signature: 100, |
||||||
|
target_signatures_per_slot: 100, |
||||||
|
..FeeRateGovernor::default() |
||||||
|
}; |
||||||
|
f = FeeRateGovernor::new_derived(&f, 0); |
||||||
|
|
||||||
|
// Ramp fees up
|
||||||
|
let mut count = 0; |
||||||
|
loop { |
||||||
|
let last_lamports_per_signature = f.lamports_per_signature; |
||||||
|
|
||||||
|
f = FeeRateGovernor::new_derived(&f, std::u64::MAX); |
||||||
|
info!("[up] f.lamports_per_signature={}", f.lamports_per_signature); |
||||||
|
|
||||||
|
// some maximum target reached
|
||||||
|
if f.lamports_per_signature == last_lamports_per_signature { |
||||||
|
break; |
||||||
|
} |
||||||
|
// shouldn't take more than 1000 steps to get to minimum
|
||||||
|
assert!(count < 1000); |
||||||
|
count += 1; |
||||||
|
} |
||||||
|
|
||||||
|
// Ramp fees down
|
||||||
|
let mut count = 0; |
||||||
|
loop { |
||||||
|
let last_lamports_per_signature = f.lamports_per_signature; |
||||||
|
f = FeeRateGovernor::new_derived(&f, 0); |
||||||
|
|
||||||
|
info!( |
||||||
|
"[down] f.lamports_per_signature={}", |
||||||
|
f.lamports_per_signature |
||||||
|
); |
||||||
|
|
||||||
|
// some minimum target reached
|
||||||
|
if f.lamports_per_signature == last_lamports_per_signature { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// shouldn't take more than 1000 steps to get to minimum
|
||||||
|
assert!(count < 1000); |
||||||
|
count += 1; |
||||||
|
} |
||||||
|
|
||||||
|
// Arrive at target rate
|
||||||
|
let mut count = 0; |
||||||
|
while f.lamports_per_signature != f.target_lamports_per_signature { |
||||||
|
f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot); |
||||||
|
info!( |
||||||
|
"[target] f.lamports_per_signature={}", |
||||||
|
f.lamports_per_signature |
||||||
|
); |
||||||
|
// shouldn't take more than 100 steps to get to target
|
||||||
|
assert!(count < 100); |
||||||
|
count += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
*/ |
@ -0,0 +1,101 @@ |
|||||||
|
[package] |
||||||
|
name = "solana-sdk" |
||||||
|
version = "1.14.13" |
||||||
|
description = "Solana SDK" |
||||||
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"] |
||||||
|
repository = "https://github.com/solana-labs/solana" |
||||||
|
homepage = "https://solana.com/" |
||||||
|
documentation = "https://docs.rs/solana-sdk" |
||||||
|
readme = "README.md" |
||||||
|
license = "Apache-2.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
[features] |
||||||
|
# "program" feature is a legacy feature retained to support v1.3 and older |
||||||
|
# programs. New development should not use this feature. Instead use the |
||||||
|
# solana-program crate |
||||||
|
program = [] |
||||||
|
|
||||||
|
default = [ |
||||||
|
"full" # functionality that is not compatible or needed for on-chain programs |
||||||
|
] |
||||||
|
full = [ |
||||||
|
"assert_matches", |
||||||
|
"byteorder", |
||||||
|
"chrono", |
||||||
|
"generic-array", |
||||||
|
"memmap2", |
||||||
|
"rand", |
||||||
|
"rand_chacha", |
||||||
|
"serde_json", |
||||||
|
# "ed25519-dalek", |
||||||
|
"ed25519-dalek-bip32", |
||||||
|
# "solana-logger", |
||||||
|
"libsecp256k1", |
||||||
|
"sha3", |
||||||
|
"digest", |
||||||
|
] |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
assert_matches = { version = "1.5.0", optional = true } |
||||||
|
base64 = "0.13" |
||||||
|
bincode = "1.3.3" |
||||||
|
bitflags = "1.3.1" |
||||||
|
borsh = "0.9.3" |
||||||
|
bs58 = "0.4.0" |
||||||
|
bytemuck = { version = "1.11.0", features = ["derive"] } |
||||||
|
byteorder = { version = "1.4.3", optional = true } |
||||||
|
chrono = { default-features = false, features = ["alloc"], version = "0.4", optional = true } |
||||||
|
derivation-path = { version = "0.2.0", default-features = false } |
||||||
|
digest = { version = "0.10.1", optional = true } |
||||||
|
ed25519-dalek-bip32 = { version = "0.2.0", optional = true } |
||||||
|
ed25519-dalek = { version = "=1.0.1", git = "https://github.com/Eclipse-Laboratories-Inc/ed25519-dalek", branch = "steven/fix-deps" } |
||||||
|
generic-array = { version = "0.14.5", default-features = false, features = ["serde", "more_lengths"], optional = true } |
||||||
|
hmac = "0.12.1" |
||||||
|
itertools = "0.10.3" |
||||||
|
lazy_static = "1.4.0" |
||||||
|
libsecp256k1 = { version = "0.6.0", optional = true } |
||||||
|
log = "0.4.17" |
||||||
|
memmap2 = { version = "0.5.3", optional = true } |
||||||
|
num-derive = "0.3" |
||||||
|
num-traits = "0.2" |
||||||
|
pbkdf2 = { version = "0.11.0", default-features = false } |
||||||
|
qstring = "0.7.2" |
||||||
|
rand = { version = "0.7.0", optional = true } |
||||||
|
rand_chacha = { version = "0.2.2", optional = true } |
||||||
|
rustversion = "1.0.7" |
||||||
|
serde = "1.0.138" |
||||||
|
serde_bytes = "0.11" |
||||||
|
serde_derive = "1.0.103" |
||||||
|
serde_json = { version = "1.0.81", optional = true } |
||||||
|
sha2 = "0.10.2" |
||||||
|
sha3 = { version = "0.10.1", optional = true } |
||||||
|
# solana-frozen-abi = { path = "../frozen-abi", version = "=1.14.13" } |
||||||
|
# solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.14.13" } |
||||||
|
# solana-logger = { path = "../logger", version = "=1.14.13", optional = true } |
||||||
|
# solana-program = { path = "program", version = "=1.14.13" } |
||||||
|
solana-sdk-macro = { path = "macro", version = "=1.14.13" } |
||||||
|
thiserror = "1.0" |
||||||
|
uriparse = "0.6.4" |
||||||
|
wasm-bindgen = "0.2" |
||||||
|
|
||||||
|
[dependencies.curve25519-dalek] |
||||||
|
version = "3.2.1" |
||||||
|
features = ["serde"] |
||||||
|
git = "https://github.com/Eclipse-Laboratories-Inc/curve25519-dalek" |
||||||
|
branch = "steven/fix-deps" |
||||||
|
|
||||||
|
[dev-dependencies] |
||||||
|
anyhow = "1.0.58" |
||||||
|
hex = "0.4.3" |
||||||
|
static_assertions = "1.1.0" |
||||||
|
tiny-bip39 = "0.8.2" |
||||||
|
|
||||||
|
[build-dependencies] |
||||||
|
rustc_version = "0.4" |
||||||
|
|
||||||
|
[package.metadata.docs.rs] |
||||||
|
targets = ["x86_64-unknown-linux-gnu"] |
||||||
|
|
||||||
|
[lib] |
||||||
|
crate-type = ["cdylib", "rlib"] |
@ -0,0 +1,23 @@ |
|||||||
|
[package] |
||||||
|
name = "solana-sdk-macro" |
||||||
|
version = "1.14.13" |
||||||
|
description = "Solana SDK Macro" |
||||||
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"] |
||||||
|
repository = "https://github.com/solana-labs/solana" |
||||||
|
homepage = "https://solana.com/" |
||||||
|
documentation = "https://docs.rs/solana-sdk-macro" |
||||||
|
license = "Apache-2.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
[lib] |
||||||
|
proc-macro = true |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
bs58 = "0.4.0" |
||||||
|
proc-macro2 = "1.0.19" |
||||||
|
quote = "1.0" |
||||||
|
syn = { version = "1.0", features = ["full", "extra-traits"] } |
||||||
|
rustversion = "1.0.7" |
||||||
|
|
||||||
|
[package.metadata.docs.rs] |
||||||
|
targets = ["x86_64-unknown-linux-gnu"] |
@ -0,0 +1,405 @@ |
|||||||
|
//! Convenience macro to declare a static public key and functions to interact with it
|
||||||
|
//!
|
||||||
|
//! Input: a single literal base58 string representation of a program's id
|
||||||
|
extern crate proc_macro; |
||||||
|
|
||||||
|
use proc_macro::TokenStream; |
||||||
|
use proc_macro2::{Delimiter, Span, TokenTree}; |
||||||
|
use quote::{quote, ToTokens}; |
||||||
|
use syn::parse::{Parse, ParseStream, Result}; |
||||||
|
use syn::{parse_macro_input, Expr, LitByte, LitStr}; |
||||||
|
|
||||||
|
fn parse_id( |
||||||
|
input: ParseStream, |
||||||
|
pubkey_type: proc_macro2::TokenStream, |
||||||
|
) -> Result<proc_macro2::TokenStream> { |
||||||
|
let id = if input.peek(syn::LitStr) { |
||||||
|
let id_literal: LitStr = input.parse()?; |
||||||
|
parse_pubkey(&id_literal, &pubkey_type)? |
||||||
|
} else { |
||||||
|
let expr: Expr = input.parse()?; |
||||||
|
quote! { #expr } |
||||||
|
}; |
||||||
|
|
||||||
|
if !input.is_empty() { |
||||||
|
let stream: proc_macro2::TokenStream = input.parse()?; |
||||||
|
return Err(syn::Error::new_spanned(stream, "unexpected token")); |
||||||
|
} |
||||||
|
Ok(id) |
||||||
|
} |
||||||
|
|
||||||
|
fn id_to_tokens( |
||||||
|
id: &proc_macro2::TokenStream, |
||||||
|
pubkey_type: proc_macro2::TokenStream, |
||||||
|
tokens: &mut proc_macro2::TokenStream, |
||||||
|
) { |
||||||
|
tokens.extend(quote! { |
||||||
|
/// The static program ID
|
||||||
|
pub static ID: #pubkey_type = #id; |
||||||
|
|
||||||
|
/// Confirms that a given pubkey is equivalent to the program ID
|
||||||
|
pub fn check_id(id: &#pubkey_type) -> bool { |
||||||
|
id == &ID |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the program ID
|
||||||
|
pub fn id() -> #pubkey_type { |
||||||
|
ID |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
#[test] |
||||||
|
fn test_id() { |
||||||
|
assert!(check_id(&id())); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
fn deprecated_id_to_tokens( |
||||||
|
id: &proc_macro2::TokenStream, |
||||||
|
pubkey_type: proc_macro2::TokenStream, |
||||||
|
tokens: &mut proc_macro2::TokenStream, |
||||||
|
) { |
||||||
|
tokens.extend(quote! { |
||||||
|
/// The static program ID
|
||||||
|
pub static ID: #pubkey_type = #id; |
||||||
|
|
||||||
|
/// Confirms that a given pubkey is equivalent to the program ID
|
||||||
|
#[deprecated()] |
||||||
|
pub fn check_id(id: &#pubkey_type) -> bool { |
||||||
|
id == &ID |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the program ID
|
||||||
|
#[deprecated()] |
||||||
|
pub fn id() -> #pubkey_type { |
||||||
|
ID |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
#[test] |
||||||
|
fn test_id() { |
||||||
|
#[allow(deprecated)] |
||||||
|
assert!(check_id(&id())); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
struct SdkPubkey(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
impl Parse for SdkPubkey { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for SdkPubkey { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
let id = &self.0; |
||||||
|
tokens.extend(quote! {#id}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct ProgramSdkPubkey(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
impl Parse for ProgramSdkPubkey { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for ProgramSdkPubkey { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
let id = &self.0; |
||||||
|
tokens.extend(quote! {#id}) |
||||||
|
} |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
struct Id(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
impl Parse for Id { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for Id { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
id_to_tokens(&self.0, quote! { Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
struct IdDeprecated(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
impl Parse for IdDeprecated { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for IdDeprecated { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
deprecated_id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct ProgramSdkId(proc_macro2::TokenStream); |
||||||
|
impl Parse for ProgramSdkId { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for ProgramSdkId { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct ProgramSdkIdDeprecated(proc_macro2::TokenStream); |
||||||
|
impl Parse for ProgramSdkIdDeprecated { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for ProgramSdkIdDeprecated { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
deprecated_id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[allow(dead_code)] // `respan` may be compiled out
|
||||||
|
struct RespanInput { |
||||||
|
to_respan: Path, |
||||||
|
respan_using: Span, |
||||||
|
} |
||||||
|
|
||||||
|
impl Parse for RespanInput { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
let to_respan: Path = input.parse()?; |
||||||
|
let _comma: Token![,] = input.parse()?; |
||||||
|
let respan_tree: TokenTree = input.parse()?; |
||||||
|
match respan_tree { |
||||||
|
TokenTree::Group(g) if g.delimiter() == Delimiter::None => { |
||||||
|
let ident: Ident = syn::parse2(g.stream())?; |
||||||
|
Ok(RespanInput { |
||||||
|
to_respan, |
||||||
|
respan_using: ident.span(), |
||||||
|
}) |
||||||
|
} |
||||||
|
TokenTree::Ident(i) => Ok(RespanInput { |
||||||
|
to_respan, |
||||||
|
respan_using: i.span(), |
||||||
|
}), |
||||||
|
val => Err(syn::Error::new_spanned( |
||||||
|
val, |
||||||
|
"expected None-delimited group", |
||||||
|
)), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A proc-macro which respans the tokens in its first argument (a `Path`)
|
||||||
|
/// to be resolved at the tokens of its second argument.
|
||||||
|
/// For internal use only.
|
||||||
|
///
|
||||||
|
/// There must be exactly one comma in the input,
|
||||||
|
/// which is used to separate the two arguments.
|
||||||
|
/// The second argument should be exactly one token.
|
||||||
|
///
|
||||||
|
/// For example, `respan!($crate::foo, with_span)`
|
||||||
|
/// produces the tokens `$crate::foo`, but resolved
|
||||||
|
/// at the span of `with_span`.
|
||||||
|
///
|
||||||
|
/// The input to this function should be very short -
|
||||||
|
/// its only purpose is to override the span of a token
|
||||||
|
/// sequence containing `$crate`. For all other purposes,
|
||||||
|
/// a more general proc-macro should be used.
|
||||||
|
#[rustversion::since(1.46.0)] // `Span::resolved_at` is stable in 1.46.0 and above
|
||||||
|
#[proc_macro] |
||||||
|
pub fn respan(input: TokenStream) -> TokenStream { |
||||||
|
// Obtain the `Path` we are going to respan, and the ident
|
||||||
|
// whose span we will be using.
|
||||||
|
let RespanInput { |
||||||
|
to_respan, |
||||||
|
respan_using, |
||||||
|
} = parse_macro_input!(input as RespanInput); |
||||||
|
// Respan all of the tokens in the `Path`
|
||||||
|
let to_respan: proc_macro2::TokenStream = to_respan |
||||||
|
.into_token_stream() |
||||||
|
.into_iter() |
||||||
|
.map(|mut t| { |
||||||
|
// Combine the location of the token with the resolution behavior of `respan_using`
|
||||||
|
let new_span: Span = t.span().resolved_at(respan_using); |
||||||
|
t.set_span(new_span); |
||||||
|
t |
||||||
|
}) |
||||||
|
.collect(); |
||||||
|
TokenStream::from(to_respan) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn pubkey(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as SdkPubkey); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn program_pubkey(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as ProgramSdkPubkey); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn declare_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as Id); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
#[proc_macro] |
||||||
|
pub fn declare_deprecated_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as IdDeprecated); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn program_declare_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as ProgramSdkId); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as ProgramSdkIdDeprecated); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
fn parse_pubkey( |
||||||
|
id_literal: &LitStr, |
||||||
|
pubkey_type: &proc_macro2::TokenStream, |
||||||
|
) -> Result<proc_macro2::TokenStream> { |
||||||
|
let id_vec = bs58::decode(id_literal.value()) |
||||||
|
.into_vec() |
||||||
|
.map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?; |
||||||
|
let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| { |
||||||
|
syn::Error::new_spanned( |
||||||
|
id_literal, |
||||||
|
format!("pubkey array is not 32 bytes long: len={}", id_vec.len()), |
||||||
|
) |
||||||
|
})?; |
||||||
|
let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site())); |
||||||
|
Ok(quote! { |
||||||
|
#pubkey_type::new_from_array( |
||||||
|
[#(#bytes,)*] |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
struct Pubkeys { |
||||||
|
method: Ident, |
||||||
|
num: usize, |
||||||
|
pubkeys: proc_macro2::TokenStream, |
||||||
|
} |
||||||
|
impl Parse for Pubkeys { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
let pubkey_type = quote! { |
||||||
|
::solana_sdk::pubkey::Pubkey |
||||||
|
}; |
||||||
|
|
||||||
|
let method = input.parse()?; |
||||||
|
let _comma: Token![,] = input.parse()?; |
||||||
|
let (num, pubkeys) = if input.peek(syn::LitStr) { |
||||||
|
let id_literal: LitStr = input.parse()?; |
||||||
|
(1, parse_pubkey(&id_literal, &pubkey_type)?) |
||||||
|
} else if input.peek(Bracket) { |
||||||
|
let pubkey_strings; |
||||||
|
bracketed!(pubkey_strings in input); |
||||||
|
let punctuated: Punctuated<LitStr, Token![,]> = |
||||||
|
Punctuated::parse_terminated(&pubkey_strings)?; |
||||||
|
let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new(); |
||||||
|
for string in punctuated.iter() { |
||||||
|
pubkeys.push(parse_pubkey(string, &pubkey_type)?); |
||||||
|
} |
||||||
|
(pubkeys.len(), quote! {#pubkeys}) |
||||||
|
} else { |
||||||
|
let stream: proc_macro2::TokenStream = input.parse()?; |
||||||
|
return Err(syn::Error::new_spanned(stream, "unexpected token")); |
||||||
|
}; |
||||||
|
|
||||||
|
Ok(Pubkeys { |
||||||
|
method, |
||||||
|
num, |
||||||
|
pubkeys, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for Pubkeys { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
let Pubkeys { |
||||||
|
method, |
||||||
|
num, |
||||||
|
pubkeys, |
||||||
|
} = self; |
||||||
|
|
||||||
|
let pubkey_type = quote! { |
||||||
|
::solana_sdk::pubkey::Pubkey |
||||||
|
}; |
||||||
|
if *num == 1 { |
||||||
|
tokens.extend(quote! { |
||||||
|
pub fn #method() -> #pubkey_type { |
||||||
|
#pubkeys |
||||||
|
} |
||||||
|
}); |
||||||
|
} else { |
||||||
|
tokens.extend(quote! { |
||||||
|
pub fn #method() -> ::std::vec::Vec<#pubkey_type> { |
||||||
|
vec![#pubkeys] |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn pubkeys(input: TokenStream) -> TokenStream { |
||||||
|
let pubkeys = parse_macro_input!(input as Pubkeys); |
||||||
|
TokenStream::from(quote! {#pubkeys}) |
||||||
|
} |
||||||
|
|
||||||
|
// The normal `wasm_bindgen` macro generates a .bss section which causes the resulting
|
||||||
|
// BPF program to fail to load, so for now this stub should be used when building for BPF
|
||||||
|
#[proc_macro_attribute] |
||||||
|
pub fn wasm_bindgen_stub(_attr: TokenStream, item: TokenStream) -> TokenStream { |
||||||
|
match parse_macro_input!(item as syn::Item) { |
||||||
|
syn::Item::Struct(mut item_struct) => { |
||||||
|
if let syn::Fields::Named(fields) = &mut item_struct.fields { |
||||||
|
// Strip out any `#[wasm_bindgen]` added to struct fields. This is custom
|
||||||
|
// syntax supplied by the normal `wasm_bindgen` macro.
|
||||||
|
for field in fields.named.iter_mut() { |
||||||
|
field.attrs.retain(|attr| { |
||||||
|
!attr |
||||||
|
.path |
||||||
|
.segments |
||||||
|
.iter() |
||||||
|
.any(|segment| segment.ident == "wasm_bindgen") |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
quote! { #item_struct } |
||||||
|
} |
||||||
|
item => { |
||||||
|
quote!(#item) |
||||||
|
} |
||||||
|
} |
||||||
|
.into() |
||||||
|
} |
||||||
|
*/ |
@ -0,0 +1 @@ |
|||||||
|
pub use solana_sdk_macro::declare_id; |
@ -0,0 +1,12 @@ |
|||||||
|
//! The [secp256k1 native program][np].
|
||||||
|
//!
|
||||||
|
//! [np]: https://docs.solana.com/developing/runtime-facilities/programs#secp256k1-program
|
||||||
|
//!
|
||||||
|
//! Constructors for secp256k1 program instructions, and documentation on the
|
||||||
|
//! program's usage can be found in [`solana_sdk::secp256k1_instruction`].
|
||||||
|
//!
|
||||||
|
//! [`solana_sdk::secp256k1_instruction`]: https://docs.rs/solana-sdk/latest/solana_sdk/secp256k1_instruction/index.html
|
||||||
|
use crate::solana::pubkey::Pubkey; |
||||||
|
use solana_sdk_macro::declare_id; |
||||||
|
|
||||||
|
declare_id!("KeccakSecp256k11111111111111111111111111111"); |
@ -0,0 +1 @@ |
|||||||
|
pub use solana_sdk_macro::declare_id; |
@ -0,0 +1,423 @@ |
|||||||
|
//! Convenience macro to declare a static public key and functions to interact with it
|
||||||
|
//!
|
||||||
|
//! Input: a single literal base58 string representation of a program's id
|
||||||
|
extern crate proc_macro; |
||||||
|
|
||||||
|
use proc_macro::{TokenStream}; |
||||||
|
use syn::{parse_macro_input, LitStr, Expr, LitByte}; |
||||||
|
use quote::{quote, ToTokens}; |
||||||
|
use syn::parse::{Parse, ParseStream, Result}; |
||||||
|
use proc_macro2::{Delimiter, Span, TokenTree}; |
||||||
|
|
||||||
|
|
||||||
|
use { |
||||||
|
// proc_macro::TokenStream,
|
||||||
|
// proc_macro2::{Delimiter, Span, TokenTree},
|
||||||
|
// quote::{quote, ToTokens},
|
||||||
|
// std::convert::TryFrom,
|
||||||
|
// syn::{
|
||||||
|
// bracketed,
|
||||||
|
// parse::{Parse, ParseStream, Result},
|
||||||
|
// parse_macro_input,
|
||||||
|
// punctuated::Punctuated,
|
||||||
|
// token::Bracket,
|
||||||
|
// Expr, Ident, LitByte, LitStr, Path, Token,
|
||||||
|
// },
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
fn parse_id( |
||||||
|
input: ParseStream, |
||||||
|
pubkey_type: proc_macro2::TokenStream, |
||||||
|
) -> Result<proc_macro2::TokenStream> { |
||||||
|
let id = if input.peek(syn::LitStr) { |
||||||
|
let id_literal: LitStr = input.parse()?; |
||||||
|
parse_pubkey(&id_literal, &pubkey_type)? |
||||||
|
} else { |
||||||
|
let expr: Expr = input.parse()?; |
||||||
|
quote! { #expr } |
||||||
|
}; |
||||||
|
|
||||||
|
if !input.is_empty() { |
||||||
|
let stream: proc_macro2::TokenStream = input.parse()?; |
||||||
|
return Err(syn::Error::new_spanned(stream, "unexpected token")); |
||||||
|
} |
||||||
|
Ok(id) |
||||||
|
} |
||||||
|
|
||||||
|
fn id_to_tokens( |
||||||
|
id: &proc_macro2::TokenStream, |
||||||
|
pubkey_type: proc_macro2::TokenStream, |
||||||
|
tokens: &mut proc_macro2::TokenStream, |
||||||
|
) { |
||||||
|
tokens.extend(quote! { |
||||||
|
/// The static program ID
|
||||||
|
pub static ID: #pubkey_type = #id; |
||||||
|
|
||||||
|
/// Confirms that a given pubkey is equivalent to the program ID
|
||||||
|
pub fn check_id(id: &#pubkey_type) -> bool { |
||||||
|
id == &ID |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the program ID
|
||||||
|
pub fn id() -> #pubkey_type { |
||||||
|
ID |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
#[test] |
||||||
|
fn test_id() { |
||||||
|
assert!(check_id(&id())); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
fn deprecated_id_to_tokens( |
||||||
|
id: &proc_macro2::TokenStream, |
||||||
|
pubkey_type: proc_macro2::TokenStream, |
||||||
|
tokens: &mut proc_macro2::TokenStream, |
||||||
|
) { |
||||||
|
tokens.extend(quote! { |
||||||
|
/// The static program ID
|
||||||
|
pub static ID: #pubkey_type = #id; |
||||||
|
|
||||||
|
/// Confirms that a given pubkey is equivalent to the program ID
|
||||||
|
#[deprecated()] |
||||||
|
pub fn check_id(id: &#pubkey_type) -> bool { |
||||||
|
id == &ID |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the program ID
|
||||||
|
#[deprecated()] |
||||||
|
pub fn id() -> #pubkey_type { |
||||||
|
ID |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
#[test] |
||||||
|
fn test_id() { |
||||||
|
#[allow(deprecated)] |
||||||
|
assert!(check_id(&id())); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
struct SdkPubkey(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
impl Parse for SdkPubkey { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for SdkPubkey { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
let id = &self.0; |
||||||
|
tokens.extend(quote! {#id}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct ProgramSdkPubkey(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
impl Parse for ProgramSdkPubkey { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for ProgramSdkPubkey { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
let id = &self.0; |
||||||
|
tokens.extend(quote! {#id}) |
||||||
|
} |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
struct Id(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
|
||||||
|
impl Parse for Id { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for Id { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
struct IdDeprecated(proc_macro2::TokenStream); |
||||||
|
|
||||||
|
impl Parse for IdDeprecated { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for IdDeprecated { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
deprecated_id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct ProgramSdkId(proc_macro2::TokenStream); |
||||||
|
impl Parse for ProgramSdkId { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for ProgramSdkId { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct ProgramSdkIdDeprecated(proc_macro2::TokenStream); |
||||||
|
impl Parse for ProgramSdkIdDeprecated { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for ProgramSdkIdDeprecated { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
deprecated_id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[allow(dead_code)] // `respan` may be compiled out
|
||||||
|
struct RespanInput { |
||||||
|
to_respan: Path, |
||||||
|
respan_using: Span, |
||||||
|
} |
||||||
|
|
||||||
|
impl Parse for RespanInput { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
let to_respan: Path = input.parse()?; |
||||||
|
let _comma: Token![,] = input.parse()?; |
||||||
|
let respan_tree: TokenTree = input.parse()?; |
||||||
|
match respan_tree { |
||||||
|
TokenTree::Group(g) if g.delimiter() == Delimiter::None => { |
||||||
|
let ident: Ident = syn::parse2(g.stream())?; |
||||||
|
Ok(RespanInput { |
||||||
|
to_respan, |
||||||
|
respan_using: ident.span(), |
||||||
|
}) |
||||||
|
} |
||||||
|
TokenTree::Ident(i) => Ok(RespanInput { |
||||||
|
to_respan, |
||||||
|
respan_using: i.span(), |
||||||
|
}), |
||||||
|
val => Err(syn::Error::new_spanned( |
||||||
|
val, |
||||||
|
"expected None-delimited group", |
||||||
|
)), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A proc-macro which respans the tokens in its first argument (a `Path`)
|
||||||
|
/// to be resolved at the tokens of its second argument.
|
||||||
|
/// For internal use only.
|
||||||
|
///
|
||||||
|
/// There must be exactly one comma in the input,
|
||||||
|
/// which is used to separate the two arguments.
|
||||||
|
/// The second argument should be exactly one token.
|
||||||
|
///
|
||||||
|
/// For example, `respan!($crate::foo, with_span)`
|
||||||
|
/// produces the tokens `$crate::foo`, but resolved
|
||||||
|
/// at the span of `with_span`.
|
||||||
|
///
|
||||||
|
/// The input to this function should be very short -
|
||||||
|
/// its only purpose is to override the span of a token
|
||||||
|
/// sequence containing `$crate`. For all other purposes,
|
||||||
|
/// a more general proc-macro should be used.
|
||||||
|
#[rustversion::since(1.46.0)] // `Span::resolved_at` is stable in 1.46.0 and above
|
||||||
|
#[proc_macro] |
||||||
|
pub fn respan(input: TokenStream) -> TokenStream { |
||||||
|
// Obtain the `Path` we are going to respan, and the ident
|
||||||
|
// whose span we will be using.
|
||||||
|
let RespanInput { |
||||||
|
to_respan, |
||||||
|
respan_using, |
||||||
|
} = parse_macro_input!(input as RespanInput); |
||||||
|
// Respan all of the tokens in the `Path`
|
||||||
|
let to_respan: proc_macro2::TokenStream = to_respan |
||||||
|
.into_token_stream() |
||||||
|
.into_iter() |
||||||
|
.map(|mut t| { |
||||||
|
// Combine the location of the token with the resolution behavior of `respan_using`
|
||||||
|
let new_span: Span = t.span().resolved_at(respan_using); |
||||||
|
t.set_span(new_span); |
||||||
|
t |
||||||
|
}) |
||||||
|
.collect(); |
||||||
|
TokenStream::from(to_respan) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn pubkey(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as SdkPubkey); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn program_pubkey(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as ProgramSdkPubkey); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn declare_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as Id); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
#[proc_macro] |
||||||
|
pub fn declare_deprecated_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as IdDeprecated); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn program_declare_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as ProgramSdkId); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream { |
||||||
|
let id = parse_macro_input!(input as ProgramSdkIdDeprecated); |
||||||
|
TokenStream::from(quote! {#id}) |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
fn parse_pubkey( |
||||||
|
id_literal: &LitStr, |
||||||
|
pubkey_type: &proc_macro2::TokenStream, |
||||||
|
) -> Result<proc_macro2::TokenStream> { |
||||||
|
let id_vec = bs58::decode(id_literal.value()) |
||||||
|
.into_vec() |
||||||
|
.map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?; |
||||||
|
let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| { |
||||||
|
syn::Error::new_spanned( |
||||||
|
id_literal, |
||||||
|
format!("pubkey array is not 32 bytes long: len={}", id_vec.len()), |
||||||
|
) |
||||||
|
})?; |
||||||
|
let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site())); |
||||||
|
Ok(quote! { |
||||||
|
#pubkey_type::new_from_array( |
||||||
|
[#(#bytes,)*] |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
struct Pubkeys { |
||||||
|
method: Ident, |
||||||
|
num: usize, |
||||||
|
pubkeys: proc_macro2::TokenStream, |
||||||
|
} |
||||||
|
impl Parse for Pubkeys { |
||||||
|
fn parse(input: ParseStream) -> Result<Self> { |
||||||
|
let pubkey_type = quote! { |
||||||
|
::solana_sdk::pubkey::Pubkey |
||||||
|
}; |
||||||
|
|
||||||
|
let method = input.parse()?; |
||||||
|
let _comma: Token![,] = input.parse()?; |
||||||
|
let (num, pubkeys) = if input.peek(syn::LitStr) { |
||||||
|
let id_literal: LitStr = input.parse()?; |
||||||
|
(1, parse_pubkey(&id_literal, &pubkey_type)?) |
||||||
|
} else if input.peek(Bracket) { |
||||||
|
let pubkey_strings; |
||||||
|
bracketed!(pubkey_strings in input); |
||||||
|
let punctuated: Punctuated<LitStr, Token![,]> = |
||||||
|
Punctuated::parse_terminated(&pubkey_strings)?; |
||||||
|
let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new(); |
||||||
|
for string in punctuated.iter() { |
||||||
|
pubkeys.push(parse_pubkey(string, &pubkey_type)?); |
||||||
|
} |
||||||
|
(pubkeys.len(), quote! {#pubkeys}) |
||||||
|
} else { |
||||||
|
let stream: proc_macro2::TokenStream = input.parse()?; |
||||||
|
return Err(syn::Error::new_spanned(stream, "unexpected token")); |
||||||
|
}; |
||||||
|
|
||||||
|
Ok(Pubkeys { |
||||||
|
method, |
||||||
|
num, |
||||||
|
pubkeys, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ToTokens for Pubkeys { |
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
||||||
|
let Pubkeys { |
||||||
|
method, |
||||||
|
num, |
||||||
|
pubkeys, |
||||||
|
} = self; |
||||||
|
|
||||||
|
let pubkey_type = quote! { |
||||||
|
::solana_sdk::pubkey::Pubkey |
||||||
|
}; |
||||||
|
if *num == 1 { |
||||||
|
tokens.extend(quote! { |
||||||
|
pub fn #method() -> #pubkey_type { |
||||||
|
#pubkeys |
||||||
|
} |
||||||
|
}); |
||||||
|
} else { |
||||||
|
tokens.extend(quote! { |
||||||
|
pub fn #method() -> ::std::vec::Vec<#pubkey_type> { |
||||||
|
vec![#pubkeys] |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[proc_macro] |
||||||
|
pub fn pubkeys(input: TokenStream) -> TokenStream { |
||||||
|
let pubkeys = parse_macro_input!(input as Pubkeys); |
||||||
|
TokenStream::from(quote! {#pubkeys}) |
||||||
|
} |
||||||
|
|
||||||
|
// The normal `wasm_bindgen` macro generates a .bss section which causes the resulting
|
||||||
|
// BPF program to fail to load, so for now this stub should be used when building for BPF
|
||||||
|
#[proc_macro_attribute] |
||||||
|
pub fn wasm_bindgen_stub(_attr: TokenStream, item: TokenStream) -> TokenStream { |
||||||
|
match parse_macro_input!(item as syn::Item) { |
||||||
|
syn::Item::Struct(mut item_struct) => { |
||||||
|
if let syn::Fields::Named(fields) = &mut item_struct.fields { |
||||||
|
// Strip out any `#[wasm_bindgen]` added to struct fields. This is custom
|
||||||
|
// syntax supplied by the normal `wasm_bindgen` macro.
|
||||||
|
for field in fields.named.iter_mut() { |
||||||
|
field.attrs.retain(|attr| { |
||||||
|
!attr |
||||||
|
.path |
||||||
|
.segments |
||||||
|
.iter() |
||||||
|
.any(|segment| segment.ident == "wasm_bindgen") |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
quote! { #item_struct } |
||||||
|
} |
||||||
|
item => { |
||||||
|
quote!(#item) |
||||||
|
} |
||||||
|
} |
||||||
|
.into() |
||||||
|
} |
||||||
|
*/ |
@ -0,0 +1,61 @@ |
|||||||
|
use url::Url; |
||||||
|
|
||||||
|
use hyperlane_core::{ |
||||||
|
config::{ConfigErrResultExt, ConfigPath, ConfigResult, FromRawConf}, |
||||||
|
ChainCommunicationError, |
||||||
|
}; |
||||||
|
|
||||||
|
/// Sealevel connection configuration
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub struct ConnectionConf { |
||||||
|
/// Fully qualified string to connect to
|
||||||
|
pub url: Url, |
||||||
|
} |
||||||
|
|
||||||
|
/// Raw Sealevel connection configuration used for better deserialization errors.
|
||||||
|
#[derive(Debug, serde::Deserialize)] |
||||||
|
pub struct RawConnectionConf { |
||||||
|
url: Option<String>, |
||||||
|
} |
||||||
|
|
||||||
|
/// An error type when parsing a connection configuration.
|
||||||
|
#[derive(thiserror::Error, Debug)] |
||||||
|
pub enum ConnectionConfError { |
||||||
|
/// Missing `url` for connection configuration
|
||||||
|
#[error("Missing `url` for connection configuration")] |
||||||
|
MissingConnectionUrl, |
||||||
|
/// Invalid `url` for connection configuration
|
||||||
|
#[error("Invalid `url` for connection configuration: `{0}` ({1})")] |
||||||
|
InvalidConnectionUrl(String, url::ParseError), |
||||||
|
} |
||||||
|
|
||||||
|
impl FromRawConf<'_, RawConnectionConf> for ConnectionConf { |
||||||
|
fn from_config_filtered( |
||||||
|
raw: RawConnectionConf, |
||||||
|
cwp: &ConfigPath, |
||||||
|
_filter: (), |
||||||
|
) -> ConfigResult<Self> { |
||||||
|
use ConnectionConfError::*; |
||||||
|
match raw { |
||||||
|
RawConnectionConf { url: Some(url) } => Ok(Self { |
||||||
|
url: url |
||||||
|
.parse() |
||||||
|
.map_err(|e| InvalidConnectionUrl(url, e)) |
||||||
|
.into_config_result(|| cwp.join("url"))?, |
||||||
|
}), |
||||||
|
RawConnectionConf { url: None } => { |
||||||
|
Err(MissingConnectionUrl).into_config_result(|| cwp.join("url")) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)] |
||||||
|
#[error(transparent)] |
||||||
|
struct SealevelNewConnectionError(#[from] anyhow::Error); |
||||||
|
|
||||||
|
impl From<SealevelNewConnectionError> for ChainCommunicationError { |
||||||
|
fn from(err: SealevelNewConnectionError) -> Self { |
||||||
|
ChainCommunicationError::from_other(err) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
use borsh::{BorshDeserialize, BorshSerialize}; |
||||||
|
use hyperlane_core::{ChainCommunicationError, ChainResult}; |
||||||
|
|
||||||
|
use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData}; |
||||||
|
use solana_client::nonblocking::rpc_client::RpcClient; |
||||||
|
use solana_sdk::{ |
||||||
|
commitment_config::CommitmentConfig, |
||||||
|
instruction::{AccountMeta, Instruction}, |
||||||
|
message::Message, |
||||||
|
signature::{Keypair, Signer}, |
||||||
|
transaction::Transaction, |
||||||
|
}; |
||||||
|
use solana_transaction_status::UiReturnDataEncoding; |
||||||
|
|
||||||
|
/// Simulates an instruction, and attempts to deserialize it into a T.
|
||||||
|
/// If no return data at all was returned, returns Ok(None).
|
||||||
|
/// If some return data was returned but deserialization was unsuccessful,
|
||||||
|
/// an Err is returned.
|
||||||
|
pub async fn simulate_instruction<T: BorshDeserialize + BorshSerialize>( |
||||||
|
rpc_client: &RpcClient, |
||||||
|
payer: &Keypair, |
||||||
|
instruction: Instruction, |
||||||
|
) -> ChainResult<Option<T>> { |
||||||
|
let commitment = CommitmentConfig::finalized(); |
||||||
|
let (recent_blockhash, _) = rpc_client |
||||||
|
.get_latest_blockhash_with_commitment(commitment) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)?; |
||||||
|
let return_data = rpc_client |
||||||
|
.simulate_transaction(&Transaction::new_unsigned(Message::new_with_blockhash( |
||||||
|
&[instruction], |
||||||
|
Some(&payer.pubkey()), |
||||||
|
&recent_blockhash, |
||||||
|
))) |
||||||
|
.await |
||||||
|
.map_err(ChainCommunicationError::from_other)? |
||||||
|
.value |
||||||
|
.return_data; |
||||||
|
|
||||||
|
if let Some(return_data) = return_data { |
||||||
|
let bytes = match return_data.data.1 { |
||||||
|
UiReturnDataEncoding::Base64 => { |
||||||
|
base64::decode(return_data.data.0).map_err(ChainCommunicationError::from_other)? |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let decoded_data = |
||||||
|
T::try_from_slice(bytes.as_slice()).map_err(ChainCommunicationError::from_other)?; |
||||||
|
|
||||||
|
return Ok(Some(decoded_data)); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(None) |
||||||
|
} |
||||||
|
|
||||||
|
/// Simulates an Instruction that will return a list of AccountMetas.
|
||||||
|
pub async fn get_account_metas( |
||||||
|
rpc_client: &RpcClient, |
||||||
|
payer: &Keypair, |
||||||
|
instruction: Instruction, |
||||||
|
) -> ChainResult<Vec<AccountMeta>> { |
||||||
|
// If there's no data at all, default to an empty vec.
|
||||||
|
let account_metas = simulate_instruction::<SimulationReturnData<Vec<SerializableAccountMeta>>>( |
||||||
|
rpc_client, |
||||||
|
payer, |
||||||
|
instruction, |
||||||
|
) |
||||||
|
.await? |
||||||
|
.map(|serializable_account_metas| { |
||||||
|
serializable_account_metas |
||||||
|
.return_data |
||||||
|
.into_iter() |
||||||
|
.map(|serializable_account_meta| serializable_account_meta.into()) |
||||||
|
.collect() |
||||||
|
}) |
||||||
|
.unwrap_or_else(Vec::new); |
||||||
|
|
||||||
|
Ok(account_metas) |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
export BASE_CONFIG="sealevel.json" |
||||||
|
export RUN_ENV="sealevel" |
||||||
|
export HYP_BASE_DB="/tmp/SEALEVEL_DB/relayer" |
||||||
|
export HYP_RELAYER_RELAYCHAINS="sealeveltest1,sealeveltest2" |
||||||
|
export HYP_BASE_METRICS=9091 |
||||||
|
export HYP_BASE_ALLOWLOCALCHECKPOINTSYNCERS=true |
||||||
|
|
||||||
|
# The first 32 bytes of test-keys/test_deployer-keypair.json as hexadecimal, |
||||||
|
# which is the secret key. |
||||||
|
export HYP_BASE_CHAINS_SEALEVELTEST1_SIGNER_KEY=892bf6949af4233e62f854cb3618bc1a3ee3341dc71ada08c4d5deca239acf4f |
||||||
|
export HYP_BASE_CHAINS_SEALEVELTEST1_SIGNER_TYPE="hexKey" |
||||||
|
export HYP_BASE_CHAINS_SEALEVELTEST2_SIGNER_KEY=892bf6949af4233e62f854cb3618bc1a3ee3341dc71ada08c4d5deca239acf4f |
||||||
|
export HYP_BASE_CHAINS_SEALEVELTEST2_SIGNER_TYPE="hexKey" |
||||||
|
|
||||||
|
export HYP_BASE_TRACING_LEVEL="debug" |
@ -0,0 +1,49 @@ |
|||||||
|
{ |
||||||
|
"environment": "sealevel", |
||||||
|
"chains": { |
||||||
|
"sealeveltest1": { |
||||||
|
"name": "SealevelTest1", |
||||||
|
"domain": "13375", |
||||||
|
"addresses": { |
||||||
|
"mailbox": "692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1", |
||||||
|
"interchainGasPaymaster": "FixmeFixmeFixmeFixmeFixmeFixmeFixmeFixmeFixm", |
||||||
|
"validatorAnnounce": "DH43ae1LwemXAboWwSh8zc9pG8j72gKUEXNi57w8fEnn" |
||||||
|
}, |
||||||
|
"signer": null, |
||||||
|
"protocol": "sealevel", |
||||||
|
"finalityBlocks": "0", |
||||||
|
"connection": { |
||||||
|
"type": "http", |
||||||
|
"url": "http://localhost:8899" |
||||||
|
}, |
||||||
|
"index": { |
||||||
|
"from": "1", |
||||||
|
"mode": "sequence" |
||||||
|
} |
||||||
|
}, |
||||||
|
"sealeveltest2": { |
||||||
|
"name": "SealevelTest2", |
||||||
|
"domain": "13376", |
||||||
|
"addresses": { |
||||||
|
"mailbox": "9tCUWNjpqcf3NUSrtp7vquYVCwbEByvLjZUrhG5dgvhj", |
||||||
|
"interchainGasPaymaster": "FixmeFixmeFixmeFixmeFixmeFixmeFixmeFixmeFixm", |
||||||
|
"validatorAnnounce": "3Uo5j2Bti9aZtrDqJmAyuwiFaJFPFoNL5yxTpVCNcUhb" |
||||||
|
}, |
||||||
|
"signer": null, |
||||||
|
"protocol": "sealevel", |
||||||
|
"finalityBlocks": "0", |
||||||
|
"connection": { |
||||||
|
"type": "http", |
||||||
|
"url": "http://localhost:8899" |
||||||
|
}, |
||||||
|
"index": { |
||||||
|
"from": "1", |
||||||
|
"mode": "sequence" |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"tracing": { |
||||||
|
"level": "info", |
||||||
|
"fmt": "pretty" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"pubkey": "E9VrvAdGRvCguN2XgXsgu9PNmMM3vZsU8LSUrM68j8ty", |
||||||
|
"account": { |
||||||
|
"lamports": 500000000000000000, |
||||||
|
"data": [ |
||||||
|
"", |
||||||
|
"base64" |
||||||
|
], |
||||||
|
"owner": "11111111111111111111111111111111", |
||||||
|
"executable": false, |
||||||
|
"rentEpoch": 0 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
[137,43,246,148,154,244,35,62,98,248,84,203,54,24,188,26,62,227,52,29,199,26,218,8,196,213,222,202,35,154,207,79,195,85,53,151,7,182,83,94,59,5,131,252,40,75,87,11,243,118,71,59,195,222,212,148,179,233,253,121,97,210,114,98] |
@ -0,0 +1,10 @@ |
|||||||
|
export BASE_CONFIG="sealevel.json" |
||||||
|
export RUN_ENV="sealevel" |
||||||
|
export HYP_BASE_DB="/tmp/SEALEVEL_DB/validator" |
||||||
|
export HYP_VALIDATOR_ORIGINCHAINNAME="sealeveltest1" |
||||||
|
export HYP_VALIDATOR_VALIDATOR_KEY="59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" |
||||||
|
export HYP_VALIDATOR_VALIDATOR_TYPE="hexKey" |
||||||
|
export HYP_VALIDATOR_REORGPERIOD="0" |
||||||
|
export HYP_VALIDATOR_INTERVAL="1" |
||||||
|
export HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE="localStorage" |
||||||
|
export HYP_VALIDATOR_CHECKPOINTSYNCER_PATH="/tmp/test_sealevel_checkpoints_0x70997970c51812dc3a010c7d01b50e0d17dc79c8" |
@ -0,0 +1,143 @@ |
|||||||
|
use crate::H256; |
||||||
|
|
||||||
|
/// Tree depth
|
||||||
|
pub const TREE_DEPTH: usize = 32; |
||||||
|
// keccak256 zero hashes
|
||||||
|
const Z_0: H256 = H256([ |
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||||
|
]); |
||||||
|
const Z_1: H256 = H256([ |
||||||
|
173, 50, 40, 182, 118, 247, 211, 205, 66, 132, 165, 68, 63, 23, 241, 150, 43, 54, 228, 145, |
||||||
|
179, 10, 64, 178, 64, 88, 73, 229, 151, 186, 95, 181, |
||||||
|
]); |
||||||
|
const Z_2: H256 = H256([ |
||||||
|
180, 193, 25, 81, 149, 124, 111, 143, 100, 44, 74, 246, 28, 214, 178, 70, 64, 254, 198, 220, |
||||||
|
127, 198, 7, 238, 130, 6, 169, 158, 146, 65, 13, 48, |
||||||
|
]); |
||||||
|
const Z_3: H256 = H256([ |
||||||
|
33, 221, 185, 163, 86, 129, 92, 63, 172, 16, 38, 182, 222, 197, 223, 49, 36, 175, 186, 219, 72, |
||||||
|
92, 155, 165, 163, 227, 57, 138, 4, 183, 186, 133, |
||||||
|
]); |
||||||
|
const Z_4: H256 = H256([ |
||||||
|
229, 135, 105, 179, 42, 27, 234, 241, 234, 39, 55, 90, 68, 9, 90, 13, 31, 182, 100, 206, 45, |
||||||
|
211, 88, 231, 252, 191, 183, 140, 38, 161, 147, 68, |
||||||
|
]); |
||||||
|
const Z_5: H256 = H256([ |
||||||
|
14, 176, 30, 191, 201, 237, 39, 80, 12, 212, 223, 201, 121, 39, 45, 31, 9, 19, 204, 159, 102, |
||||||
|
84, 13, 126, 128, 5, 129, 17, 9, 225, 207, 45, |
||||||
|
]); |
||||||
|
const Z_6: H256 = H256([ |
||||||
|
136, 124, 34, 189, 135, 80, 211, 64, 22, 172, 60, 102, 181, 255, 16, 45, 172, 221, 115, 246, |
||||||
|
176, 20, 231, 16, 181, 30, 128, 34, 175, 154, 25, 104, |
||||||
|
]); |
||||||
|
const Z_7: H256 = H256([ |
||||||
|
255, 215, 1, 87, 228, 128, 99, 252, 51, 201, 122, 5, 15, 127, 100, 2, 51, 191, 100, 108, 201, |
||||||
|
141, 149, 36, 198, 185, 43, 207, 58, 181, 111, 131, |
||||||
|
]); |
||||||
|
const Z_8: H256 = H256([ |
||||||
|
152, 103, 204, 95, 127, 25, 107, 147, 186, 225, 226, 126, 99, 32, 116, 36, 69, 210, 144, 242, |
||||||
|
38, 56, 39, 73, 139, 84, 254, 197, 57, 247, 86, 175, |
||||||
|
]); |
||||||
|
const Z_9: H256 = H256([ |
||||||
|
206, 250, 212, 229, 8, 192, 152, 185, 167, 225, 216, 254, 177, 153, 85, 251, 2, 186, 150, 117, |
||||||
|
88, 80, 120, 113, 9, 105, 211, 68, 15, 80, 84, 224, |
||||||
|
]); |
||||||
|
const Z_10: H256 = H256([ |
||||||
|
249, 220, 62, 127, 224, 22, 224, 80, 239, 242, 96, 51, 79, 24, 165, 212, 254, 57, 29, 130, 9, |
||||||
|
35, 25, 245, 150, 79, 46, 46, 183, 193, 195, 165, |
||||||
|
]); |
||||||
|
const Z_11: H256 = H256([ |
||||||
|
248, 177, 58, 73, 226, 130, 246, 9, 195, 23, 168, 51, 251, 141, 151, 109, 17, 81, 124, 87, 29, |
||||||
|
18, 33, 162, 101, 210, 90, 247, 120, 236, 248, 146, |
||||||
|
]); |
||||||
|
const Z_12: H256 = H256([ |
||||||
|
52, 144, 198, 206, 235, 69, 10, 236, 220, 130, 226, 130, 147, 3, 29, 16, 199, 215, 59, 248, 94, |
||||||
|
87, 191, 4, 26, 151, 54, 10, 162, 197, 217, 156, |
||||||
|
]); |
||||||
|
const Z_13: H256 = H256([ |
||||||
|
193, 223, 130, 217, 196, 184, 116, 19, 234, 226, 239, 4, 143, 148, 180, 211, 85, 76, 234, 115, |
||||||
|
217, 43, 15, 122, 249, 110, 2, 113, 198, 145, 226, 187, |
||||||
|
]); |
||||||
|
const Z_14: H256 = H256([ |
||||||
|
92, 103, 173, 215, 198, 202, 243, 2, 37, 106, 222, 223, 122, 177, 20, 218, 10, 207, 232, 112, |
||||||
|
212, 73, 163, 164, 137, 247, 129, 214, 89, 232, 190, 204, |
||||||
|
]); |
||||||
|
const Z_15: H256 = H256([ |
||||||
|
218, 123, 206, 159, 78, 134, 24, 182, 189, 47, 65, 50, 206, 121, 140, 220, 122, 96, 231, 225, |
||||||
|
70, 10, 114, 153, 227, 198, 52, 42, 87, 150, 38, 210, |
||||||
|
]); |
||||||
|
const Z_16: H256 = H256([ |
||||||
|
39, 51, 229, 15, 82, 110, 194, 250, 25, 162, 43, 49, 232, 237, 80, 242, 60, 209, 253, 249, 76, |
||||||
|
145, 84, 237, 58, 118, 9, 162, 241, 255, 152, 31, |
||||||
|
]); |
||||||
|
const Z_17: H256 = H256([ |
||||||
|
225, 211, 181, 200, 7, 178, 129, 228, 104, 60, 198, 214, 49, 92, 249, 91, 154, 222, 134, 65, |
||||||
|
222, 252, 179, 35, 114, 241, 193, 38, 227, 152, 239, 122, |
||||||
|
]); |
||||||
|
const Z_18: H256 = H256([ |
||||||
|
90, 45, 206, 10, 138, 127, 104, 187, 116, 86, 15, 143, 113, 131, 124, 44, 46, 187, 203, 247, |
||||||
|
255, 251, 66, 174, 24, 150, 241, 63, 124, 116, 121, 160, |
||||||
|
]); |
||||||
|
const Z_19: H256 = H256([ |
||||||
|
180, 106, 40, 182, 245, 85, 64, 248, 148, 68, 246, 61, 224, 55, 142, 61, 18, 27, 224, 158, 6, |
||||||
|
204, 157, 237, 28, 32, 230, 88, 118, 211, 106, 160, |
||||||
|
]); |
||||||
|
const Z_20: H256 = H256([ |
||||||
|
198, 94, 150, 69, 100, 71, 134, 182, 32, 226, 221, 42, 214, 72, 221, 252, 191, 74, 126, 91, 26, |
||||||
|
58, 78, 207, 231, 246, 70, 103, 163, 240, 183, 226, |
||||||
|
]); |
||||||
|
const Z_21: H256 = H256([ |
||||||
|
244, 65, 133, 136, 237, 53, 162, 69, 140, 255, 235, 57, 185, 61, 38, 241, 141, 42, 177, 59, |
||||||
|
220, 230, 174, 229, 142, 123, 153, 53, 158, 194, 223, 217, |
||||||
|
]); |
||||||
|
const Z_22: H256 = H256([ |
||||||
|
90, 156, 22, 220, 0, 214, 239, 24, 183, 147, 58, 111, 141, 198, 92, 203, 85, 102, 113, 56, 119, |
||||||
|
111, 125, 234, 16, 16, 112, 220, 135, 150, 227, 119, |
||||||
|
]); |
||||||
|
const Z_23: H256 = H256([ |
||||||
|
77, 248, 79, 64, 174, 12, 130, 41, 208, 214, 6, 158, 92, 143, 57, 167, 194, 153, 103, 122, 9, |
||||||
|
211, 103, 252, 123, 5, 227, 188, 56, 14, 230, 82, |
||||||
|
]); |
||||||
|
const Z_24: H256 = H256([ |
||||||
|
205, 199, 37, 149, 247, 76, 123, 16, 67, 208, 225, 255, 186, 183, 52, 100, 140, 131, 141, 251, |
||||||
|
5, 39, 217, 113, 182, 2, 188, 33, 108, 150, 25, 239, |
||||||
|
]); |
||||||
|
const Z_25: H256 = H256([ |
||||||
|
10, 191, 90, 201, 116, 161, 237, 87, 244, 5, 10, 165, 16, 221, 156, 116, 245, 8, 39, 123, 57, |
||||||
|
215, 151, 59, 178, 223, 204, 197, 238, 176, 97, 141, |
||||||
|
]); |
||||||
|
const Z_26: H256 = H256([ |
||||||
|
184, 205, 116, 4, 111, 243, 55, 240, 167, 191, 44, 142, 3, 225, 15, 100, 44, 24, 134, 121, 141, |
||||||
|
113, 128, 106, 177, 232, 136, 217, 229, 238, 135, 208, |
||||||
|
]); |
||||||
|
const Z_27: H256 = H256([ |
||||||
|
131, 140, 86, 85, 203, 33, 198, 203, 131, 49, 59, 90, 99, 17, 117, 223, 244, 150, 55, 114, 204, |
||||||
|
233, 16, 129, 136, 179, 74, 200, 124, 129, 196, 30, |
||||||
|
]); |
||||||
|
const Z_28: H256 = H256([ |
||||||
|
102, 46, 228, 221, 45, 215, 178, 188, 112, 121, 97, 177, 230, 70, 196, 4, 118, 105, 220, 182, |
||||||
|
88, 79, 13, 141, 119, 13, 175, 93, 126, 125, 235, 46, |
||||||
|
]); |
||||||
|
const Z_29: H256 = H256([ |
||||||
|
56, 138, 178, 14, 37, 115, 209, 113, 168, 129, 8, 231, 157, 130, 14, 152, 242, 108, 11, 132, |
||||||
|
170, 139, 47, 74, 164, 150, 141, 187, 129, 142, 163, 34, |
||||||
|
]); |
||||||
|
const Z_30: H256 = H256([ |
||||||
|
147, 35, 124, 80, 186, 117, 238, 72, 95, 76, 34, 173, 242, 247, 65, 64, 11, 223, 141, 106, 156, |
||||||
|
199, 223, 126, 202, 229, 118, 34, 22, 101, 215, 53, |
||||||
|
]); |
||||||
|
const Z_31: H256 = H256([ |
||||||
|
132, 72, 129, 139, 180, 174, 69, 98, 132, 158, 148, 158, 23, 172, 22, 224, 190, 22, 104, 142, |
||||||
|
21, 107, 92, 241, 94, 9, 140, 98, 124, 0, 86, 169, |
||||||
|
]); |
||||||
|
const Z_32: H256 = H256([ |
||||||
|
39, 174, 91, 160, 141, 114, 145, 201, 108, 140, 189, 220, 193, 72, 191, 72, 166, 214, 140, 121, |
||||||
|
116, 185, 67, 86, 245, 55, 84, 239, 97, 113, 215, 87, |
||||||
|
]); |
||||||
|
|
||||||
|
/// Precomputed zero hashes for building the merkle tree
|
||||||
|
/// A cache of the zero hashes for each layer of the tree.
|
||||||
|
pub const ZERO_HASHES: [H256; TREE_DEPTH + 1] = [ |
||||||
|
Z_0, Z_1, Z_2, Z_3, Z_4, Z_5, Z_6, Z_7, Z_8, Z_9, Z_10, Z_11, Z_12, Z_13, Z_14, Z_15, Z_16, |
||||||
|
Z_17, Z_18, Z_19, Z_20, Z_21, Z_22, Z_23, Z_24, Z_25, Z_26, Z_27, Z_28, Z_29, Z_30, Z_31, Z_32, |
||||||
|
]; |
@ -0,0 +1,310 @@ |
|||||||
|
// Based on https://github.com/paritytech/parity-common/blob/a5ef7308d6986e62431e35d3156fed0a7a585d39/primitive-types/src/lib.rs
|
||||||
|
|
||||||
|
#![allow(clippy::assign_op_pattern)] |
||||||
|
#![allow(clippy::reversed_empty_ranges)] |
||||||
|
|
||||||
|
use std::fmt::Formatter; |
||||||
|
|
||||||
|
use crate::types::serialize; |
||||||
|
use borsh::{BorshDeserialize, BorshSerialize}; |
||||||
|
use fixed_hash::{construct_fixed_hash, impl_fixed_hash_conversions}; |
||||||
|
use serde::de::Visitor; |
||||||
|
use uint::construct_uint; |
||||||
|
|
||||||
|
/// Error type for conversion.
|
||||||
|
#[derive(Debug, PartialEq, Eq)] |
||||||
|
pub enum Error { |
||||||
|
/// Overflow encountered.
|
||||||
|
Overflow, |
||||||
|
} |
||||||
|
|
||||||
|
construct_uint! { |
||||||
|
/// 128-bit unsigned integer.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)] |
||||||
|
pub struct U128(2); |
||||||
|
} |
||||||
|
construct_uint! { |
||||||
|
/// 256-bit unsigned integer.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)] |
||||||
|
pub struct U256(4); |
||||||
|
} |
||||||
|
|
||||||
|
construct_uint! { |
||||||
|
/// 512-bit unsigned integer.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)] |
||||||
|
pub struct U512(8); |
||||||
|
} |
||||||
|
|
||||||
|
construct_fixed_hash! { |
||||||
|
/// 128-bit hash type.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)] |
||||||
|
pub struct H128(16); |
||||||
|
} |
||||||
|
|
||||||
|
construct_fixed_hash! { |
||||||
|
/// 160-bit hash type.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)] |
||||||
|
pub struct H160(20); |
||||||
|
} |
||||||
|
|
||||||
|
construct_fixed_hash! { |
||||||
|
/// 256-bit hash type.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)] |
||||||
|
pub struct H256(32); |
||||||
|
} |
||||||
|
|
||||||
|
construct_fixed_hash! { |
||||||
|
/// 512-bit hash type.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)] |
||||||
|
pub struct H512(64); |
||||||
|
} |
||||||
|
|
||||||
|
struct H512Visitor; |
||||||
|
impl<'de> Visitor<'de> for H512Visitor { |
||||||
|
type Value = H512; |
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { |
||||||
|
formatter.write_str("a 512-bit hash") |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> |
||||||
|
where |
||||||
|
E: serde::de::Error, |
||||||
|
{ |
||||||
|
v.try_into() |
||||||
|
.map_err(|_| E::invalid_length(v.len(), &self)) |
||||||
|
.map(H512) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
type EthersH160 = ethers_core::types::H160; |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
type EthersH256 = ethers_core::types::H256; |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_fixed_hash_conversions!(H256, EthersH160); |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_fixed_hash_conversions!(EthersH256, H160); |
||||||
|
|
||||||
|
impl_fixed_hash_conversions!(H256, H160); |
||||||
|
impl_fixed_hash_conversions!(H512, H256); |
||||||
|
impl_fixed_hash_conversions!(H512, H160); |
||||||
|
|
||||||
|
macro_rules! impl_fixed_uint_conversions { |
||||||
|
($larger:ty, $smaller:ty) => { |
||||||
|
impl From<$smaller> for $larger { |
||||||
|
impl_fixed_uint_conversions!(@from_smaller $larger, $smaller); |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<&'a $smaller> for $larger { |
||||||
|
impl_fixed_uint_conversions!(@from_smaller $larger, &'a $smaller); |
||||||
|
} |
||||||
|
|
||||||
|
impl TryFrom<$larger> for $smaller { |
||||||
|
type Error = Error; |
||||||
|
impl_fixed_uint_conversions!(@try_from_larger $larger, $smaller); |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a $larger> for $smaller { |
||||||
|
type Error = Error; |
||||||
|
impl_fixed_uint_conversions!(@try_from_larger &'a $larger, $smaller); |
||||||
|
} |
||||||
|
}; |
||||||
|
(@from_smaller $larger:ty, $smaller:ty) => { |
||||||
|
fn from(val: $smaller) -> $larger { |
||||||
|
let mut ret = <$larger>::zero(); |
||||||
|
for i in 0..val.0.len() { |
||||||
|
ret.0[i] = val.0[i]; |
||||||
|
} |
||||||
|
ret |
||||||
|
} |
||||||
|
}; |
||||||
|
(@try_from_larger $larger:ty, $smaller:ty) => { |
||||||
|
fn try_from(val: $larger) -> Result<$smaller, Error> { |
||||||
|
let mut ret = <$smaller>::zero(); |
||||||
|
for i in 0..ret.0.len() { |
||||||
|
ret.0[i] = val.0[i]; |
||||||
|
} |
||||||
|
|
||||||
|
let mut ov = 0; |
||||||
|
for i in ret.0.len()..val.0.len() { |
||||||
|
ov |= val.0[i]; |
||||||
|
} |
||||||
|
if ov == 0 { |
||||||
|
Ok(ret) |
||||||
|
} else { |
||||||
|
Err(Error::Overflow) |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_fixed_uint_conversions!(U256, ethers_core::types::U128); |
||||||
|
impl_fixed_uint_conversions!(U256, U128); |
||||||
|
impl_fixed_uint_conversions!(U512, U128); |
||||||
|
impl_fixed_uint_conversions!(U512, U256); |
||||||
|
|
||||||
|
macro_rules! impl_f64_conversions { |
||||||
|
($ty:ty) => { |
||||||
|
impl $ty { |
||||||
|
/// Lossy saturating conversion from a `f64` to a `$ty`. Like for floating point to
|
||||||
|
/// primitive integer type conversions, this truncates fractional parts.
|
||||||
|
///
|
||||||
|
/// The conversion follows the same rules as converting `f64` to other
|
||||||
|
/// primitive integer types. Namely, the conversion of `value: f64` behaves as
|
||||||
|
/// follows:
|
||||||
|
/// - `NaN` => `0`
|
||||||
|
/// - `(-∞, 0]` => `0`
|
||||||
|
/// - `(0, $ty::MAX]` => `value as $ty`
|
||||||
|
/// - `($ty::MAX, +∞)` => `$ty::MAX`
|
||||||
|
pub fn from_f64_lossy(val: f64) -> $ty { |
||||||
|
const TY_BITS: u64 = <$ty>::zero().0.len() as u64 * <$ty>::WORD_BITS as u64; |
||||||
|
if val >= 1.0 { |
||||||
|
let bits = val.to_bits(); |
||||||
|
// NOTE: Don't consider the sign or check that the subtraction will
|
||||||
|
// underflow since we already checked that the value is greater
|
||||||
|
// than 1.0.
|
||||||
|
let exponent = ((bits >> 52) & 0x7ff) - 1023; |
||||||
|
let mantissa = (bits & 0x0f_ffff_ffff_ffff) | 0x10_0000_0000_0000; |
||||||
|
|
||||||
|
if exponent <= 52 { |
||||||
|
<$ty>::from(mantissa >> (52 - exponent)) |
||||||
|
} else if exponent < TY_BITS { |
||||||
|
<$ty>::from(mantissa) << <$ty>::from(exponent - 52) |
||||||
|
} else { |
||||||
|
<$ty>::MAX |
||||||
|
} |
||||||
|
} else { |
||||||
|
<$ty>::zero() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Lossy conversion of `$ty` to `f64`.
|
||||||
|
pub fn to_f64_lossy(self) -> f64 { |
||||||
|
let mut acc = 0.0; |
||||||
|
for i in (0..self.0.len()).rev() { |
||||||
|
acc += self.0[i] as f64 * 2.0f64.powi((i * <$ty>::WORD_BITS) as i32); |
||||||
|
} |
||||||
|
acc |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
impl_f64_conversions!(U128); |
||||||
|
impl_f64_conversions!(U256); |
||||||
|
impl_f64_conversions!(U512); |
||||||
|
|
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
macro_rules! impl_inner_conversion { |
||||||
|
($a:ty, $b:ty) => { |
||||||
|
impl From<$a> for $b { |
||||||
|
fn from(val: $a) -> Self { |
||||||
|
Self(val.0) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<&'a $a> for $b { |
||||||
|
fn from(val: &'a $a) -> Self { |
||||||
|
Self(val.0) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<$b> for $a { |
||||||
|
fn from(val: $b) -> Self { |
||||||
|
Self(val.0) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<&'a $b> for $a { |
||||||
|
fn from(val: &'a $b) -> Self { |
||||||
|
Self(val.0) |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_inner_conversion!(H128, ethers_core::types::H128); |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_inner_conversion!(H160, ethers_core::types::H160); |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_inner_conversion!(H256, ethers_core::types::H256); |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_inner_conversion!(H512, ethers_core::types::H512); |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_inner_conversion!(U128, ethers_core::types::U128); |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_inner_conversion!(U256, ethers_core::types::U256); |
||||||
|
#[cfg(feature = "ethers")] |
||||||
|
impl_inner_conversion!(U512, ethers_core::types::U512); |
||||||
|
|
||||||
|
/// Add Serde serialization support to an integer created by `construct_uint!`.
|
||||||
|
macro_rules! impl_uint_serde { |
||||||
|
($name: ident, $len: expr) => { |
||||||
|
impl serde::Serialize for $name { |
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
||||||
|
where |
||||||
|
S: serde::Serializer, |
||||||
|
{ |
||||||
|
let mut slice = [0u8; 2 + 2 * $len * 8]; |
||||||
|
let mut bytes = [0u8; $len * 8]; |
||||||
|
self.to_big_endian(&mut bytes); |
||||||
|
serialize::serialize_uint(&mut slice, &bytes, serializer) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for $name { |
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
||||||
|
where |
||||||
|
D: serde::Deserializer<'de>, |
||||||
|
{ |
||||||
|
let mut bytes = [0u8; $len * 8]; |
||||||
|
let wrote = serialize::deserialize_check_len( |
||||||
|
deserializer, |
||||||
|
serialize::ExpectedLen::Between(0, &mut bytes), |
||||||
|
)?; |
||||||
|
Ok(bytes[0..wrote].into()) |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/// Add Serde serialization support to a fixed-sized hash type created by `construct_fixed_hash!`.
|
||||||
|
macro_rules! impl_fixed_hash_serde { |
||||||
|
($name: ident, $len: expr) => { |
||||||
|
impl serde::Serialize for $name { |
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
||||||
|
where |
||||||
|
S: serde::Serializer, |
||||||
|
{ |
||||||
|
let mut slice = [0u8; 2 + 2 * $len]; |
||||||
|
serialize::serialize_raw(&mut slice, &self.0, serializer) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'de> serde::Deserialize<'de> for $name { |
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
||||||
|
where |
||||||
|
D: serde::Deserializer<'de>, |
||||||
|
{ |
||||||
|
let mut bytes = [0u8; $len]; |
||||||
|
serialize::deserialize_check_len( |
||||||
|
deserializer, |
||||||
|
serialize::ExpectedLen::Exact(&mut bytes), |
||||||
|
)?; |
||||||
|
Ok($name(bytes)) |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
impl_uint_serde!(U128, 2); |
||||||
|
impl_uint_serde!(U256, 4); |
||||||
|
impl_uint_serde!(U512, 8); |
||||||
|
|
||||||
|
impl_fixed_hash_serde!(H128, 16); |
||||||
|
impl_fixed_hash_serde!(H160, 20); |
||||||
|
impl_fixed_hash_serde!(H256, 32); |
||||||
|
impl_fixed_hash_serde!(H512, 64); |
@ -0,0 +1,349 @@ |
|||||||
|
#![allow(unused)] |
||||||
|
// Based on https://github.com/paritytech/parity-common/blob/7194def73feb7d97644303f1a6ddbab29bbb799f/primitive-types/impls/serde/src/serialize.rs
|
||||||
|
|
||||||
|
// Copyright 2020 Parity Technologies
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use core::{fmt, result::Result}; |
||||||
|
use serde::{de, Deserializer, Serializer}; |
||||||
|
|
||||||
|
static CHARS: &[u8] = b"0123456789abcdef"; |
||||||
|
|
||||||
|
/// Serialize given bytes to a 0x-prefixed hex string.
|
||||||
|
///
|
||||||
|
/// If `skip_leading_zero` initial 0s will not be printed out,
|
||||||
|
/// unless the byte string is empty, in which case `0x0` will be returned.
|
||||||
|
/// The results are consistent with `serialize_uint` output if the flag is
|
||||||
|
/// on and `serialize_raw` if the flag is off.
|
||||||
|
pub fn to_hex(bytes: &[u8], skip_leading_zero: bool) -> String { |
||||||
|
let bytes = if skip_leading_zero { |
||||||
|
let non_zero = bytes.iter().take_while(|b| **b == 0).count(); |
||||||
|
let bytes = &bytes[non_zero..]; |
||||||
|
if bytes.is_empty() { |
||||||
|
return "0x0".into(); |
||||||
|
} else { |
||||||
|
bytes |
||||||
|
} |
||||||
|
} else if bytes.is_empty() { |
||||||
|
return "0x".into(); |
||||||
|
} else { |
||||||
|
bytes |
||||||
|
}; |
||||||
|
|
||||||
|
let mut slice = vec![0u8; (bytes.len() + 1) * 2]; |
||||||
|
to_hex_raw(&mut slice, bytes, skip_leading_zero).into() |
||||||
|
} |
||||||
|
|
||||||
|
fn to_hex_raw<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str { |
||||||
|
assert!(v.len() > 1 + bytes.len() * 2); |
||||||
|
|
||||||
|
v[0] = b'0'; |
||||||
|
v[1] = b'x'; |
||||||
|
|
||||||
|
let mut idx = 2; |
||||||
|
let first_nibble = bytes[0] >> 4; |
||||||
|
if first_nibble != 0 || !skip_leading_zero { |
||||||
|
v[idx] = CHARS[first_nibble as usize]; |
||||||
|
idx += 1; |
||||||
|
} |
||||||
|
v[idx] = CHARS[(bytes[0] & 0xf) as usize]; |
||||||
|
idx += 1; |
||||||
|
|
||||||
|
for &byte in bytes.iter().skip(1) { |
||||||
|
v[idx] = CHARS[(byte >> 4) as usize]; |
||||||
|
v[idx + 1] = CHARS[(byte & 0xf) as usize]; |
||||||
|
idx += 2; |
||||||
|
} |
||||||
|
|
||||||
|
// SAFETY: all characters come either from CHARS or "0x", therefore valid UTF8
|
||||||
|
#[allow(unsafe_code)] |
||||||
|
unsafe { |
||||||
|
core::str::from_utf8_unchecked(&v[0..idx]) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Decoding bytes from hex string error.
|
||||||
|
#[derive(Debug, PartialEq, Eq)] |
||||||
|
pub enum FromHexError { |
||||||
|
/// The `0x` prefix is missing.
|
||||||
|
#[deprecated(since = "0.3.2", note = "We support non 0x-prefixed hex strings")] |
||||||
|
MissingPrefix, |
||||||
|
/// Invalid (non-hex) character encountered.
|
||||||
|
InvalidHex { |
||||||
|
/// The unexpected character.
|
||||||
|
character: char, |
||||||
|
/// Index of that occurrence.
|
||||||
|
index: usize, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(feature = "std")] |
||||||
|
impl std::error::Error for FromHexError {} |
||||||
|
|
||||||
|
impl fmt::Display for FromHexError { |
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
match *self { |
||||||
|
#[allow(deprecated)] |
||||||
|
Self::MissingPrefix => write!(fmt, "0x prefix is missing"), |
||||||
|
Self::InvalidHex { character, index } => { |
||||||
|
write!(fmt, "invalid hex character: {}, at {}", character, index) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Decode given (both 0x-prefixed or not) hex string into a vector of bytes.
|
||||||
|
///
|
||||||
|
/// Returns an error if non-hex characters are present.
|
||||||
|
pub fn from_hex(v: &str) -> Result<Vec<u8>, FromHexError> { |
||||||
|
let (v, stripped) = v.strip_prefix("0x").map_or((v, false), |v| (v, true)); |
||||||
|
|
||||||
|
let mut bytes = vec![0u8; (v.len() + 1) / 2]; |
||||||
|
from_hex_raw(v, &mut bytes, stripped)?; |
||||||
|
Ok(bytes) |
||||||
|
} |
||||||
|
|
||||||
|
/// Decode given 0x-prefix-stripped hex string into provided slice.
|
||||||
|
/// Used internally by `from_hex` and `deserialize_check_len`.
|
||||||
|
///
|
||||||
|
/// The method will panic if `bytes` have incorrect length (make sure to allocate enough beforehand).
|
||||||
|
fn from_hex_raw(v: &str, bytes: &mut [u8], stripped: bool) -> Result<usize, FromHexError> { |
||||||
|
let bytes_len = v.len(); |
||||||
|
let mut modulus = bytes_len % 2; |
||||||
|
let mut buf = 0; |
||||||
|
let mut pos = 0; |
||||||
|
for (index, byte) in v.bytes().enumerate() { |
||||||
|
buf <<= 4; |
||||||
|
|
||||||
|
match byte { |
||||||
|
b'A'..=b'F' => buf |= byte - b'A' + 10, |
||||||
|
b'a'..=b'f' => buf |= byte - b'a' + 10, |
||||||
|
b'0'..=b'9' => buf |= byte - b'0', |
||||||
|
b' ' | b'\r' | b'\n' | b'\t' => { |
||||||
|
buf >>= 4; |
||||||
|
continue; |
||||||
|
} |
||||||
|
b => { |
||||||
|
let character = char::from(b); |
||||||
|
return Err(FromHexError::InvalidHex { |
||||||
|
character, |
||||||
|
index: index + if stripped { 2 } else { 0 }, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
modulus += 1; |
||||||
|
if modulus == 2 { |
||||||
|
modulus = 0; |
||||||
|
bytes[pos] = buf; |
||||||
|
pos += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(pos) |
||||||
|
} |
||||||
|
|
||||||
|
/// Serializes a slice of bytes.
|
||||||
|
pub fn serialize_raw<S>(slice: &mut [u8], bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> |
||||||
|
where |
||||||
|
S: Serializer, |
||||||
|
{ |
||||||
|
if bytes.is_empty() { |
||||||
|
serializer.serialize_str("0x") |
||||||
|
} else { |
||||||
|
serializer.serialize_str(to_hex_raw(slice, bytes, false)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Serializes a slice of bytes.
|
||||||
|
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> |
||||||
|
where |
||||||
|
S: Serializer, |
||||||
|
{ |
||||||
|
let mut slice = vec![0u8; (bytes.len() + 1) * 2]; |
||||||
|
serialize_raw(&mut slice, bytes, serializer) |
||||||
|
} |
||||||
|
|
||||||
|
/// Serialize a slice of bytes as uint.
|
||||||
|
///
|
||||||
|
/// The representation will have all leading zeros trimmed.
|
||||||
|
pub fn serialize_uint<S>(slice: &mut [u8], bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> |
||||||
|
where |
||||||
|
S: Serializer, |
||||||
|
{ |
||||||
|
let non_zero = bytes.iter().take_while(|b| **b == 0).count(); |
||||||
|
let bytes = &bytes[non_zero..]; |
||||||
|
if bytes.is_empty() { |
||||||
|
serializer.serialize_str("0x0") |
||||||
|
} else { |
||||||
|
serializer.serialize_str(to_hex_raw(slice, bytes, true)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Expected length of bytes vector.
|
||||||
|
#[derive(Debug, PartialEq, Eq)] |
||||||
|
pub enum ExpectedLen<'a> { |
||||||
|
/// Exact length in bytes.
|
||||||
|
Exact(&'a mut [u8]), |
||||||
|
/// A bytes length between (min; slice.len()].
|
||||||
|
Between(usize, &'a mut [u8]), |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> fmt::Display for ExpectedLen<'a> { |
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
match *self { |
||||||
|
ExpectedLen::Exact(ref v) => write!(fmt, "{} bytes", v.len()), |
||||||
|
ExpectedLen::Between(min, ref v) => write!(fmt, "between ({}; {}] bytes", min, v.len()), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Deserialize into vector of bytes. This will allocate an O(n) intermediate
|
||||||
|
/// string.
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error> |
||||||
|
where |
||||||
|
D: Deserializer<'de>, |
||||||
|
{ |
||||||
|
struct Visitor; |
||||||
|
|
||||||
|
impl<'b> de::Visitor<'b> for Visitor { |
||||||
|
type Value = Vec<u8>; |
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
write!( |
||||||
|
formatter, |
||||||
|
"a (both 0x-prefixed or not) hex string or byte array" |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> { |
||||||
|
from_hex(v).map_err(E::custom) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> { |
||||||
|
self.visit_str(&v) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> { |
||||||
|
Ok(v.to_vec()) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> { |
||||||
|
Ok(v) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_seq<A: de::SeqAccess<'b>>(self, mut seq: A) -> Result<Self::Value, A::Error> { |
||||||
|
let mut bytes = vec![]; |
||||||
|
while let Some(n) = seq.next_element::<u8>()? { |
||||||
|
bytes.push(n); |
||||||
|
} |
||||||
|
Ok(bytes) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_newtype_struct<D: Deserializer<'b>>( |
||||||
|
self, |
||||||
|
deserializer: D, |
||||||
|
) -> Result<Self::Value, D::Error> { |
||||||
|
deserializer.deserialize_bytes(self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
deserializer.deserialize_str(Visitor) |
||||||
|
} |
||||||
|
|
||||||
|
/// Deserialize into vector of bytes with additional size check.
|
||||||
|
/// Returns number of bytes written.
|
||||||
|
pub fn deserialize_check_len<'a, 'de, D>( |
||||||
|
deserializer: D, |
||||||
|
len: ExpectedLen<'a>, |
||||||
|
) -> Result<usize, D::Error> |
||||||
|
where |
||||||
|
D: Deserializer<'de>, |
||||||
|
{ |
||||||
|
struct Visitor<'a> { |
||||||
|
len: ExpectedLen<'a>, |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, 'b> de::Visitor<'b> for Visitor<'a> { |
||||||
|
type Value = usize; |
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
write!( |
||||||
|
formatter, |
||||||
|
"a (both 0x-prefixed or not) hex string or byte array containing {}", |
||||||
|
self.len |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> { |
||||||
|
let (v, stripped) = v.strip_prefix("0x").map_or((v, false), |v| (v, true)); |
||||||
|
|
||||||
|
let len = v.len(); |
||||||
|
let is_len_valid = match self.len { |
||||||
|
ExpectedLen::Exact(ref slice) => len == 2 * slice.len(), |
||||||
|
ExpectedLen::Between(min, ref slice) => len <= 2 * slice.len() && len > 2 * min, |
||||||
|
}; |
||||||
|
|
||||||
|
if !is_len_valid { |
||||||
|
return Err(E::invalid_length(v.len(), &self)); |
||||||
|
} |
||||||
|
|
||||||
|
let bytes = match self.len { |
||||||
|
ExpectedLen::Exact(slice) => slice, |
||||||
|
ExpectedLen::Between(_, slice) => slice, |
||||||
|
}; |
||||||
|
|
||||||
|
from_hex_raw(v, bytes, stripped).map_err(E::custom) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> { |
||||||
|
self.visit_str(&v) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> { |
||||||
|
let len = v.len(); |
||||||
|
let is_len_valid = match self.len { |
||||||
|
ExpectedLen::Exact(ref slice) => len == slice.len(), |
||||||
|
ExpectedLen::Between(min, ref slice) => len <= slice.len() && len > min, |
||||||
|
}; |
||||||
|
|
||||||
|
if !is_len_valid { |
||||||
|
return Err(E::invalid_length(v.len(), &self)); |
||||||
|
} |
||||||
|
|
||||||
|
let bytes = match self.len { |
||||||
|
ExpectedLen::Exact(slice) => slice, |
||||||
|
ExpectedLen::Between(_, slice) => slice, |
||||||
|
}; |
||||||
|
|
||||||
|
bytes[..len].copy_from_slice(v); |
||||||
|
Ok(len) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> { |
||||||
|
self.visit_bytes(&v) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_seq<A: de::SeqAccess<'b>>(self, mut seq: A) -> Result<Self::Value, A::Error> { |
||||||
|
let mut v = vec![]; |
||||||
|
while let Some(n) = seq.next_element::<u8>()? { |
||||||
|
v.push(n); |
||||||
|
} |
||||||
|
self.visit_byte_buf(v) |
||||||
|
} |
||||||
|
|
||||||
|
fn visit_newtype_struct<D: Deserializer<'b>>( |
||||||
|
self, |
||||||
|
deserializer: D, |
||||||
|
) -> Result<Self::Value, D::Error> { |
||||||
|
deserializer.deserialize_bytes(self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
deserializer.deserialize_str(Visitor { len }) |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
/target |
||||||
|
environments/**/deploy-logs.txt |
@ -0,0 +1,127 @@ |
|||||||
|
# Hyperlane Sealevel (Solana VM) Integration |
||||||
|
|
||||||
|
# Running local end to end test |
||||||
|
|
||||||
|
A local end to end test has been written that will: |
||||||
|
|
||||||
|
1. Run a local Solana network |
||||||
|
2. Deploy two sets of core contracts (i.e. Mailbox / Multisig ISM / ValidatorAnnounce) onto this chain, one with domain 13375 and the other 13376. |
||||||
|
3. Deploy a "native" warp route on domain 13375 and a "synthetic" warp route on domain 13376 |
||||||
|
4. Send native lamports from domain 13375 to 13376 |
||||||
|
5. A validator & relayer can then be spun up to deliver the message |
||||||
|
|
||||||
|
### Build and run solana-test-validator |
||||||
|
|
||||||
|
This only needs to be done once when initially setting things up. |
||||||
|
|
||||||
|
1. Clone the `solar-eclipse` repo, which is the Eclipse fork of the Solana repo. This is needed to run the local Solana network. Check out the `steven/hyperlane-fix-deps` branch: |
||||||
|
|
||||||
|
``` |
||||||
|
git clone git@github.com:Eclipse-Laboratories-Inc/solar-eclipse --branch steven/hyperlane-fix-deps |
||||||
|
``` |
||||||
|
|
||||||
|
2. `cd` into the repo and build the `solana-test-validator` using the local `cargo` script (which ensures the correct version is used): |
||||||
|
|
||||||
|
``` |
||||||
|
./cargo build -p solana-test-validator |
||||||
|
``` |
||||||
|
|
||||||
|
### Check out `eclipse-program-library` |
||||||
|
|
||||||
|
This is a fork (with some dependency fixes) of the eclipse fork of the `solana-program-library`. This contains "SPL" programs that are commonly used programs - stuff like the token program, etc. |
||||||
|
|
||||||
|
Note these instructions previously required a different remote and branch - make sure to move to this remote & branch if you ahven't already! |
||||||
|
|
||||||
|
1. Check out the branch `trevor/steven/eclipse-1.14.13/with-tlv-lib`: |
||||||
|
|
||||||
|
``` |
||||||
|
git clone git@github.com:tkporter/eclipse-program-library.git --branch trevor/steven/eclipse-1.14.13/with-tlv-lib |
||||||
|
``` |
||||||
|
|
||||||
|
### Build the required SPL programs and Hyperlane programs |
||||||
|
|
||||||
|
This command will build all the required SPL programs (e.g. the token program, token 2022 program, SPL noop, etc...) found in the local repo of `eclipse-program-library`, |
||||||
|
and will build all the required Hyperlane programs (e.g. the Mailbox program, Validator Announce, etc...). |
||||||
|
|
||||||
|
You need to run this if any changes are made to programs that you want to be used in future runs of the end to end test. |
||||||
|
|
||||||
|
Change the paths to your local `solar-eclipse` repo and `eclipse-program-library` as necessary, and run this from the `rust` directory of hyperlane-monorepo. |
||||||
|
|
||||||
|
``` |
||||||
|
SOLAR_ECLIPSE_DIR=~/solar-eclipse ECLIPSE_PROGRAM_LIBRARY_DIR=~/eclipse-program-library ./utils/sealevel-test.bash build-only |
||||||
|
``` |
||||||
|
|
||||||
|
### Run the local Solana network |
||||||
|
|
||||||
|
This will run the `solana-test-validator` with a funded test account `E9VrvAdGRvCguN2XgXsgu9PNmMM3vZsU8LSUrM68j8ty` that will later be used for deploying contracts. It will also create some of the required SPL programs at the specified program IDs - these program IDs are consistent across Solana networks and are required by our Hyperlane programs. Change paths as necessary - the \*.so files should have been created by the prior command. The `--ledger` directory is arbitrary and is just the data dir for the Solana validator. |
||||||
|
|
||||||
|
``` |
||||||
|
mkdir -p /tmp/eclipse/ledger-dir && target/debug/solana-test-validator --reset --ledger /tmp/eclipse/ledger-dir --account E9VrvAdGRvCguN2XgXsgu9PNmMM3vZsU8LSUrM68j8ty ~/abacus-monorepo/rust/config/sealevel/test-keys/test_deployer-account.json --bpf-program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA ~/eclipse-program-library/target/deploy/spl_token.so --bpf-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb ~/eclipse-program-library/target/deploy/spl_token_2022.so --bpf-program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL ~/eclipse-program-library/target/deploy/spl_associated_token_account.so --bpf-program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV ~/eclipse-program-library/account-compression/target/deploy/spl_noop.so |
||||||
|
``` |
||||||
|
|
||||||
|
By now you should have an output like this - keep it running and move to another terminal: |
||||||
|
|
||||||
|
``` |
||||||
|
Ledger location: /tmp/eclipse/ledger-dir |
||||||
|
Log: /tmp/eclipse/ledger-dir/validator.log |
||||||
|
⠒ Initializing... |
||||||
|
⠄ Initializing... |
||||||
|
Identity: 4P5rtWdphhehU32myNQcTSMgrCRz7kdvZEnasX6fahJQ |
||||||
|
Genesis Hash: G7CY7wEzbdjh8RwqTszxrpYTqiHKvqwpaw3JbmKJjJhU |
||||||
|
Version: 1.14.13 |
||||||
|
Shred Version: 419 |
||||||
|
Gossip Address: 127.0.0.1:1024 |
||||||
|
TPU Address: 127.0.0.1:1027 |
||||||
|
JSON RPC URL: http://127.0.0.1:8899 |
||||||
|
⠒ 00:05:35 | Processed Slot: 668 | Confirmed Slot: 668 | Finalized Slot: 6 |
||||||
|
``` |
||||||
|
|
||||||
|
### Run the local end to end script |
||||||
|
|
||||||
|
Run the script found at `rust/utils/sealevel-test.bash`. This will build all required programs, deploy contracts, and test sending a warp route message. You need to supply the paths to your local `solar-eclipse` and `eclipse-program-library` repos: |
||||||
|
|
||||||
|
``` |
||||||
|
SOLAR_ECLIPSE_DIR=~/solar-eclipse ECLIPSE_PROGRAM_LIBRARY_DIR=~/eclipse-program-library ./utils/sealevel-test.bash |
||||||
|
``` |
||||||
|
|
||||||
|
Note: this won't rebuild any of the programs. If you want to rebuild them, you can either cd into them individually and run `cargo build-sbf --arch sbf`, or you can run the above bash script with `force-build-programs` as the first argument. |
||||||
|
|
||||||
|
You'll see a bunch of output here showing programs being built and deployed. Eventually you should see some logs saying `grep -q 'Message not delivered'`. At this point, the contracts have all been deployed and a native warp route transfer has been made. You can move on to running the validator and relayer. |
||||||
|
|
||||||
|
### Running the validator |
||||||
|
|
||||||
|
In a separate terminal, cd to `hyperlane-monorepo/rust`. |
||||||
|
|
||||||
|
1. Source the env vars: |
||||||
|
|
||||||
|
``` |
||||||
|
source ./config/sealevel/validator.env |
||||||
|
``` |
||||||
|
|
||||||
|
2. Run the validator (this clears the DB / checkpoints if present): |
||||||
|
|
||||||
|
``` |
||||||
|
mkdir /tmp/SEALEVEL_DB ; rm -rf /tmp/SEALEVEL_DB/validator /tmp/test_sealevel_checkpoints_0x70997970c51812dc3a010c7d01b50e0d17dc79c8/* ; CONFIG_FILES=./config/sealevel/sealevel.json cargo run --bin validator |
||||||
|
``` |
||||||
|
|
||||||
|
You should see some INFO logs about checkpoint at index 0. |
||||||
|
|
||||||
|
You can confirm things are working correctly by looking at `/tmp/CHECKPOINTS_DIR`, where the validator posts its signatures. |
||||||
|
|
||||||
|
### Running the relayer |
||||||
|
|
||||||
|
In a separate terminal, again in `hyperlane-monorepo/rust`: |
||||||
|
|
||||||
|
1. Source the env vars: |
||||||
|
|
||||||
|
``` |
||||||
|
source ./config/sealevel/relayer.env |
||||||
|
``` |
||||||
|
|
||||||
|
2. Run the relayer (the rm is to make sure the relayer's DB is cleared): |
||||||
|
|
||||||
|
``` |
||||||
|
rm -rf /tmp/SEALEVEL_DB/relayer ; RUST_BACKTRACE=full CONFIG_FILES=./config/sealevel/sealevel.json cargo run --bin relayer |
||||||
|
``` |
||||||
|
|
||||||
|
When the original `sealevel-test.bash` exits with a 0 exit code and some logs about Hyperlane Token Storage, the message has been successfully delivered! |
@ -0,0 +1,30 @@ |
|||||||
|
cargo-features = ["workspace-inheritance"] |
||||||
|
|
||||||
|
[package] |
||||||
|
name = "hyperlane-sealevel-client" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
borsh.workspace = true |
||||||
|
clap = { workspace = true, features = ["derive"] } |
||||||
|
hex.workspace = true |
||||||
|
pretty_env_logger.workspace = true |
||||||
|
serde.workspace = true |
||||||
|
serde_json.workspace = true |
||||||
|
solana-clap-utils.workspace = true |
||||||
|
solana-cli-config.workspace = true |
||||||
|
solana-client.workspace = true |
||||||
|
solana-program.workspace = true |
||||||
|
solana-sdk.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" } |
||||||
|
hyperlane-sealevel-multisig-ism-message-id = { path = "../programs/ism/multisig-ism-message-id" } |
||||||
|
hyperlane-sealevel-token = { path = "../programs/hyperlane-sealevel-token" } |
||||||
|
hyperlane-sealevel-token-collateral = { path = "../programs/hyperlane-sealevel-token-collateral" } |
||||||
|
hyperlane-sealevel-token-lib = { path = "../libraries/hyperlane-sealevel-token" } |
||||||
|
hyperlane-sealevel-token-native = { path = "../programs/hyperlane-sealevel-token-native" } |
||||||
|
hyperlane-sealevel-validator-announce = { path = "../programs/validator-announce" } |
@ -0,0 +1,160 @@ |
|||||||
|
use std::{ |
||||||
|
collections::HashMap, |
||||||
|
fs::File, |
||||||
|
io::Write, |
||||||
|
path::{Path, PathBuf}, |
||||||
|
process::{Command, Stdio}, |
||||||
|
}; |
||||||
|
|
||||||
|
use solana_client::{client_error::ClientError, rpc_client::RpcClient}; |
||||||
|
use solana_sdk::{ |
||||||
|
commitment_config::CommitmentConfig, |
||||||
|
pubkey::Pubkey, |
||||||
|
signature::{Keypair, Signer}, |
||||||
|
}; |
||||||
|
|
||||||
|
/// Open a file in append mode, or create it if it does not exist.
|
||||||
|
fn append_to(p: impl AsRef<Path>) -> File { |
||||||
|
File::options() |
||||||
|
.create(true) |
||||||
|
.append(true) |
||||||
|
.open(p) |
||||||
|
.expect("Failed to open file") |
||||||
|
} |
||||||
|
|
||||||
|
pub fn build_cmd( |
||||||
|
cmd: &[&str], |
||||||
|
log: impl AsRef<Path>, |
||||||
|
log_all: bool, |
||||||
|
wd: Option<&str>, |
||||||
|
env: Option<&HashMap<&str, &str>>, |
||||||
|
assert_success: bool, |
||||||
|
) { |
||||||
|
assert!(!cmd.is_empty(), "Must specify a command!"); |
||||||
|
let mut c = Command::new(cmd[0]); |
||||||
|
c.args(&cmd[1..]); |
||||||
|
if log_all { |
||||||
|
c.stdout(Stdio::inherit()); |
||||||
|
} else { |
||||||
|
c.stdout(append_to(log)); |
||||||
|
} |
||||||
|
if let Some(wd) = wd { |
||||||
|
c.current_dir(wd); |
||||||
|
} |
||||||
|
if let Some(env) = env { |
||||||
|
c.envs(env); |
||||||
|
} |
||||||
|
let status = c.status().expect("Failed to run command"); |
||||||
|
if assert_success { |
||||||
|
assert!( |
||||||
|
status.success(), |
||||||
|
"Command returned non-zero exit code: {}", |
||||||
|
cmd.join(" ") |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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
|
||||||
|
let exists = client |
||||||
|
.get_account_with_commitment(account, CommitmentConfig::processed())? |
||||||
|
.value |
||||||
|
.is_some(); |
||||||
|
Ok(exists) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn deploy_program_idempotent( |
||||||
|
payer_path: &str, |
||||||
|
program_keypair: &Keypair, |
||||||
|
program_keypair_path: &str, |
||||||
|
program_path: &str, |
||||||
|
url: &str, |
||||||
|
log_file: impl AsRef<Path>, |
||||||
|
) -> Result<(), ClientError> { |
||||||
|
let client = RpcClient::new(url.to_string()); |
||||||
|
if !account_exists(&client, &program_keypair.pubkey())? { |
||||||
|
deploy_program( |
||||||
|
payer_path, |
||||||
|
program_keypair_path, |
||||||
|
program_path, |
||||||
|
url, |
||||||
|
log_file, |
||||||
|
); |
||||||
|
} else { |
||||||
|
println!("Program {} already deployed", program_keypair.pubkey()); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn deploy_program( |
||||||
|
payer_path: &str, |
||||||
|
program_keypair_path: &str, |
||||||
|
program_path: &str, |
||||||
|
url: &str, |
||||||
|
log_file: impl AsRef<Path>, |
||||||
|
) { |
||||||
|
build_cmd( |
||||||
|
&[ |
||||||
|
"solana", |
||||||
|
"--url", |
||||||
|
url, |
||||||
|
"-k", |
||||||
|
payer_path, |
||||||
|
"program", |
||||||
|
"deploy", |
||||||
|
program_path, |
||||||
|
"--upgrade-authority", |
||||||
|
payer_path, |
||||||
|
"--program-id", |
||||||
|
program_keypair_path, |
||||||
|
], |
||||||
|
log_file, |
||||||
|
true, |
||||||
|
None, |
||||||
|
None, |
||||||
|
true, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn create_new_file(parent_dir: &Path, name: &str) -> PathBuf { |
||||||
|
let path = parent_dir.join(name); |
||||||
|
let _file = File::create(path.clone()) |
||||||
|
.unwrap_or_else(|_| panic!("Failed to create file {}", path.display())); |
||||||
|
path |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn create_new_directory(parent_dir: &Path, name: &str) -> PathBuf { |
||||||
|
let path = parent_dir.join(name); |
||||||
|
std::fs::create_dir_all(path.clone()) |
||||||
|
.unwrap_or_else(|_| panic!("Failed to create directory {}", path.display())); |
||||||
|
path |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn create_and_write_keypair( |
||||||
|
key_dir: &Path, |
||||||
|
key_name: &str, |
||||||
|
use_existing_key: bool, |
||||||
|
) -> (Keypair, PathBuf) { |
||||||
|
let path = key_dir.join(key_name); |
||||||
|
|
||||||
|
if use_existing_key { |
||||||
|
if let Ok(file) = File::open(path.clone()) { |
||||||
|
println!("Using existing key at path {}", path.display()); |
||||||
|
let keypair_bytes: Vec<u8> = serde_json::from_reader(file).unwrap(); |
||||||
|
let keypair = Keypair::from_bytes(&keypair_bytes[..]).unwrap(); |
||||||
|
return (keypair, path); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let keypair = Keypair::new(); |
||||||
|
let keypair_json = serde_json::to_string(&keypair.to_bytes()[..]).unwrap(); |
||||||
|
|
||||||
|
let mut file = File::create(path.clone()).expect("Failed to create keypair file"); |
||||||
|
file.write_all(keypair_json.as_bytes()) |
||||||
|
.expect("Failed to write keypair to file"); |
||||||
|
println!("Wrote keypair {} to {}", keypair.pubkey(), path.display()); |
||||||
|
|
||||||
|
(keypair, path) |
||||||
|
} |
@ -0,0 +1,264 @@ |
|||||||
|
use serde::{Deserialize, Serialize}; |
||||||
|
|
||||||
|
use solana_program::pubkey::Pubkey; |
||||||
|
use solana_sdk::signature::Signer; |
||||||
|
|
||||||
|
use std::{fs::File, io::Write, path::Path, str::FromStr}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
cmd_utils::{create_and_write_keypair, create_new_directory, create_new_file, deploy_program}, |
||||||
|
Context, CoreCmd, CoreSubCmd, |
||||||
|
}; |
||||||
|
|
||||||
|
pub(crate) fn process_core_cmd(mut ctx: Context, cmd: CoreCmd) { |
||||||
|
match cmd.cmd { |
||||||
|
CoreSubCmd::Deploy(core) => { |
||||||
|
let environments_dir = create_new_directory(&core.environments_dir, &core.environment); |
||||||
|
let chain_dir = create_new_directory(&environments_dir, &core.chain); |
||||||
|
let core_dir = create_new_directory(&chain_dir, "core"); |
||||||
|
let key_dir = create_new_directory(&core_dir, "keys"); |
||||||
|
let log_file = create_new_file(&core_dir, "deploy-logs.txt"); |
||||||
|
|
||||||
|
let ism_program_id = deploy_multisig_ism_message_id( |
||||||
|
&mut ctx, |
||||||
|
core.use_existing_keys, |
||||||
|
&key_dir, |
||||||
|
&core.built_so_dir, |
||||||
|
&log_file, |
||||||
|
); |
||||||
|
|
||||||
|
let mailbox_program_id = deploy_mailbox( |
||||||
|
&mut ctx, |
||||||
|
core.use_existing_keys, |
||||||
|
&key_dir, |
||||||
|
&core.built_so_dir, |
||||||
|
&log_file, |
||||||
|
core.local_domain, |
||||||
|
ism_program_id, |
||||||
|
); |
||||||
|
|
||||||
|
let validator_announce_program_id = deploy_validator_announce( |
||||||
|
&mut ctx, |
||||||
|
core.use_existing_keys, |
||||||
|
&key_dir, |
||||||
|
&core.built_so_dir, |
||||||
|
&log_file, |
||||||
|
mailbox_program_id, |
||||||
|
core.local_domain, |
||||||
|
); |
||||||
|
|
||||||
|
let program_ids = CoreProgramIds { |
||||||
|
mailbox: mailbox_program_id, |
||||||
|
validator_announce: validator_announce_program_id, |
||||||
|
multisig_ism_message_id: ism_program_id, |
||||||
|
}; |
||||||
|
write_program_ids(&core_dir, program_ids); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn deploy_multisig_ism_message_id( |
||||||
|
ctx: &mut Context, |
||||||
|
use_existing_key: bool, |
||||||
|
key_dir: &Path, |
||||||
|
built_so_dir: &Path, |
||||||
|
log_file: impl AsRef<Path>, |
||||||
|
) -> Pubkey { |
||||||
|
let (keypair, keypair_path) = create_and_write_keypair( |
||||||
|
key_dir, |
||||||
|
"hyperlane_sealevel_multisig_ism_message_id-keypair.json", |
||||||
|
use_existing_key, |
||||||
|
); |
||||||
|
let program_id = keypair.pubkey(); |
||||||
|
|
||||||
|
deploy_program( |
||||||
|
&ctx.payer_path, |
||||||
|
keypair_path.to_str().unwrap(), |
||||||
|
built_so_dir |
||||||
|
.join("hyperlane_sealevel_multisig_ism_message_id.so") |
||||||
|
.to_str() |
||||||
|
.unwrap(), |
||||||
|
&ctx.client.url(), |
||||||
|
log_file, |
||||||
|
); |
||||||
|
|
||||||
|
println!( |
||||||
|
"Deployed Multisig ISM Message ID at program ID {}", |
||||||
|
program_id |
||||||
|
); |
||||||
|
|
||||||
|
// Initialize
|
||||||
|
let instruction = hyperlane_sealevel_multisig_ism_message_id::instruction::init_instruction( |
||||||
|
program_id, |
||||||
|
ctx.payer.pubkey(), |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
ctx.instructions.push(instruction); |
||||||
|
ctx.send_transaction(&[&ctx.payer]); |
||||||
|
ctx.instructions.clear(); |
||||||
|
|
||||||
|
println!("Initialized Multisig ISM Message ID "); |
||||||
|
|
||||||
|
program_id |
||||||
|
} |
||||||
|
|
||||||
|
fn deploy_mailbox( |
||||||
|
ctx: &mut Context, |
||||||
|
use_existing_key: bool, |
||||||
|
key_dir: &Path, |
||||||
|
built_so_dir: &Path, |
||||||
|
log_file: impl AsRef<Path>, |
||||||
|
local_domain: u32, |
||||||
|
default_ism: Pubkey, |
||||||
|
) -> Pubkey { |
||||||
|
let (keypair, keypair_path) = create_and_write_keypair( |
||||||
|
key_dir, |
||||||
|
"hyperlane_sealevel_mailbox-keypair.json", |
||||||
|
use_existing_key, |
||||||
|
); |
||||||
|
let program_id = keypair.pubkey(); |
||||||
|
|
||||||
|
deploy_program( |
||||||
|
&ctx.payer_path, |
||||||
|
keypair_path.to_str().unwrap(), |
||||||
|
built_so_dir |
||||||
|
.join("hyperlane_sealevel_mailbox.so") |
||||||
|
.to_str() |
||||||
|
.unwrap(), |
||||||
|
&ctx.client.url(), |
||||||
|
log_file, |
||||||
|
); |
||||||
|
|
||||||
|
println!("Deployed Mailbox at program ID {}", program_id); |
||||||
|
|
||||||
|
// Initialize
|
||||||
|
let instruction = hyperlane_sealevel_mailbox::instruction::init_instruction( |
||||||
|
program_id, |
||||||
|
local_domain, |
||||||
|
default_ism, |
||||||
|
ctx.payer.pubkey(), |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
ctx.instructions.push(instruction); |
||||||
|
ctx.send_transaction(&[&ctx.payer]); |
||||||
|
ctx.instructions.clear(); |
||||||
|
|
||||||
|
println!("Initialized Mailbox"); |
||||||
|
|
||||||
|
program_id |
||||||
|
} |
||||||
|
|
||||||
|
fn deploy_validator_announce( |
||||||
|
ctx: &mut Context, |
||||||
|
use_existing_key: bool, |
||||||
|
key_dir: &Path, |
||||||
|
built_so_dir: &Path, |
||||||
|
log_file: impl AsRef<Path>, |
||||||
|
mailbox_program_id: Pubkey, |
||||||
|
local_domain: u32, |
||||||
|
) -> Pubkey { |
||||||
|
let (keypair, keypair_path) = create_and_write_keypair( |
||||||
|
key_dir, |
||||||
|
"hyperlane_sealevel_validator_announce-keypair.json", |
||||||
|
use_existing_key, |
||||||
|
); |
||||||
|
let program_id = keypair.pubkey(); |
||||||
|
|
||||||
|
deploy_program( |
||||||
|
&ctx.payer_path, |
||||||
|
keypair_path.to_str().unwrap(), |
||||||
|
built_so_dir |
||||||
|
.join("hyperlane_sealevel_validator_announce.so") |
||||||
|
.to_str() |
||||||
|
.unwrap(), |
||||||
|
&ctx.client.url(), |
||||||
|
log_file, |
||||||
|
); |
||||||
|
|
||||||
|
println!("Deployed ValidatorAnnounce at program ID {}", program_id); |
||||||
|
|
||||||
|
// Initialize
|
||||||
|
let instruction = hyperlane_sealevel_validator_announce::instruction::init_instruction( |
||||||
|
program_id, |
||||||
|
ctx.payer.pubkey(), |
||||||
|
mailbox_program_id, |
||||||
|
local_domain, |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
ctx.instructions.push(instruction); |
||||||
|
ctx.send_transaction(&[&ctx.payer]); |
||||||
|
ctx.instructions.clear(); |
||||||
|
|
||||||
|
println!("Initialized ValidatorAnnounce"); |
||||||
|
|
||||||
|
program_id |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub(crate) struct CoreProgramIds { |
||||||
|
pub mailbox: Pubkey, |
||||||
|
pub validator_announce: Pubkey, |
||||||
|
pub multisig_ism_message_id: Pubkey, |
||||||
|
} |
||||||
|
|
||||||
|
impl From<PrettyCoreProgramIds> for CoreProgramIds { |
||||||
|
fn from(program_ids: PrettyCoreProgramIds) -> Self { |
||||||
|
Self { |
||||||
|
mailbox: Pubkey::from_str(program_ids.mailbox.as_str()).unwrap(), |
||||||
|
validator_announce: Pubkey::from_str(program_ids.validator_announce.as_str()).unwrap(), |
||||||
|
multisig_ism_message_id: Pubkey::from_str(program_ids.multisig_ism_message_id.as_str()) |
||||||
|
.unwrap(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)] |
||||||
|
struct PrettyCoreProgramIds { |
||||||
|
mailbox: String, |
||||||
|
validator_announce: String, |
||||||
|
multisig_ism_message_id: String, |
||||||
|
} |
||||||
|
|
||||||
|
impl From<CoreProgramIds> for PrettyCoreProgramIds { |
||||||
|
fn from(program_ids: CoreProgramIds) -> Self { |
||||||
|
Self { |
||||||
|
mailbox: program_ids.mailbox.to_string(), |
||||||
|
validator_announce: program_ids.validator_announce.to_string(), |
||||||
|
multisig_ism_message_id: program_ids.multisig_ism_message_id.to_string(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn write_program_ids(core_dir: &Path, program_ids: CoreProgramIds) { |
||||||
|
let pretty_program_ids = PrettyCoreProgramIds::from(program_ids); |
||||||
|
|
||||||
|
let json = serde_json::to_string_pretty(&pretty_program_ids).unwrap(); |
||||||
|
let path = core_dir.join("program-ids.json"); |
||||||
|
|
||||||
|
println!("Writing program IDs to {}:\n{}", path.display(), json); |
||||||
|
|
||||||
|
let mut file = File::create(path).expect("Failed to create keypair file"); |
||||||
|
file.write_all(json.as_bytes()) |
||||||
|
.expect("Failed to write program IDs to file"); |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn read_core_program_ids( |
||||||
|
environments_dir: &Path, |
||||||
|
environment: &str, |
||||||
|
chain: &str, |
||||||
|
) -> CoreProgramIds { |
||||||
|
let path = environments_dir |
||||||
|
.join(environment) |
||||||
|
.join(chain) |
||||||
|
.join("core") |
||||||
|
.join("program-ids.json"); |
||||||
|
let file = File::open(path).expect("Failed to open program IDs file"); |
||||||
|
|
||||||
|
let pretty_program_ids: PrettyCoreProgramIds = |
||||||
|
serde_json::from_reader(file).expect("Failed to read program IDs file"); |
||||||
|
|
||||||
|
CoreProgramIds::from(pretty_program_ids) |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,566 @@ |
|||||||
|
use hyperlane_core::{utils::hex_or_base58_to_h256, H256}; |
||||||
|
use serde::{Deserialize, Serialize}; |
||||||
|
use std::{collections::HashMap, fs::File, path::Path, str::FromStr}; |
||||||
|
|
||||||
|
use solana_client::{client_error::ClientError, rpc_client::RpcClient}; |
||||||
|
use solana_program::program_error::ProgramError; |
||||||
|
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signer}; |
||||||
|
|
||||||
|
use hyperlane_sealevel_connection_client::router::RemoteRouterConfig; |
||||||
|
|
||||||
|
use hyperlane_sealevel_token::{hyperlane_token_mint_pda_seeds, spl_token, spl_token_2022}; |
||||||
|
use hyperlane_sealevel_token_lib::{ |
||||||
|
accounts::HyperlaneTokenAccount, |
||||||
|
hyperlane_token_pda_seeds, |
||||||
|
instruction::{enroll_remote_routers_instruction, Init}, |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
cmd_utils::{ |
||||||
|
account_exists, create_and_write_keypair, create_new_directory, deploy_program_idempotent, |
||||||
|
}, |
||||||
|
core::{read_core_program_ids, CoreProgramIds}, |
||||||
|
Context, WarpRouteCmd, WarpRouteSubCmd, |
||||||
|
}; |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
struct DecimalMetadata { |
||||||
|
decimals: u8, |
||||||
|
remote_decimals: Option<u8>, |
||||||
|
} |
||||||
|
|
||||||
|
impl DecimalMetadata { |
||||||
|
fn remote_decimals(&self) -> u8 { |
||||||
|
self.remote_decimals.unwrap_or(self.decimals) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)] |
||||||
|
#[serde(tag = "type", rename_all = "camelCase")] |
||||||
|
enum TokenType { |
||||||
|
Native, |
||||||
|
Synthetic(TokenMetadata), |
||||||
|
Collateral(CollateralInfo), |
||||||
|
} |
||||||
|
|
||||||
|
impl TokenType { |
||||||
|
fn program_name(&self) -> &str { |
||||||
|
match self { |
||||||
|
TokenType::Native => "hyperlane_sealevel_token_native", |
||||||
|
TokenType::Synthetic(_) => "hyperlane_sealevel_token", |
||||||
|
TokenType::Collateral(_) => "hyperlane_sealevel_token_collateral", |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
struct TokenMetadata { |
||||||
|
name: String, |
||||||
|
symbol: String, |
||||||
|
total_supply: Option<String>, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
enum SplTokenProgramType { |
||||||
|
Token, |
||||||
|
Token2022, |
||||||
|
} |
||||||
|
|
||||||
|
impl SplTokenProgramType { |
||||||
|
fn program_id(&self) -> Pubkey { |
||||||
|
match &self { |
||||||
|
SplTokenProgramType::Token => spl_token::id(), |
||||||
|
SplTokenProgramType::Token2022 => spl_token_2022::id(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
struct CollateralInfo { |
||||||
|
#[serde(rename = "token")] |
||||||
|
mint: String, |
||||||
|
spl_token_program: Option<SplTokenProgramType>, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
struct OptionalConnectionClientConfig { |
||||||
|
mailbox: Option<String>, |
||||||
|
interchain_gas_paymaster: Option<String>, |
||||||
|
interchain_security_module: Option<String>, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
struct OptionalOwnableConfig { |
||||||
|
owner: Option<String>, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
struct TokenConfig { |
||||||
|
#[serde(flatten)] |
||||||
|
token_type: TokenType, |
||||||
|
foreign_deployment: Option<String>, |
||||||
|
#[serde(flatten)] |
||||||
|
decimal_metadata: DecimalMetadata, |
||||||
|
#[serde(flatten)] |
||||||
|
ownable: OptionalOwnableConfig, |
||||||
|
#[serde(flatten)] |
||||||
|
connection_client: OptionalConnectionClientConfig, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
pub struct RpcUrlConfig { |
||||||
|
pub http: String, |
||||||
|
} |
||||||
|
|
||||||
|
/// An abridged version of the Typescript ChainMetadata
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)] |
||||||
|
#[serde(rename_all = "camelCase")] |
||||||
|
pub struct ChainMetadata { |
||||||
|
chain_id: u32, |
||||||
|
/// Hyperlane domain, only required if differs from id above
|
||||||
|
domain_id: Option<u32>, |
||||||
|
name: String, |
||||||
|
/// Collection of RPC endpoints
|
||||||
|
public_rpc_urls: Vec<RpcUrlConfig>, |
||||||
|
} |
||||||
|
|
||||||
|
impl ChainMetadata { |
||||||
|
fn client(&self) -> RpcClient { |
||||||
|
RpcClient::new_with_commitment( |
||||||
|
self.public_rpc_urls[0].http.clone(), |
||||||
|
CommitmentConfig::confirmed(), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fn domain_id(&self) -> u32 { |
||||||
|
self.domain_id.unwrap_or(self.chain_id) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn process_warp_route_cmd(mut ctx: Context, cmd: WarpRouteCmd) { |
||||||
|
match cmd.cmd { |
||||||
|
WarpRouteSubCmd::Deploy(deploy) => { |
||||||
|
let token_config_file = File::open(deploy.token_config_file).unwrap(); |
||||||
|
let token_configs: HashMap<String, TokenConfig> = |
||||||
|
serde_json::from_reader(token_config_file).unwrap(); |
||||||
|
|
||||||
|
let chain_config_file = File::open(deploy.chain_config_file).unwrap(); |
||||||
|
let chain_configs: HashMap<String, ChainMetadata> = |
||||||
|
serde_json::from_reader(chain_config_file).unwrap(); |
||||||
|
|
||||||
|
let environments_dir = |
||||||
|
create_new_directory(&deploy.environments_dir, &deploy.environment); |
||||||
|
|
||||||
|
let artifacts_dir = create_new_directory(&environments_dir, "warp-routes"); |
||||||
|
let warp_route_dir = create_new_directory(&artifacts_dir, &deploy.warp_route_name); |
||||||
|
let keys_dir = create_new_directory(&warp_route_dir, "keys"); |
||||||
|
|
||||||
|
let foreign_deployments = token_configs |
||||||
|
.iter() |
||||||
|
.filter(|(_, token_config)| token_config.foreign_deployment.is_some()) |
||||||
|
.map(|(chain_name, token_config)| { |
||||||
|
let chain_config = chain_configs.get(chain_name).unwrap(); |
||||||
|
( |
||||||
|
chain_config.domain_id(), |
||||||
|
hex_or_base58_to_h256(token_config.foreign_deployment.as_ref().unwrap()) |
||||||
|
.unwrap(), |
||||||
|
) |
||||||
|
}) |
||||||
|
.collect::<HashMap<u32, H256>>(); |
||||||
|
|
||||||
|
let mut routers: HashMap<u32, H256> = foreign_deployments; |
||||||
|
|
||||||
|
let token_configs_to_deploy = token_configs |
||||||
|
.into_iter() |
||||||
|
.filter(|(_, token_config)| token_config.foreign_deployment.is_none()) |
||||||
|
.collect::<HashMap<_, _>>(); |
||||||
|
|
||||||
|
// Deploy to chains that don't have a foreign deployment
|
||||||
|
for (chain_name, token_config) in token_configs_to_deploy.iter() { |
||||||
|
let chain_config = chain_configs |
||||||
|
.get(chain_name) |
||||||
|
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name)); |
||||||
|
|
||||||
|
if token_config.ownable.owner.is_some() { |
||||||
|
println!("WARNING: Ownership transfer is not yet supported in this deploy tooling, ownership is granted to the payer account"); |
||||||
|
} |
||||||
|
|
||||||
|
let program_id = deploy_warp_route( |
||||||
|
&mut ctx, |
||||||
|
&keys_dir, |
||||||
|
&deploy.environments_dir, |
||||||
|
&deploy.environment, |
||||||
|
&deploy.built_so_dir, |
||||||
|
chain_config, |
||||||
|
token_config, |
||||||
|
deploy.ata_payer_funding_amount, |
||||||
|
); |
||||||
|
|
||||||
|
routers.insert( |
||||||
|
chain_config.domain_id(), |
||||||
|
H256::from_slice(&program_id.to_bytes()[..]), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Now enroll routers
|
||||||
|
for (chain_name, _) in token_configs_to_deploy { |
||||||
|
let chain_config = chain_configs |
||||||
|
.get(&chain_name) |
||||||
|
.unwrap_or_else(|| panic!("Chain config not found for chain: {}", chain_name)); |
||||||
|
|
||||||
|
let domain_id = chain_config.domain_id(); |
||||||
|
let program_id: Pubkey = |
||||||
|
Pubkey::new_from_array(*routers.get(&domain_id).unwrap().as_fixed_bytes()); |
||||||
|
|
||||||
|
let enrolled_routers = get_routers(&chain_config.client(), &program_id).unwrap(); |
||||||
|
|
||||||
|
let expected_routers = routers |
||||||
|
.iter() |
||||||
|
.filter(|(router_domain_id, _)| *router_domain_id != &domain_id) |
||||||
|
.map(|(domain, router)| { |
||||||
|
( |
||||||
|
*domain, |
||||||
|
RemoteRouterConfig { |
||||||
|
domain: *domain, |
||||||
|
router: Some(*router), |
||||||
|
}, |
||||||
|
) |
||||||
|
}) |
||||||
|
.collect::<HashMap<u32, RemoteRouterConfig>>(); |
||||||
|
|
||||||
|
// Routers to enroll (or update to a Some value)
|
||||||
|
let routers_to_enroll = expected_routers |
||||||
|
.iter() |
||||||
|
.filter(|(domain, router_config)| { |
||||||
|
enrolled_routers.get(domain) != router_config.router.as_ref() |
||||||
|
}) |
||||||
|
.map(|(_, router_config)| router_config.clone()); |
||||||
|
|
||||||
|
// Routers to remove
|
||||||
|
let routers_to_unenroll = enrolled_routers |
||||||
|
.iter() |
||||||
|
.filter(|(domain, _)| !expected_routers.contains_key(domain)) |
||||||
|
.map(|(domain, _)| RemoteRouterConfig { |
||||||
|
domain: *domain, |
||||||
|
router: None, |
||||||
|
}); |
||||||
|
|
||||||
|
// All router config changes
|
||||||
|
let router_configs = routers_to_enroll |
||||||
|
.chain(routers_to_unenroll) |
||||||
|
.collect::<Vec<RemoteRouterConfig>>(); |
||||||
|
|
||||||
|
if !router_configs.is_empty() { |
||||||
|
println!( |
||||||
|
"Enrolling routers for chain: {}, program_id {}, routers: {:?}", |
||||||
|
chain_name, program_id, router_configs, |
||||||
|
); |
||||||
|
|
||||||
|
ctx.instructions.push( |
||||||
|
enroll_remote_routers_instruction( |
||||||
|
program_id, |
||||||
|
ctx.payer.pubkey(), |
||||||
|
router_configs, |
||||||
|
) |
||||||
|
.unwrap(), |
||||||
|
); |
||||||
|
ctx.send_transaction_with_client(&chain_config.client(), &[&ctx.payer]); |
||||||
|
ctx.instructions.clear(); |
||||||
|
} else { |
||||||
|
println!( |
||||||
|
"No router changes for chain: {}, program_id {}", |
||||||
|
chain_name, program_id |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let routers_by_name: HashMap<String, H256> = routers |
||||||
|
.iter() |
||||||
|
.map(|(domain_id, router)| { |
||||||
|
( |
||||||
|
chain_configs |
||||||
|
.iter() |
||||||
|
.find(|(_, chain_config)| chain_config.domain_id() == *domain_id) |
||||||
|
.unwrap() |
||||||
|
.0 |
||||||
|
.clone(), |
||||||
|
*router, |
||||||
|
) |
||||||
|
}) |
||||||
|
.collect::<HashMap<String, H256>>(); |
||||||
|
write_program_ids(&warp_route_dir, &routers_by_name); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)] |
||||||
|
fn deploy_warp_route( |
||||||
|
ctx: &mut Context, |
||||||
|
key_dir: &Path, |
||||||
|
environments_dir: &Path, |
||||||
|
environment: &str, |
||||||
|
built_so_dir: &Path, |
||||||
|
chain_config: &ChainMetadata, |
||||||
|
token_config: &TokenConfig, |
||||||
|
ata_payer_funding_amount: Option<u64>, |
||||||
|
) -> Pubkey { |
||||||
|
println!( |
||||||
|
"Attempting deploy on chain: {}\nToken config: {:?}", |
||||||
|
chain_config.name, token_config |
||||||
|
); |
||||||
|
|
||||||
|
let (keypair, keypair_path) = create_and_write_keypair( |
||||||
|
key_dir, |
||||||
|
format!( |
||||||
|
"{}-{}.json", |
||||||
|
token_config.token_type.program_name(), |
||||||
|
chain_config.name |
||||||
|
) |
||||||
|
.as_str(), |
||||||
|
true, |
||||||
|
); |
||||||
|
let program_id = keypair.pubkey(); |
||||||
|
|
||||||
|
deploy_program_idempotent( |
||||||
|
&ctx.payer_path, |
||||||
|
&keypair, |
||||||
|
keypair_path.to_str().unwrap(), |
||||||
|
built_so_dir |
||||||
|
.join(format!("{}.so", token_config.token_type.program_name())) |
||||||
|
.to_str() |
||||||
|
.unwrap(), |
||||||
|
&chain_config.public_rpc_urls[0].http, |
||||||
|
// Not used
|
||||||
|
"/", |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
let core_program_ids = read_core_program_ids(environments_dir, environment, &chain_config.name); |
||||||
|
init_warp_route_idempotent( |
||||||
|
ctx, |
||||||
|
&chain_config.client(), |
||||||
|
&core_program_ids, |
||||||
|
chain_config, |
||||||
|
token_config, |
||||||
|
program_id, |
||||||
|
ata_payer_funding_amount, |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
match &token_config.token_type { |
||||||
|
TokenType::Native => { |
||||||
|
println!("Deploying native token"); |
||||||
|
} |
||||||
|
TokenType::Synthetic(_token_metadata) => { |
||||||
|
println!("Deploying synthetic token"); |
||||||
|
} |
||||||
|
TokenType::Collateral(_collateral_info) => { |
||||||
|
println!("Deploying collateral token"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
program_id |
||||||
|
} |
||||||
|
|
||||||
|
fn init_warp_route_idempotent( |
||||||
|
ctx: &mut Context, |
||||||
|
client: &RpcClient, |
||||||
|
core_program_ids: &CoreProgramIds, |
||||||
|
_chain_config: &ChainMetadata, |
||||||
|
token_config: &TokenConfig, |
||||||
|
program_id: Pubkey, |
||||||
|
ata_payer_funding_amount: Option<u64>, |
||||||
|
) -> Result<(), ProgramError> { |
||||||
|
let (token_pda, _token_bump) = |
||||||
|
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id); |
||||||
|
|
||||||
|
if let Some(ata_payer_funding_amount) = ata_payer_funding_amount { |
||||||
|
if matches!( |
||||||
|
token_config.token_type, |
||||||
|
TokenType::Collateral(_) | TokenType::Synthetic(_) |
||||||
|
) { |
||||||
|
fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if account_exists(client, &token_pda).unwrap() { |
||||||
|
println!("Token PDA already exists, skipping init"); |
||||||
|
return Ok(()); |
||||||
|
} |
||||||
|
|
||||||
|
init_warp_route( |
||||||
|
ctx, |
||||||
|
client, |
||||||
|
core_program_ids, |
||||||
|
_chain_config, |
||||||
|
token_config, |
||||||
|
program_id, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fn fund_ata_payer_up_to( |
||||||
|
ctx: &mut Context, |
||||||
|
client: &RpcClient, |
||||||
|
program_id: Pubkey, |
||||||
|
ata_payer_funding_amount: u64, |
||||||
|
) { |
||||||
|
let (ata_payer_account, _ata_payer_bump) = Pubkey::find_program_address( |
||||||
|
hyperlane_sealevel_token::hyperlane_token_ata_payer_pda_seeds!(), |
||||||
|
&program_id, |
||||||
|
); |
||||||
|
|
||||||
|
let current_balance = client.get_balance(&ata_payer_account).unwrap(); |
||||||
|
|
||||||
|
let funding_amount = ata_payer_funding_amount.saturating_sub(current_balance); |
||||||
|
|
||||||
|
if funding_amount == 0 { |
||||||
|
println!("ATA payer fully funded with balance of {}", current_balance); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
println!( |
||||||
|
"Funding ATA payer {} with funding_amount {} to reach total balance of {}", |
||||||
|
ata_payer_account, funding_amount, ata_payer_funding_amount |
||||||
|
); |
||||||
|
ctx.instructions |
||||||
|
.push(solana_program::system_instruction::transfer( |
||||||
|
&ctx.payer.pubkey(), |
||||||
|
&ata_payer_account, |
||||||
|
funding_amount, |
||||||
|
)); |
||||||
|
ctx.send_transaction_with_client(client, &[&ctx.payer]); |
||||||
|
ctx.instructions.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
fn init_warp_route( |
||||||
|
ctx: &mut Context, |
||||||
|
client: &RpcClient, |
||||||
|
core_program_ids: &CoreProgramIds, |
||||||
|
_chain_config: &ChainMetadata, |
||||||
|
token_config: &TokenConfig, |
||||||
|
program_id: Pubkey, |
||||||
|
) -> Result<(), ProgramError> { |
||||||
|
// If the Mailbox was provided as configuration, use that. Otherwise, default to
|
||||||
|
// the Mailbox found in the core program ids.
|
||||||
|
let mailbox = token_config |
||||||
|
.connection_client |
||||||
|
.mailbox |
||||||
|
.as_ref() |
||||||
|
.map(|s| Pubkey::from_str(s).unwrap()) |
||||||
|
.unwrap_or(core_program_ids.mailbox); |
||||||
|
|
||||||
|
let init = Init { |
||||||
|
mailbox, |
||||||
|
interchain_security_module: token_config |
||||||
|
.connection_client |
||||||
|
.interchain_security_module |
||||||
|
.as_ref() |
||||||
|
.map(|s| Pubkey::from_str(s).unwrap()), |
||||||
|
decimals: token_config.decimal_metadata.decimals, |
||||||
|
remote_decimals: token_config.decimal_metadata.remote_decimals(), |
||||||
|
}; |
||||||
|
|
||||||
|
let mut init_instructions = match &token_config.token_type { |
||||||
|
TokenType::Native => vec![ |
||||||
|
hyperlane_sealevel_token_native::instruction::init_instruction( |
||||||
|
program_id, |
||||||
|
ctx.payer.pubkey(), |
||||||
|
init, |
||||||
|
)?, |
||||||
|
], |
||||||
|
TokenType::Synthetic(_token_metadata) => { |
||||||
|
let decimals = init.decimals; |
||||||
|
|
||||||
|
let mut instructions = vec![hyperlane_sealevel_token::instruction::init_instruction( |
||||||
|
program_id, |
||||||
|
ctx.payer.pubkey(), |
||||||
|
init, |
||||||
|
)?]; |
||||||
|
|
||||||
|
let (mint_account, _mint_bump) = |
||||||
|
Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id); |
||||||
|
// TODO: Also set Metaplex metadata?
|
||||||
|
instructions.push( |
||||||
|
spl_token_2022::instruction::initialize_mint2( |
||||||
|
&spl_token_2022::id(), |
||||||
|
&mint_account, |
||||||
|
&mint_account, |
||||||
|
None, |
||||||
|
decimals, |
||||||
|
) |
||||||
|
.unwrap(), |
||||||
|
); |
||||||
|
|
||||||
|
instructions |
||||||
|
} |
||||||
|
TokenType::Collateral(collateral_info) => { |
||||||
|
vec![ |
||||||
|
hyperlane_sealevel_token_collateral::instruction::init_instruction( |
||||||
|
program_id, |
||||||
|
ctx.payer.pubkey(), |
||||||
|
init, |
||||||
|
collateral_info |
||||||
|
.spl_token_program |
||||||
|
.as_ref() |
||||||
|
.expect("Cannot initalize collateral warp route without SPL token program") |
||||||
|
.program_id(), |
||||||
|
collateral_info.mint.parse().expect("Invalid mint address"), |
||||||
|
)?, |
||||||
|
] |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
ctx.instructions.append(&mut init_instructions); |
||||||
|
ctx.send_transaction_with_client(client, &[&ctx.payer]); |
||||||
|
ctx.instructions.clear(); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn get_routers( |
||||||
|
client: &RpcClient, |
||||||
|
token_program_id: &Pubkey, |
||||||
|
) -> Result<HashMap<u32, H256>, ClientError> { |
||||||
|
let (token_pda, _token_bump) = |
||||||
|
Pubkey::find_program_address(hyperlane_token_pda_seeds!(), token_program_id); |
||||||
|
|
||||||
|
let account = client.get_account(&token_pda)?; |
||||||
|
let token_data = HyperlaneTokenAccount::<()>::fetch(&mut &account.data[..]) |
||||||
|
.unwrap() |
||||||
|
.into_inner(); |
||||||
|
|
||||||
|
Ok(token_data.remote_routers) |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)] |
||||||
|
struct SerializedProgramId { |
||||||
|
hex: String, |
||||||
|
base58: String, |
||||||
|
} |
||||||
|
|
||||||
|
fn write_program_ids(warp_route_dir: &Path, routers: &HashMap<String, H256>) { |
||||||
|
let serialized_program_ids = routers |
||||||
|
.iter() |
||||||
|
.map(|(chain_name, router)| { |
||||||
|
( |
||||||
|
chain_name.clone(), |
||||||
|
SerializedProgramId { |
||||||
|
hex: format!("0x{}", hex::encode(router)), |
||||||
|
base58: Pubkey::new_from_array(router.to_fixed_bytes()).to_string(), |
||||||
|
}, |
||||||
|
) |
||||||
|
}) |
||||||
|
.collect::<HashMap<String, SerializedProgramId>>(); |
||||||
|
|
||||||
|
let program_ids_file = warp_route_dir.join("program-ids.json"); |
||||||
|
let program_ids_file = File::create(program_ids_file).unwrap(); |
||||||
|
serde_json::to_writer_pretty(program_ids_file, &serialized_program_ids).unwrap(); |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
[113,244,152,170,85,122,42,51,10,74,244,18,91,8,135,77,156,19,172,122,139,50,248,3,186,184,186,140,110,165,78,161,76,88,146,213,185,127,121,92,132,2,249,73,19,192,73,170,105,85,247,241,48,175,67,28,165,29,224,252,173,165,38,140] |
@ -0,0 +1 @@ |
|||||||
|
[135,153,145,193,50,88,169,205,206,171,48,1,17,242,3,43,225,72,101,163,93,126,105,165,159,44,243,196,182,240,4,87,22,253,47,198,217,75,23,60,181,129,251,103,140,170,111,35,152,97,16,23,64,17,198,239,79,225,120,141,55,38,60,86] |
@ -0,0 +1 @@ |
|||||||
|
[252,76,67,201,250,68,86,32,216,136,163,46,192,20,249,175,209,94,101,235,24,240,204,4,246,159,180,138,253,20,48,146,182,104,250,124,231,168,239,248,95,199,219,250,126,156,57,113,83,209,232,171,10,90,153,238,72,138,186,34,77,87,172,211] |
@ -0,0 +1,5 @@ |
|||||||
|
{ |
||||||
|
"mailbox": "692KZJaoe2KRcD6uhCQDLLXnLNA5ZLnfvdqjE4aX9iu1", |
||||||
|
"validator_announce": "DH43ae1LwemXAboWwSh8zc9pG8j72gKUEXNi57w8fEnn", |
||||||
|
"multisig_ism_message_id": "2YjtZDiUoptoSsA5eVrDCcX6wxNK6YoEVW7y82x5Z2fw" |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue