From 44bc3b874a6695a75ff08171de594972e6b06b59 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 24 Oct 2024 17:38:46 +0200 Subject: [PATCH 01/62] feat: solana vault swaps elections --- engine/src/witness/sol.rs | 27 ++- state-chain/chains/src/lib.rs | 6 + state-chain/chains/src/sol/api.rs | 35 +++- .../cf-elections/src/electoral_systems.rs | 1 + .../solana_swap_accounts_tracking.rs | 195 ++++++++++++++++++ state-chain/pallets/cf-elections/src/lib.rs | 1 + .../runtime/src/chainflip/solana_elections.rs | 105 +++++++++- 7 files changed, 358 insertions(+), 12 deletions(-) create mode 100644 state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index 8449bf96bf..f24044964e 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -21,9 +21,9 @@ use futures::FutureExt; use pallet_cf_elections::{electoral_system::ElectoralSystem, vote_storage::VoteStorage}; use state_chain_runtime::{ chainflip::solana_elections::{ - SolanaBlockHeightTracking, SolanaEgressWitnessing, SolanaElectoralSystemRunner, - SolanaFeeTracking, SolanaIngressTracking, SolanaLiveness, SolanaNonceTracking, - TransactionSuccessDetails, + SolanaBlockHeightTracking, SolanaEgressWitnessing, SolanaElectoralSystem, + SolanaElectoralSystemRunner, SolanaFeeTracking, SolanaIngressTracking, SolanaLiveness, + SolanaNonceTracking, SolanaVaultSwapTracking, TransactionSuccessDetails, }, SolanaInstance, }; @@ -175,6 +175,25 @@ impl VoterApi for SolanaLivenessVoter { } } +#[derive(Clone)] +struct SolanaVaultSwapsVoter { + client: SolRetryRpcClient, +} + +#[async_trait::async_trait] +impl VoterApi for SolanaVaultSwapsVoter { + async fn vote( + &self, + _settings: ::ElectoralSettings, + _properties: ::ElectionProperties, + ) -> Result< + <::Vote as VoteStorage>::Vote, + anyhow::Error, + > { + todo!() + } +} + pub async fn start( scope: &Scope<'_, anyhow::Error>, client: SolRetryRpcClient, @@ -201,7 +220,7 @@ where SolanaIngressTrackingVoter { client: client.clone() }, SolanaNonceTrackingVoter { client: client.clone() }, SolanaEgressWitnessingVoter { client: client.clone() }, - SolanaLivenessVoter { client }, + SolanaVaultSwapsVoter { client }, )), ) .continuously_vote() diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 763c1a5b82..ecf4bac93e 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -489,6 +489,12 @@ pub trait RegisterRedemption: ApiCall<::ChainCrypto> { ) -> Self; } +pub trait CloseSolanaVaultSwapAccounts: ApiCall<::ChainCrypto> { + fn new_unsigned( + accounts: Vec, + ) -> Result; +} + #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] pub enum AllBatchError { /// Empty transaction - the call is not required. diff --git a/state-chain/chains/src/sol/api.rs b/state-chain/chains/src/sol/api.rs index 543db442d8..2a4cd8f27a 100644 --- a/state-chain/chains/src/sol/api.rs +++ b/state-chain/chains/src/sol/api.rs @@ -4,6 +4,7 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound}; use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; use sol_prim::consts::SOL_USDC_DECIMAL; use sp_core::RuntimeDebug; use sp_std::{vec, vec::Vec}; @@ -18,9 +19,9 @@ use crate::{ SolAsset, SolHash, SolTransaction, SolanaCrypto, }, AllBatch, AllBatchError, ApiCall, CcmChannelMetadata, Chain, ChainCrypto, ChainEnvironment, - ConsolidateCall, ConsolidationError, ExecutexSwapAndCall, ExecutexSwapAndCallError, - FetchAssetParams, ForeignChainAddress, SetAggKeyWithAggKey, SetGovKeyWithAggKey, Solana, - TransferAssetParams, TransferFallback, TransferFallbackError, + CloseSolanaVaultSwapAccounts, ConsolidateCall, ConsolidationError, ExecutexSwapAndCall, + ExecutexSwapAndCallError, FetchAssetParams, ForeignChainAddress, SetAggKeyWithAggKey, + SetGovKeyWithAggKey, Solana, TransferAssetParams, TransferFallback, TransferFallbackError, }; use cf_primitives::{EgressId, ForeignChain}; @@ -37,7 +38,25 @@ pub struct ApiEnvironment; pub struct CurrentAggKey; pub type DurableNonceAndAccount = (SolAddress, SolHash); -pub type EventAccountAndSender = (SolAddress, SolAddress); + +#[derive( + Clone, + Encode, + Decode, + PartialEq, + Debug, + TypeInfo, + Copy, + Serialize, + Deserialize, + Ord, + PartialOrd, + Eq, +)] +pub struct ContractSwapAccountAndSender { + pub contract_swap_account: SolAddress, + pub swap_sender: SolAddress, +} /// Super trait combining all Environment lookups required for the Solana chain. /// Also contains some calls for easy data retrieval. @@ -522,6 +541,14 @@ impl TransferFallback for SolanaApi { } } +impl CloseSolanaVaultSwapAccounts for SolanaApi { + fn new_unsigned( + accounts: Vec, + ) -> Result { + Self::batch_close_contract_swap_accounts(accounts) + } +} + impl SetGovKeyWithAggKey for SolanaApi { fn new_unsigned( _maybe_old_key: Option<::GovKey>, diff --git a/state-chain/pallets/cf-elections/src/electoral_systems.rs b/state-chain/pallets/cf-elections/src/electoral_systems.rs index bede530e3d..c48ca0ca63 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems.rs @@ -6,6 +6,7 @@ pub mod liveness; pub mod mock; pub mod monotonic_change; pub mod monotonic_median; +pub mod solana_swap_accounts_tracking; pub mod unsafe_median; #[cfg(test)] diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs new file mode 100644 index 0000000000..36c32d2c27 --- /dev/null +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -0,0 +1,195 @@ +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_std::collections::btree_map::BTreeMap; + +use crate::{ + electoral_system::{ + AuthorityVoteOf, ConsensusVotes, ElectionReadAccess, ElectionWriteAccess, ElectoralSystem, + ElectoralWriteAccess, VotePropertiesOf, + }, + vote_storage::{self, VoteStorage}, + CorruptStorageError, ElectionIdentifier, +}; +use cf_chains::sol::{ + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES, + MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, + NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES, +}; +use cf_utilities::success_threshold_from_share_count; +use frame_support::{ + pallet_prelude::{MaybeSerializeDeserialize, Member}, + sp_runtime::traits::CheckedSub, + Parameter, +}; +use itertools::Itertools; +use sp_std::vec::Vec; + +pub trait SolanaVaultSwapAccountsHook { + fn close_accounts(accounts: Vec) -> Result<(), E>; + fn initiate_vault_swap(swap_details: SwapDetails); + fn get_number_of_available_sol_nonce_accounts() -> usize; +} + +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode)] +pub struct SolanaVaultSwapsElectoralState { + pub block_number_last_closed_accounts: BlockNumber, + pub witnessed_open_accounts: Vec, + pub closure_initiated_accounts: Vec, +} + +pub struct SolanaVaultSwapAccounts< + Account, + SwapDetails, + BlockNumber, + Settings, + Hook, + ValidatorId, + E, +> { + _phantom: core::marker::PhantomData<( + Account, + SwapDetails, + BlockNumber, + Settings, + Hook, + ValidatorId, + E, + )>, +} +impl< + E: sp_std::fmt::Debug + 'static, + Account: MaybeSerializeDeserialize + Member + Parameter + Ord, + SwapDetails: MaybeSerializeDeserialize + Member + Parameter + Ord, + BlockNumber: MaybeSerializeDeserialize + Member + Parameter + Ord + CheckedSub + Into + Copy, + Settings: Member + Parameter + MaybeSerializeDeserialize + Eq, + Hook: SolanaVaultSwapAccountsHook + 'static, + ValidatorId: Member + Parameter + Ord + MaybeSerializeDeserialize, + > ElectoralSystem + for SolanaVaultSwapAccounts +{ + type ValidatorId = ValidatorId; + type ElectoralUnsynchronisedState = SolanaVaultSwapsElectoralState; + type ElectoralUnsynchronisedStateMapKey = (); + type ElectoralUnsynchronisedStateMapValue = (); + + type ElectoralUnsynchronisedSettings = (); + type ElectoralSettings = Settings; + type ElectionIdentifierExtra = (); + type ElectionProperties = (); + type ElectionState = (); + type Vote = vote_storage::bitmap::Bitmap>; + type Consensus = Vec<(Account, SwapDetails)>; + type OnFinalizeContext = BlockNumber; + type OnFinalizeReturn = (); + + fn generate_vote_properties( + _election_identifier: ElectionIdentifier, + _previous_vote: Option<(VotePropertiesOf, AuthorityVoteOf)>, + _vote: &::PartialVote, + ) -> Result, CorruptStorageError> { + Ok(()) + } + + fn on_finalize>( + electoral_access: &mut ElectoralAccess, + election_identifiers: Vec>, + current_block_number: &Self::OnFinalizeContext, + ) -> Result { + if let Some(election_identifier) = election_identifiers + .into_iter() + .at_most_one() + .map_err(|_| CorruptStorageError::new())? + { + let mut election_access = electoral_access.election_mut(election_identifier)?; + if let Some(consensus) = election_access.check_consensus()?.has_consensus() { + election_access.delete(); + electoral_access.new_election((), (), ())?; + electoral_access.mutate_unsynchronised_state( + |_electoral_access, unsynchronised_state| { + unsynchronised_state.witnessed_open_accounts.extend( + consensus.into_iter().map(|(account, swap_details)| { + Hook::initiate_vault_swap(swap_details); + account + }), + ); + + Ok(()) + }, + )?; + } + } else { + electoral_access.new_election((), (), ())?; + } + + electoral_access.mutate_unsynchronised_state(|_, unsynchronised_state| { + //let current_block_number = BlockNumbrer; + // //frame_system::Pallet::::current_block_number(); + if Hook::get_number_of_available_sol_nonce_accounts() > + NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && + (unsynchronised_state.witnessed_open_accounts.len() >= + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES || + (*current_block_number) + .checked_sub(&unsynchronised_state.block_number_last_closed_accounts) + .expect("") + .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) + { + let accounts_to_close: Vec<_> = + if unsynchronised_state.witnessed_open_accounts.len() > + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES + { + unsynchronised_state + .witnessed_open_accounts + .drain(..MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES) + .collect() + } else { + sp_std::mem::take(&mut unsynchronised_state.witnessed_open_accounts) + }; + match Hook::close_accounts(accounts_to_close.clone()) { + Ok(()) => { + unsynchronised_state.block_number_last_closed_accounts = + *current_block_number; + unsynchronised_state.closure_initiated_accounts.extend(accounts_to_close); + Ok(()) + }, + Err(e) => { + log::error!( + "failed to build Solana CloseSolanaVaultSwapAccounts apicall: {:?}", + e + ); + Err(CorruptStorageError::new()) + }, + } + } else { + Ok(()) + } + }) + } + + fn check_consensus>( + _election_identifier: ElectionIdentifier, + _election_access: &ElectionAccess, + _previous_consensus: Option<&Self::Consensus>, + consensus_votes: ConsensusVotes, + ) -> Result, CorruptStorageError> { + let num_authorities = consensus_votes.num_authorities(); + let success_threshold = success_threshold_from_share_count(num_authorities); + let active_votes = consensus_votes.active_votes(); + let num_active_votes = active_votes.len() as u32; + Ok(if num_active_votes >= success_threshold { + let mut counts = BTreeMap::new(); + for vote in active_votes { + counts.entry(vote).and_modify(|count| *count += 1).or_insert(1); + } + counts.iter().find_map(|(vote, count)| { + if *count >= success_threshold { + Some(vote.clone()) + } else { + None + } + }) + } else { + None + }) + } +} diff --git a/state-chain/pallets/cf-elections/src/lib.rs b/state-chain/pallets/cf-elections/src/lib.rs index f309812023..48985e3a04 100644 --- a/state-chain/pallets/cf-elections/src/lib.rs +++ b/state-chain/pallets/cf-elections/src/lib.rs @@ -136,6 +136,7 @@ pub mod pallet { use cf_chains::benchmarking_value::BenchmarkValue; use cf_primitives::{AuthorityCount, EpochIndex}; use cf_traits::{AccountRoleRegistry, Chainflip, EpochInfo}; + use frame_support::sp_runtime::traits::{Header, HeaderProvider}; use crate::electoral_system::ConsensusStatus; pub use access_impls::RunnerStorageAccess; diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 792e52661b..0310c33347 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -12,8 +12,14 @@ use cf_chains::{ }; use cf_runtime_utilities::log_or_panic; use cf_traits::{ - offence_reporting::OffenceReporter, AdjustedFeeEstimationApi, Chainflip, - ElectionEgressWitnesser, GetBlockHeight, IngressSource, SolanaNonceWatch, + instances::ChainInstanceAlias, + offence_reporting::OffenceReporter, + sol::{ + api::{SolanaApi, SolanaTransactionBuildingError}, + SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, SolanaCrypto, + }, + AdjustedFeeEstimationApi, Broadcaster, Chain, Chainflip, ElectionEgressWitnesser, + FeeEstimationApi, GetBlockHeight, IngressSource, Solana, SolanaNonceWatch, }; use codec::{Decode, Encode}; @@ -22,15 +28,25 @@ use pallet_cf_elections::{ electoral_system::{ElectoralReadAccess, ElectoralSystem}, electoral_systems::{ self, - composite::{tuple_6_impls::Hooks, CompositeRunner}, + change::OnChangeHook, + composite::{tuple_6_impls::Hooks, Composite, CompositeRunner, Translator}, egress_success::OnEgressSuccess, liveness::OnCheckComplete, monotonic_change::OnChangeHook, monotonic_median::MedianChangeHook, + solana_swap_accounts_tracking::{ + SolanaVaultSwapAccountsHook, SolanaVaultSwapsElectoralState, + }, }, CorruptStorageError, ElectionIdentifier, InitialState, InitialStateOf, RunnerStorageAccess, }; +use crate::{RuntimeOrigin, SolanaIngressEgress}; +use cf_chains::{ + address::EncodedAddress, assets::any::Asset, sol::api::ContractSwapAccountAndSender, + CloseSolanaVaultSwapAccounts, +}; +use cf_primitives::{AssetAmount, TransactionHash}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{DispatchResult, FixedPointNumber, FixedU128}; @@ -40,6 +56,8 @@ use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; use cf_chains::benchmarking_value::BenchmarkValue; use sol_prim::SlotNumber; +use super::SolEnvironment; + type Instance = ::Instance; pub type SolanaElectoralSystemRunner = CompositeRunner< @@ -50,6 +68,7 @@ pub type SolanaElectoralSystemRunner = CompositeRunner< SolanaNonceTracking, SolanaEgressWitnessing, SolanaLiveness, + SolanaVaultSwapTracking, ), ::ValidatorId, RunnerStorageAccess, @@ -74,6 +93,11 @@ pub fn initial_state( (), (), (), + SolanaVaultSwapsElectoralState { + block_number_last_closed_accounts: 0, + witnessed_open_accounts: vec![], + closure_initiated_accounts: vec![], + }, ), unsynchronised_settings: ( (), @@ -90,6 +114,7 @@ pub fn initial_state( (), (), LIVENESS_CHECK_DURATION, + (), ), } } @@ -145,6 +170,16 @@ impl OnCheckComplete<::ValidatorId> for OnCheckCompleteHoo Reputation::report_many(Offence::FailedLivenessCheck(ForeignChain::Solana), validator_ids); } } +pub type SolanaVaultSwapTracking = + electoral_systems::solana_swap_accounts_tracking::SolanaVaultSwapAccounts< + ContractSwapAccountAndSender, + SolanaVaultSwapDetails, + BlockNumberFor, + (), + SolanaVaultSwapsHandler, + ::ValidatorId, + SolanaTransactionBuildingError, + >; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, TypeInfo)] pub struct TransactionSuccessDetails { @@ -227,6 +262,7 @@ impl SolanaNonceTracking, SolanaEgressWitnessing, SolanaLiveness, + SolanaVaultSwapTracking, > for SolanaElectionHooks { fn on_finalize( @@ -237,6 +273,7 @@ impl nonce_tracking_identifiers, egress_witnessing_identifiers, liveness_identifiers, + vault_swap_identifiers, ): ( Vec< ElectionIdentifier< @@ -262,8 +299,14 @@ impl >, >, Vec::ElectionIdentifierExtra>>, + Vec< + ElectionIdentifier< + ::ElectionIdentifierExtra, + >, + >, ), ) -> Result<(), CorruptStorageError> { + let current_SC_block_number = crate::System::block_number(); let block_height = SolanaBlockHeightTracking::on_finalize::< DerivedElectoralAccess< _, @@ -273,7 +316,7 @@ impl >(block_height_identifiers, &())?; SolanaLiveness::on_finalize::< DerivedElectoralAccess<_, SolanaLiveness, RunnerStorageAccess>, - >(liveness_identifiers, &(crate::System::block_number(), block_height))?; + >(liveness_identifiers, &(current_sc_block_number, block_height))?; SolanaFeeTracking::on_finalize::< DerivedElectoralAccess< _, @@ -302,6 +345,13 @@ impl RunnerStorageAccess, >, >(ingress_identifiers, &block_height)?; + SolanaVaultSwapTracking::on_finalize::< + DerivedElectoralAccess< + _, + SolanaVaultSwapTracking, + RunnerStorageAccess, + >, + >(vault_swap_identifiers, current_sc_block_number)?; Ok(()) } } @@ -472,3 +522,50 @@ impl ElectionEgressWitnesser for SolanaEgressWitnessingTrigger { }) } } + +#[derive( + Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, PartialOrd, Ord, +)] +pub struct SolanaVaultSwapDetails { + from: Asset, + to: Asset, + deposit_amount: AssetAmount, + destination_address: EncodedAddress, + tx_hash: TransactionHash, +} +pub struct SolanaVaultSwapsHandler; + +impl + SolanaVaultSwapAccountsHook< + ContractSwapAccountAndSender, + SolanaVaultSwapDetails, + SolanaTransactionBuildingError, + > for SolanaVaultSwapsHandler +{ + fn initiate_vault_swap(swap_details: SolanaVaultSwapDetails) { + let _ = SolanaIngressEgress::contract_swap_request( + RuntimeOrigin::root(), + swap_details.from, + swap_details.to, + swap_details.deposit_amount, + swap_details.destination_address, + swap_details.tx_hash, + ); + } + + fn close_accounts( + accounts: Vec, + ) -> Result<(), SolanaTransactionBuildingError> { + as CloseSolanaVaultSwapAccounts>::new_unsigned(accounts).map( + |apicall| { + let _ = >::threshold_sign_and_broadcast( + apicall, + ); + }, + ) + } + + fn get_number_of_available_sol_nonce_accounts() -> usize { + Environment::get_number_of_available_sol_nonce_accounts() + } +} From be5f0759f433f277e1898f0489cf93cd1cd02dbc Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 25 Oct 2024 12:10:50 +0200 Subject: [PATCH 02/62] feat: account closing tracking in elections --- .../solana_swap_accounts_tracking.rs | 30 ++++++++++++------- .../runtime/src/chainflip/solana_elections.rs | 15 +++++----- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 36c32d2c27..4beb8306af 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -1,7 +1,7 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -use sp_std::collections::btree_map::BTreeMap; +use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use crate::{ electoral_system::{ @@ -32,10 +32,18 @@ pub trait SolanaVaultSwapAccountsHook { } #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode)] -pub struct SolanaVaultSwapsElectoralState { +pub struct SolanaVaultSwapsElectoralState { pub block_number_last_closed_accounts: BlockNumber, pub witnessed_open_accounts: Vec, - pub closure_initiated_accounts: Vec, + pub closure_initiated_accounts: BTreeSet, +} + +#[derive( + Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, Ord, PartialOrd, +)] +pub struct SolanaVaultSwapsVote { + pub new_accounts: BTreeSet<(Account, SwapDetails)>, + pub confirm_closed_accounts: BTreeSet, } pub struct SolanaVaultSwapAccounts< @@ -78,8 +86,8 @@ impl< type ElectionIdentifierExtra = (); type ElectionProperties = (); type ElectionState = (); - type Vote = vote_storage::bitmap::Bitmap>; - type Consensus = Vec<(Account, SwapDetails)>; + type Vote = vote_storage::bitmap::Bitmap>; + type Consensus = SolanaVaultSwapsVote; type OnFinalizeContext = BlockNumber; type OnFinalizeReturn = (); @@ -108,12 +116,16 @@ impl< electoral_access.mutate_unsynchronised_state( |_electoral_access, unsynchronised_state| { unsynchronised_state.witnessed_open_accounts.extend( - consensus.into_iter().map(|(account, swap_details)| { - Hook::initiate_vault_swap(swap_details); - account + consensus.new_accounts.iter().map(|(account, swap_details)| { + Hook::initiate_vault_swap((*swap_details).clone()); + (*account).clone() }), ); + consensus.confirm_closed_accounts.into_iter().for_each(|acc| { + unsynchronised_state.closure_initiated_accounts.remove(&acc); + }); + Ok(()) }, )?; @@ -123,8 +135,6 @@ impl< } electoral_access.mutate_unsynchronised_state(|_, unsynchronised_state| { - //let current_block_number = BlockNumbrer; - // //frame_system::Pallet::::current_block_number(); if Hook::get_number_of_available_sol_nonce_accounts() > NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && (unsynchronised_state.witnessed_open_accounts.len() >= diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 0310c33347..2ef137ec32 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -22,6 +22,12 @@ use cf_traits::{ FeeEstimationApi, GetBlockHeight, IngressSource, Solana, SolanaNonceWatch, }; +use crate::{RuntimeOrigin, SolanaIngressEgress}; +use cf_chains::{ + address::EncodedAddress, assets::any::Asset, sol::api::ContractSwapAccountAndSender, + CloseSolanaVaultSwapAccounts, +}; +use cf_primitives::{AssetAmount, TransactionHash}; use codec::{Decode, Encode}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_cf_elections::{ @@ -40,13 +46,6 @@ use pallet_cf_elections::{ }, CorruptStorageError, ElectionIdentifier, InitialState, InitialStateOf, RunnerStorageAccess, }; - -use crate::{RuntimeOrigin, SolanaIngressEgress}; -use cf_chains::{ - address::EncodedAddress, assets::any::Asset, sol::api::ContractSwapAccountAndSender, - CloseSolanaVaultSwapAccounts, -}; -use cf_primitives::{AssetAmount, TransactionHash}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{DispatchResult, FixedPointNumber, FixedU128}; @@ -96,7 +95,7 @@ pub fn initial_state( SolanaVaultSwapsElectoralState { block_number_last_closed_accounts: 0, witnessed_open_accounts: vec![], - closure_initiated_accounts: vec![], + closure_initiated_accounts: BTreeSet::new(), }, ), unsynchronised_settings: ( From b9a66db435ee1c0116463746129b4133f9d9476e Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 25 Oct 2024 13:41:08 +0200 Subject: [PATCH 03/62] chore: clippy --- engine/src/witness/sol.rs | 1 + .../cf-integration-tests/src/mock_runtime.rs | 10 +++++++++- state-chain/chains/src/sol/benchmarking.rs | 13 +++++++++++-- .../solana_swap_accounts_tracking.rs | 16 +++++++++++++++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index f24044964e..ce88bef286 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -175,6 +175,7 @@ impl VoterApi for SolanaLivenessVoter { } } +#[allow(dead_code)] #[derive(Clone)] struct SolanaVaultSwapsVoter { client: SolRetryRpcClient, diff --git a/state-chain/cf-integration-tests/src/mock_runtime.rs b/state-chain/cf-integration-tests/src/mock_runtime.rs index 56251afa19..3f56e86100 100644 --- a/state-chain/cf-integration-tests/src/mock_runtime.rs +++ b/state-chain/cf-integration-tests/src/mock_runtime.rs @@ -10,7 +10,9 @@ use chainflip_node::{ chain_spec::testnet::{EXPIRY_SPAN_IN_SECONDS, REDEMPTION_TTL_SECS}, test_account_from_seed, }; -use pallet_cf_elections::InitialState; +use pallet_cf_elections::{ + electoral_systems::solana_swap_accounts_tracking::SolanaVaultSwapsElectoralState, InitialState, +}; use pallet_cf_validator::SetSizeParameters; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_consensus_grandpa::AuthorityId as GrandpaId; @@ -307,6 +309,11 @@ impl ExtBuilder { (), (), (), + SolanaVaultSwapsElectoralState { + block_number_last_closed_accounts: Default::default(), + witnessed_open_accounts: Default::default(), + closure_initiated_accounts: Default::default(), + }, ), unsynchronised_settings: ( (), @@ -328,6 +335,7 @@ impl ExtBuilder { (), (), BLOCKS_BETWEEN_LIVENESS_CHECKS, + (), ), }), }, diff --git a/state-chain/chains/src/sol/benchmarking.rs b/state-chain/chains/src/sol/benchmarking.rs index 1eb8b75659..06a8dead67 100644 --- a/state-chain/chains/src/sol/benchmarking.rs +++ b/state-chain/chains/src/sol/benchmarking.rs @@ -1,7 +1,8 @@ #![cfg(feature = "runtime-benchmarks")] use super::{ - api::SolanaApi, SolAddress, SolHash, SolMessage, SolSignature, SolTrackedData, SolTransaction, + api::{ContractSwapAccountAndSender, SolanaApi}, + SolAddress, SolHash, SolMessage, SolSignature, SolTrackedData, SolTransaction, SolanaTransactionData, }; @@ -25,7 +26,6 @@ impl BenchmarkValue for SolTrackedData { } } -#[cfg(feature = "runtime-benchmarks")] impl BenchmarkValue for SolMessage { fn benchmark_value() -> Self { Self::new_with_blockhash(&[], None, &SolHash::default().into()) @@ -66,3 +66,12 @@ impl BenchmarkValue for SolanaApi { .expect("Benchmark value for SolApi must work.") } } + +impl BenchmarkValue for ContractSwapAccountAndSender { + fn benchmark_value() -> Self { + Self { + swap_sender: BenchmarkValue::benchmark_value(), + contract_swap_account: BenchmarkValue::benchmark_value(), + } + } +} diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 4beb8306af..70a4406e75 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -3,6 +3,9 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; +#[cfg(feature = "runtime-benchmarks")] +use cf_chains::benchmarking_value::BenchmarkValue; + use crate::{ electoral_system::{ AuthorityVoteOf, ConsensusVotes, ElectionReadAccess, ElectionWriteAccess, ElectoralSystem, @@ -12,7 +15,7 @@ use crate::{ CorruptStorageError, ElectionIdentifier, }; use cf_chains::sol::{ - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES, + api::ContractSwapAccountAndSender, MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES, MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES, }; @@ -38,6 +41,17 @@ pub struct SolanaVaultSwapsElectoralState { pub closure_initiated_accounts: BTreeSet, } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for SolanaVaultSwapsElectoralState { + fn benchmark_value() -> Self { + Self { + block_number_last_closed_accounts: 1u32, + witnessed_open_accounts: vec![BenchmarkValue::benchmark_value()], + closure_initiated_accounts: BTreeSet::from([BenchmarkValue::benchmark_value()]), + } + } +} + #[derive( Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, Ord, PartialOrd, )] From b9ac4db265f01a32167c7c7a6a69d5d1e1ded8e4 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 25 Oct 2024 13:58:17 +0200 Subject: [PATCH 04/62] chore: minor --- .../solana_swap_accounts_tracking.rs | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 70a4406e75..de265867e9 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -127,67 +127,62 @@ impl< if let Some(consensus) = election_access.check_consensus()?.has_consensus() { election_access.delete(); electoral_access.new_election((), (), ())?; - electoral_access.mutate_unsynchronised_state( - |_electoral_access, unsynchronised_state| { - unsynchronised_state.witnessed_open_accounts.extend( - consensus.new_accounts.iter().map(|(account, swap_details)| { - Hook::initiate_vault_swap((*swap_details).clone()); - (*account).clone() - }), - ); + electoral_access.mutate_unsynchronised_state(|_, unsynchronised_state| { + unsynchronised_state.witnessed_open_accounts.extend( + consensus.new_accounts.iter().map(|(account, swap_details)| { + Hook::initiate_vault_swap((*swap_details).clone()); + (*account).clone() + }), + ); - consensus.confirm_closed_accounts.into_iter().for_each(|acc| { - unsynchronised_state.closure_initiated_accounts.remove(&acc); - }); + consensus.confirm_closed_accounts.into_iter().for_each(|acc| { + unsynchronised_state.closure_initiated_accounts.remove(&acc); + }); - Ok(()) - }, - )?; + Ok(()) + })?; } } else { electoral_access.new_election((), (), ())?; } - electoral_access.mutate_unsynchronised_state(|_, unsynchronised_state| { - if Hook::get_number_of_available_sol_nonce_accounts() > - NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && - (unsynchronised_state.witnessed_open_accounts.len() >= - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES || - (*current_block_number) - .checked_sub(&unsynchronised_state.block_number_last_closed_accounts) - .expect("") - .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) + let mut unsynchronised_state = electoral_access.unsynchronised_state()?; + if Hook::get_number_of_available_sol_nonce_accounts() > + NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && + (unsynchronised_state.witnessed_open_accounts.len() >= + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES || + (*current_block_number) + .checked_sub(&unsynchronised_state.block_number_last_closed_accounts) + .expect( + "current block number is always greater than when apicall was last created", + ) + .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) + { + let accounts_to_close: Vec<_> = if unsynchronised_state.witnessed_open_accounts.len() > + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES { - let accounts_to_close: Vec<_> = - if unsynchronised_state.witnessed_open_accounts.len() > - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES - { - unsynchronised_state - .witnessed_open_accounts - .drain(..MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES) - .collect() - } else { - sp_std::mem::take(&mut unsynchronised_state.witnessed_open_accounts) - }; - match Hook::close_accounts(accounts_to_close.clone()) { - Ok(()) => { - unsynchronised_state.block_number_last_closed_accounts = - *current_block_number; - unsynchronised_state.closure_initiated_accounts.extend(accounts_to_close); - Ok(()) - }, - Err(e) => { - log::error!( - "failed to build Solana CloseSolanaVaultSwapAccounts apicall: {:?}", - e - ); - Err(CorruptStorageError::new()) - }, - } + unsynchronised_state + .witnessed_open_accounts + .drain(..MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES) + .collect() } else { - Ok(()) + sp_std::mem::take(&mut unsynchronised_state.witnessed_open_accounts) + }; + match Hook::close_accounts(accounts_to_close.clone()) { + Ok(()) => { + unsynchronised_state.block_number_last_closed_accounts = *current_block_number; + unsynchronised_state.closure_initiated_accounts.extend(accounts_to_close); + electoral_access.set_unsynchronised_state(unsynchronised_state)?; + }, + Err(e) => { + log::error!( + "failed to build Solana CloseSolanaVaultSwapAccounts apicall: {:?}", + e + ); + }, } - }) + } + Ok(()) } fn check_consensus>( From 534d1df47d61aad17791a1523cb6de15d6e0f0bf Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Mon, 28 Oct 2024 18:08:01 +0100 Subject: [PATCH 05/62] fix: consensus --- .../solana_swap_accounts_tracking.rs | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index de265867e9..751cf036dc 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -196,17 +196,40 @@ impl< let active_votes = consensus_votes.active_votes(); let num_active_votes = active_votes.len() as u32; Ok(if num_active_votes >= success_threshold { - let mut counts = BTreeMap::new(); + let mut counts_votes = BTreeMap::new(); + let mut counts_new_accounts = BTreeMap::new(); + let mut counts_confirm_closed_accounts = BTreeMap::new(); + for vote in active_votes { - counts.entry(vote).and_modify(|count| *count += 1).or_insert(1); + counts_votes.entry(vote).and_modify(|count| *count += 1).or_insert(1); + } + + counts_votes.iter().for_each(|(vote, count)| { + vote.new_accounts.iter().for_each(|new_account| { + counts_new_accounts + .entry(new_account) + .and_modify(|c| *c += *count) + .or_insert(*count); + }); + vote.confirm_closed_accounts.iter().for_each(|confirm_closed_account| { + counts_confirm_closed_accounts + .entry(confirm_closed_account) + .and_modify(|c| *c += *count) + .or_insert(*count); + }); + }); + + counts_new_accounts.retain(|_, count| *count >= success_threshold); + let new_accounts = counts_new_accounts.into_keys().cloned().collect::>(); + counts_confirm_closed_accounts.retain(|_, count| *count >= success_threshold); + let confirm_closed_accounts = + counts_confirm_closed_accounts.into_keys().cloned().collect::>(); + + if new_accounts.is_empty() && confirm_closed_accounts.is_empty() { + None + } else { + Some(SolanaVaultSwapsVote { new_accounts, confirm_closed_accounts }) } - counts.iter().find_map(|(vote, count)| { - if *count >= success_threshold { - Some(vote.clone()) - } else { - None - } - }) } else { None }) From 1e8f9045d255bb8861b90ca5434fcaa49d4d0679 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Mon, 28 Oct 2024 18:14:50 +0100 Subject: [PATCH 06/62] chore: minor --- foreign-chains/solana/sol-prim/src/consts.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/foreign-chains/solana/sol-prim/src/consts.rs b/foreign-chains/solana/sol-prim/src/consts.rs index 9db587414e..73c8b9f698 100644 --- a/foreign-chains/solana/sol-prim/src/consts.rs +++ b/foreign-chains/solana/sol-prim/src/consts.rs @@ -43,3 +43,7 @@ pub const TOKEN_ACCOUNT_RENT: u64 = 2039280u64; pub const NONCE_ACCOUNT_LENGTH: u64 = 80u64; pub const SOL_USDC_DECIMAL: u8 = 6u8; + +pub const MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES: usize = 10; +pub const MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS: u32 = 14400; +pub const NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES: usize = 4; From a284434cb3d580db666235ae19bfa01c9260afd5 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Mon, 28 Oct 2024 18:18:55 +0100 Subject: [PATCH 07/62] chore: minor rename --- state-chain/cf-integration-tests/src/mock_runtime.rs | 2 +- .../electoral_systems/solana_swap_accounts_tracking.rs | 8 ++++---- state-chain/runtime/src/chainflip/solana_elections.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/state-chain/cf-integration-tests/src/mock_runtime.rs b/state-chain/cf-integration-tests/src/mock_runtime.rs index 3f56e86100..8f99cae447 100644 --- a/state-chain/cf-integration-tests/src/mock_runtime.rs +++ b/state-chain/cf-integration-tests/src/mock_runtime.rs @@ -310,7 +310,7 @@ impl ExtBuilder { (), (), SolanaVaultSwapsElectoralState { - block_number_last_closed_accounts: Default::default(), + accounts_last_closed_at: Default::default(), witnessed_open_accounts: Default::default(), closure_initiated_accounts: Default::default(), }, diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 751cf036dc..eb975ad461 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -36,7 +36,7 @@ pub trait SolanaVaultSwapAccountsHook { #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode)] pub struct SolanaVaultSwapsElectoralState { - pub block_number_last_closed_accounts: BlockNumber, + pub accounts_last_closed_at: BlockNumber, pub witnessed_open_accounts: Vec, pub closure_initiated_accounts: BTreeSet, } @@ -45,7 +45,7 @@ pub struct SolanaVaultSwapsElectoralState { impl BenchmarkValue for SolanaVaultSwapsElectoralState { fn benchmark_value() -> Self { Self { - block_number_last_closed_accounts: 1u32, + accounts_last_closed_at: 1u32, witnessed_open_accounts: vec![BenchmarkValue::benchmark_value()], closure_initiated_accounts: BTreeSet::from([BenchmarkValue::benchmark_value()]), } @@ -152,7 +152,7 @@ impl< (unsynchronised_state.witnessed_open_accounts.len() >= MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES || (*current_block_number) - .checked_sub(&unsynchronised_state.block_number_last_closed_accounts) + .checked_sub(&unsynchronised_state.accounts_last_closed_at) .expect( "current block number is always greater than when apicall was last created", ) @@ -170,7 +170,7 @@ impl< }; match Hook::close_accounts(accounts_to_close.clone()) { Ok(()) => { - unsynchronised_state.block_number_last_closed_accounts = *current_block_number; + unsynchronised_state.accounts_last_closed_at = *current_block_number; unsynchronised_state.closure_initiated_accounts.extend(accounts_to_close); electoral_access.set_unsynchronised_state(unsynchronised_state)?; }, diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 2ef137ec32..cd2c1750f0 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -93,7 +93,7 @@ pub fn initial_state( (), (), SolanaVaultSwapsElectoralState { - block_number_last_closed_accounts: 0, + accounts_last_closed_at: 0, witnessed_open_accounts: vec![], closure_initiated_accounts: BTreeSet::new(), }, From 65ca599c9cd1d93b056193e7e015dd81523b93c6 Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:29:52 +0100 Subject: [PATCH 08/62] Feat: engine logic to witness solana program swaps (#5313) * chore: add get_event_accounts * feat: add get_program_swaps * chore: add get_program_swaps * chore: improvements * chore: also update persa.rs * chore: cleanup --- Cargo.lock | 1 + engine/Cargo.toml | 32 +- engine/src/witness/sol.rs | 1 + .../witness/sol/program_swaps_witnessing.rs | 451 ++++++++++++++++++ state-chain/node/src/chain_spec/berghain.rs | 2 +- state-chain/node/src/chain_spec/devnet.rs | 2 +- .../node/src/chain_spec/perseverance.rs | 2 +- state-chain/node/src/chain_spec/sisyphos.rs | 2 +- 8 files changed, 482 insertions(+), 11 deletions(-) create mode 100644 engine/src/witness/sol/program_swaps_witnessing.rs diff --git a/Cargo.lock b/Cargo.lock index 441af10269..f0645a48a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1668,6 +1668,7 @@ dependencies = [ "base64 0.22.1", "bincode 1.3.3", "bitcoin", + "borsh", "bs58 0.5.1", "cf-amm", "cf-chains", diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 9be3fd7bed..40e1073390 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -20,6 +20,11 @@ async-channel = { workspace = true } async-trait = { workspace = true } bincode = { workspace = true } bitcoin = { workspace = true, features = ["serde"] } +borsh = { workspace = true, default_features = false, features = [ + "derive", + "unstable__schema", + "hashbrown", +] } chrono = { workspace = true, features = ["clock"] } clap = { workspace = true, features = ["derive", "env"] } config = { workspace = true } @@ -32,7 +37,6 @@ httparse = { workspace = true } http = { workspace = true } itertools = { workspace = true, default-features = true } jsonrpsee = { workspace = true, features = ["full"] } - dyn-clone = { workspace = true } ethbloom = { workspace = true } ethers = { workspace = true, features = ["rustls"] } @@ -41,7 +45,10 @@ num-bigint = { workspace = true } num-derive = { workspace = true } num-traits = { workspace = true } secp256k1 = { workspace = true, features = ["hashes"] } -serde = { workspace = true, default-features = true, features = ["derive", "rc"] } +serde = { workspace = true, default-features = true, features = [ + "derive", + "rc", +] } serde_json = { workspace = true } sha2 = { workspace = true, default-features = true } subxt = { workspace = true, features = ["substrate-compat"] } @@ -51,7 +58,9 @@ tokio-stream = { workspace = true, features = ["sync"] } url = { workspace = true } web3 = { workspace = true, features = ["ws-tls-tokio"] } zeroize = { workspace = true } -curve25519-dalek = { workspace = true, default-features = true, features = ["serde"] } +curve25519-dalek = { workspace = true, default-features = true, features = [ + "serde", +] } ed25519-dalek = { workspace = true } pin-project = { workspace = true } rand = { workspace = true, default-features = true } @@ -92,19 +101,26 @@ sol-prim = { workspace = true, features = ["pda", "str", "serde", "scale"] } # substrate deps cf-amm = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true, features = ["derive", "full"] } +codec = { workspace = true, default-features = true, features = [ + "derive", + "full", +] } frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -scale-info = { workspace = true, default-features = true, features = ["derive"] } +scale-info = { workspace = true, default-features = true, features = [ + "derive", +] } sp-core = { workspace = true, default-features = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true } -frame-metadata = { workspace = true, default-features = true, features = ["current"] } +frame-metadata = { workspace = true, default-features = true, features = [ + "current", +] } serde_bytes = { workspace = true, default-features = true } bs58 = { workspace = true, default-features = true } base64 = { workspace = true } @@ -117,7 +133,9 @@ mockall = { workspace = true, features = ["nightly"] } multisig = { workspace = true, features = ["test"] } rlp = { workspace = true, default-features = true } tempfile = { workspace = true } -cf-utilities = { workspace = true, default-features = true, features = ["test-utils"] } +cf-utilities = { workspace = true, default-features = true, features = [ + "test-utils", +] } serde_path_to_error = { workspace = true } [build-dependencies] diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index ce88bef286..341fb7bf55 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -1,6 +1,7 @@ mod egress_witnessing; mod fee_tracking; mod nonce_witnessing; +mod program_swaps_witnessing; mod sol_deposits; use crate::{ diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs new file mode 100644 index 0000000000..ceb39ad1b1 --- /dev/null +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -0,0 +1,451 @@ +use std::collections::HashSet; + +use crate::sol::{ + commitment_config::CommitmentConfig, + retry_rpc::{SolRetryRpcApi, SolRetryRpcClient}, + rpc_client_api::{RpcAccountInfoConfig, UiAccount, UiAccountData, UiAccountEncoding}, +}; +use anyhow::ensure; +use anyhow::{anyhow /* ensure */}; +use base64::Engine; +use borsh::{BorshDeserialize, BorshSerialize}; +use cf_chains::sol::SolAddress; +use futures::{stream, StreamExt, TryStreamExt}; +use itertools::Itertools; +use tracing::warn; + +const MAXIMUM_CONCURRENT_RPCS: usize = 16; +const SWAP_ENDPOINT_DATA_ACCOUNT_DISCRIMINATOR: [u8; 8] = [79, 152, 191, 225, 128, 108, 11, 139]; +const SWAP_EVENT_ACCOUNT_DISCRIMINATOR: [u8; 8] = [150, 251, 114, 94, 200, 113, 248, 70]; +// Querying less than 100 (rpc call max) as those event accounts can be quite big. +// Max length ~ 1300 bytes per account. We set it to 10 as an arbitrary number to +// avoid large queries. +const MAX_MULTIPLE_EVENT_ACCOUNTS_QUERY: usize = 10; + +#[derive(BorshDeserialize, BorshSerialize, Debug)] +struct SwapEndpointDataAccount { + discriminator: [u8; 8], + historical_number_event_accounts: u128, + open_event_accounts: Vec<[u8; sol_prim::consts::SOLANA_ADDRESS_LEN]>, +} + +#[derive(BorshDeserialize, BorshSerialize, Debug, Default)] +pub struct SwapEvent { + discriminator: [u8; 8], + sender: [u8; sol_prim::consts::SOLANA_ADDRESS_LEN], + dst_chain: u32, + dst_address: Vec, + dst_token: u32, + amount: u64, + src_token: Option<[u8; sol_prim::consts::SOLANA_ADDRESS_LEN]>, + ccm_parameters: Option, + cf_parameters: Vec, +} + +#[derive(BorshDeserialize, BorshSerialize, Debug, Default)] +pub struct CcmParams { + message: Vec, + gas_amount: u64, +} + +// 1. Query the on-chain list of opened accounts from SwapEndpointDataAccount. +// 2. Check the returned accounts against the SC opened_accounts. The SC is the source of truth for +// the opened channels we can rely on that to not query the same accounts multiple times. +// 3. If they are already seen in the SC we do nothing with them and skip the query. +// 4. If an account is in the SC but not see in the engine we report it as closed. +// 5. If they are not seen in the SC we query the account data. Then we parse the account data and +// ensure it's a valid a program swap. The new program swap needs to be reported to the SC. + +pub async fn get_program_swaps( + sol_rpc: &SolRetryRpcClient, + swap_endpoint_data_account_address: SolAddress, + sc_opened_accounts: Vec, + _token_pubkey: SolAddress, +) -> Result, anyhow::Error> { + let (new_program_swap_accounts, _closed_accounts, slot) = get_changed_program_swap_accounts( + sol_rpc, + sc_opened_accounts, + swap_endpoint_data_account_address, + ) + .await?; + + stream::iter(new_program_swap_accounts) + .chunks(MAX_MULTIPLE_EVENT_ACCOUNTS_QUERY) + .map(|new_program_swap_accounts_chunk| { + get_program_swap_event_accounts_data(sol_rpc, new_program_swap_accounts_chunk, slot) + }) + .buffered(MAXIMUM_CONCURRENT_RPCS) + .map_ok(|program_swap_account_data_chunk| { + stream::iter(program_swap_account_data_chunk.into_iter().filter_map( + |program_swap_account_data| match program_swap_account_data { + Some(data) => Some(Ok(data)), + // It could happen that some account is closed between the queries. This should + // not happen because: + // 1. Accounts in `new_program_swap_accounts` can only be accounts that have + // newly been opened and they won't be closed until consensus is reached. + // 2. If due to rpc load management the get event accounts rpc is queried at a + // slot < get swap endpoint data rpc slot, the min_context_slot will prevent + // it from being executed before that. + // This could only happen if an engine is behind and were to see the account + // opened and closed between queries. That's not reallistic as it takes minutes + // for an account to be closed and even if it were to happen it's not + // problematic as we'd have reached consensus and the engine would just filter + // it out. + None => { + warn!("Event account not found for solana event account"); + None + }, + }, + )) + }) + .try_flatten() + .try_collect() + .await + + // TODO: Submit closed_accounts and new opened accounts from SwapEvents. The only additional + // step required is checking the SwapEvent's src_token. If empty, submit it as native. Otherwise + // it should match token_pubkey. A token not matching the token_pubkey should never happen. + // The submission might be done in a layer above (sol.rs). + + // TODO: When submitting data we could technically submit the slot when the SwapEvent was + // queried for the new opened accounts. However, it's just easier to submit the slot when the + // SwapEndpointDataAccount was queried for both closed accounts and new opened accounts. +} + +async fn get_changed_program_swap_accounts( + sol_rpc: &SolRetryRpcClient, + sc_opened_accounts: Vec, + swap_endpoint_data_account_address: SolAddress, +) -> Result<(Vec, Vec, u64), anyhow::Error> { + let (_historical_number_event_accounts, open_event_accounts, slot) = + get_swap_endpoint_data(sol_rpc, swap_endpoint_data_account_address) + .await + .expect("Failed to get the event accounts"); + + let sc_opened_accounts_hashset: HashSet<_> = sc_opened_accounts.iter().collect(); + let mut new_program_swap_accounts = Vec::new(); + let mut closed_accounts = Vec::new(); + + for account in &open_event_accounts { + if !sc_opened_accounts_hashset.contains(account) { + new_program_swap_accounts.push(*account); + } + } + + let open_event_accounts_hashset: HashSet<_> = open_event_accounts.iter().collect(); + for account in sc_opened_accounts { + if !open_event_accounts_hashset.contains(&account) { + closed_accounts.push(account); + } + } + + Ok((new_program_swap_accounts, closed_accounts, slot)) +} + +// Query the list of opened accounts from SwapEndpointDataAccount. The Swap Endpoint program ensures +// that the list is updated atomically whenever a swap event account is opened or closed. +async fn get_swap_endpoint_data( + sol_rpc: &SolRetryRpcClient, + swap_endpoint_data_account_address: SolAddress, +) -> Result<(u128, Vec, u64), anyhow::Error> { + let accounts_info_response = sol_rpc + .get_multiple_accounts( + &[swap_endpoint_data_account_address], + RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: None, + commitment: Some(CommitmentConfig::finalized()), + min_context_slot: None, + }, + ) + .await; + + let slot = accounts_info_response.context.slot; + let accounts_info = accounts_info_response + .value + .into_iter() + .exactly_one() + .expect("We queried for exactly one account."); + + match accounts_info { + Some(UiAccount { data: UiAccountData::Binary(base64_string, encoding), .. }) => { + if encoding != UiAccountEncoding::Base64 { + return Err(anyhow!("Data account encoding is not base64")); + } + let bytes = base64::engine::general_purpose::STANDARD + .decode(base64_string) + .expect("Failed to decode base64 string"); + + // 8 Discriminator + 16 Historical Number Event Accounts + 4 bytes vector length + data + if bytes.len() < 28 { + return Err(anyhow!("Expected account to have at least 28 bytes")); + } + + let deserialized_data: SwapEndpointDataAccount = + SwapEndpointDataAccount::try_from_slice(&bytes) + .map_err(|e| anyhow!("Failed to deserialize data: {:?}", e))?; + + ensure!( + deserialized_data.discriminator == SWAP_ENDPOINT_DATA_ACCOUNT_DISCRIMINATOR, + "Discriminator does not match. Found: {:?}", + deserialized_data.discriminator + ); + + Ok(( + deserialized_data.historical_number_event_accounts, + deserialized_data.open_event_accounts.into_iter().map(SolAddress).collect(), + slot, + )) + }, + Some(_) => + Err(anyhow!("Expected UiAccountData::Binary(String, UiAccountEncoding::Base64)")), + None => Err(anyhow!( + "Expected swap_endpoint_data_account_address to be found: {:?}", + swap_endpoint_data_account_address + )), + } +} + +async fn get_program_swap_event_accounts_data( + sol_rpc: &SolRetryRpcClient, + program_swap_event_accounts: Vec, + min_context_slot: u64, +) -> Result>, anyhow::Error> { + let accounts_info_response = sol_rpc + .get_multiple_accounts( + program_swap_event_accounts.as_slice(), + RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: None, + commitment: Some(CommitmentConfig::finalized()), + min_context_slot: Some(min_context_slot), + }, + ) + .await; + + let _slot = accounts_info_response.context.slot; + let accounts_info = accounts_info_response.value; + + ensure!(accounts_info.len() == program_swap_event_accounts.len()); + + accounts_info + .into_iter() + .map(|accounts_info| match accounts_info { + Some(UiAccount { data: UiAccountData::Binary(base64_string, encoding), .. }) => { + if encoding != UiAccountEncoding::Base64 { + return Err(anyhow!("Data account encoding is not base64")); + } + let bytes = base64::engine::general_purpose::STANDARD + .decode(base64_string) + .expect("Failed to decode base64 string"); + + if bytes.len() < 8 { + return Err(anyhow!("Expected account to have at least 28 bytes")); + } + + let deserialized_data: SwapEvent = SwapEvent::try_from_slice(&bytes) + .map_err(|e| anyhow!("Failed to deserialize data: {:?}", e))?; + + ensure!( + deserialized_data.discriminator == SWAP_EVENT_ACCOUNT_DISCRIMINATOR, + "Discriminator does not match. Found: {:?}", + deserialized_data.discriminator + ); + + Ok(Some(deserialized_data)) + }, + Some(_) => + Err(anyhow!("Expected UiAccountData::Binary(String, UiAccountEncoding::Base64)")), + None => Ok(None), + }) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::{ + settings::{HttpEndpoint, NodeContainer}, + sol::retry_rpc::SolRetryRpcClient, + }; + + use cf_chains::{Chain, Solana}; + use futures_util::FutureExt; + use std::str::FromStr; + use utilities::task_scope; + + use super::*; + + #[tokio::test] + #[ignore] + async fn test_get_swap_endpoint_data() { + task_scope::task_scope(|scope| { + async { + let client = SolRetryRpcClient::new( + scope, + NodeContainer { + primary: HttpEndpoint { + http_endpoint: "https://api.devnet.solana.com".into(), + }, + backup: None, + }, + None, + Solana::WITNESS_PERIOD, + ) + .await + .unwrap(); + + let (historical_number_event_accounts, open_event_accounts, _) = + get_swap_endpoint_data( + &client, + // Swap Endpoint Data Account Address with no opened accounts + SolAddress::from_str("BckDu65u2ofAfaSDDEPg2qJTufKB4PvGxwcYhJ2wkBTC") + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(historical_number_event_accounts, 0_u128); + assert_eq!(open_event_accounts.len(), 0); + + let (historical_number_event_accounts, open_event_accounts, _) = + get_swap_endpoint_data( + &client, + // Swap Endpoint Data Account Address with two opened accounts + SolAddress::from_str("72HKrbbesW9FGuBoebns77uvY9fF9MEsw4HTMEeV53W9") + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(historical_number_event_accounts, 2_u128); + assert_eq!( + open_event_accounts, + vec![ + SolAddress::from_str("HhxGAt8THMtsW97Zuo5ZrhKgqsdD5EBgCx9vZ4n62xpf") + .unwrap(), + SolAddress::from_str("E81G7Q1BjierakQCfL9B5Tm485eiaRPb22bcKD2vtRfU") + .unwrap() + ] + ); + + Ok(()) + } + .boxed() + }) + .await + .unwrap(); + } + + #[tokio::test] + #[ignore] + async fn test_get_changed_program_swap_accounts() { + task_scope::task_scope(|scope| { + async { + let client = SolRetryRpcClient::new( + scope, + NodeContainer { + primary: HttpEndpoint { + http_endpoint: "https://api.devnet.solana.com".into(), + }, + backup: None, + }, + None, + Solana::WITNESS_PERIOD, + ) + .await + .unwrap(); + + let (new_program_swap_accounts, closed_accounts, _) = + get_changed_program_swap_accounts( + &client, + vec![SolAddress::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + .unwrap()], + // Swap Endpoint Data Account Address with no opened accounts + SolAddress::from_str("BckDu65u2ofAfaSDDEPg2qJTufKB4PvGxwcYhJ2wkBTC") + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(new_program_swap_accounts, vec![]); + assert_eq!( + closed_accounts, + vec![SolAddress::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") + .unwrap()] + ); + + let (new_program_swap_accounts, closed_accounts, _) = + get_changed_program_swap_accounts( + &client, + vec![SolAddress::from_str("HhxGAt8THMtsW97Zuo5ZrhKgqsdD5EBgCx9vZ4n62xpf") + .unwrap()], + // Swap Endpoint Data Account Address with two opened accounts + SolAddress::from_str("72HKrbbesW9FGuBoebns77uvY9fF9MEsw4HTMEeV53W9") + .unwrap(), + ) + .await + .unwrap(); + + println!("new_program_swap_accounts: {:?}", new_program_swap_accounts); + println!("closed_accounts: {:?}", closed_accounts); + + assert_eq!( + new_program_swap_accounts, + vec![SolAddress::from_str("E81G7Q1BjierakQCfL9B5Tm485eiaRPb22bcKD2vtRfU") + .unwrap()] + ); + assert_eq!(closed_accounts, vec![]); + + Ok(()) + } + .boxed() + }) + .await + .unwrap(); + } + + #[tokio::test] + #[ignore] + async fn test_get_program_swap_event_accounts_data() { + task_scope::task_scope(|scope| { + async { + let client = SolRetryRpcClient::new( + scope, + NodeContainer { + primary: HttpEndpoint { http_endpoint: "http://127.0.0.1:8899".into() }, + backup: None, + }, + None, + Solana::WITNESS_PERIOD, + ) + .await + .unwrap(); + + let program_swap_event_accounts_data = get_program_swap_event_accounts_data( + &client, + vec![ + SolAddress::from_str("GNrA2Ztxv1tJF3G4NVPEQtbRb9uT8rXcEY6ddPfzpnnT") + .unwrap(), + SolAddress::from_str("8yeBhX5BB4L9MfDddhwzktdmzMeNUEcvgZGPWLD3HDDY") + .unwrap(), + SolAddress::from_str("Dd1k91cWt84qJoQr3FT7EXQpSaMtZtwPwdho7RbMWtEV") + .unwrap(), + ], + 123, + ) + .await + .unwrap(); + + println!( + "program_swap_event_accounts_data: {:?}", + program_swap_event_accounts_data + ); + + Ok(()) + } + .boxed() + }) + .await + .unwrap(); + } +} diff --git a/state-chain/node/src/chain_spec/berghain.rs b/state-chain/node/src/chain_spec/berghain.rs index 567c4518b5..471c287a9d 100644 --- a/state-chain/node/src/chain_spec/berghain.rs +++ b/state-chain/node/src/chain_spec/berghain.rs @@ -159,4 +159,4 @@ pub const AUCTION_PARAMETERS: SetSizeParameters = pub const BITCOIN_SAFETY_MARGIN: u64 = 2; pub const ETHEREUM_SAFETY_MARGIN: u64 = 6; pub const ARBITRUM_SAFETY_MARGIN: u64 = 1; -pub const SOLANA_SAFETY_MARGIN: u64 = 1; //TODO: put correct value +pub const SOLANA_SAFETY_MARGIN: u64 = 1; // Unused - we use "finalized" instead diff --git a/state-chain/node/src/chain_spec/devnet.rs b/state-chain/node/src/chain_spec/devnet.rs index 78caab48db..ed57cf86d8 100644 --- a/state-chain/node/src/chain_spec/devnet.rs +++ b/state-chain/node/src/chain_spec/devnet.rs @@ -21,4 +21,4 @@ pub const AUCTION_PARAMETERS: SetSizeParameters = SetSizeParameters { pub const BITCOIN_SAFETY_MARGIN: u64 = 2; pub const ETHEREUM_SAFETY_MARGIN: u64 = 2; pub const ARBITRUM_SAFETY_MARGIN: u64 = 1; -pub const SOLANA_SAFETY_MARGIN: u64 = 1; //TODO: put correct value +pub const SOLANA_SAFETY_MARGIN: u64 = 1; // Unused - we use "finalized" instead diff --git a/state-chain/node/src/chain_spec/perseverance.rs b/state-chain/node/src/chain_spec/perseverance.rs index 4d08adc614..cfe40972c8 100644 --- a/state-chain/node/src/chain_spec/perseverance.rs +++ b/state-chain/node/src/chain_spec/perseverance.rs @@ -158,4 +158,4 @@ pub fn extra_accounts() -> Vec<(AccountId, AccountRole, FlipBalance, Option Vec<(AccountId, AccountRole, FlipBalance, Option Date: Tue, 29 Oct 2024 14:37:33 +0100 Subject: [PATCH 09/62] feat: return swap account along with swap details --- .../src/witness/sol/program_swaps_witnessing.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index ceb39ad1b1..5da665d48d 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -61,7 +61,7 @@ pub async fn get_program_swaps( swap_endpoint_data_account_address: SolAddress, sc_opened_accounts: Vec, _token_pubkey: SolAddress, -) -> Result, anyhow::Error> { +) -> Result, anyhow::Error> { let (new_program_swap_accounts, _closed_accounts, slot) = get_changed_program_swap_accounts( sol_rpc, sc_opened_accounts, @@ -77,8 +77,8 @@ pub async fn get_program_swaps( .buffered(MAXIMUM_CONCURRENT_RPCS) .map_ok(|program_swap_account_data_chunk| { stream::iter(program_swap_account_data_chunk.into_iter().filter_map( - |program_swap_account_data| match program_swap_account_data { - Some(data) => Some(Ok(data)), + |(account, program_swap_account_data)| match program_swap_account_data { + Some(data) => Some(Ok((account, data))), // It could happen that some account is closed between the queries. This should // not happen because: // 1. Accounts in `new_program_swap_accounts` can only be accounts that have @@ -210,7 +210,7 @@ async fn get_program_swap_event_accounts_data( sol_rpc: &SolRetryRpcClient, program_swap_event_accounts: Vec, min_context_slot: u64, -) -> Result>, anyhow::Error> { +) -> Result)>, anyhow::Error> { let accounts_info_response = sol_rpc .get_multiple_accounts( program_swap_event_accounts.as_slice(), @@ -228,9 +228,10 @@ async fn get_program_swap_event_accounts_data( ensure!(accounts_info.len() == program_swap_event_accounts.len()); - accounts_info + program_swap_event_accounts .into_iter() - .map(|accounts_info| match accounts_info { + .zip(accounts_info.into_iter()) + .map(|(account, accounts_info)| match accounts_info { Some(UiAccount { data: UiAccountData::Binary(base64_string, encoding), .. }) => { if encoding != UiAccountEncoding::Base64 { return Err(anyhow!("Data account encoding is not base64")); @@ -252,11 +253,11 @@ async fn get_program_swap_event_accounts_data( deserialized_data.discriminator ); - Ok(Some(deserialized_data)) + Ok((account, Some(deserialized_data))) }, Some(_) => Err(anyhow!("Expected UiAccountData::Binary(String, UiAccountEncoding::Base64)")), - None => Ok(None), + None => Ok((account, None)), }) .collect() } From a5eb16bef829319cfe7f9a64392383bac54384cc Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Tue, 29 Oct 2024 15:25:38 +0100 Subject: [PATCH 10/62] refactor: use election properties --- .../cf-integration-tests/src/mock_runtime.rs | 10 +- .../solana_swap_accounts_tracking.rs | 121 +++++++++--------- .../runtime/src/chainflip/solana_elections.rs | 12 +- 3 files changed, 67 insertions(+), 76 deletions(-) diff --git a/state-chain/cf-integration-tests/src/mock_runtime.rs b/state-chain/cf-integration-tests/src/mock_runtime.rs index 8f99cae447..1726e7b8b6 100644 --- a/state-chain/cf-integration-tests/src/mock_runtime.rs +++ b/state-chain/cf-integration-tests/src/mock_runtime.rs @@ -10,9 +10,7 @@ use chainflip_node::{ chain_spec::testnet::{EXPIRY_SPAN_IN_SECONDS, REDEMPTION_TTL_SECS}, test_account_from_seed, }; -use pallet_cf_elections::{ - electoral_systems::solana_swap_accounts_tracking::SolanaVaultSwapsElectoralState, InitialState, -}; +use pallet_cf_elections::InitialState; use pallet_cf_validator::SetSizeParameters; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_consensus_grandpa::AuthorityId as GrandpaId; @@ -309,11 +307,7 @@ impl ExtBuilder { (), (), (), - SolanaVaultSwapsElectoralState { - accounts_last_closed_at: Default::default(), - witnessed_open_accounts: Default::default(), - closure_initiated_accounts: Default::default(), - }, + Default::default(), ), unsynchronised_settings: ( (), diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index eb975ad461..1bffe81fc8 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -5,6 +5,8 @@ use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; #[cfg(feature = "runtime-benchmarks")] use cf_chains::benchmarking_value::BenchmarkValue; +#[cfg(feature = "runtime-benchmarks")] +use cf_chains::sol::api::ContractSwapAccountAndSender; use crate::{ electoral_system::{ @@ -15,7 +17,7 @@ use crate::{ CorruptStorageError, ElectionIdentifier, }; use cf_chains::sol::{ - api::ContractSwapAccountAndSender, MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES, + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES, MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES, }; @@ -34,18 +36,18 @@ pub trait SolanaVaultSwapAccountsHook { fn get_number_of_available_sol_nonce_accounts() -> usize; } +pub type SolanaVaultSwapAccountsLastClosedAt = BlockNumber; + #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode)] -pub struct SolanaVaultSwapsElectoralState { - pub accounts_last_closed_at: BlockNumber, +pub struct SolanaVaultSwapsKnownAccounts { pub witnessed_open_accounts: Vec, pub closure_initiated_accounts: BTreeSet, } #[cfg(feature = "runtime-benchmarks")] -impl BenchmarkValue for SolanaVaultSwapsElectoralState { +impl BenchmarkValue for SolanaVaultSwapsKnownAccounts { fn benchmark_value() -> Self { Self { - accounts_last_closed_at: 1u32, witnessed_open_accounts: vec![BenchmarkValue::benchmark_value()], closure_initiated_accounts: BTreeSet::from([BenchmarkValue::benchmark_value()]), } @@ -91,14 +93,14 @@ impl< for SolanaVaultSwapAccounts { type ValidatorId = ValidatorId; - type ElectoralUnsynchronisedState = SolanaVaultSwapsElectoralState; + type ElectoralUnsynchronisedState = SolanaVaultSwapAccountsLastClosedAt; type ElectoralUnsynchronisedStateMapKey = (); type ElectoralUnsynchronisedStateMapValue = (); type ElectoralUnsynchronisedSettings = (); type ElectoralSettings = Settings; type ElectionIdentifierExtra = (); - type ElectionProperties = (); + type ElectionProperties = SolanaVaultSwapsKnownAccounts; type ElectionState = (); type Vote = vote_storage::bitmap::Bitmap>; type Consensus = SolanaVaultSwapsVote; @@ -125,63 +127,64 @@ impl< { let mut election_access = electoral_access.election_mut(election_identifier)?; if let Some(consensus) = election_access.check_consensus()?.has_consensus() { + let mut known_accounts = election_access.properties()?; election_access.delete(); - electoral_access.new_election((), (), ())?; - electoral_access.mutate_unsynchronised_state(|_, unsynchronised_state| { - unsynchronised_state.witnessed_open_accounts.extend( - consensus.new_accounts.iter().map(|(account, swap_details)| { - Hook::initiate_vault_swap((*swap_details).clone()); - (*account).clone() - }), - ); - - consensus.confirm_closed_accounts.into_iter().for_each(|acc| { - unsynchronised_state.closure_initiated_accounts.remove(&acc); - }); - - Ok(()) - })?; + known_accounts.witnessed_open_accounts.extend(consensus.new_accounts.iter().map( + |(account, swap_details)| { + Hook::initiate_vault_swap((*swap_details).clone()); + (*account).clone() + }, + )); + consensus.confirm_closed_accounts.into_iter().for_each(|acc| { + known_accounts.closure_initiated_accounts.remove(&acc); + }); + + if Hook::get_number_of_available_sol_nonce_accounts() > + NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && + (known_accounts.witnessed_open_accounts.len() >= + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES || + (*current_block_number) + .checked_sub(&electoral_access.unsynchronised_state()?) + .expect("current block number is always greater than when apicall was last created") + .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) + { + let accounts_to_close: Vec<_> = if known_accounts.witnessed_open_accounts.len() > + MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES + { + known_accounts + .witnessed_open_accounts + .drain(..MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES) + .collect() + } else { + sp_std::mem::take(&mut known_accounts.witnessed_open_accounts) + }; + match Hook::close_accounts(accounts_to_close.clone()) { + Ok(()) => { + known_accounts.closure_initiated_accounts.extend(accounts_to_close); + electoral_access.set_unsynchronised_state(*current_block_number)?; + }, + Err(e) => { + log::error!( + "failed to build Solana CloseSolanaVaultSwapAccounts apicall: {:?}", + e + ); + known_accounts.witnessed_open_accounts.extend(accounts_to_close); + }, + } + } + electoral_access.new_election((), known_accounts, ())?; } } else { - electoral_access.new_election((), (), ())?; - } - - let mut unsynchronised_state = electoral_access.unsynchronised_state()?; - if Hook::get_number_of_available_sol_nonce_accounts() > - NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && - (unsynchronised_state.witnessed_open_accounts.len() >= - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES || - (*current_block_number) - .checked_sub(&unsynchronised_state.accounts_last_closed_at) - .expect( - "current block number is always greater than when apicall was last created", - ) - .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) - { - let accounts_to_close: Vec<_> = if unsynchronised_state.witnessed_open_accounts.len() > - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES - { - unsynchronised_state - .witnessed_open_accounts - .drain(..MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES) - .collect() - } else { - sp_std::mem::take(&mut unsynchronised_state.witnessed_open_accounts) - }; - match Hook::close_accounts(accounts_to_close.clone()) { - Ok(()) => { - unsynchronised_state.accounts_last_closed_at = *current_block_number; - unsynchronised_state.closure_initiated_accounts.extend(accounts_to_close); - electoral_access.set_unsynchronised_state(unsynchronised_state)?; - }, - Err(e) => { - log::error!( - "failed to build Solana CloseSolanaVaultSwapAccounts apicall: {:?}", - e - ); + electoral_access.new_election( + (), + SolanaVaultSwapsKnownAccounts { + witnessed_open_accounts: Vec::new(), + closure_initiated_accounts: BTreeSet::new(), }, - } + (), + )?; } + Ok(()) } diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index cd2c1750f0..190bc7d7aa 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -40,16 +40,14 @@ use pallet_cf_elections::{ liveness::OnCheckComplete, monotonic_change::OnChangeHook, monotonic_median::MedianChangeHook, - solana_swap_accounts_tracking::{ - SolanaVaultSwapAccountsHook, SolanaVaultSwapsElectoralState, - }, + solana_swap_accounts_tracking::SolanaVaultSwapAccountsHook, }, CorruptStorageError, ElectionIdentifier, InitialState, InitialStateOf, RunnerStorageAccess, }; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{DispatchResult, FixedPointNumber, FixedU128}; -use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; +use sp_std::vec::Vec; #[cfg(feature = "runtime-benchmarks")] use cf_chains::benchmarking_value::BenchmarkValue; @@ -92,11 +90,7 @@ pub fn initial_state( (), (), (), - SolanaVaultSwapsElectoralState { - accounts_last_closed_at: 0, - witnessed_open_accounts: vec![], - closure_initiated_accounts: BTreeSet::new(), - }, + 0, ), unsynchronised_settings: ( (), From 687f650b506ab7acbcdd06bfb9e78306fec3b850 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Wed, 30 Oct 2024 16:16:49 +0100 Subject: [PATCH 11/62] feat: engine --- engine/src/witness/sol.rs | 37 ++++++++-- .../witness/sol/program_swaps_witnessing.rs | 67 ++++++++++++++----- .../cf-integration-tests/src/mock_runtime.rs | 10 ++- state-chain/chains/src/sol/sol_tx_core.rs | 2 + state-chain/node/src/chain_spec.rs | 2 + .../runtime/src/chainflip/solana_elections.rs | 33 +++++++-- 6 files changed, 118 insertions(+), 33 deletions(-) diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index 341fb7bf55..97ed17fa39 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -17,9 +17,16 @@ use crate::{ }, }; use anyhow::Result; -use cf_chains::{sol::SolHash, Chain}; +use cf_chains::{ + sol::{api::ContractSwapAccountAndSender, SolHash}, + Chain, +}; use futures::FutureExt; -use pallet_cf_elections::{electoral_system::ElectoralSystem, vote_storage::VoteStorage}; +use pallet_cf_elections::{ + electoral_system::ElectoralSystem, + electoral_systems::solana_swap_accounts_tracking::SolanaVaultSwapsVote, + vote_storage::VoteStorage, +}; use state_chain_runtime::{ chainflip::solana_elections::{ SolanaBlockHeightTracking, SolanaEgressWitnessing, SolanaElectoralSystem, @@ -34,7 +41,8 @@ use cf_utilities::{ task_scope::{self, Scope}, }; use pallet_cf_elections::vote_storage::change::MonotonicChangeVote; -use std::{str::FromStr, sync::Arc}; +use std::{collections::BTreeSet, str::FromStr, sync::Arc}; +use utilities::{task_scope, task_scope::Scope}; #[derive(Clone)] struct SolanaBlockHeightTrackingVoter { @@ -186,13 +194,30 @@ struct SolanaVaultSwapsVoter { impl VoterApi for SolanaVaultSwapsVoter { async fn vote( &self, - _settings: ::ElectoralSettings, - _properties: ::ElectionProperties, + settings: ::ElectoralSettings, + properties: ::ElectionProperties, ) -> Result< <::Vote as VoteStorage>::Vote, anyhow::Error, > { - todo!() + program_swaps_witnessing::get_program_swaps( + &self.client, + settings.swap_endpoint_data_account_address, + properties + .witnessed_open_accounts + .into_iter() + .map(|ContractSwapAccountAndSender { contract_swap_account, .. }| { + contract_swap_account + }) + .collect(), + properties.closure_initiated_accounts, + settings.usdc_token_mint_pubkey, + ) + .await + .map(|(new_accounts, confirm_closed_accounts)| SolanaVaultSwapsVote { + new_accounts: new_accounts.into_iter().collect::>(), + confirm_closed_accounts: confirm_closed_accounts.into_iter().collect::>(), + }) } } diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 5da665d48d..db1317cf22 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use crate::sol::{ commitment_config::CommitmentConfig, @@ -9,9 +9,11 @@ use anyhow::ensure; use anyhow::{anyhow /* ensure */}; use base64::Engine; use borsh::{BorshDeserialize, BorshSerialize}; -use cf_chains::sol::SolAddress; +use cf_chains::sol::{api::ContractSwapAccountAndSender, SolAddress}; +use cf_primitives::Asset; use futures::{stream, StreamExt, TryStreamExt}; use itertools::Itertools; +use state_chain_runtime::chainflip::solana_elections::SolanaVaultSwapDetails; use tracing::warn; const MAXIMUM_CONCURRENT_RPCS: usize = 16; @@ -59,17 +61,25 @@ pub struct CcmParams { pub async fn get_program_swaps( sol_rpc: &SolRetryRpcClient, swap_endpoint_data_account_address: SolAddress, - sc_opened_accounts: Vec, - _token_pubkey: SolAddress, -) -> Result, anyhow::Error> { - let (new_program_swap_accounts, _closed_accounts, slot) = get_changed_program_swap_accounts( + sc_open_accounts: Vec, + sc_closure_initiated_accounts: BTreeSet, + usdc_token_mint_pubkey: SolAddress, +) -> Result< + ( + Vec<(ContractSwapAccountAndSender, SolanaVaultSwapDetails)>, + Vec, + ), + anyhow::Error, +> { + let (new_program_swap_accounts, closed_accounts, slot) = get_changed_program_swap_accounts( sol_rpc, - sc_opened_accounts, + sc_open_accounts, + sc_closure_initiated_accounts, swap_endpoint_data_account_address, ) .await?; - stream::iter(new_program_swap_accounts) + let new_swaps = stream::iter(new_program_swap_accounts) .chunks(MAX_MULTIPLE_EVENT_ACCOUNTS_QUERY) .map(|new_program_swap_accounts_chunk| { get_program_swap_event_accounts_data(sol_rpc, new_program_swap_accounts_chunk, slot) @@ -78,7 +88,19 @@ pub async fn get_program_swaps( .map_ok(|program_swap_account_data_chunk| { stream::iter(program_swap_account_data_chunk.into_iter().filter_map( |(account, program_swap_account_data)| match program_swap_account_data { - Some(data) => Some(Ok((account, data))), + Some(data) + if (data.src_token.is_none() || + data.src_token.is_some_and(|addr| addr == usdc_token_mint_pubkey.0)) => + Some(Ok((ContractSwapAccountAndSender { + contract_swap_account: account, + swap_sender: data.sender.into() + }, SolanaVaultSwapDetails { + from: if data.src_token.is_none() {Asset::Sol} else {Asset::SolUsdc}, + deposit_amount: data.amount.into(), + to: todo!(), + destination_address: todo!(), + tx_hash: todo!() + }))), // It could happen that some account is closed between the queries. This should // not happen because: // 1. Accounts in `new_program_swap_accounts` can only be accounts that have @@ -95,17 +117,18 @@ pub async fn get_program_swaps( warn!("Event account not found for solana event account"); None }, + _ => { + warn!("Unsupported input token for the witnessed solana vault swap, omitting the swap and the swap account."); + None + }, }, )) }) .try_flatten() .try_collect() - .await + .await; - // TODO: Submit closed_accounts and new opened accounts from SwapEvents. The only additional - // step required is checking the SwapEvent's src_token. If empty, submit it as native. Otherwise - // it should match token_pubkey. A token not matching the token_pubkey should never happen. - // The submission might be done in a layer above (sol.rs). + new_swaps.map(|swaps| (swaps, closed_accounts)) // TODO: When submitting data we could technically submit the slot when the SwapEvent was // queried for the new opened accounts. However, it's just easier to submit the slot when the @@ -115,26 +138,34 @@ pub async fn get_program_swaps( async fn get_changed_program_swap_accounts( sol_rpc: &SolRetryRpcClient, sc_opened_accounts: Vec, + sc_closure_initiated_accounts: BTreeSet, swap_endpoint_data_account_address: SolAddress, -) -> Result<(Vec, Vec, u64), anyhow::Error> { +) -> Result<(Vec, Vec, u64), anyhow::Error> { let (_historical_number_event_accounts, open_event_accounts, slot) = get_swap_endpoint_data(sol_rpc, swap_endpoint_data_account_address) .await .expect("Failed to get the event accounts"); let sc_opened_accounts_hashset: HashSet<_> = sc_opened_accounts.iter().collect(); + let sc_closure_initiated_accounts_hashset = sc_closure_initiated_accounts + .iter() + .map(|ContractSwapAccountAndSender { contract_swap_account, .. }| contract_swap_account) + .collect::>(); + let mut new_program_swap_accounts = Vec::new(); let mut closed_accounts = Vec::new(); for account in &open_event_accounts { - if !sc_opened_accounts_hashset.contains(account) { + if !sc_opened_accounts_hashset.contains(account) && + !sc_closure_initiated_accounts_hashset.contains(account) + { new_program_swap_accounts.push(*account); } } let open_event_accounts_hashset: HashSet<_> = open_event_accounts.iter().collect(); - for account in sc_opened_accounts { - if !open_event_accounts_hashset.contains(&account) { + for account in sc_closure_initiated_accounts { + if !open_event_accounts_hashset.contains(&account.contract_swap_account) { closed_accounts.push(account); } } diff --git a/state-chain/cf-integration-tests/src/mock_runtime.rs b/state-chain/cf-integration-tests/src/mock_runtime.rs index 1726e7b8b6..ea4b91f69e 100644 --- a/state-chain/cf-integration-tests/src/mock_runtime.rs +++ b/state-chain/cf-integration-tests/src/mock_runtime.rs @@ -17,7 +17,9 @@ use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_runtime::{FixedU128, Percent, Permill}; use state_chain_runtime::{ chainflip::{ - solana_elections::{SolanaFeeUnsynchronisedSettings, SolanaIngressSettings}, + solana_elections::{ + SolanaFeeUnsynchronisedSettings, SolanaIngressSettings, SolanaVaultSwapsSettings, + }, Offence, }, constants::common::*, @@ -329,7 +331,11 @@ impl ExtBuilder { (), (), BLOCKS_BETWEEN_LIVENESS_CHECKS, - (), + SolanaVaultSwapsSettings { + swap_endpoint_data_account_address: + sol_test_values::SWAP_ENDPOINT_DATA_ACCOUNT_ADDRESS, + usdc_token_mint_pubkey: sol_test_values::USDC_TOKEN_MINT_PUB_KEY, + }, ), }), }, diff --git a/state-chain/chains/src/sol/sol_tx_core.rs b/state-chain/chains/src/sol/sol_tx_core.rs index 2cb86ed8c5..6383675404 100644 --- a/state-chain/chains/src/sol/sol_tx_core.rs +++ b/state-chain/chains/src/sol/sol_tx_core.rs @@ -897,6 +897,8 @@ pub mod sol_test_values { // stored There will be a different one per each supported spl-token pub const USDC_TOKEN_VAULT_ASSOCIATED_TOKEN_ACCOUNT: SolAddress = const_address("GgqCE4bTwMy4QWVaTRTKJqETAgim49zNrH1dL6zXaTpd"); + pub const SWAP_ENDPOINT_DATA_ACCOUNT_ADDRESS: SolAddress = + const_address("GgqCE4bTwMy4QWVaTRTKJqETAgim49zNrH1dL6zXaTpd"); pub const NONCE_ACCOUNTS: [SolAddress; 10] = [ const_address("2cNMwUCF51djw2xAiiU54wz1WrU8uG4Q8Kp8nfEuwghw"), const_address("HVG21SovGzMBJDB9AQNuWb6XYq4dDZ6yUwCbRUuFnYDo"), diff --git a/state-chain/node/src/chain_spec.rs b/state-chain/node/src/chain_spec.rs index c3b2ec6c9e..a9801dc356 100644 --- a/state-chain/node/src/chain_spec.rs +++ b/state-chain/node/src/chain_spec.rs @@ -374,6 +374,7 @@ pub fn inner_cf_development_config( 100_000, sol_vault_program, sol_usdc_token_mint_pubkey, + sol_swap_endpoint_program_data_account, )), }, )) @@ -535,6 +536,7 @@ macro_rules! network_spec { 100000, sol_vault_program, sol_usdc_token_mint_pubkey, + sol_swap_endpoint_program_data_account, )), }, )) diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 190bc7d7aa..9ce1d9e088 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -79,6 +79,7 @@ pub fn initial_state( priority_fee: SolAmount, vault_program: SolAddress, usdc_token_mint_pubkey: SolAddress, + swap_endpoint_data_account_address: SolAddress, ) -> InitialStateOf { InitialState { unsynchronised_state: ( @@ -107,7 +108,7 @@ pub fn initial_state( (), (), LIVENESS_CHECK_DURATION, - (), + SolanaVaultSwapsSettings { swap_endpoint_data_account_address, usdc_token_mint_pubkey }, ), } } @@ -168,7 +169,7 @@ pub type SolanaVaultSwapTracking = ContractSwapAccountAndSender, SolanaVaultSwapDetails, BlockNumberFor, - (), + SolanaVaultSwapsSettings, SolanaVaultSwapsHandler, ::ValidatorId, SolanaTransactionBuildingError, @@ -520,11 +521,11 @@ impl ElectionEgressWitnesser for SolanaEgressWitnessingTrigger { Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, PartialOrd, Ord, )] pub struct SolanaVaultSwapDetails { - from: Asset, - to: Asset, - deposit_amount: AssetAmount, - destination_address: EncodedAddress, - tx_hash: TransactionHash, + pub from: Asset, + pub to: Asset, + pub deposit_amount: AssetAmount, + pub destination_address: EncodedAddress, + pub tx_hash: TransactionHash, } pub struct SolanaVaultSwapsHandler; @@ -562,3 +563,21 @@ impl Environment::get_number_of_available_sol_nonce_accounts() } } + +#[derive( + Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, PartialOrd, Ord, +)] +pub struct SolanaVaultSwapsSettings { + pub swap_endpoint_data_account_address: SolAddress, + pub usdc_token_mint_pubkey: SolAddress, +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for SolanaVaultSwapsSettings { + fn benchmark_value() -> Self { + Self { + swap_endpoint_data_account_address: BenchmarkValue::benchmark_value(), + usdc_token_mint_pubkey: BenchmarkValue::benchmark_value(), + } + } +} From 84cd7da9310ab8b274a6f6e8d021a8a6f72c8101 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Wed, 30 Oct 2024 17:07:53 +0100 Subject: [PATCH 12/62] feat: vault swap details --- engine/src/witness/sol/program_swaps_witnessing.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index db1317cf22..45c210f9e3 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -9,7 +9,10 @@ use anyhow::ensure; use anyhow::{anyhow /* ensure */}; use base64::Engine; use borsh::{BorshDeserialize, BorshSerialize}; -use cf_chains::sol::{api::ContractSwapAccountAndSender, SolAddress}; +use cf_chains::{ + address::EncodedAddress, + sol::{api::ContractSwapAccountAndSender, SolAddress}, +}; use cf_primitives::Asset; use futures::{stream, StreamExt, TryStreamExt}; use itertools::Itertools; @@ -97,9 +100,9 @@ pub async fn get_program_swaps( }, SolanaVaultSwapDetails { from: if data.src_token.is_none() {Asset::Sol} else {Asset::SolUsdc}, deposit_amount: data.amount.into(), - to: todo!(), - destination_address: todo!(), - tx_hash: todo!() + destination_address: EncodedAddress::from_chain_bytes(data.dst_chain.try_into().map_err(|e| warn!("error while parsing destination chain for solana vault swap:{}. Omitting swap", e)).ok()?, data.dst_address.to_vec()).map_err(|e| warn!("failed to decode the destination chain address for solana vault swap:{}. Omitting swap", e)).ok()?, + to: data.dst_token.try_into().map_err(|e| warn!("error while decoding destination token for solana vault swap: {}. Omitting swap", e)).ok()?, + tx_hash: Default::default(), // TODO }))), // It could happen that some account is closed between the queries. This should // not happen because: From 24c5a6d4d70e7b1f46c7089423f9a6e0d8434adc Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:38:18 +0200 Subject: [PATCH 13/62] chore: bouncer solana program swaps (#5318) * chore: first iteration adding anchor * chore: small cleanup * chore: syntax improvement * chore: add comment * chore: update compute units --- bouncer/shared/contract_swap.ts | 342 ++++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 bouncer/shared/contract_swap.ts diff --git a/bouncer/shared/contract_swap.ts b/bouncer/shared/contract_swap.ts new file mode 100644 index 0000000000..9cf80a9ad4 --- /dev/null +++ b/bouncer/shared/contract_swap.ts @@ -0,0 +1,342 @@ +import * as anchor from '@coral-xyz/anchor'; +// import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'; + +import { + InternalAsset as Asset, + executeSwap, + ExecuteSwapParams, + approveVault, + Asset as SCAsset, + Chains, + InternalAsset, + Chain, +} from '@chainflip/cli'; +import { HDNodeWallet, Wallet, getDefaultProvider } from 'ethers'; +import { PublicKey, sendAndConfirmTransaction, Keypair } from '@solana/web3.js'; +import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from '@solana/spl-token'; +import { + observeBalanceIncrease, + getContractAddress, + observeCcmReceived, + amountToFineAmount, + defaultAssetAmounts, + chainFromAsset, + getEvmEndpoint, + assetDecimals, + stateChainAssetFromAsset, + chainGasAsset, + evmChains, + getSolWhaleKeyPair, + getSolConnection, +} from './utils'; +import { getBalance } from './get_balance'; +import { CcmDepositMetadata } from '../shared/new_swap'; +import { send } from './send'; +import { SwapContext, SwapStatus } from './swap_context'; + +import VaultIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/vault.json'; +import SwapEndpointIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/swap_endpoint.json'; +import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint'; +import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/vault'; + +// Workaround because of anchor issue +const { BN } = anchor.default; + +const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc']; + +export async function executeContractSwap( + srcAsset: Asset, + destAsset: Asset, + destAddress: string, + wallet: HDNodeWallet, + messageMetadata?: CcmDepositMetadata, +): ReturnType { + const srcChain = chainFromAsset(srcAsset); + const destChain = chainFromAsset(destAsset); + + const networkOptions = { + signer: wallet, + network: 'localnet', + vaultContractAddress: getContractAddress(srcChain, 'VAULT'), + srcTokenContractAddress: getContractAddress(srcChain, srcAsset), + } as const; + const txOptions = { + // This is run with fresh addresses to prevent nonce issues. Will be 1 for ERC20s. + gasLimit: srcChain === Chains.Arbitrum ? 10000000n : 200000n, + } as const; + + const receipt = await executeSwap( + { + destChain, + destAsset: stateChainAssetFromAsset(destAsset), + // It is important that this is large enough to result in + // an amount larger than existential (e.g. on Polkadot): + amount: amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), + destAddress, + srcAsset: stateChainAssetFromAsset(srcAsset), + srcChain, + ccmParams: messageMetadata && { + gasBudget: messageMetadata.gasBudget.toString(), + message: messageMetadata.message, + cfParameters: messageMetadata.cfParameters, + }, + } as ExecuteSwapParams, + networkOptions, + txOptions, + ); + + return receipt; +} + +// Temporary before the SDK implements this. +export async function executeSolContractSwap( + srcAsset: Asset, + destAsset: Asset, + destAddress: string, + messageMetadata?: CcmDepositMetadata, +) { + const destChain = chainFromAsset(destAsset); + + // const solanaSwapEndpointId = new PublicKey(getContractAddress('Solana', 'SWAP_ENDPOINT')); + const solanaVaultDataAccount = new PublicKey(getContractAddress('Solana', 'DATA_ACCOUNT')); + const swapEndpointDataAccount = new PublicKey( + getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'), + ); + const whaleKeypair = getSolWhaleKeyPair(); + + // We should just be able to do this instead but it's not working... + // const wallet = new NodeWallet(whaleKeypair); + // const provider = new anchor.AnchorProvider(connection, wallet, { + // commitment: 'processed', + // }); + // const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl, provider); + // const vaultProgram = new anchor.Program(VaultIdl, provider); + + // The current workaround requires having the wallet in a id.json and then set the ANCHOR_WALLET env. + // TODO: Depending on how the SDK is implemented we can remove this. + process.env.ANCHOR_WALLET = 'shared/solana_keypair.json'; + + const connection = getSolConnection(); + const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); + const vaultProgram = new anchor.Program(VaultIdl as Vault); + + const newEventAccountKeypair = Keypair.generate(); + const fetchedDataAccount = await vaultProgram.account.dataAccount.fetch(solanaVaultDataAccount); + const aggKey = fetchedDataAccount.aggKey; + + const tx = + srcAsset === 'Sol' + ? await cfSwapEndpointProgram.methods + .xSwapNative({ + amount: new BN( + amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), + ), + dstChain: Number(destChain), + dstAddress: Buffer.from(destAddress), + dstToken: Number(stateChainAssetFromAsset(destAsset)), + ccmParameters: messageMetadata + ? { + message: Buffer.from(messageMetadata.message.slice(2), 'hex'), + gasAmount: new BN(messageMetadata.gasBudget), + } + : null, + cfParameters: Buffer.from(messageMetadata?.cfParameters?.slice(2) ?? '', 'hex'), + }) + .accountsPartial({ + dataAccount: solanaVaultDataAccount, + aggKey, + from: whaleKeypair.publicKey, + eventDataAccount: newEventAccountKeypair.publicKey, + swapEndpointDataAccount, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([whaleKeypair, newEventAccountKeypair]) + .transaction() + : await cfSwapEndpointProgram.methods + .xSwapToken({ + amount: new BN( + amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), + ), + dstChain: Number(destChain), + dstAddress: Buffer.from(destAddress), + dstToken: Number(stateChainAssetFromAsset(destAsset)), + ccmParameters: messageMetadata + ? { + message: Buffer.from(messageMetadata.message.slice(2), 'hex'), + gasAmount: new BN(messageMetadata.gasBudget), + } + : null, + cfParameters: Buffer.from(messageMetadata?.cfParameters?.slice(2) ?? '', 'hex'), + decimals: assetDecimals(srcAsset), + }) + .accountsPartial({ + dataAccount: solanaVaultDataAccount, + tokenVaultAssociatedTokenAccount: new PublicKey( + getContractAddress('Solana', 'TOKEN_VAULT_ATA'), + ), + from: whaleKeypair.publicKey, + fromTokenAccount: getAssociatedTokenAddressSync( + new PublicKey(getContractAddress('Solana', 'SolUsdc')), + whaleKeypair.publicKey, + false, + ), + eventDataAccount: newEventAccountKeypair.publicKey, + swapEndpointDataAccount, + tokenSupportedAccount: new PublicKey( + getContractAddress('Solana', 'SolUsdcTokenSupport'), + ), + tokenProgram: TOKEN_PROGRAM_ID, + mint: new PublicKey(getContractAddress('Solana', 'SolUsdc')), + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([whaleKeypair, newEventAccountKeypair]) + .transaction(); + const txHash = await sendAndConfirmTransaction(connection, tx, [ + whaleKeypair, + newEventAccountKeypair, + ]); + + console.log('tx', txHash); + return txHash; +} +export type ContractSwapParams = { + sourceAsset: Asset; + destAsset: Asset; + destAddress: string; + txHash: string; +}; + +export async function performSwapViaContract( + sourceAsset: Asset, + destAsset: Asset, + destAddress: string, + swapTag = '', + messageMetadata?: CcmDepositMetadata, + swapContext?: SwapContext, + log = true, +): Promise { + const tag = swapTag ?? ''; + + const srcChain = chainFromAsset(sourceAsset); + let wallet; + + try { + if (evmChains.includes(srcChain)) { + // Generate a new wallet for each contract swap to prevent nonce issues when running in parallel + // with other swaps via deposit channels. + const mnemonic = Wallet.createRandom().mnemonic?.phrase ?? ''; + if (mnemonic === '') { + throw new Error('Failed to create random mnemonic'); + } + wallet = Wallet.fromPhrase(mnemonic).connect(getDefaultProvider(getEvmEndpoint(srcChain))); + + // Fund new key with native asset and asset to swap. + await send(chainGasAsset(srcChain) as InternalAsset, wallet.address); + await send(sourceAsset, wallet.address); + + if (erc20Assets.includes(sourceAsset)) { + // Doing effectively infinite approvals to make sure it doesn't fail. + // eslint-disable-next-line @typescript-eslint/no-use-before-define + await approveTokenVault( + sourceAsset, + ( + BigInt( + amountToFineAmount(defaultAssetAmounts(sourceAsset), assetDecimals(sourceAsset)), + ) * 100n + ).toString(), + wallet, + ); + } + } + swapContext?.updateStatus(swapTag, SwapStatus.ContractApproved); + + const oldBalance = await getBalance(destAsset, destAddress); + if (log) { + console.log(`${tag} Old balance: ${oldBalance}`); + console.log( + `${tag} Executing (${sourceAsset}) contract swap to(${destAsset}) ${destAddress}. Current balance: ${oldBalance}`, + ); + } + + let txHash: string; + let sourceAddress: string; + + // TODO: Temporary before the SDK implements this. + if (evmChains.includes(srcChain)) { + // To uniquely identify the contractSwap, we need to use the TX hash. This is only known + // after sending the transaction, so we send it first and observe the events afterwards. + // There are still multiple blocks of safety margin inbetween before the event is emitted + const receipt = await executeContractSwap( + sourceAsset, + destAsset, + destAddress, + wallet!, + messageMetadata, + ); + txHash = receipt.hash; + sourceAddress = wallet!.address.toLowerCase(); + } else { + txHash = await executeSolContractSwap( + sourceAsset, + destAsset, + destAddress, + // wallet!, + messageMetadata, + ); + sourceAddress = getSolWhaleKeyPair().publicKey.toBase58(); + } + + swapContext?.updateStatus(swapTag, SwapStatus.ContractExecuted); + + const ccmEventEmitted = messageMetadata + ? observeCcmReceived(sourceAsset, destAsset, destAddress, messageMetadata, sourceAddress) + : Promise.resolve(); + + const [newBalance] = await Promise.all([ + observeBalanceIncrease(destAsset, destAddress, oldBalance), + ccmEventEmitted, + ]); + if (log) { + console.log(`${tag} Swap success! New balance: ${newBalance}!`); + } + swapContext?.updateStatus(swapTag, SwapStatus.Success); + return { + sourceAsset, + destAsset, + destAddress, + txHash, + }; + } catch (err) { + console.error('err:', err); + swapContext?.updateStatus(swapTag, SwapStatus.Failure); + if (err instanceof Error) { + console.log(err.stack); + } + throw new Error(`${tag} ${err}`); + } +} +export async function approveTokenVault(srcAsset: Asset, amount: string, wallet: HDNodeWallet) { + if (!erc20Assets.includes(srcAsset)) { + throw new Error(`Unsupported asset, not an ERC20: ${srcAsset}`); + } + + const chain = chainFromAsset(srcAsset as Asset); + + await approveVault( + { + amount, + srcChain: chain as Chain, + srcAsset: stateChainAssetFromAsset(srcAsset) as SCAsset, + }, + { + signer: wallet, + network: 'localnet', + vaultContractAddress: getContractAddress(chain, 'VAULT'), + srcTokenContractAddress: getContractAddress(chain, srcAsset), + }, + // This is run with fresh addresses to prevent nonce issues + { + nonce: 0, + }, + ); +} From 33da84b0c659dfa6a0897b1e9cd07006c7839043 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Tue, 29 Oct 2024 15:37:20 +1300 Subject: [PATCH 14/62] Fixed bouncer --- bouncer/shared/contract_swap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bouncer/shared/contract_swap.ts b/bouncer/shared/contract_swap.ts index 9cf80a9ad4..3f647b1696 100644 --- a/bouncer/shared/contract_swap.ts +++ b/bouncer/shared/contract_swap.ts @@ -40,7 +40,7 @@ import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0/ import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/vault'; // Workaround because of anchor issue -const { BN } = anchor.default; +const { BN } = anchor; const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc']; From a8c60a071656f5c555acc1bed27f3bf7b15a2eed Mon Sep 17 00:00:00 2001 From: albert Date: Thu, 31 Oct 2024 11:21:16 +0100 Subject: [PATCH 15/62] chore: fix issues and renamings --- engine/src/witness/sol.rs | 6 +- .../witness/sol/program_swaps_witnessing.rs | 22 ++--- foreign-chains/solana/sol-prim/src/consts.rs | 2 +- state-chain/chains/src/lib.rs | 3 +- state-chain/chains/src/sol.rs | 8 +- state-chain/chains/src/sol/api.rs | 16 ++-- state-chain/chains/src/sol/benchmarking.rs | 6 +- state-chain/chains/src/sol/sol_tx_core.rs | 95 ++++++++++--------- .../chains/src/sol/transaction_builder.rs | 34 +++---- .../solana_swap_accounts_tracking.rs | 14 +-- .../runtime/src/chainflip/solana_elections.rs | 13 ++- 11 files changed, 111 insertions(+), 108 deletions(-) diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index 97ed17fa39..ef2a74bb9d 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -18,7 +18,7 @@ use crate::{ }; use anyhow::Result; use cf_chains::{ - sol::{api::ContractSwapAccountAndSender, SolHash}, + sol::{api::VaultSwapAccountAndSender, SolHash}, Chain, }; use futures::FutureExt; @@ -206,9 +206,7 @@ impl VoterApi for SolanaVaultSwapsVoter { properties .witnessed_open_accounts .into_iter() - .map(|ContractSwapAccountAndSender { contract_swap_account, .. }| { - contract_swap_account - }) + .map(|VaultSwapAccountAndSender { vault_swap_account, .. }| vault_swap_account) .collect(), properties.closure_initiated_accounts, settings.usdc_token_mint_pubkey, diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 45c210f9e3..7619d2d134 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -11,7 +11,8 @@ use base64::Engine; use borsh::{BorshDeserialize, BorshSerialize}; use cf_chains::{ address::EncodedAddress, - sol::{api::ContractSwapAccountAndSender, SolAddress}, + assets::sol::Asset as SolAsset, + sol::{api::VaultSwapAccountAndSender, SolAddress}, }; use cf_primitives::Asset; use futures::{stream, StreamExt, TryStreamExt}; @@ -65,13 +66,10 @@ pub async fn get_program_swaps( sol_rpc: &SolRetryRpcClient, swap_endpoint_data_account_address: SolAddress, sc_open_accounts: Vec, - sc_closure_initiated_accounts: BTreeSet, + sc_closure_initiated_accounts: BTreeSet, usdc_token_mint_pubkey: SolAddress, ) -> Result< - ( - Vec<(ContractSwapAccountAndSender, SolanaVaultSwapDetails)>, - Vec, - ), + (Vec<(VaultSwapAccountAndSender, SolanaVaultSwapDetails)>, Vec), anyhow::Error, > { let (new_program_swap_accounts, closed_accounts, slot) = get_changed_program_swap_accounts( @@ -94,8 +92,8 @@ pub async fn get_program_swaps( Some(data) if (data.src_token.is_none() || data.src_token.is_some_and(|addr| addr == usdc_token_mint_pubkey.0)) => - Some(Ok((ContractSwapAccountAndSender { - contract_swap_account: account, + Some(Ok((VaultSwapAccountAndSender { + vault_swap_account: account, swap_sender: data.sender.into() }, SolanaVaultSwapDetails { from: if data.src_token.is_none() {Asset::Sol} else {Asset::SolUsdc}, @@ -141,9 +139,9 @@ pub async fn get_program_swaps( async fn get_changed_program_swap_accounts( sol_rpc: &SolRetryRpcClient, sc_opened_accounts: Vec, - sc_closure_initiated_accounts: BTreeSet, + sc_closure_initiated_accounts: BTreeSet, swap_endpoint_data_account_address: SolAddress, -) -> Result<(Vec, Vec, u64), anyhow::Error> { +) -> Result<(Vec, Vec, u64), anyhow::Error> { let (_historical_number_event_accounts, open_event_accounts, slot) = get_swap_endpoint_data(sol_rpc, swap_endpoint_data_account_address) .await @@ -152,7 +150,7 @@ async fn get_changed_program_swap_accounts( let sc_opened_accounts_hashset: HashSet<_> = sc_opened_accounts.iter().collect(); let sc_closure_initiated_accounts_hashset = sc_closure_initiated_accounts .iter() - .map(|ContractSwapAccountAndSender { contract_swap_account, .. }| contract_swap_account) + .map(|VaultSwapAccountAndSender { vault_swap_account, .. }| vault_swap_account) .collect::>(); let mut new_program_swap_accounts = Vec::new(); @@ -168,7 +166,7 @@ async fn get_changed_program_swap_accounts( let open_event_accounts_hashset: HashSet<_> = open_event_accounts.iter().collect(); for account in sc_closure_initiated_accounts { - if !open_event_accounts_hashset.contains(&account.contract_swap_account) { + if !open_event_accounts_hashset.contains(&account.vault_swap_account) { closed_accounts.push(account); } } diff --git a/foreign-chains/solana/sol-prim/src/consts.rs b/foreign-chains/solana/sol-prim/src/consts.rs index 73c8b9f698..a80c364606 100644 --- a/foreign-chains/solana/sol-prim/src/consts.rs +++ b/foreign-chains/solana/sol-prim/src/consts.rs @@ -44,6 +44,6 @@ pub const NONCE_ACCOUNT_LENGTH: u64 = 80u64; pub const SOL_USDC_DECIMAL: u8 = 6u8; -pub const MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES: usize = 10; +pub const MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES: usize = 10; pub const MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS: u32 = 14400; pub const NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES: usize = 4; diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index ecf4bac93e..15bc91532f 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -4,6 +4,7 @@ #![feature(split_array)] use core::{fmt::Display, iter::Step}; +use sol::api::VaultSwapAccountAndSender; use sp_std::marker::PhantomData; use crate::{ @@ -491,7 +492,7 @@ pub trait RegisterRedemption: ApiCall<::ChainCrypto> { pub trait CloseSolanaVaultSwapAccounts: ApiCall<::ChainCrypto> { fn new_unsigned( - accounts: Vec, + accounts: Vec, ) -> Result; } diff --git a/state-chain/chains/src/sol.rs b/state-chain/chains/src/sol.rs index 5042b4771d..85beeb93fc 100644 --- a/state-chain/chains/src/sol.rs +++ b/state-chain/chains/src/sol.rs @@ -26,8 +26,10 @@ pub use crate::assets::sol::Asset as SolAsset; use crate::benchmarking_value::BenchmarkValue; pub use sol_prim::{ consts::{ - LAMPORTS_PER_SIGNATURE, MAX_TRANSACTION_LENGTH, MICROLAMPORTS_PER_LAMPORT, - TOKEN_ACCOUNT_RENT, + LAMPORTS_PER_SIGNATURE, MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES, + MAX_TRANSACTION_LENGTH, MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, + MICROLAMPORTS_PER_LAMPORT, + NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES, TOKEN_ACCOUNT_RENT, }, pda::{Pda as DerivedAddressBuilder, PdaError as AddressDerivationError}, Address as SolAddress, Amount as SolAmount, ComputeLimit as SolComputeLimit, Digest as SolHash, @@ -153,7 +155,7 @@ pub mod compute_units_costs { pub const COMPUTE_UNITS_PER_ROTATION: SolComputeLimit = 8_000u32; pub const COMPUTE_UNITS_PER_SET_GOV_KEY: SolComputeLimit = 15_000u32; pub const COMPUTE_UNITS_PER_BUMP_DERIVATION: SolComputeLimit = 2_000u32; - pub const COMPUTE_UNITS_PER_CLOSE_EVENT_ACCOUNTS: SolComputeLimit = 10_000u32; + pub const COMPUTE_UNITS_PER_CLOSE_VAULT_SWAP_ACCOUNTS: SolComputeLimit = 10_000u32; pub const COMPUTE_UNITS_PER_CLOSE_ACCOUNT: SolComputeLimit = 10_000u32; /// This is equivalent to a priority fee diff --git a/state-chain/chains/src/sol/api.rs b/state-chain/chains/src/sol/api.rs index 2a4cd8f27a..25dcc6e742 100644 --- a/state-chain/chains/src/sol/api.rs +++ b/state-chain/chains/src/sol/api.rs @@ -53,8 +53,8 @@ pub type DurableNonceAndAccount = (SolAddress, SolHash); PartialOrd, Eq, )] -pub struct ContractSwapAccountAndSender { - pub contract_swap_account: SolAddress, +pub struct VaultSwapAccountAndSender { + pub vault_swap_account: SolAddress, pub swap_sender: SolAddress, } @@ -404,8 +404,8 @@ impl SolanaApi { }) } - pub fn batch_close_event_accounts( - event_accounts: Vec, + pub fn batch_close_vault_swap_accounts( + vault_swap_accounts: Vec, ) -> Result { // Lookup environment variables, such as aggkey and durable nonce. let agg_key = Environment::current_agg_key()?; @@ -414,8 +414,8 @@ impl SolanaApi { let durable_nonce = Environment::nonce_account()?; // Build the transaction - let transaction = SolanaTransactionBuilder::close_event_accounts( - event_accounts, + let transaction = SolanaTransactionBuilder::close_vault_swap_accounts( + vault_swap_accounts, sol_api_environment.vault_program_data_account, sol_api_environment.swap_endpoint_program, sol_api_environment.swap_endpoint_program_data_account, @@ -543,9 +543,9 @@ impl TransferFallback for SolanaApi { impl CloseSolanaVaultSwapAccounts for SolanaApi { fn new_unsigned( - accounts: Vec, + accounts: Vec, ) -> Result { - Self::batch_close_contract_swap_accounts(accounts) + Self::batch_close_vault_swap_accounts(accounts) } } diff --git a/state-chain/chains/src/sol/benchmarking.rs b/state-chain/chains/src/sol/benchmarking.rs index 06a8dead67..aad9226fde 100644 --- a/state-chain/chains/src/sol/benchmarking.rs +++ b/state-chain/chains/src/sol/benchmarking.rs @@ -1,7 +1,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::{ - api::{ContractSwapAccountAndSender, SolanaApi}, + api::{VaultSwapAccountAndSender, SolanaApi}, SolAddress, SolHash, SolMessage, SolSignature, SolTrackedData, SolTransaction, SolanaTransactionData, }; @@ -67,11 +67,11 @@ impl BenchmarkValue for SolanaApi { } } -impl BenchmarkValue for ContractSwapAccountAndSender { +impl BenchmarkValue for VaultSwapAccountAndSender { fn benchmark_value() -> Self { Self { swap_sender: BenchmarkValue::benchmark_value(), - contract_swap_account: BenchmarkValue::benchmark_value(), + vault_swap_account: BenchmarkValue::benchmark_value(), } } } diff --git a/state-chain/chains/src/sol/sol_tx_core.rs b/state-chain/chains/src/sol/sol_tx_core.rs index 6383675404..ba3b08daeb 100644 --- a/state-chain/chains/src/sol/sol_tx_core.rs +++ b/state-chain/chains/src/sol/sol_tx_core.rs @@ -874,8 +874,9 @@ impl FromStr for Hash { pub mod sol_test_values { use crate::{ sol::{ - signing_key::SolSigningKey, sol_tx_core::signer::Signer, SolAddress, SolAmount, - SolAsset, SolCcmAccounts, SolCcmAddress, SolComputeLimit, SolHash, + api::VaultSwapAccountAndSender, signing_key::SolSigningKey, + sol_tx_core::signer::Signer, SolAddress, SolAmount, SolAsset, SolCcmAccounts, + SolCcmAddress, SolComputeLimit, SolHash, }, CcmChannelMetadata, CcmDepositMetadata, ForeignChain, ForeignChainAddress, }; @@ -915,51 +916,51 @@ pub mod sol_test_values { const_address("35uYgHdfZQT4kHkaaXQ6ZdCkK5LFrsk43btTLbGCRCNT"); pub const SWAP_ENDPOINT_PROGRAM_DATA_ACCOUNT: SolAddress = const_address("2tmtGLQcBd11BMiE9B1tAkQXwmPNgR79Meki2Eme4Ec9"); - pub const EVENT_AND_SENDER_ACCOUNTS: [(SolAddress, SolAddress); 11] = [ - ( - const_address("2cHcSNtikMpjxJfwwoYL3udpy7hedRExyhakk2eZ6cYA"), - const_address("7tVhSXxGfZyHQem8MdZVB6SoRsrvV4H8h1rX6hwBuvEA"), - ), - ( - const_address("6uuU1NFyThN3KJpU9mYXkGSmd8Qgncmd9aYAWYN71VkC"), - const_address("P3GYr1Z67jdBVimzFjMXQpeuew5TY5txoZ9CvqASpaP"), - ), - ( - const_address("DmAom3kp2ZKk9cnbWEsnbkLHkp3sx9ef1EX6GWj1JRUB"), - const_address("CS7yX5TKX36ugF4bycmVQ5vqB2ZbNVC5tvtrtLP92GDW"), - ), - ( - const_address("CJSdHgxwHLEbTsxKsJk9UyJxUEgku2UC9GXRTzR2ieSh"), - const_address("2taCR53epDtdrFZBxzKcbmv3cb5Umc5x9k2YCjmTDAnH"), - ), - ( - const_address("7DGwjsQEFA7XzZS9z5YbMhYGzWJSh5T78hRrU47RDTd2"), - const_address("FDPzoZj951Hq92jhoFdyzAVyUjyXhL8VEnqBhyjsDhow"), - ), - ( - const_address("A6yYXUmZHa32mcFRnwwq8ZQKCEYUn9ewF1vWn2wsXN5a"), - const_address("9bNNNU9B52VPVGm6zRccwPEexDHD1ntndD2aNu2un3ca"), - ), - ( - const_address("2F3365PULNzt7moa9GgHARy7Lumj5ptDQF7wDt6xeuHK"), - const_address("4m5t38fJsvULKaPyWZKWjzfbvnzBGL86BTRNk5vLLUrh"), - ), - ( - const_address("8sCBWv9tzdf2iC4GNj61UBN6TZpzsLP5Ppv9x1ENX4HT"), - const_address("A3P5kfRU1vgZn7GjNMomS8ye6GHsoHC4JoVNUotMbDPE"), - ), - ( - const_address("3b1FkNvnvKJ4TzKeft7wA47VfYpjkoHPE4ER13ZTNecX"), - const_address("ERwuPnX66dCZqj85kH9QQJmwcVrzcczBnu8onJY2R7tG"), - ), - ( - const_address("Bnrp9X562krXVfaY8FnwJa3Mxp1gbDCrvGNW1qc99rKe"), - const_address("2aoZg41FFnTBnuHpkfHdFsCuPz8DhN4dsUW5386XwE8g"), - ), - ( - const_address("EuLceVgXMaJNPT7C88pnL7DRWcf1poy9BCeWY1GL8Agd"), - const_address("G1iXMtwUU76JGau9cJm6N8wBTmcsvyXuJcC7PtfU1TXZ"), - ), + pub const EVENT_AND_SENDER_ACCOUNTS: [VaultSwapAccountAndSender; 11] = [ + VaultSwapAccountAndSender { + vault_swap_account: const_address("2cHcSNtikMpjxJfwwoYL3udpy7hedRExyhakk2eZ6cYA"), + swap_sender: const_address("7tVhSXxGfZyHQem8MdZVB6SoRsrvV4H8h1rX6hwBuvEA"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("6uuU1NFyThN3KJpU9mYXkGSmd8Qgncmd9aYAWYN71VkC"), + swap_sender: const_address("P3GYr1Z67jdBVimzFjMXQpeuew5TY5txoZ9CvqASpaP"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("DmAom3kp2ZKk9cnbWEsnbkLHkp3sx9ef1EX6GWj1JRUB"), + swap_sender: const_address("CS7yX5TKX36ugF4bycmVQ5vqB2ZbNVC5tvtrtLP92GDW"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("CJSdHgxwHLEbTsxKsJk9UyJxUEgku2UC9GXRTzR2ieSh"), + swap_sender: const_address("2taCR53epDtdrFZBxzKcbmv3cb5Umc5x9k2YCjmTDAnH"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("7DGwjsQEFA7XzZS9z5YbMhYGzWJSh5T78hRrU47RDTd2"), + swap_sender: const_address("FDPzoZj951Hq92jhoFdyzAVyUjyXhL8VEnqBhyjsDhow"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("A6yYXUmZHa32mcFRnwwq8ZQKCEYUn9ewF1vWn2wsXN5a"), + swap_sender: const_address("9bNNNU9B52VPVGm6zRccwPEexDHD1ntndD2aNu2un3ca"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("2F3365PULNzt7moa9GgHARy7Lumj5ptDQF7wDt6xeuHK"), + swap_sender: const_address("4m5t38fJsvULKaPyWZKWjzfbvnzBGL86BTRNk5vLLUrh"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("8sCBWv9tzdf2iC4GNj61UBN6TZpzsLP5Ppv9x1ENX4HT"), + swap_sender: const_address("A3P5kfRU1vgZn7GjNMomS8ye6GHsoHC4JoVNUotMbDPE"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("3b1FkNvnvKJ4TzKeft7wA47VfYpjkoHPE4ER13ZTNecX"), + swap_sender: const_address("ERwuPnX66dCZqj85kH9QQJmwcVrzcczBnu8onJY2R7tG"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("Bnrp9X562krXVfaY8FnwJa3Mxp1gbDCrvGNW1qc99rKe"), + swap_sender: const_address("2aoZg41FFnTBnuHpkfHdFsCuPz8DhN4dsUW5386XwE8g"), + }, + VaultSwapAccountAndSender { + vault_swap_account: const_address("EuLceVgXMaJNPT7C88pnL7DRWcf1poy9BCeWY1GL8Agd"), + swap_sender: const_address("G1iXMtwUU76JGau9cJm6N8wBTmcsvyXuJcC7PtfU1TXZ"), + }, ]; pub const RAW_KEYPAIR: [u8; 32] = [ 6, 151, 150, 20, 145, 210, 176, 113, 98, 200, 192, 80, 73, 63, 133, 232, 208, 124, 81, 213, diff --git a/state-chain/chains/src/sol/transaction_builder.rs b/state-chain/chains/src/sol/transaction_builder.rs index 1bc32d9adc..1a2fd637e0 100644 --- a/state-chain/chains/src/sol/transaction_builder.rs +++ b/state-chain/chains/src/sol/transaction_builder.rs @@ -11,11 +11,11 @@ use sol_prim::consts::{ use crate::{ sol::{ - api::{DurableNonceAndAccount, EventAccountAndSender, SolanaTransactionBuildingError}, + api::{DurableNonceAndAccount, SolanaTransactionBuildingError, VaultSwapAccountAndSender}, compute_units_costs::{ compute_limit_with_buffer, BASE_COMPUTE_UNITS_PER_TX, COMPUTE_UNITS_PER_BUMP_DERIVATION, COMPUTE_UNITS_PER_CLOSE_ACCOUNT, - COMPUTE_UNITS_PER_CLOSE_EVENT_ACCOUNTS, COMPUTE_UNITS_PER_FETCH_NATIVE, + COMPUTE_UNITS_PER_CLOSE_VAULT_SWAP_ACCOUNTS, COMPUTE_UNITS_PER_FETCH_NATIVE, COMPUTE_UNITS_PER_FETCH_TOKEN, COMPUTE_UNITS_PER_ROTATION, COMPUTE_UNITS_PER_SET_GOV_KEY, COMPUTE_UNITS_PER_TRANSFER_NATIVE, COMPUTE_UNITS_PER_TRANSFER_TOKEN, @@ -445,8 +445,8 @@ impl SolanaTransactionBuilder { /// Creates an instruction to close a number of open event swap accounts created via program /// swap. - pub fn close_event_accounts( - event_accounts: Vec, + pub fn close_vault_swap_accounts( + vault_swap_accounts: Vec, vault_program_data_account: SolAddress, swap_endpoint_program: SolAddress, swap_endpoint_data_account: SolAddress, @@ -454,14 +454,14 @@ impl SolanaTransactionBuilder { durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, ) -> Result { - let number_of_accounts = event_accounts.len(); - let event_and_sender_vec: Vec = event_accounts + let number_of_accounts = vault_swap_accounts.len(); + let swap_and_sender_vec: Vec = vault_swap_accounts .into_iter() // Both event account and payee should be writable and non-signers - .flat_map(|(event_account, payee)| { + .flat_map(|VaultSwapAccountAndSender { vault_swap_account, swap_sender }| { vec![ - AccountMeta::new(event_account.into(), false), - AccountMeta::new(payee.into(), false), + AccountMeta::new(vault_swap_account.into(), false), + AccountMeta::new(swap_sender.into(), false), ] }) .collect(); @@ -476,7 +476,7 @@ impl SolanaTransactionBuilder { agg_key.into(), compute_price, compute_limit_with_buffer( - COMPUTE_UNITS_PER_CLOSE_EVENT_ACCOUNTS + + COMPUTE_UNITS_PER_CLOSE_VAULT_SWAP_ACCOUNTS + COMPUTE_UNITS_PER_CLOSE_ACCOUNT * number_of_accounts as u32, ), ) @@ -725,11 +725,11 @@ mod test { } #[test] - fn can_close_event_accounts() { + fn can_close_vault_swap_accounts() { let env = api_env(); - let event_accounts = vec![EVENT_AND_SENDER_ACCOUNTS[0]]; - let transaction = SolanaTransactionBuilder::close_event_accounts( - event_accounts, + let vault_swap_accounts = vec![EVENT_AND_SENDER_ACCOUNTS[0]]; + let transaction = SolanaTransactionBuilder::close_vault_swap_accounts( + vault_swap_accounts, env.vault_program_data_account, env.swap_endpoint_program, env.swap_endpoint_program_data_account, @@ -739,18 +739,18 @@ mod test { ) .unwrap(); - // Serialized tx built in `close_event_accounts` test + // Serialized tx built in `close_vault_swap_accounts` test let expected_serialized_tx = hex_literal::hex!("01026e2d4bdca9e638b59507a70ea62ad88f098ffb25df028a19288702698fdf6d1cf77618b2123c0205a8e8d272ba8ea645b7e75c606ca3aa4356b65fa52ca20b0100050af79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17e5cc1f4d51a40626e11c783b75a45a4922615ecd7f5320b9d4d46481a196a317eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1921c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2665730decf59d4cd6db8437dab77302287431eb7562b5997601851a0eab6946f00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000000e14940a2247d0a8a33650d7dfe12d269ecabce61c1219b5a6dcdb6961026e091ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004050302070004040000000600090340420f000000000006000502307500000905080003010408a5663d01b94dbd79").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } #[test] - fn can_close_max_event_accounts() { + fn can_close_max_vault_swap_accounts() { let env = api_env(); // We can close 11 accounts without reaching the transaction length limit. - let transaction = SolanaTransactionBuilder::close_event_accounts( + let transaction = SolanaTransactionBuilder::close_vault_swap_accounts( EVENT_AND_SENDER_ACCOUNTS.to_vec(), env.vault_program_data_account, env.swap_endpoint_program, diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 1bffe81fc8..9fb7e0fc7d 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -6,7 +6,7 @@ use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; #[cfg(feature = "runtime-benchmarks")] use cf_chains::benchmarking_value::BenchmarkValue; #[cfg(feature = "runtime-benchmarks")] -use cf_chains::sol::api::ContractSwapAccountAndSender; +use cf_chains::sol::api::VaultSwapAccountAndSender; use crate::{ electoral_system::{ @@ -17,7 +17,7 @@ use crate::{ CorruptStorageError, ElectionIdentifier, }; use cf_chains::sol::{ - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES, + MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES, MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES, }; @@ -45,10 +45,10 @@ pub struct SolanaVaultSwapsKnownAccounts { } #[cfg(feature = "runtime-benchmarks")] -impl BenchmarkValue for SolanaVaultSwapsKnownAccounts { +impl BenchmarkValue for SolanaVaultSwapsKnownAccounts { fn benchmark_value() -> Self { Self { - witnessed_open_accounts: vec![BenchmarkValue::benchmark_value()], + witnessed_open_accounts: sp_std::vec![BenchmarkValue::benchmark_value()], closure_initiated_accounts: BTreeSet::from([BenchmarkValue::benchmark_value()]), } } @@ -142,18 +142,18 @@ impl< if Hook::get_number_of_available_sol_nonce_accounts() > NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && (known_accounts.witnessed_open_accounts.len() >= - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES || + MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES || (*current_block_number) .checked_sub(&electoral_access.unsynchronised_state()?) .expect("current block number is always greater than when apicall was last created") .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) { let accounts_to_close: Vec<_> = if known_accounts.witnessed_open_accounts.len() > - MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES + MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES { known_accounts .witnessed_open_accounts - .drain(..MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES) + .drain(..MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES) .collect() } else { sp_std::mem::take(&mut known_accounts.witnessed_open_accounts) diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 9ce1d9e088..1d24824104 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -5,8 +5,11 @@ use crate::{ use cf_chains::{ instances::{ChainInstanceAlias, SolanaInstance}, sol::{ - api::SolanaTransactionType, SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, - SolanaCrypto, + api::{ + SolanaApi, SolanaTransactionBuildingError, SolanaTransactionType, + VaultSwapAccountAndSender, + }, + SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, SolanaCrypto, }, Chain, FeeEstimationApi, ForeignChain, Solana, }; @@ -531,13 +534,13 @@ pub struct SolanaVaultSwapsHandler; impl SolanaVaultSwapAccountsHook< - ContractSwapAccountAndSender, + VaultSwapAccountAndSender, SolanaVaultSwapDetails, SolanaTransactionBuildingError, > for SolanaVaultSwapsHandler { fn initiate_vault_swap(swap_details: SolanaVaultSwapDetails) { - let _ = SolanaIngressEgress::contract_swap_request( + let _ = SolanaIngressEgress::vault_swap_request( RuntimeOrigin::root(), swap_details.from, swap_details.to, @@ -548,7 +551,7 @@ impl } fn close_accounts( - accounts: Vec, + accounts: Vec, ) -> Result<(), SolanaTransactionBuildingError> { as CloseSolanaVaultSwapAccounts>::new_unsigned(accounts).map( |apicall| { From b3d4fa3d3d89895b852be47e0507e9ea2e5ea103 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 31 Oct 2024 12:35:18 +0100 Subject: [PATCH 16/62] chore: get block number from runtime --- state-chain/pallets/cf-elections/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/state-chain/pallets/cf-elections/src/lib.rs b/state-chain/pallets/cf-elections/src/lib.rs index 48985e3a04..f309812023 100644 --- a/state-chain/pallets/cf-elections/src/lib.rs +++ b/state-chain/pallets/cf-elections/src/lib.rs @@ -136,7 +136,6 @@ pub mod pallet { use cf_chains::benchmarking_value::BenchmarkValue; use cf_primitives::{AuthorityCount, EpochIndex}; use cf_traits::{AccountRoleRegistry, Chainflip, EpochInfo}; - use frame_support::sp_runtime::traits::{Header, HeaderProvider}; use crate::electoral_system::ConsensusStatus; pub use access_impls::RunnerStorageAccess; From a5656393e1b7e14855c413f962bb13177b21eab3 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 31 Oct 2024 12:42:50 +0100 Subject: [PATCH 17/62] chore: vault_swap_request remove origin --- state-chain/pallets/cf-ingress-egress/src/lib.rs | 2 +- .../runtime/src/chainflip/solana_elections.rs | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index 51e7553ca4..93d4f53bb6 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -2119,7 +2119,7 @@ impl, I: 'static> Pallet { Ok(()) } - fn process_vault_swap_request( + pub fn process_vault_swap_request( source_asset: TargetChainAsset, deposit_amount: ::ChainAmount, destination_asset: Asset, diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 1d24824104..20d6154ea4 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -540,13 +540,18 @@ impl > for SolanaVaultSwapsHandler { fn initiate_vault_swap(swap_details: SolanaVaultSwapDetails) { - let _ = SolanaIngressEgress::vault_swap_request( - RuntimeOrigin::root(), - swap_details.from, - swap_details.to, + SolanaIngressEgress::process_vault_swap_request( + swap_details.from.into(), swap_details.deposit_amount, + swap_details.to, swap_details.destination_address, + Default::default(), // TODO swap_details.tx_hash, + Default::default(), // TODO + Default::default(), // TODO + Default::default(), // TODO + Default::default(), // TODO + Default::default(), // TODO ); } From 0c6de224ecd5cb4ec01ccf29098624bfd0f18eef Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 31 Oct 2024 14:37:14 +0100 Subject: [PATCH 18/62] fix: add is_vote_desired() --- .../electoral_systems/solana_swap_accounts_tracking.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 9fb7e0fc7d..4f1d3f3c0e 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -115,6 +115,14 @@ impl< Ok(()) } + fn is_vote_desired>( + _election_identifier_with_extra: crate::electoral_system::ElectionIdentifierOf, + _election_access: &ElectionAccess, + _current_vote: Option<(VotePropertiesOf, AuthorityVoteOf)>, + ) -> Result { + Ok(true) + } + fn on_finalize>( electoral_access: &mut ElectoralAccess, election_identifiers: Vec>, From 2965d788a84895f85434410488e07a01f3509c23 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 31 Oct 2024 16:25:47 +0100 Subject: [PATCH 19/62] fix: clippy, tests, benchmarks --- .../witness/sol/program_swaps_witnessing.rs | 31 ++++++++++++++----- .../cf-integration-tests/src/mock_runtime.rs | 1 + state-chain/chains/src/benchmarking_value.rs | 1 + state-chain/chains/src/sol/benchmarking.rs | 2 +- .../solana_swap_accounts_tracking.rs | 15 +++++++++ .../runtime/src/chainflip/solana_elections.rs | 16 +++++++++- 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 7619d2d134..ac9596f745 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -302,9 +302,9 @@ mod tests { }; use cf_chains::{Chain, Solana}; + use cf_utilities::task_scope; use futures_util::FutureExt; use std::str::FromStr; - use utilities::task_scope; use super::*; @@ -391,8 +391,14 @@ mod tests { let (new_program_swap_accounts, closed_accounts, _) = get_changed_program_swap_accounts( &client, - vec![SolAddress::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") - .unwrap()], + vec![], + BTreeSet::from([VaultSwapAccountAndSender { + vault_swap_account: SolAddress::from_str( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + ) + .unwrap(), + swap_sender: Default::default(), + }]), // Swap Endpoint Data Account Address with no opened accounts SolAddress::from_str("BckDu65u2ofAfaSDDEPg2qJTufKB4PvGxwcYhJ2wkBTC") .unwrap(), @@ -403,15 +409,26 @@ mod tests { assert_eq!(new_program_swap_accounts, vec![]); assert_eq!( closed_accounts, - vec![SolAddress::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") - .unwrap()] + vec![VaultSwapAccountAndSender { + vault_swap_account: SolAddress::from_str( + "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + ) + .unwrap(), + swap_sender: Default::default(), + }] ); let (new_program_swap_accounts, closed_accounts, _) = get_changed_program_swap_accounts( &client, - vec![SolAddress::from_str("HhxGAt8THMtsW97Zuo5ZrhKgqsdD5EBgCx9vZ4n62xpf") - .unwrap()], + vec![], + BTreeSet::from([VaultSwapAccountAndSender { + vault_swap_account: SolAddress::from_str( + "HhxGAt8THMtsW97Zuo5ZrhKgqsdD5EBgCx9vZ4n62xpf", + ) + .unwrap(), + swap_sender: Default::default(), + }]), // Swap Endpoint Data Account Address with two opened accounts SolAddress::from_str("72HKrbbesW9FGuBoebns77uvY9fF9MEsw4HTMEeV53W9") .unwrap(), diff --git a/state-chain/cf-integration-tests/src/mock_runtime.rs b/state-chain/cf-integration-tests/src/mock_runtime.rs index ea4b91f69e..82a3e1d365 100644 --- a/state-chain/cf-integration-tests/src/mock_runtime.rs +++ b/state-chain/cf-integration-tests/src/mock_runtime.rs @@ -320,6 +320,7 @@ impl ExtBuilder { (), (), (), + (), ), settings: ( (), diff --git a/state-chain/chains/src/benchmarking_value.rs b/state-chain/chains/src/benchmarking_value.rs index 9af451aaff..1705250c93 100644 --- a/state-chain/chains/src/benchmarking_value.rs +++ b/state-chain/chains/src/benchmarking_value.rs @@ -258,3 +258,4 @@ impl_tuple_benchmark_value!(A, B, C); impl_tuple_benchmark_value!(A, B, C, D); impl_tuple_benchmark_value!(A, B, C, D, EE); impl_tuple_benchmark_value!(A, B, C, D, EE, F); +impl_tuple_benchmark_value!(A, B, C, D, EE, F, GG); diff --git a/state-chain/chains/src/sol/benchmarking.rs b/state-chain/chains/src/sol/benchmarking.rs index aad9226fde..71bdd3818f 100644 --- a/state-chain/chains/src/sol/benchmarking.rs +++ b/state-chain/chains/src/sol/benchmarking.rs @@ -1,7 +1,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::{ - api::{VaultSwapAccountAndSender, SolanaApi}, + api::{SolanaApi, VaultSwapAccountAndSender}, SolAddress, SolHash, SolMessage, SolSignature, SolTrackedData, SolTransaction, SolanaTransactionData, }; diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 4f1d3f3c0e..9636003534 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -62,6 +62,21 @@ pub struct SolanaVaultSwapsVote { pub confirm_closed_accounts: BTreeSet, } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue + for SolanaVaultSwapsVote +{ + fn benchmark_value() -> Self { + Self { + new_accounts: BTreeSet::from([ + BenchmarkValue::benchmark_value(), + BenchmarkValue::benchmark_value(), + ]), + confirm_closed_accounts: BTreeSet::from([BenchmarkValue::benchmark_value()]), + } + } +} + pub struct SolanaVaultSwapAccounts< Account, SwapDetails, diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 20d6154ea4..bad1026f9d 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -530,6 +530,20 @@ pub struct SolanaVaultSwapDetails { pub destination_address: EncodedAddress, pub tx_hash: TransactionHash, } + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for SolanaVaultSwapDetails { + fn benchmark_value() -> Self { + Self { + from: BenchmarkValue::benchmark_value(), + to: BenchmarkValue::benchmark_value(), + deposit_amount: BenchmarkValue::benchmark_value(), + destination_address: BenchmarkValue::benchmark_value(), + tx_hash: BenchmarkValue::benchmark_value(), + } + } +} + pub struct SolanaVaultSwapsHandler; impl @@ -541,7 +555,7 @@ impl { fn initiate_vault_swap(swap_details: SolanaVaultSwapDetails) { SolanaIngressEgress::process_vault_swap_request( - swap_details.from.into(), + swap_details.from, swap_details.deposit_amount, swap_details.to, swap_details.destination_address, From 5d23dbfab4e056866241685f3439d784657508f1 Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:39:08 +0100 Subject: [PATCH 20/62] Feat: Expand Solana Vault Swap functionality and fix bouncer (#5375) * feat: add creation slot and update image * chore: fix anchor import * chore: add ccm metadata * chore: update with creation slot and prints * chore: bouncer fixes * chore: prevent rpc call if empty list * chore: fix issues all around * chore: fix lint * chore: remove prints * chore: address comment --- .cargo/config.toml | 2 +- bouncer/shared/contract_interfaces.ts | 2 +- bouncer/shared/evm_vault_swap.ts | 88 +- bouncer/tests/all_swaps.ts | 9 + .../download-sol-program-idls.sh | 3 + .../cf_tester.json | 0 .../v1.0.0-swap-endpoint/cf_tester.ts | 541 +++++ .../swap_endpoint.json | 4 + .../v1.0.0-swap-endpoint/swap_endpoint.ts | 665 ++++++ .../vault.json | 0 .../v1.0.0-swap-endpoint/vault.ts | 2012 +++++++++++++++++ engine/src/witness/arb.rs | 2 +- engine/src/witness/common/cf_parameters.rs | 3 +- engine/src/witness/eth.rs | 4 +- engine/src/witness/evm/vault.rs | 2 +- .../witness/sol/program_swaps_witnessing.rs | 66 +- localnet/docker-compose.yml | 2 +- state-chain/chains/src/lib.rs | 30 +- state-chain/node/src/chain_spec/testnet.rs | 20 +- state-chain/primitives/src/lib.rs | 18 +- .../runtime/src/chainflip/solana_elections.rs | 27 +- 21 files changed, 3433 insertions(+), 67 deletions(-) rename contract-interfaces/sol-program-idls/{v1.0.0 => v1.0.0-swap-endpoint}/cf_tester.json (100%) create mode 100644 contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/cf_tester.ts rename contract-interfaces/sol-program-idls/{v1.0.0 => v1.0.0-swap-endpoint}/swap_endpoint.json (99%) create mode 100644 contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint.ts rename contract-interfaces/sol-program-idls/{v1.0.0 => v1.0.0-swap-endpoint}/vault.json (100%) create mode 100644 contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault.ts diff --git a/.cargo/config.toml b/.cargo/config.toml index 6ef693e4f1..7828587f04 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,7 +2,7 @@ CF_ETH_CONTRACT_ABI_ROOT = { value = "contract-interfaces/eth-contract-abis", relative = true } CF_ETH_CONTRACT_ABI_TAG = "v1.1.2" CF_SOL_PROGRAM_IDL_ROOT = { value = "contract-interfaces/sol-program-idls", relative = true } -CF_SOL_PROGRAM_IDL_TAG = "v1.0.0" +CF_SOL_PROGRAM_IDL_TAG = "v1.0.0-swap-endpoint" CF_ARB_CONTRACT_ABI_ROOT = { value = "contract-interfaces/arb-contract-abis", relative = true } CF_TEST_CONFIG_ROOT = { value = "engine/config/testing", relative = true } diff --git a/bouncer/shared/contract_interfaces.ts b/bouncer/shared/contract_interfaces.ts index b909d41364..a7dd2d4ff6 100644 --- a/bouncer/shared/contract_interfaces.ts +++ b/bouncer/shared/contract_interfaces.ts @@ -13,7 +13,7 @@ function loadContractCached(abiPath: string) { }; } const CF_ETH_CONTRACT_ABI_TAG = 'v1.1.2'; -const CF_SOL_PROGRAM_IDL_TAG = 'v1.0.0'; +const CF_SOL_PROGRAM_IDL_TAG = 'v1.0.0-swap-endpoint'; export const getErc20abi = loadContractCached( '../contract-interfaces/eth-contract-abis/IERC20.json', ); diff --git a/bouncer/shared/evm_vault_swap.ts b/bouncer/shared/evm_vault_swap.ts index d0fb6774bc..a19eda40de 100644 --- a/bouncer/shared/evm_vault_swap.ts +++ b/bouncer/shared/evm_vault_swap.ts @@ -1,6 +1,5 @@ import * as anchor from '@coral-xyz/anchor'; // import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'; - import { InternalAsset as Asset, executeSwap, @@ -9,6 +8,7 @@ import { Asset as SCAsset, Chains, Chain, + assetConstants, } from '@chainflip/cli'; import { HDNodeWallet } from 'ethers'; import { randomBytes } from 'crypto'; @@ -28,19 +28,20 @@ import { evmChains, getSolWhaleKeyPair, getSolConnection, + chainContractId, + decodeSolAddress, + decodeDotAddressForContract, } from './utils'; import { getBalance } from './get_balance'; import { CcmDepositMetadata, DcaParams, FillOrKillParamsX128 } from './new_swap'; import { SwapContext, SwapStatus } from './swap_context'; -import VaultIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/vault.json'; -import SwapEndpointIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/swap_endpoint.json'; - -import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint'; -import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/vault'; +import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint'; +import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault'; +import { getSolanaSwapEndpointIdl, getSolanaVaultIdl } from './contract_interfaces'; // Workaround because of anchor issue -const { BN } = anchor; +const { BN } = anchor.default; const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc']; @@ -118,7 +119,7 @@ export async function executeVaultSwap( return receipt; } // Temporary before the SDK implements this. -export async function executeSolContractSwap( +export async function executeSolVaultSwap( srcAsset: Asset, destAsset: Asset, destAddress: string, @@ -146,6 +147,11 @@ export async function executeSolContractSwap( process.env.ANCHOR_WALLET = 'shared/solana_keypair.json'; const connection = getSolConnection(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const VaultIdl: any = await getSolanaVaultIdl(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const SwapEndpointIdl: any = await getSolanaSwapEndpointIdl(); + const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); const vaultProgram = new anchor.Program(VaultIdl as Vault); @@ -153,24 +159,57 @@ export async function executeSolContractSwap( const fetchedDataAccount = await vaultProgram.account.dataAccount.fetch(solanaVaultDataAccount); const aggKey = fetchedDataAccount.aggKey; + const amount = new BN(amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset))); + + let cfParameters; + + if (messageMetadata) { + // TODO: Currently manually encoded. To use SDK/BrokerApi. + switch (destChain) { + case Chains.Ethereum: + case Chains.Arbitrum: + cfParameters = + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + break; + default: + throw new Error(`Unsupported chain: ${destChain}`); + } + } else { + // TODO: Currently manually encoded. To use SDK/BrokerApi. + switch (destChain) { + case Chains.Ethereum: + case Chains.Arbitrum: + cfParameters = + '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + break; + case Chains.Polkadot: + cfParameters = + '0x000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + break; + // TODO: Not supporting BTC for now because the encoding is annoying. + default: + throw new Error(`Unsupported chain: ${destChain}`); + } + } + + const destinationAddress = + destChain === Chains.Polkadot ? decodeDotAddressForContract(destAddress) : destAddress; + const tx = srcAsset === 'Sol' ? await cfSwapEndpointProgram.methods .xSwapNative({ - amount: new BN( - amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), - ), - dstChain: Number(destChain), - dstAddress: Buffer.from(destAddress), - dstToken: Number(stateChainAssetFromAsset(destAsset)), + amount, + dstChain: chainContractId(destChain), + dstAddress: Buffer.from(destinationAddress.slice(2), 'hex'), + dstToken: assetConstants[destAsset].contractId, ccmParameters: messageMetadata ? { message: Buffer.from(messageMetadata.message.slice(2), 'hex'), gasAmount: new BN(messageMetadata.gasBudget), } : null, - // TODO: Encode cfParameters from ccmAdditionalData and other vault swap parameters - cfParameters: Buffer.from(messageMetadata?.ccmAdditionalData?.slice(2) ?? '', 'hex'), + cfParameters: Buffer.from(cfParameters!.slice(2) ?? '', 'hex'), }) .accountsPartial({ dataAccount: solanaVaultDataAccount, @@ -184,20 +223,17 @@ export async function executeSolContractSwap( .transaction() : await cfSwapEndpointProgram.methods .xSwapToken({ - amount: new BN( - amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), - ), - dstChain: Number(destChain), - dstAddress: Buffer.from(destAddress), - dstToken: Number(stateChainAssetFromAsset(destAsset)), + amount, + dstChain: chainContractId(destChain), + dstAddress: Buffer.from(destinationAddress.slice(2), 'hex'), + dstToken: assetConstants[destAsset].contractId, ccmParameters: messageMetadata ? { message: Buffer.from(messageMetadata.message.slice(2), 'hex'), gasAmount: new BN(messageMetadata.gasBudget), } : null, - // TODO: Encode cfParameters from ccmAdditionalData and other vault swap parameters - cfParameters: Buffer.from(messageMetadata?.ccmAdditionalData?.slice(2) ?? '', 'hex'), + cfParameters: Buffer.from(cfParameters!.slice(2) ?? '', 'hex'), decimals: assetDecimals(srcAsset), }) .accountsPartial({ @@ -296,8 +332,8 @@ export async function performVaultSwap( txHash = receipt.hash; sourceAddress = wallet!.address.toLowerCase(); } else { - txHash = await executeSolContractSwap(sourceAsset, destAsset, destAddress, messageMetadata); - sourceAddress = getSolWhaleKeyPair().publicKey.toBase58(); + txHash = await executeSolVaultSwap(sourceAsset, destAsset, destAddress, messageMetadata); + sourceAddress = decodeSolAddress(getSolWhaleKeyPair().publicKey.toBase58()); } swapContext?.updateStatus(swapTag, SwapStatus.VaultContractExecuted); diff --git a/bouncer/tests/all_swaps.ts b/bouncer/tests/all_swaps.ts index 9f777ff8ef..a940b823e2 100644 --- a/bouncer/tests/all_swaps.ts +++ b/bouncer/tests/all_swaps.ts @@ -68,5 +68,14 @@ async function main() { }); }); + // Not doing BTC due to encoding issues in vault_swap + appendSwap('Sol', 'Eth', testVaultSwap); + appendSwap('Sol', 'Usdc', testVaultSwap, true); + appendSwap('Sol', 'ArbEth', testVaultSwap); + appendSwap('Sol', 'ArbEth', testVaultSwap, true); + appendSwap('Sol', 'Dot', testVaultSwap); + appendSwap('SolUsdc', 'Eth', testVaultSwap); + appendSwap('SolUsdc', 'Flip', testVaultSwap, true); + await Promise.all(allSwaps); } diff --git a/contract-interfaces/sol-program-idls/download-sol-program-idls.sh b/contract-interfaces/sol-program-idls/download-sol-program-idls.sh index a6f892928e..6bcd10b839 100755 --- a/contract-interfaces/sol-program-idls/download-sol-program-idls.sh +++ b/contract-interfaces/sol-program-idls/download-sol-program-idls.sh @@ -30,8 +30,11 @@ gh release download \ unzip -u ${ZIP_FILE} \ 'vault.json' \ + 'vault.ts' \ 'cf_tester.json' \ + 'cf_tester.ts' \ 'swap_endpoint.json' \ + 'swap_endpoint.ts' \ -d $TARGET_DIR rm ${ZIP_FILE} \ No newline at end of file diff --git a/contract-interfaces/sol-program-idls/v1.0.0/cf_tester.json b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/cf_tester.json similarity index 100% rename from contract-interfaces/sol-program-idls/v1.0.0/cf_tester.json rename to contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/cf_tester.json diff --git a/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/cf_tester.ts b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/cf_tester.ts new file mode 100644 index 0000000000..b449ed17ac --- /dev/null +++ b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/cf_tester.ts @@ -0,0 +1,541 @@ +/** + * Program IDL in camelCase format in order to be used in JS/TS. + * + * Note that this is only a type helper and is not the actual IDL. The original + * IDL can be found at `target/idl/cf_tester.json`. + */ +export type CfTester = { + "address": "8pBPaVfTAcjLeNfC187Fkvi9b1XEFhRNJ95BQXXVksmH", + "metadata": { + "name": "cfTester", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "cfReceiveNative", + "discriminator": [ + 228, + 51, + 109, + 5, + 176, + 83, + 201, + 81 + ], + "accounts": [ + { + "name": "receiverNative", + "writable": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "instructionSysvar", + "address": "Sysvar1nstructions1111111111111111111111111" + } + ], + "args": [ + { + "name": "sourceChain", + "type": "u32" + }, + { + "name": "sourceAddress", + "type": "bytes" + }, + { + "name": "message", + "type": "bytes" + }, + { + "name": "amount", + "type": "u64" + } + ] + }, + { + "name": "cfReceiveToken", + "discriminator": [ + 66, + 95, + 143, + 16, + 13, + 3, + 3, + 83 + ], + "accounts": [ + { + "name": "receiverTokenAccount", + "writable": true + }, + { + "name": "tokenProgram", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "mint" + }, + { + "name": "instructionSysvar", + "address": "Sysvar1nstructions1111111111111111111111111" + } + ], + "args": [ + { + "name": "sourceChain", + "type": "u32" + }, + { + "name": "sourceAddress", + "type": "bytes" + }, + { + "name": "message", + "type": "bytes" + }, + { + "name": "amount", + "type": "u64" + } + ] + }, + { + "name": "contractSwapNative", + "discriminator": [ + 254, + 187, + 162, + 188, + 29, + 232, + 174, + 193 + ], + "accounts": [ + { + "name": "vault" + }, + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "writable": true + }, + { + "name": "pda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "arg", + "path": "seed" + } + ] + } + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "eventAuthority" + } + ], + "args": [ + { + "name": "seed", + "type": "bytes" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "amount", + "type": "u64" + }, + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + } + ] + }, + { + "name": "contractSwapToken", + "discriminator": [ + 95, + 144, + 96, + 179, + 3, + 2, + 75, + 95 + ], + "accounts": [ + { + "name": "vault" + }, + { + "name": "dataAccount" + }, + { + "name": "tokenVaultAssociatedTokenAcount", + "writable": true + }, + { + "name": "pda", + "pda": { + "seeds": [ + { + "kind": "arg", + "path": "seed" + } + ] + } + }, + { + "name": "pdaAssociatedTokenAccount", + "writable": true + }, + { + "name": "tokenSupportedAccount" + }, + { + "name": "tokenProgram", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "mint" + }, + { + "name": "eventAuthority" + } + ], + "args": [ + { + "name": "seed", + "type": "bytes" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "amount", + "type": "u64" + }, + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + }, + { + "name": "decimals", + "type": "u8" + } + ] + }, + { + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 100, + 97, + 116, + 97 + ] + } + ] + } + }, + { + "name": "initializer", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "newAggKey", + "type": "pubkey" + }, + { + "name": "newGovKey", + "type": "pubkey" + }, + { + "name": "newTokenVaultPda", + "type": "pubkey" + }, + { + "name": "tokenVaultPdaBump", + "type": "u8" + }, + { + "name": "upgradeSignerPda", + "type": "pubkey" + }, + { + "name": "upgradeSignerPdaBump", + "type": "u8" + } + ] + } + ], + "accounts": [ + { + "name": "dataAccount", + "discriminator": [ + 85, + 240, + 182, + 158, + 76, + 7, + 18, + 233 + ] + }, + { + "name": "supportedToken", + "discriminator": [ + 56, + 162, + 96, + 99, + 193, + 245, + 204, + 108 + ] + } + ], + "events": [ + { + "name": "receivedCcm", + "discriminator": [ + 220, + 233, + 232, + 105, + 128, + 112, + 80, + 63 + ] + } + ], + "types": [ + { + "name": "ccmParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "message", + "type": "bytes" + }, + { + "name": "gasAmount", + "type": "u64" + } + ] + } + }, + { + "name": "dataAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "aggKey", + "type": "pubkey" + }, + { + "name": "govKey", + "type": "pubkey" + }, + { + "name": "tokenVaultPda", + "type": "pubkey" + }, + { + "name": "tokenVaultBump", + "type": "u8" + }, + { + "name": "upgradeSignerPda", + "type": "pubkey" + }, + { + "name": "upgradeSignerPdaBump", + "type": "u8" + }, + { + "name": "suspended", + "type": "bool" + }, + { + "name": "suspendedIxSwaps", + "type": "bool" + }, + { + "name": "suspendedEventSwaps", + "type": "bool" + }, + { + "name": "minNativeSwapAmount", + "type": "u64" + }, + { + "name": "maxDstAddressLen", + "type": "u16" + }, + { + "name": "maxCcmMessageLen", + "type": "u32" + }, + { + "name": "maxCfParametersLen", + "type": "u32" + }, + { + "name": "maxEventAccounts", + "type": "u32" + } + ] + } + }, + { + "name": "receivedCcm", + "type": { + "kind": "struct", + "fields": [ + { + "name": "sourceChain", + "type": "u32" + }, + { + "name": "sourceAddress", + "type": "bytes" + }, + { + "name": "message", + "type": "bytes" + }, + { + "name": "amount", + "type": "u64" + }, + { + "name": "remainingPubkeys", + "type": { + "vec": "pubkey" + } + }, + { + "name": "remainingIsSigner", + "type": { + "vec": "bool" + } + }, + { + "name": "remainingIsWritable", + "type": { + "vec": "bool" + } + } + ] + } + }, + { + "name": "supportedToken", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenMintPubkey", + "type": "pubkey" + }, + { + "name": "minSwapAmount", + "type": "u64" + } + ] + } + } + ] +}; diff --git a/contract-interfaces/sol-program-idls/v1.0.0/swap_endpoint.json b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint.json similarity index 99% rename from contract-interfaces/sol-program-idls/v1.0.0/swap_endpoint.json rename to contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint.json index 19f570dfd6..5fcb28951d 100644 --- a/contract-interfaces/sol-program-idls/v1.0.0/swap_endpoint.json +++ b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint.json @@ -528,6 +528,10 @@ "type": { "kind": "struct", "fields": [ + { + "name": "creation_slot", + "type": "u64" + }, { "name": "sender", "type": "pubkey" diff --git a/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint.ts b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint.ts new file mode 100644 index 0000000000..f57e3f0edb --- /dev/null +++ b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint.ts @@ -0,0 +1,665 @@ +/** + * Program IDL in camelCase format in order to be used in JS/TS. + * + * Note that this is only a type helper and is not the actual IDL. The original + * IDL can be found at `target/idl/swap_endpoint.json`. + */ +export type SwapEndpoint = { + "address": "35uYgHdfZQT4kHkaaXQ6ZdCkK5LFrsk43btTLbGCRCNT", + "metadata": { + "name": "swapEndpoint", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "closeEventAccounts", + "discriminator": [ + 165, + 102, + 61, + 1, + 185, + 77, + 189, + 121 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "writable": true, + "signer": true + }, + { + "name": "swapEndpointDataAccount", + "writable": true + } + ], + "args": [] + }, + { + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ + { + "name": "swapEndpointDataAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 100, + 97, + 116, + 97 + ] + } + ] + } + }, + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "xSwapNative", + "discriminator": [ + 163, + 38, + 92, + 226, + 243, + 105, + 141, + 196 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "writable": true + }, + { + "name": "from", + "writable": true, + "signer": true + }, + { + "name": "eventDataAccount", + "writable": true, + "signer": true + }, + { + "name": "swapEndpointDataAccount", + "writable": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "swapNativeParams", + "type": { + "defined": { + "name": "swapNativeParams" + } + } + } + ] + }, + { + "name": "xSwapToken", + "discriminator": [ + 69, + 50, + 252, + 99, + 229, + 83, + 119, + 235 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "tokenVaultAssociatedTokenAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "data_account.token_vault_pda", + "account": "dataAccount" + }, + { + "kind": "const", + "value": [ + 6, + 221, + 246, + 225, + 215, + 101, + 161, + 147, + 217, + 203, + 225, + 70, + 206, + 235, + 121, + 172, + 28, + 180, + 133, + 237, + 95, + 91, + 55, + 145, + 58, + 140, + 245, + 133, + 126, + 255, + 0, + 169 + ] + }, + { + "kind": "account", + "path": "mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "from", + "writable": true, + "signer": true + }, + { + "name": "fromTokenAccount", + "writable": true + }, + { + "name": "eventDataAccount", + "writable": true, + "signer": true + }, + { + "name": "swapEndpointDataAccount", + "writable": true + }, + { + "name": "tokenSupportedAccount" + }, + { + "name": "tokenProgram", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "mint" + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "swapTokenParams", + "type": { + "defined": { + "name": "swapTokenParams" + } + } + } + ] + } + ], + "accounts": [ + { + "name": "dataAccount", + "discriminator": [ + 85, + 240, + 182, + 158, + 76, + 7, + 18, + 233 + ] + }, + { + "name": "supportedToken", + "discriminator": [ + 56, + 162, + 96, + 99, + 193, + 245, + 204, + 108 + ] + }, + { + "name": "swapEndpointDataAccount", + "discriminator": [ + 79, + 152, + 191, + 225, + 128, + 108, + 11, + 139 + ] + }, + { + "name": "swapEvent", + "discriminator": [ + 150, + 251, + 114, + 94, + 200, + 113, + 248, + 70 + ] + } + ], + "events": [ + { + "name": "cantDeserializeEventAccount", + "discriminator": [ + 248, + 26, + 198, + 175, + 8, + 58, + 229, + 137 + ] + }, + { + "name": "eventAccountNotTracked", + "discriminator": [ + 202, + 29, + 253, + 107, + 20, + 196, + 36, + 210 + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "mathOverflow", + "msg": "Overflow in arithmetic operation" + }, + { + "code": 6001, + "name": "mathUnderflow", + "msg": "Underflow in arithmetic operation" + } + ], + "types": [ + { + "name": "cantDeserializeEventAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "eventAccount", + "type": "pubkey" + }, + { + "name": "payee", + "type": "pubkey" + } + ] + } + }, + { + "name": "ccmParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "message", + "type": "bytes" + }, + { + "name": "gasAmount", + "type": "u64" + } + ] + } + }, + { + "name": "dataAccount", + "docs": [ + "* ****************************************************************************\n * *************************** IMPORTANT NOTE *********************************\n * ****************************************************************************\n * If the vault is upgraded and the DataAccount struct is modified we need to\n * check the compatibility and ensure there is a proper migration process, given\n * that the Vault bytecode is the only thing being upgraded, not the data account.\n *\n * The easiest approach on upgrade is keeping the DataAccount unchanged and use\n * a new account struct for any new data that is required.\n *\n * DO NOT MODIFY THIS WITHOUT UNDERSTANDING THE CONSEQUENCES!\n * ****************************************************************************\n * ****************************************************************************" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "aggKey", + "type": "pubkey" + }, + { + "name": "govKey", + "type": "pubkey" + }, + { + "name": "tokenVaultPda", + "type": "pubkey" + }, + { + "name": "tokenVaultBump", + "type": "u8" + }, + { + "name": "upgradeSignerPda", + "type": "pubkey" + }, + { + "name": "upgradeSignerPdaBump", + "type": "u8" + }, + { + "name": "suspended", + "type": "bool" + }, + { + "name": "suspendedLegacySwaps", + "type": "bool" + }, + { + "name": "suspendedEventSwaps", + "type": "bool" + }, + { + "name": "minNativeSwapAmount", + "type": "u64" + }, + { + "name": "maxDstAddressLen", + "type": "u16" + }, + { + "name": "maxCcmMessageLen", + "type": "u32" + }, + { + "name": "maxCfParametersLen", + "type": "u32" + }, + { + "name": "maxEventAccounts", + "type": "u32" + } + ] + } + }, + { + "name": "eventAccountNotTracked", + "type": { + "kind": "struct", + "fields": [ + { + "name": "eventAccount", + "type": "pubkey" + }, + { + "name": "payee", + "type": "pubkey" + } + ] + } + }, + { + "name": "supportedToken", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenMintPubkey", + "type": "pubkey" + }, + { + "name": "minSwapAmount", + "type": "u64" + } + ] + } + }, + { + "name": "swapEndpointDataAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "historicalNumberEventAccounts", + "type": "u128" + }, + { + "name": "openEventAccounts", + "type": { + "vec": "pubkey" + } + } + ] + } + }, + { + "name": "swapEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "creationSlot", + "type": "u64" + }, + { + "name": "sender", + "type": "pubkey" + }, + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "amount", + "type": "u64" + }, + { + "name": "srcToken", + "type": { + "option": "pubkey" + } + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + } + ] + } + }, + { + "name": "swapNativeParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + } + ] + } + }, + { + "name": "swapTokenParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + }, + { + "name": "decimals", + "type": "u8" + } + ] + } + } + ] +}; diff --git a/contract-interfaces/sol-program-idls/v1.0.0/vault.json b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault.json similarity index 100% rename from contract-interfaces/sol-program-idls/v1.0.0/vault.json rename to contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault.json diff --git a/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault.ts b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault.ts new file mode 100644 index 0000000000..1bca1885c4 --- /dev/null +++ b/contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault.ts @@ -0,0 +1,2012 @@ +/** + * Program IDL in camelCase format in order to be used in JS/TS. + * + * Note that this is only a type helper and is not the actual IDL. The original + * IDL can be found at `target/idl/vault.json`. + */ +export type Vault = { + "address": "8inHGLHXegST3EPLcpisQe9D1hDT9r7DJjS395L3yuYf", + "metadata": { + "name": "vault", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "disableTokenSupport", + "discriminator": [ + 72, + 4, + 35, + 144, + 194, + 49, + 71, + 64 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true + }, + { + "name": "govKey", + "writable": true, + "signer": true + }, + { + "name": "tokenSupportedAccount", + "writable": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "enableTokenSupport", + "discriminator": [ + 125, + 160, + 180, + 50, + 26, + 27, + 112, + 153 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true + }, + { + "name": "govKey", + "writable": true, + "signer": true + }, + { + "name": "tokenSupportedAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 117, + 112, + 112, + 111, + 114, + 116, + 101, + 100, + 95, + 116, + 111, + 107, + 101, + 110 + ] + }, + { + "kind": "account", + "path": "mint" + } + ] + } + }, + { + "name": "mint" + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "minSwapAmount", + "type": "u64" + } + ] + }, + { + "name": "executeCcmNativeCall", + "discriminator": [ + 125, + 5, + 11, + 227, + 128, + 66, + 224, + 178 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "signer": true + }, + { + "name": "receiverNative", + "writable": true + }, + { + "name": "cfReceiver", + "docs": [ + "the aggregate key signature." + ] + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "instructionSysvar", + "address": "Sysvar1nstructions1111111111111111111111111" + } + ], + "args": [ + { + "name": "sourceChain", + "type": "u32" + }, + { + "name": "sourceAddress", + "type": "bytes" + }, + { + "name": "message", + "type": "bytes" + }, + { + "name": "amount", + "type": "u64" + } + ] + }, + { + "name": "executeCcmTokenCall", + "discriminator": [ + 108, + 184, + 162, + 123, + 159, + 222, + 170, + 35 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "signer": true + }, + { + "name": "receiverTokenAccount", + "writable": true + }, + { + "name": "cfReceiver", + "docs": [ + "without passing the aggregate key signature." + ] + }, + { + "name": "tokenProgram", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "mint" + }, + { + "name": "instructionSysvar", + "address": "Sysvar1nstructions1111111111111111111111111" + } + ], + "args": [ + { + "name": "sourceChain", + "type": "u32" + }, + { + "name": "sourceAddress", + "type": "bytes" + }, + { + "name": "message", + "type": "bytes" + }, + { + "name": "amount", + "type": "u64" + } + ] + }, + { + "name": "fetchNative", + "discriminator": [ + 142, + 36, + 101, + 143, + 108, + 89, + 41, + 140 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "writable": true, + "signer": true + }, + { + "name": "depositChannelPda", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 99, + 104, + 97, + 110, + 110, + 101, + 108 + ] + }, + { + "kind": "arg", + "path": "seed" + } + ] + } + }, + { + "name": "depositChannelHistoricalFetch", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 104, + 105, + 115, + 116, + 95, + 102, + 101, + 116, + 99, + 104 + ] + }, + { + "kind": "account", + "path": "depositChannelPda" + } + ] + } + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "seed", + "type": "bytes" + }, + { + "name": "bump", + "type": "u8" + } + ] + }, + { + "name": "fetchTokens", + "discriminator": [ + 73, + 71, + 16, + 100, + 44, + 176, + 198, + 70 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "writable": true, + "signer": true + }, + { + "name": "depositChannelPda", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 99, + 104, + 97, + 110, + 110, + 101, + 108 + ] + }, + { + "kind": "arg", + "path": "seed" + } + ] + } + }, + { + "name": "depositChannelAssociatedTokenAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "depositChannelPda" + }, + { + "kind": "const", + "value": [ + 6, + 221, + 246, + 225, + 215, + 101, + 161, + 147, + 217, + 203, + 225, + 70, + 206, + 235, + 121, + 172, + 28, + 180, + 133, + 237, + 95, + 91, + 55, + 145, + 58, + 140, + 245, + 133, + 126, + 255, + 0, + 169 + ] + }, + { + "kind": "account", + "path": "mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "tokenVaultAssociatedTokenAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "data_account.token_vault_pda", + "account": "dataAccount" + }, + { + "kind": "const", + "value": [ + 6, + 221, + 246, + 225, + 215, + 101, + 161, + 147, + 217, + 203, + 225, + 70, + 206, + 235, + 121, + 172, + 28, + 180, + 133, + 237, + 95, + 91, + 55, + 145, + 58, + 140, + 245, + 133, + 126, + 255, + 0, + 169 + ] + }, + { + "kind": "account", + "path": "mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "mint" + }, + { + "name": "tokenProgram", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "depositChannelHistoricalFetch", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 104, + 105, + 115, + 116, + 95, + 102, + 101, + 116, + 99, + 104 + ] + }, + { + "kind": "account", + "path": "depositChannelAssociatedTokenAccount" + } + ] + } + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "seed", + "type": "bytes" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "decimals", + "type": "u8" + } + ] + }, + { + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 100, + 97, + 116, + 97 + ] + } + ] + } + }, + { + "name": "initializer", + "writable": true, + "signer": true, + "address": "HfasueN6RNPjSM6rKGH5dga6kS2oUF8siGH3m4MXPURp" + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "newAggKey", + "type": "pubkey" + }, + { + "name": "newGovKey", + "type": "pubkey" + }, + { + "name": "expectedTokenVaultPda", + "type": "pubkey" + }, + { + "name": "expectedTokenVaultPdaBump", + "type": "u8" + }, + { + "name": "expectedUpgradeSignerPda", + "type": "pubkey" + }, + { + "name": "expectedUpgradeSignerPdaBump", + "type": "u8" + }, + { + "name": "suspendedVault", + "type": "bool" + }, + { + "name": "suspendedLegacySwaps", + "type": "bool" + }, + { + "name": "suspendedEventSwaps", + "type": "bool" + }, + { + "name": "minNativeSwapAmount", + "type": "u64" + }, + { + "name": "maxDstAddressLen", + "type": "u16" + }, + { + "name": "maxCcmMessageLen", + "type": "u32" + }, + { + "name": "maxCfParametersLen", + "type": "u32" + }, + { + "name": "maxEventAccounts", + "type": "u32" + } + ] + }, + { + "name": "rotateAggKey", + "discriminator": [ + 78, + 81, + 143, + 171, + 221, + 165, + 214, + 139 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true + }, + { + "name": "aggKey", + "writable": true, + "signer": true + }, + { + "name": "newAggKey", + "writable": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "skipTransferFunds", + "type": "bool" + } + ] + }, + { + "name": "setGovKeyWithAggKey", + "discriminator": [ + 66, + 64, + 58, + 40, + 15, + 75, + 215, + 162 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true + }, + { + "name": "aggKey", + "signer": true + } + ], + "args": [ + { + "name": "newGovKey", + "type": "pubkey" + } + ] + }, + { + "name": "setGovKeyWithGovKey", + "discriminator": [ + 251, + 142, + 231, + 255, + 111, + 143, + 165, + 106 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true + }, + { + "name": "govKey", + "signer": true + } + ], + "args": [ + { + "name": "newGovKey", + "type": "pubkey" + } + ] + }, + { + "name": "setProgramSwapsParameters", + "discriminator": [ + 129, + 254, + 31, + 151, + 111, + 149, + 135, + 77 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true + }, + { + "name": "govKey", + "signer": true + } + ], + "args": [ + { + "name": "minNativeSwapAmount", + "type": "u64" + }, + { + "name": "maxDstAddressLen", + "type": "u16" + }, + { + "name": "maxCcmMessageLen", + "type": "u32" + }, + { + "name": "maxCfParametersLen", + "type": "u32" + }, + { + "name": "maxEventAccounts", + "type": "u32" + } + ] + }, + { + "name": "setSuspendedState", + "discriminator": [ + 145, + 13, + 20, + 161, + 30, + 62, + 226, + 32 + ], + "accounts": [ + { + "name": "dataAccount", + "writable": true + }, + { + "name": "govKey", + "signer": true + } + ], + "args": [ + { + "name": "suspend", + "type": "bool" + }, + { + "name": "suspendLegacySwaps", + "type": "bool" + }, + { + "name": "suspendEventSwaps", + "type": "bool" + } + ] + }, + { + "name": "transferTokens", + "discriminator": [ + 54, + 180, + 238, + 175, + 74, + 85, + 126, + 188 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "signer": true + }, + { + "name": "tokenVaultPda" + }, + { + "name": "tokenVaultAssociatedTokenAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "data_account.token_vault_pda", + "account": "dataAccount" + }, + { + "kind": "const", + "value": [ + 6, + 221, + 246, + 225, + 215, + 101, + 161, + 147, + 217, + 203, + 225, + 70, + 206, + 235, + 121, + 172, + 28, + 180, + 133, + 237, + 95, + 91, + 55, + 145, + 58, + 140, + 245, + 133, + 126, + 255, + 0, + 169 + ] + }, + { + "kind": "account", + "path": "mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "toTokenAccount", + "writable": true + }, + { + "name": "mint" + }, + { + "name": "tokenProgram", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + } + ], + "args": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "decimals", + "type": "u8" + } + ] + }, + { + "name": "transferVaultUpgradeAuthority", + "discriminator": [ + 114, + 247, + 72, + 110, + 145, + 65, + 236, + 153 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "signer": true + }, + { + "name": "programDataAddress", + "writable": true + }, + { + "name": "programAddress" + }, + { + "name": "newAuthority" + }, + { + "name": "signerPda", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 105, + 103, + 110, + 101, + 114 + ] + } + ] + } + }, + { + "name": "bpfLoaderUpgradeable", + "address": "BPFLoaderUpgradeab1e11111111111111111111111" + } + ], + "args": [] + }, + { + "name": "upgradeProgram", + "docs": [ + "* ****************************************************************************\n * *************************** IMPORTANT NOTE *********************************\n * ****************************************************************************\n * The signer_pda is the upgrade authority for the vault program. Changing this\n * logic should be done with caution and understanding the consequences.\n *\n * DO NOT MODIFY THIS WITHOUT UNDERSTANDING THE CONSEQUENCES!\n * ****************************************************************************\n * ****************************************************************************" + ], + "discriminator": [ + 223, + 236, + 39, + 89, + 111, + 204, + 114, + 37 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "govKey", + "signer": true + }, + { + "name": "programDataAddress", + "writable": true + }, + { + "name": "programAddress", + "writable": true + }, + { + "name": "bufferAddress", + "writable": true + }, + { + "name": "spillAddress", + "writable": true + }, + { + "name": "sysvarRent", + "address": "SysvarRent111111111111111111111111111111111" + }, + { + "name": "sysvarClock", + "address": "SysvarC1ock11111111111111111111111111111111" + }, + { + "name": "signerPda", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 105, + 103, + 110, + 101, + 114 + ] + } + ] + } + }, + { + "name": "bpfLoaderUpgradeable", + "address": "BPFLoaderUpgradeab1e11111111111111111111111" + } + ], + "args": [] + }, + { + "name": "xSwapNativeLegacy", + "discriminator": [ + 97, + 21, + 117, + 255, + 21, + 6, + 232, + 176 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "aggKey", + "writable": true + }, + { + "name": "from", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "eventAuthority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + } + ] + }, + { + "name": "xSwapTokenLegacy", + "discriminator": [ + 248, + 32, + 195, + 34, + 38, + 180, + 117, + 127 + ], + "accounts": [ + { + "name": "dataAccount" + }, + { + "name": "tokenVaultAssociatedTokenAccount", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "data_account.token_vault_pda", + "account": "dataAccount" + }, + { + "kind": "const", + "value": [ + 6, + 221, + 246, + 225, + 215, + 101, + 161, + 147, + 217, + 203, + 225, + 70, + 206, + 235, + 121, + 172, + 28, + 180, + 133, + 237, + 95, + 91, + 55, + 145, + 58, + 140, + 245, + 133, + 126, + 255, + 0, + 169 + ] + }, + { + "kind": "account", + "path": "mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "from", + "signer": true + }, + { + "name": "fromTokenAccount", + "writable": true + }, + { + "name": "tokenSupportedAccount" + }, + { + "name": "tokenProgram", + "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + }, + { + "name": "mint" + }, + { + "name": "eventAuthority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + }, + { + "name": "decimals", + "type": "u8" + } + ] + } + ], + "accounts": [ + { + "name": "dataAccount", + "discriminator": [ + 85, + 240, + 182, + 158, + 76, + 7, + 18, + 233 + ] + }, + { + "name": "depositChannelHistoricalFetch", + "discriminator": [ + 188, + 68, + 197, + 38, + 48, + 192, + 81, + 100 + ] + }, + { + "name": "supportedToken", + "discriminator": [ + 56, + 162, + 96, + 99, + 193, + 245, + 204, + 108 + ] + } + ], + "events": [ + { + "name": "aggKeyRotated", + "discriminator": [ + 133, + 39, + 145, + 216, + 63, + 154, + 134, + 245 + ] + }, + { + "name": "govKeyRotated", + "discriminator": [ + 71, + 44, + 22, + 197, + 63, + 250, + 150, + 83 + ] + }, + { + "name": "govKeySetByAggKey", + "discriminator": [ + 135, + 202, + 24, + 202, + 91, + 182, + 141, + 24 + ] + }, + { + "name": "govKeySetByGovKey", + "discriminator": [ + 198, + 58, + 153, + 108, + 67, + 162, + 174, + 167 + ] + }, + { + "name": "programSwapsParametersSet", + "discriminator": [ + 173, + 82, + 238, + 223, + 74, + 121, + 243, + 142 + ] + }, + { + "name": "suspended", + "discriminator": [ + 220, + 179, + 163, + 2, + 249, + 252, + 157, + 102 + ] + }, + { + "name": "swapEvent", + "discriminator": [ + 64, + 198, + 205, + 232, + 38, + 8, + 113, + 226 + ] + }, + { + "name": "tokenSupportDisabled", + "discriminator": [ + 35, + 252, + 131, + 176, + 50, + 103, + 135, + 13 + ] + }, + { + "name": "tokenSupportEnabled", + "discriminator": [ + 11, + 203, + 178, + 141, + 170, + 213, + 67, + 234 + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "invalidTokenVaultAccount", + "msg": "Token Vault pda account does not match the expected program id" + }, + { + "code": 6001, + "name": "suspended", + "msg": "Vault program is suspended" + }, + { + "code": 6002, + "name": "invalidRemainingAccount", + "msg": "An invalid account is provided as a remaining account" + }, + { + "code": 6003, + "name": "invalidRemainingAccountSigner", + "msg": "A remaining account can't be a signer" + }, + { + "code": 6004, + "name": "unchangedSuspendedState", + "msg": "Suspended state unchanged" + }, + { + "code": 6005, + "name": "invalidSwapParameters", + "msg": "Invalid swap parameters" + }, + { + "code": 6006, + "name": "invalidTokenVaultBump", + "msg": "Invalid token vault bump" + }, + { + "code": 6007, + "name": "invalidUpgradeSignerPda", + "msg": "Invalid upgrade signer pda" + }, + { + "code": 6008, + "name": "invalidUpgradeSignerPdaBump", + "msg": "Invalid upgrade signer bump" + }, + { + "code": 6009, + "name": "suspendedIxSwaps", + "msg": "Instruction swaps are suspended" + }, + { + "code": 6010, + "name": "suspendedEventSwaps", + "msg": "Event swaps are suspended" + }, + { + "code": 6011, + "name": "nativeAmountBelowMinimumSwapAmount", + "msg": "Native amount below minimum swap amount" + }, + { + "code": 6012, + "name": "tokenAmountBelowMinimumSwapAmount", + "msg": "Token amount below minimum swap amount" + }, + { + "code": 6013, + "name": "destinationAddressExceedsMaxLength", + "msg": "Destination address exceeds max length" + }, + { + "code": 6014, + "name": "cfParametersExceedsMaxLength", + "msg": "Cf Parameters exceeds max length" + }, + { + "code": 6015, + "name": "ccmMessageExceedsMaxLength", + "msg": "Ccm message exceeds max length" + } + ], + "types": [ + { + "name": "aggKeyRotated", + "type": { + "kind": "struct", + "fields": [ + { + "name": "oldAggKey", + "type": "pubkey" + }, + { + "name": "newAggKey", + "type": "pubkey" + } + ] + } + }, + { + "name": "ccmParams", + "type": { + "kind": "struct", + "fields": [ + { + "name": "message", + "type": "bytes" + }, + { + "name": "gasAmount", + "type": "u64" + } + ] + } + }, + { + "name": "dataAccount", + "docs": [ + "* ****************************************************************************\n * *************************** IMPORTANT NOTE *********************************\n * ****************************************************************************\n * If the vault is upgraded and the DataAccount struct is modified we need to\n * check the compatibility and ensure there is a proper migration process, given\n * that the Vault bytecode is the only thing being upgraded, not the data account.\n *\n * The easiest approach on upgrade is keeping the DataAccount unchanged and use\n * a new account struct for any new data that is required.\n *\n * DO NOT MODIFY THIS WITHOUT UNDERSTANDING THE CONSEQUENCES!\n * ****************************************************************************\n * ****************************************************************************" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "aggKey", + "type": "pubkey" + }, + { + "name": "govKey", + "type": "pubkey" + }, + { + "name": "tokenVaultPda", + "type": "pubkey" + }, + { + "name": "tokenVaultBump", + "type": "u8" + }, + { + "name": "upgradeSignerPda", + "type": "pubkey" + }, + { + "name": "upgradeSignerPdaBump", + "type": "u8" + }, + { + "name": "suspended", + "type": "bool" + }, + { + "name": "suspendedLegacySwaps", + "type": "bool" + }, + { + "name": "suspendedEventSwaps", + "type": "bool" + }, + { + "name": "minNativeSwapAmount", + "type": "u64" + }, + { + "name": "maxDstAddressLen", + "type": "u16" + }, + { + "name": "maxCcmMessageLen", + "type": "u32" + }, + { + "name": "maxCfParametersLen", + "type": "u32" + }, + { + "name": "maxEventAccounts", + "type": "u32" + } + ] + } + }, + { + "name": "depositChannelHistoricalFetch", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "type": "u128" + } + ] + } + }, + { + "name": "govKeyRotated", + "type": { + "kind": "struct", + "fields": [ + { + "name": "oldGovKey", + "type": "pubkey" + }, + { + "name": "newGovKey", + "type": "pubkey" + } + ] + } + }, + { + "name": "govKeySetByAggKey", + "type": { + "kind": "struct", + "fields": [ + { + "name": "oldGovKey", + "type": "pubkey" + }, + { + "name": "newGovKey", + "type": "pubkey" + } + ] + } + }, + { + "name": "govKeySetByGovKey", + "type": { + "kind": "struct", + "fields": [ + { + "name": "oldGovKey", + "type": "pubkey" + }, + { + "name": "newGovKey", + "type": "pubkey" + } + ] + } + }, + { + "name": "programSwapsParametersSet", + "type": { + "kind": "struct", + "fields": [ + { + "name": "oldMinSwapAmount", + "type": "u64" + }, + { + "name": "oldMaxDstAddressLen", + "type": "u16" + }, + { + "name": "oldMaxCcmMessageLen", + "type": "u32" + }, + { + "name": "oldMaxCfParametersLen", + "type": "u32" + }, + { + "name": "oldMaxEventAccounts", + "type": "u32" + }, + { + "name": "newMinSwapAmount", + "type": "u64" + }, + { + "name": "newMaxDstAddressLen", + "type": "u16" + }, + { + "name": "newMaxCcmMessageLen", + "type": "u32" + }, + { + "name": "newMaxCfParametersLen", + "type": "u32" + }, + { + "name": "newMaxEventAccounts", + "type": "u32" + } + ] + } + }, + { + "name": "supportedToken", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenMintPubkey", + "type": "pubkey" + }, + { + "name": "minSwapAmount", + "type": "u64" + } + ] + } + }, + { + "name": "suspended", + "type": { + "kind": "struct", + "fields": [ + { + "name": "suspended", + "type": "bool" + }, + { + "name": "suspendedLegacySwaps", + "type": "bool" + }, + { + "name": "suspendedEventSwaps", + "type": "bool" + } + ] + } + }, + { + "name": "swapEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "dstChain", + "type": "u32" + }, + { + "name": "dstAddress", + "type": "bytes" + }, + { + "name": "dstToken", + "type": "u32" + }, + { + "name": "srcToken", + "type": { + "option": "pubkey" + } + }, + { + "name": "amount", + "type": "u64" + }, + { + "name": "sender", + "type": "pubkey" + }, + { + "name": "ccmParameters", + "type": { + "option": { + "defined": { + "name": "ccmParams" + } + } + } + }, + { + "name": "cfParameters", + "type": "bytes" + } + ] + } + }, + { + "name": "tokenSupportDisabled", + "type": { + "kind": "struct", + "fields": [ + { + "name": "oldTokenSupported", + "type": { + "defined": { + "name": "supportedToken" + } + } + } + ] + } + }, + { + "name": "tokenSupportEnabled", + "type": { + "kind": "struct", + "fields": [ + { + "name": "oldTokenSupported", + "type": { + "defined": { + "name": "supportedToken" + } + } + }, + { + "name": "newTokenSupported", + "type": { + "defined": { + "name": "supportedToken" + } + } + } + ] + } + } + ] +}; diff --git a/engine/src/witness/arb.rs b/engine/src/witness/arb.rs index a3b6714126..8550c3aa2f 100644 --- a/engine/src/witness/arb.rs +++ b/engine/src/witness/arb.rs @@ -3,7 +3,7 @@ mod chain_tracking; use std::{collections::HashMap, sync::Arc}; use cf_chains::{assets::arb::Asset as ArbAsset, evm::DepositDetails, Arbitrum}; -use cf_primitives::{AffiliateShortId, EpochIndex}; +use cf_primitives::{AffiliateShortId, EpochIndex, ShortId}; use cf_utilities::task_scope::Scope; use futures_core::Future; use sp_core::H160; diff --git a/engine/src/witness/common/cf_parameters.rs b/engine/src/witness/common/cf_parameters.rs index 7b494304a4..0fa5d9bd17 100644 --- a/engine/src/witness/common/cf_parameters.rs +++ b/engine/src/witness/common/cf_parameters.rs @@ -1,5 +1,5 @@ use cf_chains::{CcmAdditionalData, ChannelRefundParameters}; -use cf_primitives::{AffiliateShortId, BasisPoints, Beneficiaries, DcaParameters}; +use cf_primitives::{AffiliateShortId, BasisPoints, Beneficiaries, DcaParameters, ShortId}; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -16,6 +16,7 @@ pub struct CfParameters { } pub type VersionedCcmCfParameters = VersionedCfParameters; +pub type CcmCfParameters = CfParameters; #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Debug)] pub struct VaultSwapParameters { diff --git a/engine/src/witness/eth.rs b/engine/src/witness/eth.rs index 390d10647c..5d3642668f 100644 --- a/engine/src/witness/eth.rs +++ b/engine/src/witness/eth.rs @@ -4,7 +4,9 @@ mod state_chain_gateway; use std::{collections::HashMap, sync::Arc}; use cf_chains::{evm::DepositDetails, Ethereum}; -use cf_primitives::{chains::assets::eth::Asset as EthAsset, AffiliateShortId, EpochIndex}; +use cf_primitives::{ + chains::assets::eth::Asset as EthAsset, AffiliateShortId, EpochIndex, ShortId, +}; use cf_utilities::task_scope::Scope; use futures_core::Future; use sp_core::H160; diff --git a/engine/src/witness/evm/vault.rs b/engine/src/witness/evm/vault.rs index c1223c4fca..1da416afd1 100644 --- a/engine/src/witness/evm/vault.rs +++ b/engine/src/witness/evm/vault.rs @@ -12,7 +12,7 @@ use super::{ }, contract_common::{events_at_block, Event}, }; -use cf_primitives::{AffiliateShortId, AssetAmount, EpochIndex}; +use cf_primitives::{AffiliateShortId, AssetAmount, EpochIndex, ShortId}; use futures_core::Future; use anyhow::{anyhow, Result}; diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index ac9596f745..4398638bbc 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -1,3 +1,5 @@ +use super::super::common::cf_parameters::*; +use codec::Decode; use std::collections::{BTreeSet, HashSet}; use crate::sol::{ @@ -13,6 +15,7 @@ use cf_chains::{ address::EncodedAddress, assets::sol::Asset as SolAsset, sol::{api::VaultSwapAccountAndSender, SolAddress}, + CcmChannelMetadata, CcmDepositMetadata, ForeignChainAddress, }; use cf_primitives::Asset; use futures::{stream, StreamExt, TryStreamExt}; @@ -38,6 +41,7 @@ struct SwapEndpointDataAccount { #[derive(BorshDeserialize, BorshSerialize, Debug, Default)] pub struct SwapEvent { discriminator: [u8; 8], + creation_slot: u64, sender: [u8; sol_prim::consts::SOLANA_ADDRESS_LEN], dst_chain: u32, dst_address: Vec, @@ -80,6 +84,10 @@ pub async fn get_program_swaps( ) .await?; + if new_program_swap_accounts.is_empty() { + return Ok((vec![], closed_accounts)); + } + let new_swaps = stream::iter(new_program_swap_accounts) .chunks(MAX_MULTIPLE_EVENT_ACCOUNTS_QUERY) .map(|new_program_swap_accounts_chunk| { @@ -91,17 +99,53 @@ pub async fn get_program_swaps( |(account, program_swap_account_data)| match program_swap_account_data { Some(data) if (data.src_token.is_none() || - data.src_token.is_some_and(|addr| addr == usdc_token_mint_pubkey.0)) => - Some(Ok((VaultSwapAccountAndSender { - vault_swap_account: account, - swap_sender: data.sender.into() - }, SolanaVaultSwapDetails { - from: if data.src_token.is_none() {Asset::Sol} else {Asset::SolUsdc}, - deposit_amount: data.amount.into(), - destination_address: EncodedAddress::from_chain_bytes(data.dst_chain.try_into().map_err(|e| warn!("error while parsing destination chain for solana vault swap:{}. Omitting swap", e)).ok()?, data.dst_address.to_vec()).map_err(|e| warn!("failed to decode the destination chain address for solana vault swap:{}. Omitting swap", e)).ok()?, - to: data.dst_token.try_into().map_err(|e| warn!("error while decoding destination token for solana vault swap: {}. Omitting swap", e)).ok()?, - tx_hash: Default::default(), // TODO - }))), + data.src_token.is_some_and(|addr| addr == usdc_token_mint_pubkey.0)) => { + + let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { + None => { + let CfParameters { ccm_additional_data: (), vault_swap_parameters } = + CfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + (None, vault_swap_parameters) + }, + Some(ccm_parameters) => { + let CfParameters { ccm_additional_data, vault_swap_parameters } = + CcmCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CcmCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + + let deposit_metadata = Some(CcmDepositMetadata { + source_chain: cf_primitives::ForeignChain::Solana, // TODO: Pass chain id from above? + source_address: Some(ForeignChainAddress::Sol(data.sender.into())), + channel_metadata: CcmChannelMetadata { + message: ccm_parameters.message + .to_vec() + .try_into() + .map_err(|_| anyhow!("Failed to deposit CCM: `message` too long.")).ok()?, + gas_budget: ccm_parameters.gas_amount.try_into().map_err(|_| anyhow!("Failed to deposit CCM: `gas_amount` too long.")).ok()?, + ccm_additional_data, + }, + }); + (deposit_metadata, vault_swap_parameters) + } + }; + + Some(Ok((VaultSwapAccountAndSender { + vault_swap_account: account, + swap_sender: data.sender.into() + }, SolanaVaultSwapDetails { + from: if data.src_token.is_none() {SolAsset::Sol} else {SolAsset::SolUsdc}, + deposit_amount: data.amount, + destination_address: EncodedAddress::from_chain_bytes(data.dst_chain.try_into().map_err(|e| warn!("error while parsing destination chain for solana vault swap:{}. Omitting swap", e)).ok()?, data.dst_address.to_vec()).map_err(|e| warn!("failed to decode the destination chain address for solana vault swap:{}. Omitting swap", e)).ok()?, + to: data.dst_token.try_into().map_err(|e| warn!("error while decoding destination token for solana vault swap: {}. Omitting swap", e)).ok()?, + deposit_metadata, + // TODO: These two will potentially be a TransactionId type + swap_account: account, + creation_slot: data.creation_slot, + broker_fees: vault_swap_parameters.broker_fees, + refund_params: Some(vault_swap_parameters.refund_params), + dca_params: vault_swap_parameters.dca_params, + boost_fee: vault_swap_parameters.boost_fee, + }))) + } + // It could happen that some account is closed between the queries. This should // not happen because: // 1. Accounts in `new_program_swap_accounts` can only be accounts that have diff --git a/localnet/docker-compose.yml b/localnet/docker-compose.yml index 67456a3695..8ed8b1ceaf 100644 --- a/localnet/docker-compose.yml +++ b/localnet/docker-compose.yml @@ -9,7 +9,7 @@ services: command: /bin/sh -c "cp -R /initial-state/* /localnet-initial-state" solana-init: - image: ghcr.io/chainflip-io/solana-localnet-ledger:v1.0.0 + image: ghcr.io/chainflip-io/solana-localnet-ledger:v1.0.0-swap-endpoint pull_policy: if_not_present container_name: init-solana platform: linux/amd64 diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 15bc91532f..3ca0b1bbdd 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -649,7 +649,18 @@ mod bounded_hex { /// Deposit channel Metadata for Cross-Chain-Message. #[derive( - Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, Serialize, Deserialize, MaxEncodedLen, + Clone, + Debug, + PartialEq, + Eq, + Encode, + Decode, + TypeInfo, + Serialize, + Deserialize, + MaxEncodedLen, + PartialOrd, + Ord, )] pub struct CcmChannelMetadata { /// Call data used after the message is egressed. @@ -681,7 +692,9 @@ pub enum CcmFailReason { InvalidMetadata, } -#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, Serialize, Deserialize)] +#[derive( + Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, Serialize, Deserialize, PartialOrd, Ord, +)] pub struct CcmDepositMetadataGeneric
{ pub channel_metadata: CcmChannelMetadata, pub source_chain: ForeignChain, @@ -815,7 +828,18 @@ pub struct SwapRefundParameters { } #[derive( - Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Serialize, Deserialize, + Clone, + Debug, + PartialEq, + Eq, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, + PartialOrd, + Ord, )] pub struct ChannelRefundParametersGeneric { pub retry_duration: cf_primitives::BlockNumber, diff --git a/state-chain/node/src/chain_spec/testnet.rs b/state-chain/node/src/chain_spec/testnet.rs index 273b6099d9..da8110a2ab 100644 --- a/state-chain/node/src/chain_spec/testnet.rs +++ b/state-chain/node/src/chain_spec/testnet.rs @@ -63,43 +63,43 @@ pub const ENV: StateChainEnvironment = StateChainEnvironment { sol_durable_nonces_and_accounts: [ ( const_address("2cNMwUCF51djw2xAiiU54wz1WrU8uG4Q8Kp8nfEuwghw"), - const_hash("A1wWyYH6T4JZzXYruSWUpLU7kkpdY9maudB3x7rHNNP7"), + const_hash("8T217weMrePR8VqqiY1J6VQKn5GfDXDwTPuYekPffNTo"), ), ( const_address("HVG21SovGzMBJDB9AQNuWb6XYq4dDZ6yUwCbRUuFnYDo"), - const_hash("F9VLxNFCawVXfFmXNR7FrmzKQwX2S2XNxzDbH7odXRWe"), + const_hash("Hvg5WgDgdhcex1TsJW8PiPqcxUizLitoEmXcCShmXVWJ"), ), ( const_address("HDYArziNzyuNMrK89igisLrXFe78ti8cvkcxfx4qdU2p"), - const_hash("G3DdyM6td7FBow79k8YH7TPNPcyLDNT45KZHb4Yoe8cp"), + const_hash("8vM4M9MWoYZE7YDGhpUhoetabY8dwaz4AcDR9hbCHd7u"), ), ( const_address("HLPsNyxBqfq2tLE31v6RiViLp2dTXtJRgHgsWgNDRPs2"), - const_hash("4Nr4Af3JFTd5LcnM57S4BQmpp9ui2YUvcHkqWA2J3DTR"), + const_hash("3fWpjCEzbHNU8qQD8YqoE5PfFahHNp4nwVVgJzxwTZya"), ), ( const_address("GKMP63TqzbueWTrFYjRwMNkAyTHpQ54notRbAbMDmePM"), - const_hash("BPb9ZAEDR91zxgXxFRuC9hrvpDW7oeJWLcvzPTxX5H8H"), + const_hash("96CDysvpx87Cd4TnxsMajFA9cKwFid1tMUFWnQWnifpJ"), ), ( const_address("EpmHm2aSPsB5ZZcDjqDhQ86h1BV32GFCbGSMuC58Y2tn"), - const_hash("8VrLY1NVmRTAB1qP4DXyokdjKB9WrySTyaqHsEy3HAFw"), + const_hash("BjY7LRovNVwGEh5BGcfK4bZcjVan4YzuyFqcBwraG9Bj"), ), ( const_address("9yBZNMrLrtspj4M7bEf2X6tqbqHxD2vNETw8qSdvJHMa"), - const_hash("2cVt69yHAGFCKAnt2j3n9o9J2d4iWn2mtcgXg4H8fHRG"), + const_hash("Hb35byiENfrMFwznb5TUxdAWN52dV81tWYWT3N99VRWr"), ), ( const_address("J9dT7asYJFGS68NdgDCYjzU2Wi8uBoBusSHN1Z6JLWna"), - const_hash("EEcKK35PVEKqLGk7Nq6uHcpJXTHbFuwjkQHqqvvmUtta"), + const_hash("4TbbvCow8yHxnzdMT22gUt3JvHwAF8dbscBCLRezmpCY"), ), ( const_address("GUMpVpQFNYJvSbyTtUarZVL7UDUgErKzDTSVJhekUX55"), - const_hash("FDwdT8bEqUHA4e8KVbDncMgQXJwbfpgyvYK9ZTtxjS5F"), + const_hash("FooctpZoHqoSjDE983JTJRyovN5Py6PZiiubd53gLFMv"), ), ( const_address("AUiHYbzH7qLZSkb3u7nAqtvqC7e41sEzgWjBEvXrpfGv"), - const_hash("Hgr7AdqH4nQiau7raNF2ShTZqUfnE3D5AMX2nwYzHuJT"), + const_hash("HCNp5KwKadPNiPs3nY1DtVcDfFkuUE2uUBsBueZbnkWc"), ), ], sol_swap_endpoint_program: SolAddress(bs58_array( diff --git a/state-chain/primitives/src/lib.rs b/state-chain/primitives/src/lib.rs index 82396fbd46..37150de704 100644 --- a/state-chain/primitives/src/lib.rs +++ b/state-chain/primitives/src/lib.rs @@ -403,7 +403,18 @@ pub type Affiliates = BoundedVec, ConstU32>; pub type Beneficiaries = BoundedVec, ConstU32>; #[derive( - Clone, Debug, PartialEq, Eq, MaxEncodedLen, Encode, Decode, TypeInfo, Serialize, Deserialize, + Clone, + Debug, + PartialEq, + Eq, + MaxEncodedLen, + Encode, + Decode, + TypeInfo, + Serialize, + Deserialize, + PartialOrd, + Ord, )] pub struct Beneficiary { pub account: Id, @@ -421,6 +432,8 @@ pub struct Beneficiary { TypeInfo, Serialize, Deserialize, + PartialOrd, + Ord, )] pub struct DcaParameters { /// The number of individual swaps to be executed @@ -428,3 +441,6 @@ pub struct DcaParameters { /// The interval in blocks between each swap. pub chunk_interval: u32, } + +// TODO: Define this / implement it on the SC - PRO-1743. +pub type ShortId = u8; diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index bad1026f9d..d1e3608575 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -11,8 +11,10 @@ use cf_chains::{ }, SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, SolanaCrypto, }, - Chain, FeeEstimationApi, ForeignChain, Solana, + CcmDepositMetadata, Chain, ChannelRefundParameters, CloseSolanaVaultSwapAccounts, + FeeEstimationApi, ForeignChain, Solana, }; +use cf_primitives::{BasisPoints, DcaParameters, ShortId, TransactionHash}; use cf_runtime_utilities::log_or_panic; use cf_traits::{ instances::ChainInstanceAlias, @@ -528,7 +530,14 @@ pub struct SolanaVaultSwapDetails { pub to: Asset, pub deposit_amount: AssetAmount, pub destination_address: EncodedAddress, - pub tx_hash: TransactionHash, + pub deposit_metadata: Option, + // TODO: These two will potentially be a TransactionId type + pub swap_account: SolAddress, + pub creation_slot: u64, + pub broker_fees: cf_primitives::Beneficiaries, + pub refund_params: Option, + pub dca_params: Option, + pub boost_fee: Option, } #[cfg(feature = "runtime-benchmarks")] @@ -559,13 +568,13 @@ impl swap_details.deposit_amount, swap_details.to, swap_details.destination_address, - Default::default(), // TODO - swap_details.tx_hash, - Default::default(), // TODO - Default::default(), // TODO - Default::default(), // TODO - Default::default(), // TODO - Default::default(), // TODO + swap_details.deposit_metadata, + Default::default(), // TODO txHash PRO-1760 + Default::default(), + Default::default(), // TODO in PRO-1743 + swap_details.refund_params, + swap_details.dca_params, + swap_details.boost_fee.unwrap_or_default(), ); } From 4d8af6194def0f8135afad63e23a609b61aeb524 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 1 Nov 2024 16:58:27 +0100 Subject: [PATCH 21/62] fix: BenchmarkValue shit, clippy and election order --- engine/src/witness/sol.rs | 1 + .../witness/sol/program_swaps_witnessing.rs | 2 +- state-chain/chains/src/benchmarking_value.rs | 61 ++++++++++++++++++- state-chain/chains/src/lib.rs | 33 ++++++++++ .../runtime/src/chainflip/solana_elections.rs | 12 +++- 5 files changed, 104 insertions(+), 5 deletions(-) diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index ef2a74bb9d..83dcb9d34c 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -245,6 +245,7 @@ where SolanaIngressTrackingVoter { client: client.clone() }, SolanaNonceTrackingVoter { client: client.clone() }, SolanaEgressWitnessingVoter { client: client.clone() }, + SolanaLivenessVoter { client: client.clone() }, SolanaVaultSwapsVoter { client }, )), ) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 4398638bbc..ddbeda3ff5 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -119,7 +119,7 @@ pub async fn get_program_swaps( .to_vec() .try_into() .map_err(|_| anyhow!("Failed to deposit CCM: `message` too long.")).ok()?, - gas_budget: ccm_parameters.gas_amount.try_into().map_err(|_| anyhow!("Failed to deposit CCM: `gas_amount` too long.")).ok()?, + gas_budget: ccm_parameters.gas_amount.into(), ccm_additional_data, }, }); diff --git a/state-chain/chains/src/benchmarking_value.rs b/state-chain/chains/src/benchmarking_value.rs index 1705250c93..661b3c3a74 100644 --- a/state-chain/chains/src/benchmarking_value.rs +++ b/state-chain/chains/src/benchmarking_value.rs @@ -4,6 +4,8 @@ use cf_primitives::{ Asset, }; #[cfg(feature = "runtime-benchmarks")] +use cf_primitives::{Beneficiary, DcaParameters, ForeignChain, ShortId, MAX_AFFILIATES}; +#[cfg(feature = "runtime-benchmarks")] use core::str::FromStr; #[cfg(feature = "runtime-benchmarks")] @@ -235,9 +237,66 @@ impl BenchmarkValue for Utxo { } } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for U256 { + fn benchmark_value() -> Self { + Self([1u64; 4]) + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for DcaParameters { + fn benchmark_value() -> Self { + Self { + number_of_chunks: BenchmarkValue::benchmark_value(), + chunk_interval: BenchmarkValue::benchmark_value(), + } + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for Beneficiary { + fn benchmark_value() -> Self { + Self { account: BenchmarkValue::benchmark_value(), bps: BenchmarkValue::benchmark_value() } + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue + for sp_runtime::BoundedVec, sp_core::ConstU32<{ MAX_AFFILIATES + 1 }>> +{ + fn benchmark_value() -> Self { + sp_runtime::BoundedVec::try_from(vec![BenchmarkValue::benchmark_value()]).unwrap() + } +} + +#[macro_export] +macro_rules! impl_bounded_vec_benchmark_value { + ($element:ty, $n:literal) => { + #[cfg(feature = "runtime-benchmarks")] + impl BenchmarkValue for sp_runtime::BoundedVec<$element, sp_core::ConstU32<{ $n }>> { + fn benchmark_value() -> Self { + sp_runtime::BoundedVec::try_from(vec![BenchmarkValue::benchmark_value()]).unwrap() + } + } + }; +} + +impl_bounded_vec_benchmark_value!(u8, 10000); +impl_bounded_vec_benchmark_value!(u8, 1000); + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for ForeignChain { + fn benchmark_value() -> Self { + Self::Ethereum + } +} + impl_default_benchmark_value!(()); -impl_default_benchmark_value!(u32); impl_default_benchmark_value!(u64); +impl_default_benchmark_value!(u32); +impl_default_benchmark_value!(u16); +impl_default_benchmark_value!(u8); #[macro_export] macro_rules! impl_tuple_benchmark_value { diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 3ca0b1bbdd..646d8defca 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -677,6 +677,17 @@ pub struct CcmChannelMetadata { pub ccm_additional_data: CcmAdditionalData, } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for CcmChannelMetadata { + fn benchmark_value() -> Self { + Self { + message: BenchmarkValue::benchmark_value(), + gas_budget: BenchmarkValue::benchmark_value(), + ccm_additional_data: BenchmarkValue::benchmark_value(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] pub struct CcmSwapAmounts { pub principal_swap_amount: AssetAmount, @@ -701,6 +712,17 @@ pub struct CcmDepositMetadataGeneric
{ pub source_address: Option
, } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for CcmDepositMetadataGeneric
{ + fn benchmark_value() -> Self { + Self { + channel_metadata: BenchmarkValue::benchmark_value(), + source_chain: BenchmarkValue::benchmark_value(), + source_address: Some(BenchmarkValue::benchmark_value()), + } + } +} + impl
CcmDepositMetadataGeneric
{ pub fn into_swap_metadata( self, @@ -847,6 +869,17 @@ pub struct ChannelRefundParametersGeneric { pub min_price: Price, } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for ChannelRefundParametersGeneric { + fn benchmark_value() -> Self { + Self { + retry_duration: BenchmarkValue::benchmark_value(), + refund_address: BenchmarkValue::benchmark_value(), + min_price: BenchmarkValue::benchmark_value(), + } + } +} + pub type ChannelRefundParameters = ChannelRefundParametersGeneric; pub type ChannelRefundParametersEncoded = ChannelRefundParametersGeneric; diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index d1e3608575..0e5d938459 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -14,7 +14,7 @@ use cf_chains::{ CcmDepositMetadata, Chain, ChannelRefundParameters, CloseSolanaVaultSwapAccounts, FeeEstimationApi, ForeignChain, Solana, }; -use cf_primitives::{BasisPoints, DcaParameters, ShortId, TransactionHash}; +use cf_primitives::{BasisPoints, DcaParameters, ShortId}; use cf_runtime_utilities::log_or_panic; use cf_traits::{ instances::ChainInstanceAlias, @@ -548,7 +548,13 @@ impl BenchmarkValue for SolanaVaultSwapDetails { to: BenchmarkValue::benchmark_value(), deposit_amount: BenchmarkValue::benchmark_value(), destination_address: BenchmarkValue::benchmark_value(), - tx_hash: BenchmarkValue::benchmark_value(), + deposit_metadata: Some(BenchmarkValue::benchmark_value()), + swap_account: BenchmarkValue::benchmark_value(), + creation_slot: BenchmarkValue::benchmark_value(), + broker_fees: BenchmarkValue::benchmark_value(), + refund_params: Some(BenchmarkValue::benchmark_value()), + dca_params: Some(BenchmarkValue::benchmark_value()), + boost_fee: Some(BenchmarkValue::benchmark_value()), } } } @@ -570,7 +576,7 @@ impl swap_details.destination_address, swap_details.deposit_metadata, Default::default(), // TODO txHash PRO-1760 - Default::default(), + (), Default::default(), // TODO in PRO-1743 swap_details.refund_params, swap_details.dca_params, From a2c8bc6fad1a848ba49320c97111fccb9798e6da Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 1 Nov 2024 18:26:45 +0100 Subject: [PATCH 22/62] feat: is_vote_needed --- .../solana_swap_accounts_tracking.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 9636003534..9a71fe2390 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -138,6 +138,20 @@ impl< Ok(true) } + fn is_vote_needed( + (_, current_partial_vote, _): ( + VotePropertiesOf, + ::PartialVote, + AuthorityVoteOf, + ), + (proposed_partial_vote, _): ( + ::PartialVote, + ::Vote, + ), + ) -> bool { + current_partial_vote != proposed_partial_vote + } + fn on_finalize>( electoral_access: &mut ElectoralAccess, election_identifiers: Vec>, From 8758806fa9ea016038795b02321760910bb7d62f Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:26:24 +0100 Subject: [PATCH 23/62] Test: bouncer solana event closure (#5378) * chore: add check * chore: remove logging * chore: nit --- bouncer/shared/evm_vault_swap.ts | 43 +++++++++++++++++++++++++++ bouncer/tests/all_concurrent_tests.ts | 3 ++ bouncer/tests/all_swaps.ts | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/bouncer/shared/evm_vault_swap.ts b/bouncer/shared/evm_vault_swap.ts index a19eda40de..67e8b0506e 100644 --- a/bouncer/shared/evm_vault_swap.ts +++ b/bouncer/shared/evm_vault_swap.ts @@ -31,6 +31,7 @@ import { chainContractId, decodeSolAddress, decodeDotAddressForContract, + sleep, } from './utils'; import { getBalance } from './get_balance'; import { CcmDepositMetadata, DcaParams, FillOrKillParamsX128 } from './new_swap'; @@ -45,6 +46,8 @@ const { BN } = anchor.default; const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc']; +const createdEventAccounts: PublicKey[] = []; + export async function executeVaultSwap( sourceAsset: Asset, destAsset: Asset, @@ -156,6 +159,8 @@ export async function executeSolVaultSwap( const vaultProgram = new anchor.Program(VaultIdl as Vault); const newEventAccountKeypair = Keypair.generate(); + createdEventAccounts.push(newEventAccountKeypair.publicKey); + const fetchedDataAccount = await vaultProgram.account.dataAccount.fetch(solanaVaultDataAccount); const aggKey = fetchedDataAccount.aggKey; @@ -389,3 +394,41 @@ export async function approveTokenVault(sourceAsset: Asset, amount: string, wall }, ); } + +export async function checkSolEventAccountsClosure( + eventAccounts: PublicKey[] = createdEventAccounts, +) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const SwapEndpointIdl: any = await getSolanaSwapEndpointIdl(); + const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); + const swapEndpointDataAccountAddress = new PublicKey( + getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'), + ); + + const maxRetries = 50; // 300 seconds + + for (let attempt = 0; attempt < maxRetries; attempt++) { + const swapEndpointDataAccount = + await cfSwapEndpointProgram.account.swapEndpointDataAccount.fetch( + swapEndpointDataAccountAddress, + ); + + if (swapEndpointDataAccount.openEventAccounts.length >= 10) { + await sleep(6000); + } else { + const onChainOpenedAccounts = swapEndpointDataAccount.openEventAccounts.map((element) => + element.toString(), + ); + for (const eventAccount of eventAccounts) { + if (!onChainOpenedAccounts.includes(eventAccount.toString())) { + const accountInfo = await getSolConnection().getAccountInfo(eventAccount); + if (accountInfo !== null) { + throw new Error('Event account still exists, should have been closed'); + } + } + } + return; + } + } + throw new Error('Timed out waiting for event accounts to be closed'); +} diff --git a/bouncer/tests/all_concurrent_tests.ts b/bouncer/tests/all_concurrent_tests.ts index d5b26a92b5..089a18d4ba 100755 --- a/bouncer/tests/all_concurrent_tests.ts +++ b/bouncer/tests/all_concurrent_tests.ts @@ -16,6 +16,7 @@ import { depositChannelCreation } from './request_swap_deposit_address_with_affi import { testDCASwaps } from './DCA_test'; import { testBrokerLevelScreening } from './broker_level_screening'; import { testBtcVaultSwap } from './btc_vault_swap'; +import { checkSolEventAccountsClosure } from '../shared/vault_swap'; async function runAllConcurrentTests() { // Specify the number of nodes via providing an argument to this script. @@ -67,6 +68,8 @@ async function runAllConcurrentTests() { await Promise.all([broadcastAborted.stop(), feeDeficitRefused.stop()]); + await checkSolEventAccountsClosure(); + await checkAvailabilityAllSolanaNonces(); } diff --git a/bouncer/tests/all_swaps.ts b/bouncer/tests/all_swaps.ts index a940b823e2..55d203d827 100644 --- a/bouncer/tests/all_swaps.ts +++ b/bouncer/tests/all_swaps.ts @@ -68,7 +68,7 @@ async function main() { }); }); - // Not doing BTC due to encoding issues in vault_swap + // Not doing BTC due to encoding complexity in vault_swap. Will be fixed once SDK supports it. appendSwap('Sol', 'Eth', testVaultSwap); appendSwap('Sol', 'Usdc', testVaultSwap, true); appendSwap('Sol', 'ArbEth', testVaultSwap); From a96b1dd374a536a5922a63b4194c1a2b0fb49378 Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:42:27 +0100 Subject: [PATCH 24/62] Chore: refactor Solana program idls (#5381) * chore: refactor ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH * chore: expand macro * wip: move accounts in to idl macro * chore: also update fetch account info * feat: sol idl types and checked account decoding * chore: nit and run ci * chore: revert ci --------- Co-authored-by: Daniel --- Cargo.lock | 1 - .../v1.0.0/types/swap_endpoint.ts | 661 ------ .../sol-program-idls/v1.0.0/types/vault.ts | 2012 ----------------- engine/Cargo.toml | 5 - .../witness/sol/program_swaps_witnessing.rs | 78 +- engine/src/witness/sol/sol_deposits.rs | 25 +- state-chain/chains/src/sol/sol_tx_core.rs | 1 + .../sol/sol_tx_core/program_instructions.rs | 230 +- 8 files changed, 248 insertions(+), 2765 deletions(-) delete mode 100644 contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint.ts delete mode 100644 contract-interfaces/sol-program-idls/v1.0.0/types/vault.ts diff --git a/Cargo.lock b/Cargo.lock index f0645a48a2..441af10269 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1668,7 +1668,6 @@ dependencies = [ "base64 0.22.1", "bincode 1.3.3", "bitcoin", - "borsh", "bs58 0.5.1", "cf-amm", "cf-chains", diff --git a/contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint.ts b/contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint.ts deleted file mode 100644 index 8eae9704d2..0000000000 --- a/contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint.ts +++ /dev/null @@ -1,661 +0,0 @@ -/** - * Program IDL in camelCase format in order to be used in JS/TS. - * - * Note that this is only a type helper and is not the actual IDL. The original - * IDL can be found at `target/idl/swap_endpoint.json`. - */ -export type SwapEndpoint = { - "address": "35uYgHdfZQT4kHkaaXQ6ZdCkK5LFrsk43btTLbGCRCNT", - "metadata": { - "name": "swapEndpoint", - "version": "0.1.0", - "spec": "0.1.0", - "description": "Created with Anchor" - }, - "instructions": [ - { - "name": "closeEventAccounts", - "discriminator": [ - 165, - 102, - 61, - 1, - 185, - 77, - 189, - 121 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "writable": true, - "signer": true - }, - { - "name": "swapEndpointDataAccount", - "writable": true - } - ], - "args": [] - }, - { - "name": "initialize", - "discriminator": [ - 175, - 175, - 109, - 31, - 13, - 152, - 155, - 237 - ], - "accounts": [ - { - "name": "swapEndpointDataAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 100, - 97, - 116, - 97 - ] - } - ] - } - }, - { - "name": "signer", - "writable": true, - "signer": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "xSwapNative", - "discriminator": [ - 163, - 38, - 92, - 226, - 243, - 105, - 141, - 196 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "writable": true - }, - { - "name": "from", - "writable": true, - "signer": true - }, - { - "name": "eventDataAccount", - "writable": true, - "signer": true - }, - { - "name": "swapEndpointDataAccount", - "writable": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "swapNativeParams", - "type": { - "defined": { - "name": "swapNativeParams" - } - } - } - ] - }, - { - "name": "xSwapToken", - "discriminator": [ - 69, - 50, - 252, - 99, - 229, - 83, - 119, - 235 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "tokenVaultAssociatedTokenAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "data_account.token_vault_pda", - "account": "dataAccount" - }, - { - "kind": "const", - "value": [ - 6, - 221, - 246, - 225, - 215, - 101, - 161, - 147, - 217, - 203, - 225, - 70, - 206, - 235, - 121, - 172, - 28, - 180, - 133, - 237, - 95, - 91, - 55, - 145, - 58, - 140, - 245, - 133, - 126, - 255, - 0, - 169 - ] - }, - { - "kind": "account", - "path": "mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 - ] - } - } - }, - { - "name": "from", - "writable": true, - "signer": true - }, - { - "name": "fromTokenAccount", - "writable": true - }, - { - "name": "eventDataAccount", - "writable": true, - "signer": true - }, - { - "name": "swapEndpointDataAccount", - "writable": true - }, - { - "name": "tokenSupportedAccount" - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - }, - { - "name": "mint" - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "swapTokenParams", - "type": { - "defined": { - "name": "swapTokenParams" - } - } - } - ] - } - ], - "accounts": [ - { - "name": "dataAccount", - "discriminator": [ - 85, - 240, - 182, - 158, - 76, - 7, - 18, - 233 - ] - }, - { - "name": "supportedToken", - "discriminator": [ - 56, - 162, - 96, - 99, - 193, - 245, - 204, - 108 - ] - }, - { - "name": "swapEndpointDataAccount", - "discriminator": [ - 79, - 152, - 191, - 225, - 128, - 108, - 11, - 139 - ] - }, - { - "name": "swapEvent", - "discriminator": [ - 150, - 251, - 114, - 94, - 200, - 113, - 248, - 70 - ] - } - ], - "events": [ - { - "name": "cantDeserializeEventAccount", - "discriminator": [ - 248, - 26, - 198, - 175, - 8, - 58, - 229, - 137 - ] - }, - { - "name": "eventAccountNotTracked", - "discriminator": [ - 202, - 29, - 253, - 107, - 20, - 196, - 36, - 210 - ] - } - ], - "errors": [ - { - "code": 6000, - "name": "mathOverflow", - "msg": "Overflow in arithmetic operation" - }, - { - "code": 6001, - "name": "mathUnderflow", - "msg": "Underflow in arithmetic operation" - } - ], - "types": [ - { - "name": "cantDeserializeEventAccount", - "type": { - "kind": "struct", - "fields": [ - { - "name": "eventAccount", - "type": "pubkey" - }, - { - "name": "payee", - "type": "pubkey" - } - ] - } - }, - { - "name": "ccmParams", - "type": { - "kind": "struct", - "fields": [ - { - "name": "message", - "type": "bytes" - }, - { - "name": "gasAmount", - "type": "u64" - } - ] - } - }, - { - "name": "dataAccount", - "docs": [ - "* ****************************************************************************\n * *************************** IMPORTANT NOTE *********************************\n * ****************************************************************************\n * If the vault is upgraded and the DataAccount struct is modified we need to\n * check the compatibility and ensure there is a proper migration process, given\n * that the Vault bytecode is the only thing being upgraded, not the data account.\n *\n * The easiest approach on upgrade is keeping the DataAccount unchanged and use\n * a new account struct for any new data that is required.\n *\n * DO NOT MODIFY THIS WITHOUT UNDERSTANDING THE CONSEQUENCES!\n * ****************************************************************************\n * ****************************************************************************" - ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "aggKey", - "type": "pubkey" - }, - { - "name": "govKey", - "type": "pubkey" - }, - { - "name": "tokenVaultPda", - "type": "pubkey" - }, - { - "name": "tokenVaultBump", - "type": "u8" - }, - { - "name": "upgradeSignerPda", - "type": "pubkey" - }, - { - "name": "upgradeSignerPdaBump", - "type": "u8" - }, - { - "name": "suspended", - "type": "bool" - }, - { - "name": "suspendedLegacySwaps", - "type": "bool" - }, - { - "name": "suspendedEventSwaps", - "type": "bool" - }, - { - "name": "minNativeSwapAmount", - "type": "u64" - }, - { - "name": "maxDstAddressLen", - "type": "u16" - }, - { - "name": "maxCcmMessageLen", - "type": "u32" - }, - { - "name": "maxCfParametersLen", - "type": "u32" - }, - { - "name": "maxEventAccounts", - "type": "u32" - } - ] - } - }, - { - "name": "eventAccountNotTracked", - "type": { - "kind": "struct", - "fields": [ - { - "name": "eventAccount", - "type": "pubkey" - }, - { - "name": "payee", - "type": "pubkey" - } - ] - } - }, - { - "name": "supportedToken", - "type": { - "kind": "struct", - "fields": [ - { - "name": "tokenMintPubkey", - "type": "pubkey" - }, - { - "name": "minSwapAmount", - "type": "u64" - } - ] - } - }, - { - "name": "swapEndpointDataAccount", - "type": { - "kind": "struct", - "fields": [ - { - "name": "historicalNumberEventAccounts", - "type": "u128" - }, - { - "name": "openEventAccounts", - "type": { - "vec": "pubkey" - } - } - ] - } - }, - { - "name": "swapEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "sender", - "type": "pubkey" - }, - { - "name": "dstChain", - "type": "u32" - }, - { - "name": "dstAddress", - "type": "bytes" - }, - { - "name": "dstToken", - "type": "u32" - }, - { - "name": "amount", - "type": "u64" - }, - { - "name": "srcToken", - "type": { - "option": "pubkey" - } - }, - { - "name": "ccmParameters", - "type": { - "option": { - "defined": { - "name": "ccmParams" - } - } - } - }, - { - "name": "cfParameters", - "type": "bytes" - } - ] - } - }, - { - "name": "swapNativeParams", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amount", - "type": "u64" - }, - { - "name": "dstChain", - "type": "u32" - }, - { - "name": "dstAddress", - "type": "bytes" - }, - { - "name": "dstToken", - "type": "u32" - }, - { - "name": "ccmParameters", - "type": { - "option": { - "defined": { - "name": "ccmParams" - } - } - } - }, - { - "name": "cfParameters", - "type": "bytes" - } - ] - } - }, - { - "name": "swapTokenParams", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amount", - "type": "u64" - }, - { - "name": "dstChain", - "type": "u32" - }, - { - "name": "dstAddress", - "type": "bytes" - }, - { - "name": "dstToken", - "type": "u32" - }, - { - "name": "ccmParameters", - "type": { - "option": { - "defined": { - "name": "ccmParams" - } - } - } - }, - { - "name": "cfParameters", - "type": "bytes" - }, - { - "name": "decimals", - "type": "u8" - } - ] - } - } - ] -}; diff --git a/contract-interfaces/sol-program-idls/v1.0.0/types/vault.ts b/contract-interfaces/sol-program-idls/v1.0.0/types/vault.ts deleted file mode 100644 index 1bca1885c4..0000000000 --- a/contract-interfaces/sol-program-idls/v1.0.0/types/vault.ts +++ /dev/null @@ -1,2012 +0,0 @@ -/** - * Program IDL in camelCase format in order to be used in JS/TS. - * - * Note that this is only a type helper and is not the actual IDL. The original - * IDL can be found at `target/idl/vault.json`. - */ -export type Vault = { - "address": "8inHGLHXegST3EPLcpisQe9D1hDT9r7DJjS395L3yuYf", - "metadata": { - "name": "vault", - "version": "0.1.0", - "spec": "0.1.0", - "description": "Created with Anchor" - }, - "instructions": [ - { - "name": "disableTokenSupport", - "discriminator": [ - 72, - 4, - 35, - 144, - 194, - 49, - 71, - 64 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true - }, - { - "name": "govKey", - "writable": true, - "signer": true - }, - { - "name": "tokenSupportedAccount", - "writable": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "enableTokenSupport", - "discriminator": [ - 125, - 160, - 180, - 50, - 26, - 27, - 112, - 153 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true - }, - { - "name": "govKey", - "writable": true, - "signer": true - }, - { - "name": "tokenSupportedAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 117, - 112, - 112, - 111, - 114, - 116, - 101, - 100, - 95, - 116, - 111, - 107, - 101, - 110 - ] - }, - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "mint" - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "minSwapAmount", - "type": "u64" - } - ] - }, - { - "name": "executeCcmNativeCall", - "discriminator": [ - 125, - 5, - 11, - 227, - 128, - 66, - 224, - 178 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "signer": true - }, - { - "name": "receiverNative", - "writable": true - }, - { - "name": "cfReceiver", - "docs": [ - "the aggregate key signature." - ] - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "instructionSysvar", - "address": "Sysvar1nstructions1111111111111111111111111" - } - ], - "args": [ - { - "name": "sourceChain", - "type": "u32" - }, - { - "name": "sourceAddress", - "type": "bytes" - }, - { - "name": "message", - "type": "bytes" - }, - { - "name": "amount", - "type": "u64" - } - ] - }, - { - "name": "executeCcmTokenCall", - "discriminator": [ - 108, - 184, - 162, - 123, - 159, - 222, - 170, - 35 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "signer": true - }, - { - "name": "receiverTokenAccount", - "writable": true - }, - { - "name": "cfReceiver", - "docs": [ - "without passing the aggregate key signature." - ] - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - }, - { - "name": "mint" - }, - { - "name": "instructionSysvar", - "address": "Sysvar1nstructions1111111111111111111111111" - } - ], - "args": [ - { - "name": "sourceChain", - "type": "u32" - }, - { - "name": "sourceAddress", - "type": "bytes" - }, - { - "name": "message", - "type": "bytes" - }, - { - "name": "amount", - "type": "u64" - } - ] - }, - { - "name": "fetchNative", - "discriminator": [ - 142, - 36, - 101, - 143, - 108, - 89, - 41, - 140 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "writable": true, - "signer": true - }, - { - "name": "depositChannelPda", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 99, - 104, - 97, - 110, - 110, - 101, - 108 - ] - }, - { - "kind": "arg", - "path": "seed" - } - ] - } - }, - { - "name": "depositChannelHistoricalFetch", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 104, - 105, - 115, - 116, - 95, - 102, - 101, - 116, - 99, - 104 - ] - }, - { - "kind": "account", - "path": "depositChannelPda" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "seed", - "type": "bytes" - }, - { - "name": "bump", - "type": "u8" - } - ] - }, - { - "name": "fetchTokens", - "discriminator": [ - 73, - 71, - 16, - 100, - 44, - 176, - 198, - 70 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "writable": true, - "signer": true - }, - { - "name": "depositChannelPda", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 99, - 104, - 97, - 110, - 110, - 101, - 108 - ] - }, - { - "kind": "arg", - "path": "seed" - } - ] - } - }, - { - "name": "depositChannelAssociatedTokenAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "depositChannelPda" - }, - { - "kind": "const", - "value": [ - 6, - 221, - 246, - 225, - 215, - 101, - 161, - 147, - 217, - 203, - 225, - 70, - 206, - 235, - 121, - 172, - 28, - 180, - 133, - 237, - 95, - 91, - 55, - 145, - 58, - 140, - 245, - 133, - 126, - 255, - 0, - 169 - ] - }, - { - "kind": "account", - "path": "mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 - ] - } - } - }, - { - "name": "tokenVaultAssociatedTokenAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "data_account.token_vault_pda", - "account": "dataAccount" - }, - { - "kind": "const", - "value": [ - 6, - 221, - 246, - 225, - 215, - 101, - 161, - 147, - 217, - 203, - 225, - 70, - 206, - 235, - 121, - 172, - 28, - 180, - 133, - 237, - 95, - 91, - 55, - 145, - 58, - 140, - 245, - 133, - 126, - 255, - 0, - 169 - ] - }, - { - "kind": "account", - "path": "mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 - ] - } - } - }, - { - "name": "mint" - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - }, - { - "name": "depositChannelHistoricalFetch", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 104, - 105, - 115, - 116, - 95, - 102, - 101, - 116, - 99, - 104 - ] - }, - { - "kind": "account", - "path": "depositChannelAssociatedTokenAccount" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "seed", - "type": "bytes" - }, - { - "name": "bump", - "type": "u8" - }, - { - "name": "decimals", - "type": "u8" - } - ] - }, - { - "name": "initialize", - "discriminator": [ - 175, - 175, - 109, - 31, - 13, - 152, - 155, - 237 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 100, - 97, - 116, - 97 - ] - } - ] - } - }, - { - "name": "initializer", - "writable": true, - "signer": true, - "address": "HfasueN6RNPjSM6rKGH5dga6kS2oUF8siGH3m4MXPURp" - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "newAggKey", - "type": "pubkey" - }, - { - "name": "newGovKey", - "type": "pubkey" - }, - { - "name": "expectedTokenVaultPda", - "type": "pubkey" - }, - { - "name": "expectedTokenVaultPdaBump", - "type": "u8" - }, - { - "name": "expectedUpgradeSignerPda", - "type": "pubkey" - }, - { - "name": "expectedUpgradeSignerPdaBump", - "type": "u8" - }, - { - "name": "suspendedVault", - "type": "bool" - }, - { - "name": "suspendedLegacySwaps", - "type": "bool" - }, - { - "name": "suspendedEventSwaps", - "type": "bool" - }, - { - "name": "minNativeSwapAmount", - "type": "u64" - }, - { - "name": "maxDstAddressLen", - "type": "u16" - }, - { - "name": "maxCcmMessageLen", - "type": "u32" - }, - { - "name": "maxCfParametersLen", - "type": "u32" - }, - { - "name": "maxEventAccounts", - "type": "u32" - } - ] - }, - { - "name": "rotateAggKey", - "discriminator": [ - 78, - 81, - 143, - 171, - 221, - 165, - 214, - 139 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true - }, - { - "name": "aggKey", - "writable": true, - "signer": true - }, - { - "name": "newAggKey", - "writable": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "skipTransferFunds", - "type": "bool" - } - ] - }, - { - "name": "setGovKeyWithAggKey", - "discriminator": [ - 66, - 64, - 58, - 40, - 15, - 75, - 215, - 162 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true - }, - { - "name": "aggKey", - "signer": true - } - ], - "args": [ - { - "name": "newGovKey", - "type": "pubkey" - } - ] - }, - { - "name": "setGovKeyWithGovKey", - "discriminator": [ - 251, - 142, - 231, - 255, - 111, - 143, - 165, - 106 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true - }, - { - "name": "govKey", - "signer": true - } - ], - "args": [ - { - "name": "newGovKey", - "type": "pubkey" - } - ] - }, - { - "name": "setProgramSwapsParameters", - "discriminator": [ - 129, - 254, - 31, - 151, - 111, - 149, - 135, - 77 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true - }, - { - "name": "govKey", - "signer": true - } - ], - "args": [ - { - "name": "minNativeSwapAmount", - "type": "u64" - }, - { - "name": "maxDstAddressLen", - "type": "u16" - }, - { - "name": "maxCcmMessageLen", - "type": "u32" - }, - { - "name": "maxCfParametersLen", - "type": "u32" - }, - { - "name": "maxEventAccounts", - "type": "u32" - } - ] - }, - { - "name": "setSuspendedState", - "discriminator": [ - 145, - 13, - 20, - 161, - 30, - 62, - 226, - 32 - ], - "accounts": [ - { - "name": "dataAccount", - "writable": true - }, - { - "name": "govKey", - "signer": true - } - ], - "args": [ - { - "name": "suspend", - "type": "bool" - }, - { - "name": "suspendLegacySwaps", - "type": "bool" - }, - { - "name": "suspendEventSwaps", - "type": "bool" - } - ] - }, - { - "name": "transferTokens", - "discriminator": [ - 54, - 180, - 238, - 175, - 74, - 85, - 126, - 188 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "signer": true - }, - { - "name": "tokenVaultPda" - }, - { - "name": "tokenVaultAssociatedTokenAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "data_account.token_vault_pda", - "account": "dataAccount" - }, - { - "kind": "const", - "value": [ - 6, - 221, - 246, - 225, - 215, - 101, - 161, - 147, - 217, - 203, - 225, - 70, - 206, - 235, - 121, - 172, - 28, - 180, - 133, - 237, - 95, - 91, - 55, - 145, - 58, - 140, - 245, - 133, - 126, - 255, - 0, - 169 - ] - }, - { - "kind": "account", - "path": "mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 - ] - } - } - }, - { - "name": "toTokenAccount", - "writable": true - }, - { - "name": "mint" - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - } - ], - "args": [ - { - "name": "amount", - "type": "u64" - }, - { - "name": "decimals", - "type": "u8" - } - ] - }, - { - "name": "transferVaultUpgradeAuthority", - "discriminator": [ - 114, - 247, - 72, - 110, - 145, - 65, - 236, - 153 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "signer": true - }, - { - "name": "programDataAddress", - "writable": true - }, - { - "name": "programAddress" - }, - { - "name": "newAuthority" - }, - { - "name": "signerPda", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 105, - 103, - 110, - 101, - 114 - ] - } - ] - } - }, - { - "name": "bpfLoaderUpgradeable", - "address": "BPFLoaderUpgradeab1e11111111111111111111111" - } - ], - "args": [] - }, - { - "name": "upgradeProgram", - "docs": [ - "* ****************************************************************************\n * *************************** IMPORTANT NOTE *********************************\n * ****************************************************************************\n * The signer_pda is the upgrade authority for the vault program. Changing this\n * logic should be done with caution and understanding the consequences.\n *\n * DO NOT MODIFY THIS WITHOUT UNDERSTANDING THE CONSEQUENCES!\n * ****************************************************************************\n * ****************************************************************************" - ], - "discriminator": [ - 223, - 236, - 39, - 89, - 111, - 204, - 114, - 37 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "govKey", - "signer": true - }, - { - "name": "programDataAddress", - "writable": true - }, - { - "name": "programAddress", - "writable": true - }, - { - "name": "bufferAddress", - "writable": true - }, - { - "name": "spillAddress", - "writable": true - }, - { - "name": "sysvarRent", - "address": "SysvarRent111111111111111111111111111111111" - }, - { - "name": "sysvarClock", - "address": "SysvarC1ock11111111111111111111111111111111" - }, - { - "name": "signerPda", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 105, - 103, - 110, - 101, - 114 - ] - } - ] - } - }, - { - "name": "bpfLoaderUpgradeable", - "address": "BPFLoaderUpgradeab1e11111111111111111111111" - } - ], - "args": [] - }, - { - "name": "xSwapNativeLegacy", - "discriminator": [ - 97, - 21, - 117, - 255, - 21, - 6, - 232, - 176 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "aggKey", - "writable": true - }, - { - "name": "from", - "writable": true, - "signer": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "eventAuthority", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 95, - 95, - 101, - 118, - 101, - 110, - 116, - 95, - 97, - 117, - 116, - 104, - 111, - 114, - 105, - 116, - 121 - ] - } - ] - } - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "amount", - "type": "u64" - }, - { - "name": "dstChain", - "type": "u32" - }, - { - "name": "dstAddress", - "type": "bytes" - }, - { - "name": "dstToken", - "type": "u32" - }, - { - "name": "ccmParameters", - "type": { - "option": { - "defined": { - "name": "ccmParams" - } - } - } - }, - { - "name": "cfParameters", - "type": "bytes" - } - ] - }, - { - "name": "xSwapTokenLegacy", - "discriminator": [ - 248, - 32, - 195, - 34, - 38, - 180, - 117, - 127 - ], - "accounts": [ - { - "name": "dataAccount" - }, - { - "name": "tokenVaultAssociatedTokenAccount", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "data_account.token_vault_pda", - "account": "dataAccount" - }, - { - "kind": "const", - "value": [ - 6, - 221, - 246, - 225, - 215, - 101, - 161, - 147, - 217, - 203, - 225, - 70, - 206, - 235, - 121, - 172, - 28, - 180, - 133, - 237, - 95, - 91, - 55, - 145, - 58, - 140, - 245, - 133, - 126, - 255, - 0, - 169 - ] - }, - { - "kind": "account", - "path": "mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 - ] - } - } - }, - { - "name": "from", - "signer": true - }, - { - "name": "fromTokenAccount", - "writable": true - }, - { - "name": "tokenSupportedAccount" - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - }, - { - "name": "mint" - }, - { - "name": "eventAuthority", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 95, - 95, - 101, - 118, - 101, - 110, - 116, - 95, - 97, - 117, - 116, - 104, - 111, - 114, - 105, - 116, - 121 - ] - } - ] - } - }, - { - "name": "program" - } - ], - "args": [ - { - "name": "amount", - "type": "u64" - }, - { - "name": "dstChain", - "type": "u32" - }, - { - "name": "dstAddress", - "type": "bytes" - }, - { - "name": "dstToken", - "type": "u32" - }, - { - "name": "ccmParameters", - "type": { - "option": { - "defined": { - "name": "ccmParams" - } - } - } - }, - { - "name": "cfParameters", - "type": "bytes" - }, - { - "name": "decimals", - "type": "u8" - } - ] - } - ], - "accounts": [ - { - "name": "dataAccount", - "discriminator": [ - 85, - 240, - 182, - 158, - 76, - 7, - 18, - 233 - ] - }, - { - "name": "depositChannelHistoricalFetch", - "discriminator": [ - 188, - 68, - 197, - 38, - 48, - 192, - 81, - 100 - ] - }, - { - "name": "supportedToken", - "discriminator": [ - 56, - 162, - 96, - 99, - 193, - 245, - 204, - 108 - ] - } - ], - "events": [ - { - "name": "aggKeyRotated", - "discriminator": [ - 133, - 39, - 145, - 216, - 63, - 154, - 134, - 245 - ] - }, - { - "name": "govKeyRotated", - "discriminator": [ - 71, - 44, - 22, - 197, - 63, - 250, - 150, - 83 - ] - }, - { - "name": "govKeySetByAggKey", - "discriminator": [ - 135, - 202, - 24, - 202, - 91, - 182, - 141, - 24 - ] - }, - { - "name": "govKeySetByGovKey", - "discriminator": [ - 198, - 58, - 153, - 108, - 67, - 162, - 174, - 167 - ] - }, - { - "name": "programSwapsParametersSet", - "discriminator": [ - 173, - 82, - 238, - 223, - 74, - 121, - 243, - 142 - ] - }, - { - "name": "suspended", - "discriminator": [ - 220, - 179, - 163, - 2, - 249, - 252, - 157, - 102 - ] - }, - { - "name": "swapEvent", - "discriminator": [ - 64, - 198, - 205, - 232, - 38, - 8, - 113, - 226 - ] - }, - { - "name": "tokenSupportDisabled", - "discriminator": [ - 35, - 252, - 131, - 176, - 50, - 103, - 135, - 13 - ] - }, - { - "name": "tokenSupportEnabled", - "discriminator": [ - 11, - 203, - 178, - 141, - 170, - 213, - 67, - 234 - ] - } - ], - "errors": [ - { - "code": 6000, - "name": "invalidTokenVaultAccount", - "msg": "Token Vault pda account does not match the expected program id" - }, - { - "code": 6001, - "name": "suspended", - "msg": "Vault program is suspended" - }, - { - "code": 6002, - "name": "invalidRemainingAccount", - "msg": "An invalid account is provided as a remaining account" - }, - { - "code": 6003, - "name": "invalidRemainingAccountSigner", - "msg": "A remaining account can't be a signer" - }, - { - "code": 6004, - "name": "unchangedSuspendedState", - "msg": "Suspended state unchanged" - }, - { - "code": 6005, - "name": "invalidSwapParameters", - "msg": "Invalid swap parameters" - }, - { - "code": 6006, - "name": "invalidTokenVaultBump", - "msg": "Invalid token vault bump" - }, - { - "code": 6007, - "name": "invalidUpgradeSignerPda", - "msg": "Invalid upgrade signer pda" - }, - { - "code": 6008, - "name": "invalidUpgradeSignerPdaBump", - "msg": "Invalid upgrade signer bump" - }, - { - "code": 6009, - "name": "suspendedIxSwaps", - "msg": "Instruction swaps are suspended" - }, - { - "code": 6010, - "name": "suspendedEventSwaps", - "msg": "Event swaps are suspended" - }, - { - "code": 6011, - "name": "nativeAmountBelowMinimumSwapAmount", - "msg": "Native amount below minimum swap amount" - }, - { - "code": 6012, - "name": "tokenAmountBelowMinimumSwapAmount", - "msg": "Token amount below minimum swap amount" - }, - { - "code": 6013, - "name": "destinationAddressExceedsMaxLength", - "msg": "Destination address exceeds max length" - }, - { - "code": 6014, - "name": "cfParametersExceedsMaxLength", - "msg": "Cf Parameters exceeds max length" - }, - { - "code": 6015, - "name": "ccmMessageExceedsMaxLength", - "msg": "Ccm message exceeds max length" - } - ], - "types": [ - { - "name": "aggKeyRotated", - "type": { - "kind": "struct", - "fields": [ - { - "name": "oldAggKey", - "type": "pubkey" - }, - { - "name": "newAggKey", - "type": "pubkey" - } - ] - } - }, - { - "name": "ccmParams", - "type": { - "kind": "struct", - "fields": [ - { - "name": "message", - "type": "bytes" - }, - { - "name": "gasAmount", - "type": "u64" - } - ] - } - }, - { - "name": "dataAccount", - "docs": [ - "* ****************************************************************************\n * *************************** IMPORTANT NOTE *********************************\n * ****************************************************************************\n * If the vault is upgraded and the DataAccount struct is modified we need to\n * check the compatibility and ensure there is a proper migration process, given\n * that the Vault bytecode is the only thing being upgraded, not the data account.\n *\n * The easiest approach on upgrade is keeping the DataAccount unchanged and use\n * a new account struct for any new data that is required.\n *\n * DO NOT MODIFY THIS WITHOUT UNDERSTANDING THE CONSEQUENCES!\n * ****************************************************************************\n * ****************************************************************************" - ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "aggKey", - "type": "pubkey" - }, - { - "name": "govKey", - "type": "pubkey" - }, - { - "name": "tokenVaultPda", - "type": "pubkey" - }, - { - "name": "tokenVaultBump", - "type": "u8" - }, - { - "name": "upgradeSignerPda", - "type": "pubkey" - }, - { - "name": "upgradeSignerPdaBump", - "type": "u8" - }, - { - "name": "suspended", - "type": "bool" - }, - { - "name": "suspendedLegacySwaps", - "type": "bool" - }, - { - "name": "suspendedEventSwaps", - "type": "bool" - }, - { - "name": "minNativeSwapAmount", - "type": "u64" - }, - { - "name": "maxDstAddressLen", - "type": "u16" - }, - { - "name": "maxCcmMessageLen", - "type": "u32" - }, - { - "name": "maxCfParametersLen", - "type": "u32" - }, - { - "name": "maxEventAccounts", - "type": "u32" - } - ] - } - }, - { - "name": "depositChannelHistoricalFetch", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amount", - "type": "u128" - } - ] - } - }, - { - "name": "govKeyRotated", - "type": { - "kind": "struct", - "fields": [ - { - "name": "oldGovKey", - "type": "pubkey" - }, - { - "name": "newGovKey", - "type": "pubkey" - } - ] - } - }, - { - "name": "govKeySetByAggKey", - "type": { - "kind": "struct", - "fields": [ - { - "name": "oldGovKey", - "type": "pubkey" - }, - { - "name": "newGovKey", - "type": "pubkey" - } - ] - } - }, - { - "name": "govKeySetByGovKey", - "type": { - "kind": "struct", - "fields": [ - { - "name": "oldGovKey", - "type": "pubkey" - }, - { - "name": "newGovKey", - "type": "pubkey" - } - ] - } - }, - { - "name": "programSwapsParametersSet", - "type": { - "kind": "struct", - "fields": [ - { - "name": "oldMinSwapAmount", - "type": "u64" - }, - { - "name": "oldMaxDstAddressLen", - "type": "u16" - }, - { - "name": "oldMaxCcmMessageLen", - "type": "u32" - }, - { - "name": "oldMaxCfParametersLen", - "type": "u32" - }, - { - "name": "oldMaxEventAccounts", - "type": "u32" - }, - { - "name": "newMinSwapAmount", - "type": "u64" - }, - { - "name": "newMaxDstAddressLen", - "type": "u16" - }, - { - "name": "newMaxCcmMessageLen", - "type": "u32" - }, - { - "name": "newMaxCfParametersLen", - "type": "u32" - }, - { - "name": "newMaxEventAccounts", - "type": "u32" - } - ] - } - }, - { - "name": "supportedToken", - "type": { - "kind": "struct", - "fields": [ - { - "name": "tokenMintPubkey", - "type": "pubkey" - }, - { - "name": "minSwapAmount", - "type": "u64" - } - ] - } - }, - { - "name": "suspended", - "type": { - "kind": "struct", - "fields": [ - { - "name": "suspended", - "type": "bool" - }, - { - "name": "suspendedLegacySwaps", - "type": "bool" - }, - { - "name": "suspendedEventSwaps", - "type": "bool" - } - ] - } - }, - { - "name": "swapEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "dstChain", - "type": "u32" - }, - { - "name": "dstAddress", - "type": "bytes" - }, - { - "name": "dstToken", - "type": "u32" - }, - { - "name": "srcToken", - "type": { - "option": "pubkey" - } - }, - { - "name": "amount", - "type": "u64" - }, - { - "name": "sender", - "type": "pubkey" - }, - { - "name": "ccmParameters", - "type": { - "option": { - "defined": { - "name": "ccmParams" - } - } - } - }, - { - "name": "cfParameters", - "type": "bytes" - } - ] - } - }, - { - "name": "tokenSupportDisabled", - "type": { - "kind": "struct", - "fields": [ - { - "name": "oldTokenSupported", - "type": { - "defined": { - "name": "supportedToken" - } - } - } - ] - } - }, - { - "name": "tokenSupportEnabled", - "type": { - "kind": "struct", - "fields": [ - { - "name": "oldTokenSupported", - "type": { - "defined": { - "name": "supportedToken" - } - } - }, - { - "name": "newTokenSupported", - "type": { - "defined": { - "name": "supportedToken" - } - } - } - ] - } - } - ] -}; diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 40e1073390..cce550e7d3 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -20,11 +20,6 @@ async-channel = { workspace = true } async-trait = { workspace = true } bincode = { workspace = true } bitcoin = { workspace = true, features = ["serde"] } -borsh = { workspace = true, default_features = false, features = [ - "derive", - "unstable__schema", - "hashbrown", -] } chrono = { workspace = true, features = ["clock"] } clap = { workspace = true, features = ["derive", "env"] } config = { workspace = true } diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index ddbeda3ff5..54c29c7955 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -7,14 +7,19 @@ use crate::sol::{ retry_rpc::{SolRetryRpcApi, SolRetryRpcClient}, rpc_client_api::{RpcAccountInfoConfig, UiAccount, UiAccountData, UiAccountEncoding}, }; -use anyhow::ensure; -use anyhow::{anyhow /* ensure */}; +use anyhow::{anyhow, ensure}; use base64::Engine; -use borsh::{BorshDeserialize, BorshSerialize}; use cf_chains::{ address::EncodedAddress, assets::sol::Asset as SolAsset, - sol::{api::VaultSwapAccountAndSender, SolAddress}, + sol::{ + api::VaultSwapAccountAndSender, + sol_tx_core::program_instructions::{ + swap_endpoints::types::{SwapEndpointDataAccount, SwapEvent}, + ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH, + }, + SolAddress, + }, CcmChannelMetadata, CcmDepositMetadata, ForeignChainAddress, }; use cf_primitives::Asset; @@ -24,40 +29,11 @@ use state_chain_runtime::chainflip::solana_elections::SolanaVaultSwapDetails; use tracing::warn; const MAXIMUM_CONCURRENT_RPCS: usize = 16; -const SWAP_ENDPOINT_DATA_ACCOUNT_DISCRIMINATOR: [u8; 8] = [79, 152, 191, 225, 128, 108, 11, 139]; -const SWAP_EVENT_ACCOUNT_DISCRIMINATOR: [u8; 8] = [150, 251, 114, 94, 200, 113, 248, 70]; // Querying less than 100 (rpc call max) as those event accounts can be quite big. // Max length ~ 1300 bytes per account. We set it to 10 as an arbitrary number to // avoid large queries. const MAX_MULTIPLE_EVENT_ACCOUNTS_QUERY: usize = 10; -#[derive(BorshDeserialize, BorshSerialize, Debug)] -struct SwapEndpointDataAccount { - discriminator: [u8; 8], - historical_number_event_accounts: u128, - open_event_accounts: Vec<[u8; sol_prim::consts::SOLANA_ADDRESS_LEN]>, -} - -#[derive(BorshDeserialize, BorshSerialize, Debug, Default)] -pub struct SwapEvent { - discriminator: [u8; 8], - creation_slot: u64, - sender: [u8; sol_prim::consts::SOLANA_ADDRESS_LEN], - dst_chain: u32, - dst_address: Vec, - dst_token: u32, - amount: u64, - src_token: Option<[u8; sol_prim::consts::SOLANA_ADDRESS_LEN]>, - ccm_parameters: Option, - cf_parameters: Vec, -} - -#[derive(BorshDeserialize, BorshSerialize, Debug, Default)] -pub struct CcmParams { - message: Vec, - gas_amount: u64, -} - // 1. Query the on-chain list of opened accounts from SwapEndpointDataAccount. // 2. Check the returned accounts against the SC opened_accounts. The SC is the source of truth for // the opened channels we can rely on that to not query the same accounts multiple times. @@ -99,7 +75,7 @@ pub async fn get_program_swaps( |(account, program_swap_account_data)| match program_swap_account_data { Some(data) if (data.src_token.is_none() || - data.src_token.is_some_and(|addr| addr == usdc_token_mint_pubkey.0)) => { + data.src_token.is_some_and(|addr| addr == usdc_token_mint_pubkey.into())) => { let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { None => { @@ -253,23 +229,21 @@ async fn get_swap_endpoint_data( .expect("Failed to decode base64 string"); // 8 Discriminator + 16 Historical Number Event Accounts + 4 bytes vector length + data - if bytes.len() < 28 { + if bytes.len() < ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH + 20 { return Err(anyhow!("Expected account to have at least 28 bytes")); } - let deserialized_data: SwapEndpointDataAccount = - SwapEndpointDataAccount::try_from_slice(&bytes) + let swap_endpoint_data_account = + SwapEndpointDataAccount::check_and_deserialize(&bytes[..]) .map_err(|e| anyhow!("Failed to deserialize data: {:?}", e))?; - ensure!( - deserialized_data.discriminator == SWAP_ENDPOINT_DATA_ACCOUNT_DISCRIMINATOR, - "Discriminator does not match. Found: {:?}", - deserialized_data.discriminator - ); - Ok(( - deserialized_data.historical_number_event_accounts, - deserialized_data.open_event_accounts.into_iter().map(SolAddress).collect(), + swap_endpoint_data_account.historical_number_event_accounts, + swap_endpoint_data_account + .open_event_accounts + .into_iter() + .map(|acc| acc.into()) + .collect(), slot, )) }, @@ -316,20 +290,10 @@ async fn get_program_swap_event_accounts_data( .decode(base64_string) .expect("Failed to decode base64 string"); - if bytes.len() < 8 { - return Err(anyhow!("Expected account to have at least 28 bytes")); - } - - let deserialized_data: SwapEvent = SwapEvent::try_from_slice(&bytes) + let swap_event: SwapEvent = SwapEvent::check_and_deserialize(&bytes[..]) .map_err(|e| anyhow!("Failed to deserialize data: {:?}", e))?; - ensure!( - deserialized_data.discriminator == SWAP_EVENT_ACCOUNT_DISCRIMINATOR, - "Discriminator does not match. Found: {:?}", - deserialized_data.discriminator - ); - - Ok((account, Some(deserialized_data))) + Ok((account, Some(swap_event))) }, Some(_) => Err(anyhow!("Expected UiAccountData::Binary(String, UiAccountEncoding::Base64)")), diff --git a/engine/src/witness/sol/sol_deposits.rs b/engine/src/witness/sol/sol_deposits.rs index 02de0b880c..3e16fdb0cb 100644 --- a/engine/src/witness/sol/sol_deposits.rs +++ b/engine/src/witness/sol/sol_deposits.rs @@ -1,7 +1,12 @@ use anyhow::ensure; use base64::Engine; use cf_chains::sol::{ - sol_tx_core::address_derivation::{derive_associated_token_account, derive_fetch_account}, + sol_tx_core::{ + address_derivation::{derive_associated_token_account, derive_fetch_account}, + program_instructions::{ + types::DepositChannelHistoricalFetch, ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH, + }, + }, SolAddress, SolAmount, }; use cf_primitives::chains::assets::sol::Asset; @@ -25,9 +30,8 @@ use crate::sol::{ pub use sol_prim::consts::{SYSTEM_PROGRAM_ID, TOKEN_PROGRAM_ID}; // 16 (u128) + 8 (discriminator) -const FETCH_ACCOUNT_BYTE_LENGTH: usize = 24; +const FETCH_ACCOUNT_BYTE_LENGTH: usize = 16 + ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH; const MAX_MULTIPLE_ACCOUNTS_QUERY: usize = 100; -const FETCH_ACCOUNT_DISCRIMINATOR: [u8; 8] = [188, 68, 197, 38, 48, 192, 81, 100]; const MAXIMUM_CONCURRENT_RPCS: usize = 16; /// We track Solana (Sol and SPL-token) deposits by periodically querying the @@ -270,22 +274,17 @@ fn parse_fetch_account_amount( return Err(anyhow::anyhow!("Data account encoding is not base64")); } - let mut bytes = base64::engine::general_purpose::STANDARD + let bytes = base64::engine::general_purpose::STANDARD .decode(base64_string) .expect("Failed to decode base64 string"); ensure!(bytes.len() == FETCH_ACCOUNT_BYTE_LENGTH); - let discriminator: Vec = bytes.drain(..8).collect(); - - ensure!( - discriminator == FETCH_ACCOUNT_DISCRIMINATOR, - "Discriminator does not match expected value" - ); - - let array: [u8; 16] = bytes.try_into().expect("Byte slice length doesn't match u128"); + let deserialized_data: DepositChannelHistoricalFetch = + DepositChannelHistoricalFetch::check_and_deserialize(&bytes[..]) + .map_err(|e| anyhow::anyhow!("Failed to deserialize data: {:?}", e))?; - Ok(u128::from_le_bytes(array)) + Ok(deserialized_data.amount) }, _ => Err(anyhow::anyhow!("Data account encoding is not base64")), } diff --git a/state-chain/chains/src/sol/sol_tx_core.rs b/state-chain/chains/src/sol/sol_tx_core.rs index ba3b08daeb..830a7c4399 100644 --- a/state-chain/chains/src/sol/sol_tx_core.rs +++ b/state-chain/chains/src/sol/sol_tx_core.rs @@ -658,6 +658,7 @@ pub struct CompiledInstruction { PartialOrd, Copy, BorshSerialize, + BorshDeserialize, )] pub struct Pubkey(pub [u8; 32]); diff --git a/state-chain/chains/src/sol/sol_tx_core/program_instructions.rs b/state-chain/chains/src/sol/sol_tx_core/program_instructions.rs index dccf261237..f725a2d740 100644 --- a/state-chain/chains/src/sol/sol_tx_core/program_instructions.rs +++ b/state-chain/chains/src/sol/sol_tx_core/program_instructions.rs @@ -1,6 +1,6 @@ use super::{AccountMeta, Instruction, Pubkey}; -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use cf_utilities::SliceToArray; use core::str::FromStr; use scale_info::prelude::string::String; @@ -284,6 +284,30 @@ macro_rules! solana_program { } ),+ $(,)? } + $(, + types: [ + $( + $type_name:ident { + $( + $type_arg:ident: $type_arg_type:ty + ),+ + $(,)? + } + ),+ + $(,)? + ] + )? + $(, + accounts: [ + $( + { + $account_type:ident, + discriminator: $discriminator:expr $(,)? + } + ),+ + $(,)? + ] + )? ) => { pub struct $program { program_id: Pubkey, @@ -334,6 +358,46 @@ macro_rules! solana_program { } )+ + $( + pub mod types { + use super::*; + + $( + #[derive(BorshDeserialize, BorshSerialize, Debug, Default, Clone, PartialEq, Eq)] + pub struct $type_name { + $( + pub $type_arg: $type_arg_type, + )+ + } + )+ + } + )? + + $( + pub mod accounts { + use super::*; + $( + impl super::types::$account_type { + pub const fn discriminator() -> [u8; 8] { + $discriminator + } + + pub fn check_and_deserialize(bytes: &[u8]) -> borsh::io::Result { + use borsh::io::{ErrorKind, Error}; + if bytes.len() < ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH { + return Err(Error::new(ErrorKind::Other, "No account discriminator")); + } + let (discriminator, rest) = bytes.split_at(ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH); + if discriminator != Self::discriminator() { + return Err(Error::new(ErrorKind::Other, "Unexpected account discriminator")); + } + Self::try_from_slice(rest) + } + } + )+ + } + )? + #[cfg(test)] mod test { use super::*; @@ -365,6 +429,54 @@ macro_rules! solana_program { }); } + + $( + #[test] + fn types_exist_in_idl() { + use std::collections::BTreeMap; + test(|idl| { + $( + let ty = idl.types.iter().find(|ty| ty.name == stringify!($type_name)).expect("Type not found in IDL").ty.clone(); + assert!(ty.kind == "struct", "Non-struct IDL types not supported."); + let fields = ty.fields.into_iter().map(|field| (field.name, field.ty)).collect::>(); + $( + assert_eq!( + fields.get(stringify!($type_arg)).map(|f| f.to_string()), + Some(stringify!($type_arg_type).to_owned()), + "Field {} of type {} not found in IDL", + stringify!($type_arg), + stringify!($type_arg_type), + ); + )+ + )+ + }); + } + )? + $( + #[test] + fn accounts_exist_in_idl() { + test(|idl| { + let defined_in_idl = idl.accounts.iter().map(|acc| acc.name.clone()).collect::>(); + let defined_in_code = [ + $( + stringify!($account_type).to_owned(), + )+ + ].into_iter().collect::>(); + assert!( + defined_in_code.is_subset(&defined_in_idl), + "Some accounts are not defined in the IDL: {:?}", + defined_in_code.difference(&defined_in_idl).cloned().collect::>() + ); + $( + assert_eq!( + types::$account_type::discriminator(), + idl.accounts.iter().find(|acc| acc.name == stringify!($account_type)).unwrap().discriminator + ); + )+ + }); + } + )? + $( #[cfg(test)] mod $call_name { @@ -626,9 +738,25 @@ solana_program!( bpf_loader_upgradeable: { signer: false, writable: false }, ] }, - } + }, + types: [ + DepositChannelHistoricalFetch { + amount: u128, + } + ], + accounts: [ + { + DepositChannelHistoricalFetch, + discriminator: [188, 68, 197, 38, 48, 192, 81, 100], + }, + ] ); +pub const FETCH_ACCOUNT_DISCRIMINATOR: [u8; ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH] = + types::DepositChannelHistoricalFetch::discriminator(); + +pub const ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH: usize = 8; + pub mod swap_endpoints { use super::*; @@ -646,9 +774,45 @@ pub mod swap_endpoints { agg_key: { signer: true, writable: true }, swap_endpoint_data_account: { signer: false, writable: true }, ] + } + }, + types: [ + CcmParams { + message: Vec, + gas_amount: u64, }, - } + SwapEvent { + creation_slot: u64, + sender: Pubkey, + dst_chain: u32, + dst_address: Vec, + dst_token: u32, + amount: u64, + src_token: Option, + ccm_parameters: Option, + cf_parameters: Vec, + }, + SwapEndpointDataAccount { + historical_number_event_accounts: u128, + open_event_accounts: Vec, + }, + ], + accounts: [ + { + SwapEvent, + discriminator: [150, 251, 114, 94, 200, 113, 248, 70], + }, + { + SwapEndpointDataAccount, + discriminator: [79, 152, 191, 225, 128, 108, 11, 139], + }, + ] ); + + pub const SWAP_EVENT_ACCOUNT_DISCRIMINATOR: [u8; ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH] = + types::SwapEvent::discriminator(); + pub const SWAP_ENDPOINT_DATA_ACCOUNT_DISCRIMINATOR: [u8; ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH] = + types::SwapEndpointDataAccount::discriminator(); } #[cfg(test)] @@ -668,35 +832,39 @@ mod idl { pub struct IdlArg { pub name: String, #[serde(rename = "type")] - pub ty: IdlType, + pub ty: IdlFieldType, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] - pub enum IdlType { + pub enum IdlFieldType { Bytes, U8, U16, U64, U32, + U128, Bool, Pubkey, Defined { name: String }, - Option(Box), + Option(Box), + Vec(Box), } - impl std::fmt::Display for IdlType { + impl std::fmt::Display for IdlFieldType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - IdlType::Bytes => write!(f, "Vec"), - IdlType::U8 => write!(f, "u8"), - IdlType::U16 => write!(f, "u16"), - IdlType::U64 => write!(f, "u64"), - IdlType::U32 => write!(f, "u32"), - IdlType::Bool => write!(f, "bool"), - IdlType::Pubkey => write!(f, "Pubkey"), - IdlType::Defined { name } => write!(f, "{}", name), - IdlType::Option(ty) => write!(f, "Option<{}>", ty), + IdlFieldType::Bytes => write!(f, "Vec"), + IdlFieldType::U8 => write!(f, "u8"), + IdlFieldType::U16 => write!(f, "u16"), + IdlFieldType::U64 => write!(f, "u64"), + IdlFieldType::U32 => write!(f, "u32"), + IdlFieldType::U128 => write!(f, "u128"), + IdlFieldType::Bool => write!(f, "bool"), + IdlFieldType::Pubkey => write!(f, "Pubkey"), + IdlFieldType::Defined { name } => write!(f, "{}", name), + IdlFieldType::Option(ty) => write!(f, "Option<{}>", ty), + IdlFieldType::Vec(ty) => write!(f, "Vec<{}>", ty), } } } @@ -727,12 +895,36 @@ mod idl { pub description: String, } + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct IdlAccount { + pub name: String, + pub discriminator: [u8; 8], + } + + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct IdlType { + pub kind: String, + pub fields: Vec, + } + + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] + #[serde(rename_all = "camelCase")] + pub struct IdlTypes { + pub name: String, + #[serde(rename = "type")] + pub ty: IdlType, + } + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct Idl { pub address: String, pub metadata: IdlMetadata, pub instructions: Vec, pub errors: Vec, + pub accounts: Vec, + pub types: Vec, } impl Idl { @@ -742,5 +934,11 @@ mod idl { .find(|instr| instr.name == name) .expect("instruction not found") } + pub fn account(&self, name: &str) -> &IdlAccount { + self.accounts + .iter() + .find(|account| account.name == name) + .expect("account not found") + } } } From 178cd107210509e3febef4e5dfc6d032f0991e5e Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Tue, 5 Nov 2024 15:15:45 +0100 Subject: [PATCH 25/62] chore: address comments --- .../solana_swap_accounts_tracking.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index 9a71fe2390..f9bd6cab93 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -185,16 +185,13 @@ impl< .expect("current block number is always greater than when apicall was last created") .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) { - let accounts_to_close: Vec<_> = if known_accounts.witnessed_open_accounts.len() > + let accounts_to_close: Vec<_> = known_accounts + .witnessed_open_accounts + .drain(..sp_std::cmp::min( + known_accounts.witnessed_open_accounts.len(), MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES - { - known_accounts - .witnessed_open_accounts - .drain(..MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES) - .collect() - } else { - sp_std::mem::take(&mut known_accounts.witnessed_open_accounts) - }; + )) + .collect(); match Hook::close_accounts(accounts_to_close.clone()) { Ok(()) => { known_accounts.closure_initiated_accounts.extend(accounts_to_close); @@ -202,7 +199,7 @@ impl< }, Err(e) => { log::error!( - "failed to build Solana CloseSolanaVaultSwapAccounts apicall: {:?}", + "failed to close accounts: {:?}", e ); known_accounts.witnessed_open_accounts.extend(accounts_to_close); From eef290e07c2608790d3218eaa07ddc0a415e3f2a Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 5 Nov 2024 17:13:34 +0100 Subject: [PATCH 26/62] chore: improve comment --- state-chain/runtime/src/chainflip/solana_elections.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 0e5d938459..53447ab0bf 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -531,7 +531,7 @@ pub struct SolanaVaultSwapDetails { pub deposit_amount: AssetAmount, pub destination_address: EncodedAddress, pub deposit_metadata: Option, - // TODO: These two will potentially be a TransactionId type + // TODO: swap_account and creation_slot might be pulled into TransactionInId type (PRO-1760) pub swap_account: SolAddress, pub creation_slot: u64, pub broker_fees: cf_primitives::Beneficiaries, From d6f4f17ef85c53afa486c416850b923e1a2e0fff Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 5 Nov 2024 17:21:53 +0100 Subject: [PATCH 27/62] chore: nit --- .../src/electoral_systems/solana_swap_accounts_tracking.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index f9bd6cab93..b2ed82779d 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -168,8 +168,8 @@ impl< election_access.delete(); known_accounts.witnessed_open_accounts.extend(consensus.new_accounts.iter().map( |(account, swap_details)| { - Hook::initiate_vault_swap((*swap_details).clone()); - (*account).clone() + Hook::initiate_vault_swap(swap_details.clone()); + account.clone() }, )); consensus.confirm_closed_accounts.into_iter().for_each(|acc| { @@ -199,7 +199,7 @@ impl< }, Err(e) => { log::error!( - "failed to close accounts: {:?}", + "Failed to initiate account closure: {:?}", e ); known_accounts.witnessed_open_accounts.extend(accounts_to_close); From a40398864751e732890a0ffd59386002a5597186 Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 5 Nov 2024 17:26:01 +0100 Subject: [PATCH 28/62] chore: nit CcmCfParameters --- engine/src/witness/sol/program_swaps_witnessing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 54c29c7955..c9957c41dc 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -84,7 +84,7 @@ pub async fn get_program_swaps( (None, vault_swap_parameters) }, Some(ccm_parameters) => { - let CfParameters { ccm_additional_data, vault_swap_parameters } = + let CcmCfParameters { ccm_additional_data, vault_swap_parameters } = CcmCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CcmCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; let deposit_metadata = Some(CcmDepositMetadata { From dc66695cc2913d571d92aad204697a3e3c0d0e79 Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 5 Nov 2024 17:35:35 +0100 Subject: [PATCH 29/62] chore: remove unnecessary comment --- state-chain/primitives/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/state-chain/primitives/src/lib.rs b/state-chain/primitives/src/lib.rs index 37150de704..39f114a1e6 100644 --- a/state-chain/primitives/src/lib.rs +++ b/state-chain/primitives/src/lib.rs @@ -442,5 +442,4 @@ pub struct DcaParameters { pub chunk_interval: u32, } -// TODO: Define this / implement it on the SC - PRO-1743. pub type ShortId = u8; From eceba13037c7a8c773b0e0da84a062476d566787 Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 5 Nov 2024 18:05:05 +0100 Subject: [PATCH 30/62] chore: return hashshet in get_swap_endpoint_data --- engine/src/witness/sol/program_swaps_witnessing.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index c9957c41dc..4102292320 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -112,7 +112,6 @@ pub async fn get_program_swaps( destination_address: EncodedAddress::from_chain_bytes(data.dst_chain.try_into().map_err(|e| warn!("error while parsing destination chain for solana vault swap:{}. Omitting swap", e)).ok()?, data.dst_address.to_vec()).map_err(|e| warn!("failed to decode the destination chain address for solana vault swap:{}. Omitting swap", e)).ok()?, to: data.dst_token.try_into().map_err(|e| warn!("error while decoding destination token for solana vault swap: {}. Omitting swap", e)).ok()?, deposit_metadata, - // TODO: These two will potentially be a TransactionId type swap_account: account, creation_slot: data.creation_slot, broker_fees: vault_swap_parameters.broker_fees, @@ -184,9 +183,8 @@ async fn get_changed_program_swap_accounts( } } - let open_event_accounts_hashset: HashSet<_> = open_event_accounts.iter().collect(); for account in sc_closure_initiated_accounts { - if !open_event_accounts_hashset.contains(&account.vault_swap_account) { + if !open_event_accounts.contains(&account.vault_swap_account) { closed_accounts.push(account); } } @@ -199,7 +197,7 @@ async fn get_changed_program_swap_accounts( async fn get_swap_endpoint_data( sol_rpc: &SolRetryRpcClient, swap_endpoint_data_account_address: SolAddress, -) -> Result<(u128, Vec, u64), anyhow::Error> { +) -> Result<(u128, HashSet, u64), anyhow::Error> { let accounts_info_response = sol_rpc .get_multiple_accounts( &[swap_endpoint_data_account_address], @@ -243,7 +241,7 @@ async fn get_swap_endpoint_data( .open_event_accounts .into_iter() .map(|acc| acc.into()) - .collect(), + .collect::>(), slot, )) }, @@ -367,6 +365,8 @@ mod tests { SolAddress::from_str("E81G7Q1BjierakQCfL9B5Tm485eiaRPb22bcKD2vtRfU") .unwrap() ] + .into_iter() + .collect() ); Ok(()) From 4927a012643e4d048279ab9aed9def23dac31c72 Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 5 Nov 2024 18:16:13 +0100 Subject: [PATCH 31/62] chore: reduce duplication of data removing unnecessary hashet --- engine/src/witness/sol/program_swaps_witnessing.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 4102292320..a3de3db5a6 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -167,17 +167,13 @@ async fn get_changed_program_swap_accounts( .expect("Failed to get the event accounts"); let sc_opened_accounts_hashset: HashSet<_> = sc_opened_accounts.iter().collect(); - let sc_closure_initiated_accounts_hashset = sc_closure_initiated_accounts - .iter() - .map(|VaultSwapAccountAndSender { vault_swap_account, .. }| vault_swap_account) - .collect::>(); let mut new_program_swap_accounts = Vec::new(); let mut closed_accounts = Vec::new(); for account in &open_event_accounts { if !sc_opened_accounts_hashset.contains(account) && - !sc_closure_initiated_accounts_hashset.contains(account) + !sc_closure_initiated_accounts.iter().any(|x| &x.vault_swap_account == account) { new_program_swap_accounts.push(*account); } From ce0ec19c8c7a3273f4db8a93a25ec47abab87e4c Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Tue, 5 Nov 2024 18:26:47 +0100 Subject: [PATCH 32/62] fix: filter out errored rpc calls --- engine/src/witness/sol/program_swaps_witnessing.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index a3de3db5a6..42347eb331 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -145,6 +145,7 @@ pub async fn get_program_swaps( )) }) .try_flatten() + .filter(|item| futures::future::ready(item.is_ok())) .try_collect() .await; From 9783c869084ca558e5799e39f21bf79de1d536ee Mon Sep 17 00:00:00 2001 From: albert Date: Wed, 6 Nov 2024 08:59:10 +0100 Subject: [PATCH 33/62] chore: workaround for bouncer lint --- bouncer/shared/evm_vault_swap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bouncer/shared/evm_vault_swap.ts b/bouncer/shared/evm_vault_swap.ts index 67e8b0506e..366e03899f 100644 --- a/bouncer/shared/evm_vault_swap.ts +++ b/bouncer/shared/evm_vault_swap.ts @@ -41,7 +41,7 @@ import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0- import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault'; import { getSolanaSwapEndpointIdl, getSolanaVaultIdl } from './contract_interfaces'; -// Workaround because of anchor issue +// @ts-expect-error workaround because of anchor issue const { BN } = anchor.default; const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc']; From 9efd869fa490462f23170d143b0559c5400e141d Mon Sep 17 00:00:00 2001 From: albert Date: Wed, 6 Nov 2024 10:07:31 +0100 Subject: [PATCH 34/62] chore: address comments --- engine/src/witness/sol.rs | 9 +++-- .../witness/sol/program_swaps_witnessing.rs | 38 ++++++++----------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index 83dcb9d34c..56d9a61c49 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -41,7 +41,11 @@ use cf_utilities::{ task_scope::{self, Scope}, }; use pallet_cf_elections::vote_storage::change::MonotonicChangeVote; -use std::{collections::BTreeSet, str::FromStr, sync::Arc}; +use std::{ + collections::{BTreeSet, HashSet}, + str::FromStr, + sync::Arc, +}; use utilities::{task_scope, task_scope::Scope}; #[derive(Clone)] @@ -184,7 +188,6 @@ impl VoterApi for SolanaLivenessVoter { } } -#[allow(dead_code)] #[derive(Clone)] struct SolanaVaultSwapsVoter { client: SolRetryRpcClient, @@ -207,7 +210,7 @@ impl VoterApi for SolanaVaultSwapsVoter { .witnessed_open_accounts .into_iter() .map(|VaultSwapAccountAndSender { vault_swap_account, .. }| vault_swap_account) - .collect(), + .collect::>(), properties.closure_initiated_accounts, settings.usdc_token_mint_pubkey, ) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 42347eb331..44ee4d3749 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -45,7 +45,7 @@ const MAX_MULTIPLE_EVENT_ACCOUNTS_QUERY: usize = 10; pub async fn get_program_swaps( sol_rpc: &SolRetryRpcClient, swap_endpoint_data_account_address: SolAddress, - sc_open_accounts: Vec, + sc_open_accounts: HashSet, sc_closure_initiated_accounts: BTreeSet, usdc_token_mint_pubkey: SolAddress, ) -> Result< @@ -158,7 +158,7 @@ pub async fn get_program_swaps( async fn get_changed_program_swap_accounts( sol_rpc: &SolRetryRpcClient, - sc_opened_accounts: Vec, + sc_opened_accounts: HashSet, sc_closure_initiated_accounts: BTreeSet, swap_endpoint_data_account_address: SolAddress, ) -> Result<(Vec, Vec, u64), anyhow::Error> { @@ -167,24 +167,18 @@ async fn get_changed_program_swap_accounts( .await .expect("Failed to get the event accounts"); - let sc_opened_accounts_hashset: HashSet<_> = sc_opened_accounts.iter().collect(); - - let mut new_program_swap_accounts = Vec::new(); - let mut closed_accounts = Vec::new(); - - for account in &open_event_accounts { - if !sc_opened_accounts_hashset.contains(account) && - !sc_closure_initiated_accounts.iter().any(|x| &x.vault_swap_account == account) - { - new_program_swap_accounts.push(*account); - } - } - - for account in sc_closure_initiated_accounts { - if !open_event_accounts.contains(&account.vault_swap_account) { - closed_accounts.push(account); - } - } + let new_program_swap_accounts: Vec<_> = open_event_accounts + .iter() + .filter(|account| { + !sc_opened_accounts.contains(account) && + !sc_closure_initiated_accounts.iter().any(|x| &x.vault_swap_account == *account) + }) + .cloned() + .collect(); + let closed_accounts: Vec<_> = sc_closure_initiated_accounts + .into_iter() + .filter(|account| !open_event_accounts.contains(&account.vault_swap_account)) + .collect(); Ok((new_program_swap_accounts, closed_accounts, slot)) } @@ -396,7 +390,7 @@ mod tests { let (new_program_swap_accounts, closed_accounts, _) = get_changed_program_swap_accounts( &client, - vec![], + Default::default(), BTreeSet::from([VaultSwapAccountAndSender { vault_swap_account: SolAddress::from_str( "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", @@ -426,7 +420,7 @@ mod tests { let (new_program_swap_accounts, closed_accounts, _) = get_changed_program_swap_accounts( &client, - vec![], + Default::default(), BTreeSet::from([VaultSwapAccountAndSender { vault_swap_account: SolAddress::from_str( "HhxGAt8THMtsW97Zuo5ZrhKgqsdD5EBgCx9vZ4n62xpf", From 18f793f345384574e56737e44230763e766de82a Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 7 Nov 2024 15:33:12 +0100 Subject: [PATCH 35/62] chore: address comments --- .../solana_swap_accounts_tracking.rs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs index b2ed82779d..8270320e59 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs @@ -24,7 +24,7 @@ use cf_chains::sol::{ use cf_utilities::success_threshold_from_share_count; use frame_support::{ pallet_prelude::{MaybeSerializeDeserialize, Member}, - sp_runtime::traits::CheckedSub, + sp_runtime::traits::Saturating, Parameter, }; use itertools::Itertools; @@ -100,7 +100,7 @@ impl< E: sp_std::fmt::Debug + 'static, Account: MaybeSerializeDeserialize + Member + Parameter + Ord, SwapDetails: MaybeSerializeDeserialize + Member + Parameter + Ord, - BlockNumber: MaybeSerializeDeserialize + Member + Parameter + Ord + CheckedSub + Into + Copy, + BlockNumber: MaybeSerializeDeserialize + Member + Parameter + Ord + Saturating + Into + Copy, Settings: Member + Parameter + MaybeSerializeDeserialize + Eq, Hook: SolanaVaultSwapAccountsHook + 'static, ValidatorId: Member + Parameter + Ord + MaybeSerializeDeserialize, @@ -176,32 +176,39 @@ impl< known_accounts.closure_initiated_accounts.remove(&acc); }); + // Since closing accounts is a low priority action, we wait for certain number of + // sol nonces to be free for us to initiate account closures which indicates that + // there is not enough Chainflip activity on the sol side and so we can process + // account closures. + // + // we also wait for certain number of accounts to buffer up or allow a certain + // amount of time to pass before initiating account closures. if Hook::get_number_of_available_sol_nonce_accounts() > NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES && (known_accounts.witnessed_open_accounts.len() >= MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES || (*current_block_number) - .checked_sub(&electoral_access.unsynchronised_state()?) - .expect("current block number is always greater than when apicall was last created") + // current block number is always greater than when apicall was last + // created + .saturating_sub(electoral_access.unsynchronised_state()?) .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) { let accounts_to_close: Vec<_> = known_accounts - .witnessed_open_accounts - .drain(..sp_std::cmp::min( - known_accounts.witnessed_open_accounts.len(), - MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES - )) - .collect(); + .witnessed_open_accounts + .drain( + ..sp_std::cmp::min( + known_accounts.witnessed_open_accounts.len(), + MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES, + ), + ) + .collect(); match Hook::close_accounts(accounts_to_close.clone()) { Ok(()) => { known_accounts.closure_initiated_accounts.extend(accounts_to_close); electoral_access.set_unsynchronised_state(*current_block_number)?; }, Err(e) => { - log::error!( - "Failed to initiate account closure: {:?}", - e - ); + log::error!("Failed to initiate account closure: {:?}", e); known_accounts.witnessed_open_accounts.extend(accounts_to_close); }, } From a16fdbd4adb7feb9b6270e64bab9a454282b4edb Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 7 Nov 2024 15:37:31 +0100 Subject: [PATCH 36/62] chore: rename file --- engine/src/witness/sol.rs | 3 +-- state-chain/pallets/cf-elections/src/electoral_systems.rs | 2 +- ...wap_accounts_tracking.rs => solana_vault_swap_accounts.rs} | 0 state-chain/runtime/src/chainflip/solana_elections.rs | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) rename state-chain/pallets/cf-elections/src/electoral_systems/{solana_swap_accounts_tracking.rs => solana_vault_swap_accounts.rs} (100%) diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index 56d9a61c49..50e6200dc0 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -24,8 +24,7 @@ use cf_chains::{ use futures::FutureExt; use pallet_cf_elections::{ electoral_system::ElectoralSystem, - electoral_systems::solana_swap_accounts_tracking::SolanaVaultSwapsVote, - vote_storage::VoteStorage, + electoral_systems::solana_vault_swap_accounts::SolanaVaultSwapsVote, vote_storage::VoteStorage, }; use state_chain_runtime::{ chainflip::solana_elections::{ diff --git a/state-chain/pallets/cf-elections/src/electoral_systems.rs b/state-chain/pallets/cf-elections/src/electoral_systems.rs index c48ca0ca63..c7a51f6fc8 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems.rs @@ -6,7 +6,7 @@ pub mod liveness; pub mod mock; pub mod monotonic_change; pub mod monotonic_median; -pub mod solana_swap_accounts_tracking; +pub mod solana_vault_swap_accounts; pub mod unsafe_median; #[cfg(test)] diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs similarity index 100% rename from state-chain/pallets/cf-elections/src/electoral_systems/solana_swap_accounts_tracking.rs rename to state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 53447ab0bf..cba5de4a03 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -45,7 +45,7 @@ use pallet_cf_elections::{ liveness::OnCheckComplete, monotonic_change::OnChangeHook, monotonic_median::MedianChangeHook, - solana_swap_accounts_tracking::SolanaVaultSwapAccountsHook, + solana_vault_swap_accounts::SolanaVaultSwapAccountsHook, }, CorruptStorageError, ElectionIdentifier, InitialState, InitialStateOf, RunnerStorageAccess, }; @@ -171,7 +171,7 @@ impl OnCheckComplete<::ValidatorId> for OnCheckCompleteHoo } pub type SolanaVaultSwapTracking = electoral_systems::solana_swap_accounts_tracking::SolanaVaultSwapAccounts< - ContractSwapAccountAndSender, + VaultSwapAccountAndSender, SolanaVaultSwapDetails, BlockNumberFor, SolanaVaultSwapsSettings, From 8329c22de2079cb9eca5513dca58b9cd1af4909e Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 7 Nov 2024 16:10:12 +0100 Subject: [PATCH 37/62] chore: make src_token safer, add unwrap() --- .../witness/sol/program_swaps_witnessing.rs | 105 +++++++++--------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 44ee4d3749..6afec11945 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -73,53 +73,59 @@ pub async fn get_program_swaps( .map_ok(|program_swap_account_data_chunk| { stream::iter(program_swap_account_data_chunk.into_iter().filter_map( |(account, program_swap_account_data)| match program_swap_account_data { - Some(data) - if (data.src_token.is_none() || - data.src_token.is_some_and(|addr| addr == usdc_token_mint_pubkey.into())) => { - - let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { - None => { - let CfParameters { ccm_additional_data: (), vault_swap_parameters } = - CfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; - (None, vault_swap_parameters) + Some(data) => { + let from_asset = if data.src_token.is_none() { + SolAsset::Sol + } else if data.src_token.unwrap() == usdc_token_mint_pubkey.into() { + SolAsset::SolUsdc + } else { + warn!("Unsupported input token for the witnessed solana vault swap, omitting the swap and the swap account."); + None? + }; + + let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { + None => { + let CfParameters { ccm_additional_data: (), vault_swap_parameters } = + CfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + (None, vault_swap_parameters) + }, + Some(ccm_parameters) => { + let CcmCfParameters { ccm_additional_data, vault_swap_parameters } = + CcmCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CcmCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + + let deposit_metadata = Some(CcmDepositMetadata { + source_chain: cf_primitives::ForeignChain::Solana, // TODO: Pass chain id from above? + source_address: Some(ForeignChainAddress::Sol(data.sender.into())), + channel_metadata: CcmChannelMetadata { + message: ccm_parameters.message + .to_vec() + .try_into() + .map_err(|_| anyhow!("Failed to deposit CCM: `message` too long.")).ok()?, + gas_budget: ccm_parameters.gas_amount.into(), + ccm_additional_data, }, - Some(ccm_parameters) => { - let CcmCfParameters { ccm_additional_data, vault_swap_parameters } = - CcmCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CcmCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; - - let deposit_metadata = Some(CcmDepositMetadata { - source_chain: cf_primitives::ForeignChain::Solana, // TODO: Pass chain id from above? - source_address: Some(ForeignChainAddress::Sol(data.sender.into())), - channel_metadata: CcmChannelMetadata { - message: ccm_parameters.message - .to_vec() - .try_into() - .map_err(|_| anyhow!("Failed to deposit CCM: `message` too long.")).ok()?, - gas_budget: ccm_parameters.gas_amount.into(), - ccm_additional_data, - }, - }); - (deposit_metadata, vault_swap_parameters) - } - }; - - Some(Ok((VaultSwapAccountAndSender { - vault_swap_account: account, - swap_sender: data.sender.into() - }, SolanaVaultSwapDetails { - from: if data.src_token.is_none() {SolAsset::Sol} else {SolAsset::SolUsdc}, - deposit_amount: data.amount, - destination_address: EncodedAddress::from_chain_bytes(data.dst_chain.try_into().map_err(|e| warn!("error while parsing destination chain for solana vault swap:{}. Omitting swap", e)).ok()?, data.dst_address.to_vec()).map_err(|e| warn!("failed to decode the destination chain address for solana vault swap:{}. Omitting swap", e)).ok()?, - to: data.dst_token.try_into().map_err(|e| warn!("error while decoding destination token for solana vault swap: {}. Omitting swap", e)).ok()?, - deposit_metadata, - swap_account: account, - creation_slot: data.creation_slot, - broker_fees: vault_swap_parameters.broker_fees, - refund_params: Some(vault_swap_parameters.refund_params), - dca_params: vault_swap_parameters.dca_params, - boost_fee: vault_swap_parameters.boost_fee, - }))) + }); + (deposit_metadata, vault_swap_parameters) } + }; + + Some(Ok::<_, anyhow::Error>((VaultSwapAccountAndSender { + vault_swap_account: account, + swap_sender: data.sender.into() + }, SolanaVaultSwapDetails { + from: from_asset, + deposit_amount: data.amount, + destination_address: EncodedAddress::from_chain_bytes(data.dst_chain.try_into().map_err(|e| warn!("error while parsing destination chain for solana vault swap:{}. Omitting swap", e)).ok()?, data.dst_address.to_vec()).map_err(|e| warn!("failed to decode the destination chain address for solana vault swap:{}. Omitting swap", e)).ok()?, + to: data.dst_token.try_into().map_err(|e| warn!("error while decoding destination token for solana vault swap: {}. Omitting swap", e)).ok()?, + deposit_metadata, + swap_account: account, + creation_slot: data.creation_slot, + broker_fees: vault_swap_parameters.broker_fees, + refund_params: Some(vault_swap_parameters.refund_params), + dca_params: vault_swap_parameters.dca_params, + boost_fee: vault_swap_parameters.boost_fee, + }))) + }, // It could happen that some account is closed between the queries. This should // not happen because: @@ -137,19 +143,16 @@ pub async fn get_program_swaps( warn!("Event account not found for solana event account"); None }, - _ => { - warn!("Unsupported input token for the witnessed solana vault swap, omitting the swap and the swap account."); - None - }, }, )) }) .try_flatten() .filter(|item| futures::future::ready(item.is_ok())) .try_collect() - .await; + .await + .unwrap(); - new_swaps.map(|swaps| (swaps, closed_accounts)) + Ok((new_swaps, closed_accounts)) // TODO: When submitting data we could technically submit the slot when the SwapEvent was // queried for the new opened accounts. However, it's just easier to submit the slot when the From 51ed8063be5beac26bb09adec1199e269153396a Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 7 Nov 2024 18:13:23 +0100 Subject: [PATCH 38/62] chore: pull out common code --- .../solana_vault_swap_accounts.rs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs index 8270320e59..649a1ccbcf 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs @@ -249,25 +249,19 @@ impl< } counts_votes.iter().for_each(|(vote, count)| { - vote.new_accounts.iter().for_each(|new_account| { - counts_new_accounts - .entry(new_account) - .and_modify(|c| *c += *count) - .or_insert(*count); - }); - vote.confirm_closed_accounts.iter().for_each(|confirm_closed_account| { - counts_confirm_closed_accounts - .entry(confirm_closed_account) - .and_modify(|c| *c += *count) - .or_insert(*count); - }); + count_votes(&vote.new_accounts, &mut counts_new_accounts, count); + count_votes( + &vote.confirm_closed_accounts, + &mut counts_confirm_closed_accounts, + count, + ); }); counts_new_accounts.retain(|_, count| *count >= success_threshold); - let new_accounts = counts_new_accounts.into_keys().cloned().collect::>(); + let new_accounts = counts_new_accounts.into_keys().collect::>(); counts_confirm_closed_accounts.retain(|_, count| *count >= success_threshold); let confirm_closed_accounts = - counts_confirm_closed_accounts.into_keys().cloned().collect::>(); + counts_confirm_closed_accounts.into_keys().collect::>(); if new_accounts.is_empty() && confirm_closed_accounts.is_empty() { None @@ -279,3 +273,16 @@ impl< }) } } + +pub fn count_votes( + accounts: &BTreeSet, + counts_accounts: &mut BTreeMap, + count: &u32, +) { + accounts.iter().for_each(|account| { + counts_accounts + .entry((*account).clone()) + .and_modify(|c| *c += *count) + .or_insert(*count); + }); +} From 3896a89fe211cf99eb23120464a285942fb92acd Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 8 Nov 2024 14:53:09 +0100 Subject: [PATCH 39/62] fix: chore: address comments --- .../src/witness/sol/program_swaps_witnessing.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 6afec11945..6c1f3b31ed 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -74,14 +74,16 @@ pub async fn get_program_swaps( stream::iter(program_swap_account_data_chunk.into_iter().filter_map( |(account, program_swap_account_data)| match program_swap_account_data { Some(data) => { - let from_asset = if data.src_token.is_none() { - SolAsset::Sol - } else if data.src_token.unwrap() == usdc_token_mint_pubkey.into() { - SolAsset::SolUsdc + let from_asset = if let Some(token) = data.src_token { + if token == usdc_token_mint_pubkey.into() { + SolAsset::SolUsdc + } else { + warn!("Unsupported output token for the witnessed solana vault swap, omitting the swap and the swap account."); + return None; + } } else { - warn!("Unsupported input token for the witnessed solana vault swap, omitting the swap and the swap account."); - None? - }; + SolAsset::SolUsdc + }; let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { None => { From f1bf1e578d417d83d4806563374406939de07445 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 8 Nov 2024 14:58:41 +0100 Subject: [PATCH 40/62] chore: address comments --- engine/src/witness/sol/program_swaps_witnessing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 6c1f3b31ed..25a830d108 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -142,7 +142,7 @@ pub async fn get_program_swaps( // problematic as we'd have reached consensus and the engine would just filter // it out. None => { - warn!("Event account not found for solana event account"); + warn!("Event account not found for solana event account: {}", account); None }, }, From c6811f827b7bcd5a91e322de3278d6f7e396eab8 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 14 Nov 2024 14:20:17 +0100 Subject: [PATCH 41/62] chore: rebase related fixes, integrating latest features from main --- engine/src/elections/voter_api.rs | 2 +- engine/src/witness/arb.rs | 2 +- engine/src/witness/common/cf_parameters.rs | 3 +- engine/src/witness/eth.rs | 4 +- engine/src/witness/evm/vault.rs | 2 +- engine/src/witness/sol.rs | 12 ++--- .../witness/sol/program_swaps_witnessing.rs | 15 +++--- .../cf-integration-tests/src/solana.rs | 4 +- state-chain/chains/src/benchmarking_value.rs | 10 ++++ .../chains/src/sol/transaction_builder.rs | 2 +- .../src/electoral_systems/composite.rs | 3 +- .../solana_vault_swap_accounts.rs | 13 ++--- .../src/vote_storage/composite.rs | 2 +- .../src/vote_storage/individual/composite.rs | 2 +- .../runtime/src/chainflip/solana_elections.rs | 50 ++++++++----------- 15 files changed, 62 insertions(+), 64 deletions(-) diff --git a/engine/src/elections/voter_api.rs b/engine/src/elections/voter_api.rs index 0b0dd50a4a..3d1c30a36d 100644 --- a/engine/src/elections/voter_api.rs +++ b/engine/src/elections/voter_api.rs @@ -78,4 +78,4 @@ macro_rules! generate_voter_api_tuple_impls { } } -generate_voter_api_tuple_impls!(tuple_6_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0))); +generate_voter_api_tuple_impls!(tuple_7_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0), (GG, G0))); diff --git a/engine/src/witness/arb.rs b/engine/src/witness/arb.rs index 8550c3aa2f..a3b6714126 100644 --- a/engine/src/witness/arb.rs +++ b/engine/src/witness/arb.rs @@ -3,7 +3,7 @@ mod chain_tracking; use std::{collections::HashMap, sync::Arc}; use cf_chains::{assets::arb::Asset as ArbAsset, evm::DepositDetails, Arbitrum}; -use cf_primitives::{AffiliateShortId, EpochIndex, ShortId}; +use cf_primitives::{AffiliateShortId, EpochIndex}; use cf_utilities::task_scope::Scope; use futures_core::Future; use sp_core::H160; diff --git a/engine/src/witness/common/cf_parameters.rs b/engine/src/witness/common/cf_parameters.rs index 0fa5d9bd17..7b494304a4 100644 --- a/engine/src/witness/common/cf_parameters.rs +++ b/engine/src/witness/common/cf_parameters.rs @@ -1,5 +1,5 @@ use cf_chains::{CcmAdditionalData, ChannelRefundParameters}; -use cf_primitives::{AffiliateShortId, BasisPoints, Beneficiaries, DcaParameters, ShortId}; +use cf_primitives::{AffiliateShortId, BasisPoints, Beneficiaries, DcaParameters}; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -16,7 +16,6 @@ pub struct CfParameters { } pub type VersionedCcmCfParameters = VersionedCfParameters; -pub type CcmCfParameters = CfParameters; #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Debug)] pub struct VaultSwapParameters { diff --git a/engine/src/witness/eth.rs b/engine/src/witness/eth.rs index 5d3642668f..390d10647c 100644 --- a/engine/src/witness/eth.rs +++ b/engine/src/witness/eth.rs @@ -4,9 +4,7 @@ mod state_chain_gateway; use std::{collections::HashMap, sync::Arc}; use cf_chains::{evm::DepositDetails, Ethereum}; -use cf_primitives::{ - chains::assets::eth::Asset as EthAsset, AffiliateShortId, EpochIndex, ShortId, -}; +use cf_primitives::{chains::assets::eth::Asset as EthAsset, AffiliateShortId, EpochIndex}; use cf_utilities::task_scope::Scope; use futures_core::Future; use sp_core::H160; diff --git a/engine/src/witness/evm/vault.rs b/engine/src/witness/evm/vault.rs index 1da416afd1..c1223c4fca 100644 --- a/engine/src/witness/evm/vault.rs +++ b/engine/src/witness/evm/vault.rs @@ -12,7 +12,7 @@ use super::{ }, contract_common::{events_at_block, Event}, }; -use cf_primitives::{AffiliateShortId, AssetAmount, EpochIndex, ShortId}; +use cf_primitives::{AffiliateShortId, AssetAmount, EpochIndex}; use futures_core::Future; use anyhow::{anyhow, Result}; diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index 50e6200dc0..03cfc93c57 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -28,24 +28,20 @@ use pallet_cf_elections::{ }; use state_chain_runtime::{ chainflip::solana_elections::{ - SolanaBlockHeightTracking, SolanaEgressWitnessing, SolanaElectoralSystem, - SolanaElectoralSystemRunner, SolanaFeeTracking, SolanaIngressTracking, SolanaLiveness, - SolanaNonceTracking, SolanaVaultSwapTracking, TransactionSuccessDetails, + SolanaBlockHeightTracking, SolanaEgressWitnessing, SolanaElectoralSystemRunner, + SolanaFeeTracking, SolanaIngressTracking, SolanaLiveness, SolanaNonceTracking, + SolanaVaultSwapTracking, TransactionSuccessDetails, }, SolanaInstance, }; -use cf_utilities::{ - metrics::CHAIN_TRACKING, - task_scope::{self, Scope}, -}; +use cf_utilities::{metrics::CHAIN_TRACKING, task_scope, task_scope::Scope}; use pallet_cf_elections::vote_storage::change::MonotonicChangeVote; use std::{ collections::{BTreeSet, HashSet}, str::FromStr, sync::Arc, }; -use utilities::{task_scope, task_scope::Scope}; #[derive(Clone)] struct SolanaBlockHeightTrackingVoter { diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 25a830d108..70fb59988f 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -22,7 +22,6 @@ use cf_chains::{ }, CcmChannelMetadata, CcmDepositMetadata, ForeignChainAddress, }; -use cf_primitives::Asset; use futures::{stream, StreamExt, TryStreamExt}; use itertools::Itertools; use state_chain_runtime::chainflip::solana_elections::SolanaVaultSwapDetails; @@ -80,10 +79,10 @@ pub async fn get_program_swaps( } else { warn!("Unsupported output token for the witnessed solana vault swap, omitting the swap and the swap account."); return None; - } + } } else { SolAsset::SolUsdc - }; + }; let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { None => { @@ -92,8 +91,8 @@ pub async fn get_program_swaps( (None, vault_swap_parameters) }, Some(ccm_parameters) => { - let CcmCfParameters { ccm_additional_data, vault_swap_parameters } = - CcmCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CcmCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + let VersionedCfParameters::V0(CfParameters { ccm_additional_data, vault_swap_parameters }) = + VersionedCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding VersionedCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; let deposit_metadata = Some(CcmDepositMetadata { source_chain: cf_primitives::ForeignChain::Solana, // TODO: Pass chain id from above? @@ -122,7 +121,11 @@ pub async fn get_program_swaps( deposit_metadata, swap_account: account, creation_slot: data.creation_slot, - broker_fees: vault_swap_parameters.broker_fees, + // todo: get this from vault_swap_parameters (functionalit around this changed recently). + broker_fees: cf_primitives::Beneficiary { + account: sp_runtime::AccountId32::new(Default::default()), + bps: 0, + }, refund_params: Some(vault_swap_parameters.refund_params), dca_params: vault_swap_parameters.dca_params, boost_fee: vault_swap_parameters.boost_fee, diff --git a/state-chain/cf-integration-tests/src/solana.rs b/state-chain/cf-integration-tests/src/solana.rs index 10fd8026e4..b448b01ba6 100644 --- a/state-chain/cf-integration-tests/src/solana.rs +++ b/state-chain/cf-integration-tests/src/solana.rs @@ -29,9 +29,9 @@ use frame_support::{ use pallet_cf_elections::{ electoral_systems::{ blockchain::delta_based_ingress::ChannelTotalIngressedFor, - composite::tuple_6_impls::CompositeElectionIdentifierExtra, + composite::tuple_7_impls::CompositeElectionIdentifierExtra, }, - vote_storage::{composite::tuple_6_impls::CompositeVote, AuthorityVote}, + vote_storage::{composite::tuple_7_impls::CompositeVote, AuthorityVote}, CompositeAuthorityVoteOf, CompositeElectionIdentifierOf, MAXIMUM_VOTES_PER_EXTRINSIC, }; use pallet_cf_ingress_egress::{DepositWitness, FetchOrTransfer}; diff --git a/state-chain/chains/src/benchmarking_value.rs b/state-chain/chains/src/benchmarking_value.rs index 661b3c3a74..69aa21fc7b 100644 --- a/state-chain/chains/src/benchmarking_value.rs +++ b/state-chain/chains/src/benchmarking_value.rs @@ -261,6 +261,16 @@ impl BenchmarkValue for Beneficiary { } } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for Beneficiary { + fn benchmark_value() -> Self { + Self { + account: sp_runtime::AccountId32::new([1u8; 32]), + bps: BenchmarkValue::benchmark_value(), + } + } +} + #[cfg(feature = "runtime-benchmarks")] impl BenchmarkValue for sp_runtime::BoundedVec, sp_core::ConstU32<{ MAX_AFFILIATES + 1 }>> diff --git a/state-chain/chains/src/sol/transaction_builder.rs b/state-chain/chains/src/sol/transaction_builder.rs index 1a2fd637e0..380c861cb0 100644 --- a/state-chain/chains/src/sol/transaction_builder.rs +++ b/state-chain/chains/src/sol/transaction_builder.rs @@ -468,7 +468,7 @@ impl SolanaTransactionBuilder { let instructions = vec![SwapEndpointProgram::with_id(swap_endpoint_program) .close_event_accounts(vault_program_data_account, agg_key, swap_endpoint_data_account) - .with_remaining_accounts(event_and_sender_vec)]; + .with_remaining_accounts(swap_and_sender_vec)]; Self::build( instructions, diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs b/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs index 8d7d3fdc7f..1c0f49e18f 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs @@ -13,6 +13,7 @@ pub mod tags { pub struct D; pub struct EE; pub struct FF; + pub struct GG; } macro_rules! generate_electoral_system_tuple_impls { @@ -458,4 +459,4 @@ macro_rules! generate_electoral_system_tuple_impls { }; } -generate_electoral_system_tuple_impls!(tuple_6_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0))); +generate_electoral_system_tuple_impls!(tuple_7_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0), (GG, G0))); diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs index 649a1ccbcf..91e1f71af0 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs @@ -131,7 +131,6 @@ impl< } fn is_vote_desired>( - _election_identifier_with_extra: crate::electoral_system::ElectionIdentifierOf, _election_access: &ElectionAccess, _current_vote: Option<(VotePropertiesOf, AuthorityVoteOf)>, ) -> Result { @@ -153,7 +152,6 @@ impl< } fn on_finalize>( - electoral_access: &mut ElectoralAccess, election_identifiers: Vec>, current_block_number: &Self::OnFinalizeContext, ) -> Result { @@ -162,7 +160,7 @@ impl< .at_most_one() .map_err(|_| CorruptStorageError::new())? { - let mut election_access = electoral_access.election_mut(election_identifier)?; + let election_access = ElectoralAccess::election_mut(election_identifier); if let Some(consensus) = election_access.check_consensus()?.has_consensus() { let mut known_accounts = election_access.properties()?; election_access.delete(); @@ -190,7 +188,7 @@ impl< (*current_block_number) // current block number is always greater than when apicall was last // created - .saturating_sub(electoral_access.unsynchronised_state()?) + .saturating_sub(ElectoralAccess::unsynchronised_state()?) .into() >= MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS) { let accounts_to_close: Vec<_> = known_accounts @@ -205,7 +203,7 @@ impl< match Hook::close_accounts(accounts_to_close.clone()) { Ok(()) => { known_accounts.closure_initiated_accounts.extend(accounts_to_close); - electoral_access.set_unsynchronised_state(*current_block_number)?; + ElectoralAccess::set_unsynchronised_state(*current_block_number)?; }, Err(e) => { log::error!("Failed to initiate account closure: {:?}", e); @@ -213,10 +211,10 @@ impl< }, } } - electoral_access.new_election((), known_accounts, ())?; + ElectoralAccess::new_election((), known_accounts, ())?; } } else { - electoral_access.new_election( + ElectoralAccess::new_election( (), SolanaVaultSwapsKnownAccounts { witnessed_open_accounts: Vec::new(), @@ -230,7 +228,6 @@ impl< } fn check_consensus>( - _election_identifier: ElectionIdentifier, _election_access: &ElectionAccess, _previous_consensus: Option<&Self::Consensus>, consensus_votes: ConsensusVotes, diff --git a/state-chain/pallets/cf-elections/src/vote_storage/composite.rs b/state-chain/pallets/cf-elections/src/vote_storage/composite.rs index 88e1099710..3de8dc9e97 100644 --- a/state-chain/pallets/cf-elections/src/vote_storage/composite.rs +++ b/state-chain/pallets/cf-elections/src/vote_storage/composite.rs @@ -274,4 +274,4 @@ macro_rules! generate_vote_storage_tuple_impls { } } -generate_vote_storage_tuple_impls!(tuple_6_impls: (A, B, C, D, EE, FF)); +generate_vote_storage_tuple_impls!(tuple_7_impls: (A, B, C, D, EE, FF, GG)); diff --git a/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs b/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs index 6258427b69..33accbb882 100644 --- a/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs +++ b/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs @@ -76,4 +76,4 @@ macro_rules! generate_individual_vote_storage_tuple_impls { } #[cfg(test)] generate_individual_vote_storage_tuple_impls!(tuple_2_impls: (A, B)); -generate_individual_vote_storage_tuple_impls!(tuple_6_impls: (A, B, C, D, EE, FF)); +generate_individual_vote_storage_tuple_impls!(tuple_7_impls: (A, B, C, D, EE, FF, GG)); diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index cba5de4a03..d01ea30642 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -1,8 +1,10 @@ use crate::{ - Environment, Offence, Reputation, Runtime, SolanaBroadcaster, SolanaChainTracking, + AccountId, Environment, Offence, Reputation, Runtime, SolanaBroadcaster, SolanaChainTracking, SolanaIngressEgress, SolanaThresholdSigner, }; use cf_chains::{ + address::EncodedAddress, + assets::{any::Asset, sol::Asset as SolAsset}, instances::{ChainInstanceAlias, SolanaInstance}, sol::{ api::{ @@ -14,33 +16,19 @@ use cf_chains::{ CcmDepositMetadata, Chain, ChannelRefundParameters, CloseSolanaVaultSwapAccounts, FeeEstimationApi, ForeignChain, Solana, }; -use cf_primitives::{BasisPoints, DcaParameters, ShortId}; +use cf_primitives::{BasisPoints, Beneficiary, DcaParameters}; use cf_runtime_utilities::log_or_panic; use cf_traits::{ - instances::ChainInstanceAlias, - offence_reporting::OffenceReporter, - sol::{ - api::{SolanaApi, SolanaTransactionBuildingError}, - SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, SolanaCrypto, - }, - AdjustedFeeEstimationApi, Broadcaster, Chain, Chainflip, ElectionEgressWitnesser, - FeeEstimationApi, GetBlockHeight, IngressSource, Solana, SolanaNonceWatch, + offence_reporting::OffenceReporter, AdjustedFeeEstimationApi, Broadcaster, Chainflip, + ElectionEgressWitnesser, GetBlockHeight, IngressSource, SolanaNonceWatch, }; - -use crate::{RuntimeOrigin, SolanaIngressEgress}; -use cf_chains::{ - address::EncodedAddress, assets::any::Asset, sol::api::ContractSwapAccountAndSender, - CloseSolanaVaultSwapAccounts, -}; -use cf_primitives::{AssetAmount, TransactionHash}; use codec::{Decode, Encode}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_cf_elections::{ electoral_system::{ElectoralReadAccess, ElectoralSystem}, electoral_systems::{ self, - change::OnChangeHook, - composite::{tuple_6_impls::Hooks, Composite, CompositeRunner, Translator}, + composite::{tuple_7_impls::Hooks, CompositeRunner}, egress_success::OnEgressSuccess, liveness::OnCheckComplete, monotonic_change::OnChangeHook, @@ -52,7 +40,7 @@ use pallet_cf_elections::{ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{DispatchResult, FixedPointNumber, FixedU128}; -use sp_std::vec::Vec; +use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; #[cfg(feature = "runtime-benchmarks")] use cf_chains::benchmarking_value::BenchmarkValue; @@ -105,6 +93,7 @@ pub fn initial_state( (), (), (), + (), ), settings: ( (), @@ -170,7 +159,7 @@ impl OnCheckComplete<::ValidatorId> for OnCheckCompleteHoo } } pub type SolanaVaultSwapTracking = - electoral_systems::solana_swap_accounts_tracking::SolanaVaultSwapAccounts< + electoral_systems::solana_vault_swap_accounts::SolanaVaultSwapAccounts< VaultSwapAccountAndSender, SolanaVaultSwapDetails, BlockNumberFor, @@ -305,7 +294,7 @@ impl >, ), ) -> Result<(), CorruptStorageError> { - let current_SC_block_number = crate::System::block_number(); + let current_sc_block_number = crate::System::block_number(); let block_height = SolanaBlockHeightTracking::on_finalize::< DerivedElectoralAccess< _, @@ -350,7 +339,7 @@ impl SolanaVaultSwapTracking, RunnerStorageAccess, >, - >(vault_swap_identifiers, current_sc_block_number)?; + >(vault_swap_identifiers, ¤t_sc_block_number)?; Ok(()) } } @@ -383,7 +372,7 @@ impl BenchmarkValue for SolanaIngressSettings { } } -use pallet_cf_elections::electoral_systems::composite::tuple_6_impls::DerivedElectoralAccess; +use pallet_cf_elections::electoral_systems::composite::tuple_7_impls::DerivedElectoralAccess; pub struct SolanaChainTrackingProvider; impl GetBlockHeight for SolanaChainTrackingProvider { @@ -526,15 +515,15 @@ impl ElectionEgressWitnesser for SolanaEgressWitnessingTrigger { Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, PartialOrd, Ord, )] pub struct SolanaVaultSwapDetails { - pub from: Asset, + pub from: SolAsset, pub to: Asset, - pub deposit_amount: AssetAmount, + pub deposit_amount: SolAmount, pub destination_address: EncodedAddress, pub deposit_metadata: Option, // TODO: swap_account and creation_slot might be pulled into TransactionInId type (PRO-1760) pub swap_account: SolAddress, pub creation_slot: u64, - pub broker_fees: cf_primitives::Beneficiaries, + pub broker_fees: cf_primitives::Beneficiary, pub refund_params: Option, pub dca_params: Option, pub boost_fee: Option, @@ -577,7 +566,12 @@ impl swap_details.deposit_metadata, Default::default(), // TODO txHash PRO-1760 (), - Default::default(), // TODO in PRO-1743 + // TODO in PRO-1743 + Beneficiary { + account: sp_runtime::AccountId32::new(Default::default()), + bps: Default::default(), + }, + Default::default(), swap_details.refund_params, swap_details.dca_params, swap_details.boost_fee.unwrap_or_default(), From f53e86c24d36318dd8aee88e1782db7da14138b1 Mon Sep 17 00:00:00 2001 From: albert Date: Thu, 14 Nov 2024 15:02:31 +0100 Subject: [PATCH 42/62] chore: fixes after merge --- bouncer/shared/contract_swap.ts | 342 -------------------------- bouncer/tests/all_concurrent_tests.ts | 2 +- 2 files changed, 1 insertion(+), 343 deletions(-) delete mode 100644 bouncer/shared/contract_swap.ts diff --git a/bouncer/shared/contract_swap.ts b/bouncer/shared/contract_swap.ts deleted file mode 100644 index 3f647b1696..0000000000 --- a/bouncer/shared/contract_swap.ts +++ /dev/null @@ -1,342 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -// import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'; - -import { - InternalAsset as Asset, - executeSwap, - ExecuteSwapParams, - approveVault, - Asset as SCAsset, - Chains, - InternalAsset, - Chain, -} from '@chainflip/cli'; -import { HDNodeWallet, Wallet, getDefaultProvider } from 'ethers'; -import { PublicKey, sendAndConfirmTransaction, Keypair } from '@solana/web3.js'; -import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { - observeBalanceIncrease, - getContractAddress, - observeCcmReceived, - amountToFineAmount, - defaultAssetAmounts, - chainFromAsset, - getEvmEndpoint, - assetDecimals, - stateChainAssetFromAsset, - chainGasAsset, - evmChains, - getSolWhaleKeyPair, - getSolConnection, -} from './utils'; -import { getBalance } from './get_balance'; -import { CcmDepositMetadata } from '../shared/new_swap'; -import { send } from './send'; -import { SwapContext, SwapStatus } from './swap_context'; - -import VaultIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/vault.json'; -import SwapEndpointIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/swap_endpoint.json'; -import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint'; -import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/vault'; - -// Workaround because of anchor issue -const { BN } = anchor; - -const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc']; - -export async function executeContractSwap( - srcAsset: Asset, - destAsset: Asset, - destAddress: string, - wallet: HDNodeWallet, - messageMetadata?: CcmDepositMetadata, -): ReturnType { - const srcChain = chainFromAsset(srcAsset); - const destChain = chainFromAsset(destAsset); - - const networkOptions = { - signer: wallet, - network: 'localnet', - vaultContractAddress: getContractAddress(srcChain, 'VAULT'), - srcTokenContractAddress: getContractAddress(srcChain, srcAsset), - } as const; - const txOptions = { - // This is run with fresh addresses to prevent nonce issues. Will be 1 for ERC20s. - gasLimit: srcChain === Chains.Arbitrum ? 10000000n : 200000n, - } as const; - - const receipt = await executeSwap( - { - destChain, - destAsset: stateChainAssetFromAsset(destAsset), - // It is important that this is large enough to result in - // an amount larger than existential (e.g. on Polkadot): - amount: amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), - destAddress, - srcAsset: stateChainAssetFromAsset(srcAsset), - srcChain, - ccmParams: messageMetadata && { - gasBudget: messageMetadata.gasBudget.toString(), - message: messageMetadata.message, - cfParameters: messageMetadata.cfParameters, - }, - } as ExecuteSwapParams, - networkOptions, - txOptions, - ); - - return receipt; -} - -// Temporary before the SDK implements this. -export async function executeSolContractSwap( - srcAsset: Asset, - destAsset: Asset, - destAddress: string, - messageMetadata?: CcmDepositMetadata, -) { - const destChain = chainFromAsset(destAsset); - - // const solanaSwapEndpointId = new PublicKey(getContractAddress('Solana', 'SWAP_ENDPOINT')); - const solanaVaultDataAccount = new PublicKey(getContractAddress('Solana', 'DATA_ACCOUNT')); - const swapEndpointDataAccount = new PublicKey( - getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'), - ); - const whaleKeypair = getSolWhaleKeyPair(); - - // We should just be able to do this instead but it's not working... - // const wallet = new NodeWallet(whaleKeypair); - // const provider = new anchor.AnchorProvider(connection, wallet, { - // commitment: 'processed', - // }); - // const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl, provider); - // const vaultProgram = new anchor.Program(VaultIdl, provider); - - // The current workaround requires having the wallet in a id.json and then set the ANCHOR_WALLET env. - // TODO: Depending on how the SDK is implemented we can remove this. - process.env.ANCHOR_WALLET = 'shared/solana_keypair.json'; - - const connection = getSolConnection(); - const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); - const vaultProgram = new anchor.Program(VaultIdl as Vault); - - const newEventAccountKeypair = Keypair.generate(); - const fetchedDataAccount = await vaultProgram.account.dataAccount.fetch(solanaVaultDataAccount); - const aggKey = fetchedDataAccount.aggKey; - - const tx = - srcAsset === 'Sol' - ? await cfSwapEndpointProgram.methods - .xSwapNative({ - amount: new BN( - amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), - ), - dstChain: Number(destChain), - dstAddress: Buffer.from(destAddress), - dstToken: Number(stateChainAssetFromAsset(destAsset)), - ccmParameters: messageMetadata - ? { - message: Buffer.from(messageMetadata.message.slice(2), 'hex'), - gasAmount: new BN(messageMetadata.gasBudget), - } - : null, - cfParameters: Buffer.from(messageMetadata?.cfParameters?.slice(2) ?? '', 'hex'), - }) - .accountsPartial({ - dataAccount: solanaVaultDataAccount, - aggKey, - from: whaleKeypair.publicKey, - eventDataAccount: newEventAccountKeypair.publicKey, - swapEndpointDataAccount, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .signers([whaleKeypair, newEventAccountKeypair]) - .transaction() - : await cfSwapEndpointProgram.methods - .xSwapToken({ - amount: new BN( - amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), - ), - dstChain: Number(destChain), - dstAddress: Buffer.from(destAddress), - dstToken: Number(stateChainAssetFromAsset(destAsset)), - ccmParameters: messageMetadata - ? { - message: Buffer.from(messageMetadata.message.slice(2), 'hex'), - gasAmount: new BN(messageMetadata.gasBudget), - } - : null, - cfParameters: Buffer.from(messageMetadata?.cfParameters?.slice(2) ?? '', 'hex'), - decimals: assetDecimals(srcAsset), - }) - .accountsPartial({ - dataAccount: solanaVaultDataAccount, - tokenVaultAssociatedTokenAccount: new PublicKey( - getContractAddress('Solana', 'TOKEN_VAULT_ATA'), - ), - from: whaleKeypair.publicKey, - fromTokenAccount: getAssociatedTokenAddressSync( - new PublicKey(getContractAddress('Solana', 'SolUsdc')), - whaleKeypair.publicKey, - false, - ), - eventDataAccount: newEventAccountKeypair.publicKey, - swapEndpointDataAccount, - tokenSupportedAccount: new PublicKey( - getContractAddress('Solana', 'SolUsdcTokenSupport'), - ), - tokenProgram: TOKEN_PROGRAM_ID, - mint: new PublicKey(getContractAddress('Solana', 'SolUsdc')), - systemProgram: anchor.web3.SystemProgram.programId, - }) - .signers([whaleKeypair, newEventAccountKeypair]) - .transaction(); - const txHash = await sendAndConfirmTransaction(connection, tx, [ - whaleKeypair, - newEventAccountKeypair, - ]); - - console.log('tx', txHash); - return txHash; -} -export type ContractSwapParams = { - sourceAsset: Asset; - destAsset: Asset; - destAddress: string; - txHash: string; -}; - -export async function performSwapViaContract( - sourceAsset: Asset, - destAsset: Asset, - destAddress: string, - swapTag = '', - messageMetadata?: CcmDepositMetadata, - swapContext?: SwapContext, - log = true, -): Promise { - const tag = swapTag ?? ''; - - const srcChain = chainFromAsset(sourceAsset); - let wallet; - - try { - if (evmChains.includes(srcChain)) { - // Generate a new wallet for each contract swap to prevent nonce issues when running in parallel - // with other swaps via deposit channels. - const mnemonic = Wallet.createRandom().mnemonic?.phrase ?? ''; - if (mnemonic === '') { - throw new Error('Failed to create random mnemonic'); - } - wallet = Wallet.fromPhrase(mnemonic).connect(getDefaultProvider(getEvmEndpoint(srcChain))); - - // Fund new key with native asset and asset to swap. - await send(chainGasAsset(srcChain) as InternalAsset, wallet.address); - await send(sourceAsset, wallet.address); - - if (erc20Assets.includes(sourceAsset)) { - // Doing effectively infinite approvals to make sure it doesn't fail. - // eslint-disable-next-line @typescript-eslint/no-use-before-define - await approveTokenVault( - sourceAsset, - ( - BigInt( - amountToFineAmount(defaultAssetAmounts(sourceAsset), assetDecimals(sourceAsset)), - ) * 100n - ).toString(), - wallet, - ); - } - } - swapContext?.updateStatus(swapTag, SwapStatus.ContractApproved); - - const oldBalance = await getBalance(destAsset, destAddress); - if (log) { - console.log(`${tag} Old balance: ${oldBalance}`); - console.log( - `${tag} Executing (${sourceAsset}) contract swap to(${destAsset}) ${destAddress}. Current balance: ${oldBalance}`, - ); - } - - let txHash: string; - let sourceAddress: string; - - // TODO: Temporary before the SDK implements this. - if (evmChains.includes(srcChain)) { - // To uniquely identify the contractSwap, we need to use the TX hash. This is only known - // after sending the transaction, so we send it first and observe the events afterwards. - // There are still multiple blocks of safety margin inbetween before the event is emitted - const receipt = await executeContractSwap( - sourceAsset, - destAsset, - destAddress, - wallet!, - messageMetadata, - ); - txHash = receipt.hash; - sourceAddress = wallet!.address.toLowerCase(); - } else { - txHash = await executeSolContractSwap( - sourceAsset, - destAsset, - destAddress, - // wallet!, - messageMetadata, - ); - sourceAddress = getSolWhaleKeyPair().publicKey.toBase58(); - } - - swapContext?.updateStatus(swapTag, SwapStatus.ContractExecuted); - - const ccmEventEmitted = messageMetadata - ? observeCcmReceived(sourceAsset, destAsset, destAddress, messageMetadata, sourceAddress) - : Promise.resolve(); - - const [newBalance] = await Promise.all([ - observeBalanceIncrease(destAsset, destAddress, oldBalance), - ccmEventEmitted, - ]); - if (log) { - console.log(`${tag} Swap success! New balance: ${newBalance}!`); - } - swapContext?.updateStatus(swapTag, SwapStatus.Success); - return { - sourceAsset, - destAsset, - destAddress, - txHash, - }; - } catch (err) { - console.error('err:', err); - swapContext?.updateStatus(swapTag, SwapStatus.Failure); - if (err instanceof Error) { - console.log(err.stack); - } - throw new Error(`${tag} ${err}`); - } -} -export async function approveTokenVault(srcAsset: Asset, amount: string, wallet: HDNodeWallet) { - if (!erc20Assets.includes(srcAsset)) { - throw new Error(`Unsupported asset, not an ERC20: ${srcAsset}`); - } - - const chain = chainFromAsset(srcAsset as Asset); - - await approveVault( - { - amount, - srcChain: chain as Chain, - srcAsset: stateChainAssetFromAsset(srcAsset) as SCAsset, - }, - { - signer: wallet, - network: 'localnet', - vaultContractAddress: getContractAddress(chain, 'VAULT'), - srcTokenContractAddress: getContractAddress(chain, srcAsset), - }, - // This is run with fresh addresses to prevent nonce issues - { - nonce: 0, - }, - ); -} diff --git a/bouncer/tests/all_concurrent_tests.ts b/bouncer/tests/all_concurrent_tests.ts index 089a18d4ba..bca1bfa1f3 100755 --- a/bouncer/tests/all_concurrent_tests.ts +++ b/bouncer/tests/all_concurrent_tests.ts @@ -16,7 +16,7 @@ import { depositChannelCreation } from './request_swap_deposit_address_with_affi import { testDCASwaps } from './DCA_test'; import { testBrokerLevelScreening } from './broker_level_screening'; import { testBtcVaultSwap } from './btc_vault_swap'; -import { checkSolEventAccountsClosure } from '../shared/vault_swap'; +import { checkSolEventAccountsClosure } from '../shared/evm_vault_swap'; async function runAllConcurrentTests() { // Specify the number of nodes via providing an argument to this script. From 5788216843bb3913a8213a2a2c70e0edf6dd6c97 Mon Sep 17 00:00:00 2001 From: albert Date: Thu, 14 Nov 2024 17:23:28 +0100 Subject: [PATCH 43/62] feat: update engine broker and affiliate fees --- .../witness/sol/program_swaps_witnessing.rs | 15 ++++++---- state-chain/chains/src/benchmarking_value.rs | 20 ++++++++++++- .../runtime/src/chainflip/solana_elections.rs | 28 +++++++++---------- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 70fb59988f..4f3b026071 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -121,12 +121,15 @@ pub async fn get_program_swaps( deposit_metadata, swap_account: account, creation_slot: data.creation_slot, - // todo: get this from vault_swap_parameters (functionalit around this changed recently). - broker_fees: cf_primitives::Beneficiary { - account: sp_runtime::AccountId32::new(Default::default()), - bps: 0, - }, - refund_params: Some(vault_swap_parameters.refund_params), + broker_fee: vault_swap_parameters.broker_fee, + affiliate_fees: vault_swap_parameters + .affiliate_fees + .into_iter() + .map(|entry| cf_primitives::Beneficiary { account: entry.affiliate.into(), bps: entry.fee.into() }) + .collect_vec() + .try_into() + .expect("runtime supports at least as many affiliates as we allow in cf_parameters encoding"), + refund_params: vault_swap_parameters.refund_params, dca_params: vault_swap_parameters.dca_params, boost_fee: vault_swap_parameters.boost_fee, }))) diff --git a/state-chain/chains/src/benchmarking_value.rs b/state-chain/chains/src/benchmarking_value.rs index 69aa21fc7b..2a0e5ddb36 100644 --- a/state-chain/chains/src/benchmarking_value.rs +++ b/state-chain/chains/src/benchmarking_value.rs @@ -4,7 +4,9 @@ use cf_primitives::{ Asset, }; #[cfg(feature = "runtime-benchmarks")] -use cf_primitives::{Beneficiary, DcaParameters, ForeignChain, ShortId, MAX_AFFILIATES}; +use cf_primitives::{ + AffiliateShortId, Beneficiary, DcaParameters, ForeignChain, ShortId, MAX_AFFILIATES, +}; #[cfg(feature = "runtime-benchmarks")] use core::str::FromStr; @@ -280,6 +282,22 @@ impl BenchmarkValue } } +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue for AffiliateShortId { + fn benchmark_value() -> Self { + cf_primitives::AffiliateShortId(BenchmarkValue::benchmark_value()) + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkValue + for frame_support::BoundedVec, sp_core::ConstU32> +{ + fn benchmark_value() -> Self { + sp_runtime::BoundedVec::try_from(vec![BenchmarkValue::benchmark_value()]).unwrap() + } +} + #[macro_export] macro_rules! impl_bounded_vec_benchmark_value { ($element:ty, $n:literal) => { diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index d01ea30642..8963423bce 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -16,7 +16,7 @@ use cf_chains::{ CcmDepositMetadata, Chain, ChannelRefundParameters, CloseSolanaVaultSwapAccounts, FeeEstimationApi, ForeignChain, Solana, }; -use cf_primitives::{BasisPoints, Beneficiary, DcaParameters}; +use cf_primitives::{AffiliateShortId, Affiliates, Beneficiary, DcaParameters}; use cf_runtime_utilities::log_or_panic; use cf_traits::{ offence_reporting::OffenceReporter, AdjustedFeeEstimationApi, Broadcaster, Chainflip, @@ -520,13 +520,14 @@ pub struct SolanaVaultSwapDetails { pub deposit_amount: SolAmount, pub destination_address: EncodedAddress, pub deposit_metadata: Option, - // TODO: swap_account and creation_slot might be pulled into TransactionInId type (PRO-1760) + // TODO: swap_account and creation_slot will be pulled into TransactionInId type (PRO-1760) pub swap_account: SolAddress, pub creation_slot: u64, - pub broker_fees: cf_primitives::Beneficiary, - pub refund_params: Option, + pub broker_fee: Beneficiary, + pub refund_params: ChannelRefundParameters, pub dca_params: Option, - pub boost_fee: Option, + pub boost_fee: u8, + pub affiliate_fees: Affiliates, } #[cfg(feature = "runtime-benchmarks")] @@ -540,10 +541,11 @@ impl BenchmarkValue for SolanaVaultSwapDetails { deposit_metadata: Some(BenchmarkValue::benchmark_value()), swap_account: BenchmarkValue::benchmark_value(), creation_slot: BenchmarkValue::benchmark_value(), - broker_fees: BenchmarkValue::benchmark_value(), - refund_params: Some(BenchmarkValue::benchmark_value()), + broker_fee: BenchmarkValue::benchmark_value(), + refund_params: BenchmarkValue::benchmark_value(), dca_params: Some(BenchmarkValue::benchmark_value()), - boost_fee: Some(BenchmarkValue::benchmark_value()), + boost_fee: BenchmarkValue::benchmark_value(), + affiliate_fees: BenchmarkValue::benchmark_value(), } } } @@ -566,15 +568,11 @@ impl swap_details.deposit_metadata, Default::default(), // TODO txHash PRO-1760 (), - // TODO in PRO-1743 - Beneficiary { - account: sp_runtime::AccountId32::new(Default::default()), - bps: Default::default(), - }, - Default::default(), + swap_details.broker_fee, + swap_details.affiliate_fees, swap_details.refund_params, swap_details.dca_params, - swap_details.boost_fee.unwrap_or_default(), + swap_details.boost_fee.into(), ); } From 89fa7c5a370c59cd6ba9786290f34722296825fa Mon Sep 17 00:00:00 2001 From: albert Date: Thu, 14 Nov 2024 18:29:19 +0100 Subject: [PATCH 44/62] chore: update dummy hardcoded test cf_parameters --- bouncer/shared/evm_vault_swap.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bouncer/shared/evm_vault_swap.ts b/bouncer/shared/evm_vault_swap.ts index 91b21bdc6d..2baff2035b 100644 --- a/bouncer/shared/evm_vault_swap.ts +++ b/bouncer/shared/evm_vault_swap.ts @@ -185,7 +185,7 @@ export async function executeSolVaultSwap( case Chains.Ethereum: case Chains.Arbitrum: cfParameters = - '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + '0x000001000000040101010101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; break; default: throw new Error(`Unsupported chain: ${destChain}`); @@ -196,11 +196,11 @@ export async function executeSolVaultSwap( case Chains.Ethereum: case Chains.Arbitrum: cfParameters = - '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + '0001000000040101010101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; break; case Chains.Polkadot: cfParameters = - '0x000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + '0001000000010404040404040404040404040404040404040404040404040404040404040404000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; break; // TODO: Not supporting BTC for now because the encoding is annoying. default: From 39381d14ba77d2c0cd8441b9dddcdc4c2a84be4f Mon Sep 17 00:00:00 2001 From: albert Date: Fri, 15 Nov 2024 07:55:05 +0100 Subject: [PATCH 45/62] chore: refactor bouncer vault swaps --- bouncer/cf-abis | 1 - bouncer/shared/evm_vault_swap.ts | 318 +------------------------- bouncer/shared/perform_swap.ts | 122 ++++++++++ bouncer/shared/sol_vault_swap.ts | 210 +++++++++++++++++ bouncer/shared/swapping.ts | 3 +- bouncer/shared/utils.ts | 7 + bouncer/tests/DCA_test.ts | 9 +- bouncer/tests/all_concurrent_tests.ts | 2 +- bouncer/tests/all_swaps.ts | 3 +- bouncer/tests/fill_or_kill.ts | 7 +- 10 files changed, 355 insertions(+), 327 deletions(-) delete mode 120000 bouncer/cf-abis create mode 100644 bouncer/shared/sol_vault_swap.ts diff --git a/bouncer/cf-abis b/bouncer/cf-abis deleted file mode 120000 index cced4bce38..0000000000 --- a/bouncer/cf-abis +++ /dev/null @@ -1 +0,0 @@ -../eth-contract-abis/perseverance-rc17 \ No newline at end of file diff --git a/bouncer/shared/evm_vault_swap.ts b/bouncer/shared/evm_vault_swap.ts index 2baff2035b..b2aedb77f6 100644 --- a/bouncer/shared/evm_vault_swap.ts +++ b/bouncer/shared/evm_vault_swap.ts @@ -1,5 +1,3 @@ -import * as anchor from '@coral-xyz/anchor'; -// import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'; import { InternalAsset as Asset, executeSwap, @@ -8,17 +6,12 @@ import { Asset as SCAsset, Chains, Chain, - assetConstants, } from '@chainflip/cli'; import { HDNodeWallet } from 'ethers'; import { randomBytes } from 'crypto'; -import { PublicKey, sendAndConfirmTransaction, Keypair } from '@solana/web3.js'; -import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from '@solana/spl-token'; import Keyring from '../polkadot/keyring'; import { - observeBalanceIncrease, getContractAddress, - observeCcmReceived, amountToFineAmount, defaultAssetAmounts, chainFromAsset, @@ -26,30 +19,12 @@ import { stateChainAssetFromAsset, createEvmWalletAndFund, newAddress, - evmChains, - getSolWhaleKeyPair, - getSolConnection, - chainContractId, - decodeSolAddress, - decodeDotAddressForContract, - sleep, } from './utils'; -import { getBalance } from './get_balance'; import { CcmDepositMetadata, DcaParams, FillOrKillParamsX128 } from './new_swap'; -import { SwapContext, SwapStatus } from './swap_context'; - -import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint'; -import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault'; -import { getSolanaSwapEndpointIdl, getSolanaVaultIdl } from './contract_interfaces'; - -// @ts-expect-error workaround because of anchor issue -const { BN } = anchor.default; const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc']; -const createdEventAccounts: PublicKey[] = []; - -export async function executeVaultSwap( +export async function executeEvmVaultSwap( sourceAsset: Asset, destAsset: Asset, destAddress: string, @@ -85,7 +60,7 @@ export async function executeVaultSwap( if (erc20Assets.includes(sourceAsset)) { // Doing effectively infinite approvals to make sure it doesn't fail. // eslint-disable-next-line @typescript-eslint/no-use-before-define - await approveTokenVault( + await approveEvmTokenVault( sourceAsset, (BigInt(amountToFineAmount(amountToSwap, assetDecimals(sourceAsset))) * 100n).toString(), evmWallet, @@ -132,255 +107,12 @@ export async function executeVaultSwap( return receipt; } -// Temporary before the SDK implements this. -export async function executeSolVaultSwap( - srcAsset: Asset, - destAsset: Asset, - destAddress: string, - messageMetadata?: CcmDepositMetadata, -) { - const destChain = chainFromAsset(destAsset); - - // const solanaSwapEndpointId = new PublicKey(getContractAddress('Solana', 'SWAP_ENDPOINT')); - const solanaVaultDataAccount = new PublicKey(getContractAddress('Solana', 'DATA_ACCOUNT')); - const swapEndpointDataAccount = new PublicKey( - getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'), - ); - const whaleKeypair = getSolWhaleKeyPair(); - - // We should just be able to do this instead but it's not working... - // const wallet = new NodeWallet(whaleKeypair); - // const provider = new anchor.AnchorProvider(connection, wallet, { - // commitment: 'processed', - // }); - // const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl, provider); - // const vaultProgram = new anchor.Program(VaultIdl, provider); - - // The current workaround requires having the wallet in a id.json and then set the ANCHOR_WALLET env. - // TODO: Depending on how the SDK is implemented we can remove this. - process.env.ANCHOR_WALLET = 'shared/solana_keypair.json'; - - const connection = getSolConnection(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const VaultIdl: any = await getSolanaVaultIdl(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const SwapEndpointIdl: any = await getSolanaSwapEndpointIdl(); - - const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); - const vaultProgram = new anchor.Program(VaultIdl as Vault); - const newEventAccountKeypair = Keypair.generate(); - createdEventAccounts.push(newEventAccountKeypair.publicKey); - - const fetchedDataAccount = await vaultProgram.account.dataAccount.fetch(solanaVaultDataAccount); - const aggKey = fetchedDataAccount.aggKey; - - const amount = new BN(amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset))); - - let cfParameters; - - if (messageMetadata) { - // TODO: Currently manually encoded. To use SDK/BrokerApi. - switch (destChain) { - case Chains.Ethereum: - case Chains.Arbitrum: - cfParameters = - '0x000001000000040101010101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; - break; - default: - throw new Error(`Unsupported chain: ${destChain}`); - } - } else { - // TODO: Currently manually encoded. To use SDK/BrokerApi. - switch (destChain) { - case Chains.Ethereum: - case Chains.Arbitrum: - cfParameters = - '0001000000040101010101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; - break; - case Chains.Polkadot: - cfParameters = - '0001000000010404040404040404040404040404040404040404040404040404040404040404000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; - break; - // TODO: Not supporting BTC for now because the encoding is annoying. - default: - throw new Error(`Unsupported chain: ${destChain}`); - } - } - - const destinationAddress = - destChain === Chains.Polkadot ? decodeDotAddressForContract(destAddress) : destAddress; - - const tx = - srcAsset === 'Sol' - ? await cfSwapEndpointProgram.methods - .xSwapNative({ - amount, - dstChain: chainContractId(destChain), - dstAddress: Buffer.from(destinationAddress.slice(2), 'hex'), - dstToken: assetConstants[destAsset].contractId, - ccmParameters: messageMetadata - ? { - message: Buffer.from(messageMetadata.message.slice(2), 'hex'), - gasAmount: new BN(messageMetadata.gasBudget), - } - : null, - cfParameters: Buffer.from(cfParameters!.slice(2) ?? '', 'hex'), - }) - .accountsPartial({ - dataAccount: solanaVaultDataAccount, - aggKey, - from: whaleKeypair.publicKey, - eventDataAccount: newEventAccountKeypair.publicKey, - swapEndpointDataAccount, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .signers([whaleKeypair, newEventAccountKeypair]) - .transaction() - : await cfSwapEndpointProgram.methods - .xSwapToken({ - amount, - dstChain: chainContractId(destChain), - dstAddress: Buffer.from(destinationAddress.slice(2), 'hex'), - dstToken: assetConstants[destAsset].contractId, - ccmParameters: messageMetadata - ? { - message: Buffer.from(messageMetadata.message.slice(2), 'hex'), - gasAmount: new BN(messageMetadata.gasBudget), - } - : null, - cfParameters: Buffer.from(cfParameters!.slice(2) ?? '', 'hex'), - decimals: assetDecimals(srcAsset), - }) - .accountsPartial({ - dataAccount: solanaVaultDataAccount, - tokenVaultAssociatedTokenAccount: new PublicKey( - getContractAddress('Solana', 'TOKEN_VAULT_ATA'), - ), - from: whaleKeypair.publicKey, - fromTokenAccount: getAssociatedTokenAddressSync( - new PublicKey(getContractAddress('Solana', 'SolUsdc')), - whaleKeypair.publicKey, - false, - ), - eventDataAccount: newEventAccountKeypair.publicKey, - swapEndpointDataAccount, - tokenSupportedAccount: new PublicKey( - getContractAddress('Solana', 'SolUsdcTokenSupport'), - ), - tokenProgram: TOKEN_PROGRAM_ID, - mint: new PublicKey(getContractAddress('Solana', 'SolUsdc')), - systemProgram: anchor.web3.SystemProgram.programId, - }) - .signers([whaleKeypair, newEventAccountKeypair]) - .transaction(); - const txHash = await sendAndConfirmTransaction(connection, tx, [ - whaleKeypair, - newEventAccountKeypair, - ]); - - console.log('tx', txHash); - return txHash; -} - -export type VaultSwapParams = { - sourceAsset: Asset; - destAsset: Asset; - destAddress: string; - txHash: string; -}; - -export async function performVaultSwap( +export async function approveEvmTokenVault( sourceAsset: Asset, - destAsset: Asset, - destAddress: string, - swapTag = '', - messageMetadata?: CcmDepositMetadata, - swapContext?: SwapContext, - log = true, - amount?: string, - boostFeeBps?: number, - fillOrKillParams?: FillOrKillParamsX128, - dcaParams?: DcaParams, -): Promise { - const tag = swapTag ?? ''; - const amountToSwap = amount ?? defaultAssetAmounts(sourceAsset); - const srcChain = chainFromAsset(sourceAsset); - - try { - let wallet; - let txHash: string; - let sourceAddress: string; - - if (evmChains.includes(srcChain)) { - // Generate a new wallet for each vault swap to prevent nonce issues when running in parallel - // with other swaps via deposit channels. - wallet = await createEvmWalletAndFund(sourceAsset); - sourceAddress = wallet!.address.toLowerCase(); - } else { - sourceAddress = getSolWhaleKeyPair().publicKey.toBase58(); - } - - const oldBalance = await getBalance(destAsset, destAddress); - if (log) { - console.log(`${tag} Old balance: ${oldBalance}`); - console.log( - `${tag} Executing (${sourceAsset}) vault swap to(${destAsset}) ${destAddress}. Current balance: ${oldBalance}`, - ); - } - - // TODO: Temporary before the SDK implements this. - if (evmChains.includes(srcChain)) { - // To uniquely identify the VaultSwap, we need to use the TX hash. This is only known - // after sending the transaction, so we send it first and observe the events afterwards. - // There are still multiple blocks of safety margin inbetween before the event is emitted - const receipt = await executeVaultSwap( - sourceAsset, - destAsset, - destAddress, - messageMetadata, - amountToSwap, - boostFeeBps, - fillOrKillParams, - dcaParams, - wallet, - ); - txHash = receipt.hash; - sourceAddress = wallet!.address.toLowerCase(); - } else { - txHash = await executeSolVaultSwap(sourceAsset, destAsset, destAddress, messageMetadata); - sourceAddress = decodeSolAddress(getSolWhaleKeyPair().publicKey.toBase58()); - } - swapContext?.updateStatus(swapTag, SwapStatus.VaultContractExecuted); - - const ccmEventEmitted = messageMetadata - ? observeCcmReceived(sourceAsset, destAsset, destAddress, messageMetadata, sourceAddress) - : Promise.resolve(); - - const [newBalance] = await Promise.all([ - observeBalanceIncrease(destAsset, destAddress, oldBalance), - ccmEventEmitted, - ]); - if (log) { - console.log(`${tag} Swap success! New balance: ${newBalance}!`); - } - swapContext?.updateStatus(swapTag, SwapStatus.Success); - return { - sourceAsset, - destAsset, - destAddress, - txHash, - }; - } catch (err) { - console.error('err:', err); - swapContext?.updateStatus(swapTag, SwapStatus.Failure); - if (err instanceof Error) { - console.log(err.stack); - } - throw new Error(`${tag} ${err}`); - } -} -export async function approveTokenVault(sourceAsset: Asset, amount: string, wallet: HDNodeWallet) { + amount: string, + wallet: HDNodeWallet, +) { if (!erc20Assets.includes(sourceAsset)) { throw new Error(`Unsupported asset, not an ERC20: ${sourceAsset}`); } @@ -405,41 +137,3 @@ export async function approveTokenVault(sourceAsset: Asset, amount: string, wall }, ); } - -export async function checkSolEventAccountsClosure( - eventAccounts: PublicKey[] = createdEventAccounts, -) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const SwapEndpointIdl: any = await getSolanaSwapEndpointIdl(); - const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); - const swapEndpointDataAccountAddress = new PublicKey( - getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'), - ); - - const maxRetries = 50; // 300 seconds - - for (let attempt = 0; attempt < maxRetries; attempt++) { - const swapEndpointDataAccount = - await cfSwapEndpointProgram.account.swapEndpointDataAccount.fetch( - swapEndpointDataAccountAddress, - ); - - if (swapEndpointDataAccount.openEventAccounts.length >= 10) { - await sleep(6000); - } else { - const onChainOpenedAccounts = swapEndpointDataAccount.openEventAccounts.map((element) => - element.toString(), - ); - for (const eventAccount of eventAccounts) { - if (!onChainOpenedAccounts.includes(eventAccount.toString())) { - const accountInfo = await getSolConnection().getAccountInfo(eventAccount); - if (accountInfo !== null) { - throw new Error('Event account still exists, should have been closed'); - } - } - } - return; - } - } - throw new Error('Timed out waiting for event accounts to be closed'); -} diff --git a/bouncer/shared/perform_swap.ts b/bouncer/shared/perform_swap.ts index a698803304..8a537e8a4e 100644 --- a/bouncer/shared/perform_swap.ts +++ b/bouncer/shared/perform_swap.ts @@ -14,10 +14,17 @@ import { chainFromAsset, observeSwapRequested, SwapRequestType, + evmChains, + createEvmWalletAndFund, + getSolWhaleKeyPair, + decodeSolAddress, + VaultSwapParams, } from '../shared/utils'; import { CcmDepositMetadata } from '../shared/new_swap'; import { SwapContext, SwapStatus } from './swap_context'; import { getChainflipApi, observeEvent } from './utils/substrate'; +import { executeEvmVaultSwap } from './evm_vault_swap'; +import { executeSolVaultSwap } from './sol_vault_swap'; function encodeDestinationAddress(address: string, destAsset: Asset): string { let destAddress = address; @@ -233,3 +240,118 @@ export async function performAndTrackSwap( else throw new Error('Failed to retrieve broadcastId!'); console.log(`${tag} broadcast executed succesfully, swap is complete!`); } + +export async function executeVaultSwap( + sourceAsset: Asset, + destAsset: Asset, + destAddress: string, + messageMetadata?: CcmDepositMetadata, + amount?: string, + boostFeeBps?: number, + fillOrKillParams?: FillOrKillParamsX128, + dcaParams?: DcaParams, +) { + let sourceAddress: string; + let txHash: string; + + const srcChain = chainFromAsset(sourceAsset); + + if (evmChains.includes(srcChain)) { + // Generate a new wallet for each vault swap to prevent nonce issues when running in parallel + // with other swaps via deposit channels. + const wallet = await createEvmWalletAndFund(sourceAsset); + sourceAddress = wallet.address.toLowerCase(); + + // To uniquely identify the VaultSwap, we need to use the TX hash. This is only known + // after sending the transaction, so we send it first and observe the events afterwards. + // There are still multiple blocks of safety margin inbetween before the event is emitted + const receipt = await executeEvmVaultSwap( + sourceAsset, + destAsset, + destAddress, + messageMetadata, + amount, + boostFeeBps, + fillOrKillParams, + dcaParams, + wallet, + ); + txHash = receipt.hash; + sourceAddress = wallet.address.toLowerCase(); + } else { + // Temporary until we implement the Solana encoding in the SDK/BrokerApi + if (boostFeeBps || fillOrKillParams || dcaParams) { + throw new Error( + 'BoostFeeBps, FillOrKillParams and DcaParams are not supported for Solana vault swaps for now', + ); + } + txHash = await executeSolVaultSwap(sourceAsset, destAsset, destAddress, messageMetadata); + sourceAddress = decodeSolAddress(getSolWhaleKeyPair().publicKey.toBase58()); + } + + return { txHash, sourceAddress }; +} + +export async function performVaultSwap( + sourceAsset: Asset, + destAsset: Asset, + destAddress: string, + swapTag = '', + messageMetadata?: CcmDepositMetadata, + swapContext?: SwapContext, + log = true, + amount?: string, + boostFeeBps?: number, + fillOrKillParams?: FillOrKillParamsX128, + dcaParams?: DcaParams, +): Promise { + const tag = swapTag ?? ''; + + const oldBalance = await getBalance(destAsset, destAddress); + if (log) { + console.log(`${tag} Old balance: ${oldBalance}`); + console.log( + `${tag} Executing (${sourceAsset}) vault swap to(${destAsset}) ${destAddress}. Current balance: ${oldBalance}`, + ); + } + + try { + const { txHash, sourceAddress } = await executeVaultSwap( + sourceAsset, + destAsset, + destAddress, + messageMetadata, + amount, + boostFeeBps, + fillOrKillParams, + dcaParams, + ); + swapContext?.updateStatus(swapTag, SwapStatus.VaultContractExecuted); + + const ccmEventEmitted = messageMetadata + ? observeCcmReceived(sourceAsset, destAsset, destAddress, messageMetadata, sourceAddress) + : Promise.resolve(); + + const [newBalance] = await Promise.all([ + observeBalanceIncrease(destAsset, destAddress, oldBalance), + ccmEventEmitted, + ]); + if (log) { + console.log(`${tag} Swap success! New balance: ${newBalance}!`); + } + swapContext?.updateStatus(swapTag, SwapStatus.Success); + return { + sourceAsset, + destAsset, + destAddress, + txHash, + }; + } catch (err) { + console.error('err:', err); + swapContext?.updateStatus(swapTag, SwapStatus.Failure); + if (err instanceof Error) { + console.log(err.stack); + } + throw new Error(`${tag} ${err}`); + } +} diff --git a/bouncer/shared/sol_vault_swap.ts b/bouncer/shared/sol_vault_swap.ts new file mode 100644 index 0000000000..e10025c89a --- /dev/null +++ b/bouncer/shared/sol_vault_swap.ts @@ -0,0 +1,210 @@ +import * as anchor from '@coral-xyz/anchor'; + +import { InternalAsset as Asset, Chains, assetConstants } from '@chainflip/cli'; +import { PublicKey, sendAndConfirmTransaction, Keypair } from '@solana/web3.js'; +import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from '@solana/spl-token'; +import { + getContractAddress, + amountToFineAmount, + defaultAssetAmounts, + chainFromAsset, + assetDecimals, + getSolWhaleKeyPair, + getSolConnection, + chainContractId, + decodeDotAddressForContract, + sleep, +} from './utils'; +import { CcmDepositMetadata } from './new_swap'; + +import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/swap_endpoint'; +import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0-swap-endpoint/vault'; +import { getSolanaSwapEndpointIdl, getSolanaVaultIdl } from './contract_interfaces'; + +// @ts-expect-error workaround because of anchor issue +const { BN } = anchor.default; + +const createdEventAccounts: PublicKey[] = []; + +// Temporary before the SDK implements this. +export async function executeSolVaultSwap( + srcAsset: Asset, + destAsset: Asset, + destAddress: string, + messageMetadata?: CcmDepositMetadata, + amount?: string, +) { + const destChain = chainFromAsset(destAsset); + + const solanaVaultDataAccount = new PublicKey(getContractAddress('Solana', 'DATA_ACCOUNT')); + const swapEndpointDataAccount = new PublicKey( + getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'), + ); + const whaleKeypair = getSolWhaleKeyPair(); + + // Using AnchorProvider runs into issues so instead we store the wallet in id.json and then + // set the ANCHOR_WALLET env. Depending on how the SDK is implemented we can remove this. + process.env.ANCHOR_WALLET = 'shared/solana_keypair.json'; + + const connection = getSolConnection(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const VaultIdl: any = await getSolanaVaultIdl(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const SwapEndpointIdl: any = await getSolanaSwapEndpointIdl(); + + const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); + const vaultProgram = new anchor.Program(VaultIdl as Vault); + + const newEventAccountKeypair = Keypair.generate(); + createdEventAccounts.push(newEventAccountKeypair.publicKey); + + const fetchedDataAccount = await vaultProgram.account.dataAccount.fetch(solanaVaultDataAccount); + const aggKey = fetchedDataAccount.aggKey; + + const amountToSwap = new BN( + amountToFineAmount(amount ?? defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)), + ); + + let cfParameters; + + if (messageMetadata) { + // TODO: Currently manually encoded. To use SDK/BrokerApi. + switch (destChain) { + case Chains.Ethereum: + case Chains.Arbitrum: + cfParameters = + '0x000001000000040101010101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; + break; + default: + throw new Error(`Unsupported chain: ${destChain}`); + } + } else { + // TODO: Currently manually encoded. To use SDK/BrokerApi. + switch (destChain) { + case Chains.Ethereum: + case Chains.Arbitrum: + cfParameters = + '0001000000040101010101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; + break; + case Chains.Polkadot: + cfParameters = + '0001000000010404040404040404040404040404040404040404040404040404040404040404000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; + break; + // TODO: Not supporting BTC for now because the encoding is annoying. + default: + throw new Error(`Unsupported chain: ${destChain}`); + } + } + + const destinationAddress = + destChain === Chains.Polkadot ? decodeDotAddressForContract(destAddress) : destAddress; + + const tx = + srcAsset === 'Sol' + ? await cfSwapEndpointProgram.methods + .xSwapNative({ + amount: amountToSwap, + dstChain: chainContractId(destChain), + dstAddress: Buffer.from(destinationAddress.slice(2), 'hex'), + dstToken: assetConstants[destAsset].contractId, + ccmParameters: messageMetadata + ? { + message: Buffer.from(messageMetadata.message.slice(2), 'hex'), + gasAmount: new BN(messageMetadata.gasBudget), + } + : null, + cfParameters: Buffer.from(cfParameters!.slice(2) ?? '', 'hex'), + }) + .accountsPartial({ + dataAccount: solanaVaultDataAccount, + aggKey, + from: whaleKeypair.publicKey, + eventDataAccount: newEventAccountKeypair.publicKey, + swapEndpointDataAccount, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([whaleKeypair, newEventAccountKeypair]) + .transaction() + : await cfSwapEndpointProgram.methods + .xSwapToken({ + amount: amountToSwap, + dstChain: chainContractId(destChain), + dstAddress: Buffer.from(destinationAddress.slice(2), 'hex'), + dstToken: assetConstants[destAsset].contractId, + ccmParameters: messageMetadata + ? { + message: Buffer.from(messageMetadata.message.slice(2), 'hex'), + gasAmount: new BN(messageMetadata.gasBudget), + } + : null, + cfParameters: Buffer.from(cfParameters!.slice(2) ?? '', 'hex'), + decimals: assetDecimals(srcAsset), + }) + .accountsPartial({ + dataAccount: solanaVaultDataAccount, + tokenVaultAssociatedTokenAccount: new PublicKey( + getContractAddress('Solana', 'TOKEN_VAULT_ATA'), + ), + from: whaleKeypair.publicKey, + fromTokenAccount: getAssociatedTokenAddressSync( + new PublicKey(getContractAddress('Solana', 'SolUsdc')), + whaleKeypair.publicKey, + false, + ), + eventDataAccount: newEventAccountKeypair.publicKey, + swapEndpointDataAccount, + tokenSupportedAccount: new PublicKey( + getContractAddress('Solana', 'SolUsdcTokenSupport'), + ), + tokenProgram: TOKEN_PROGRAM_ID, + mint: new PublicKey(getContractAddress('Solana', 'SolUsdc')), + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([whaleKeypair, newEventAccountKeypair]) + .transaction(); + const txHash = await sendAndConfirmTransaction(connection, tx, [ + whaleKeypair, + newEventAccountKeypair, + ]); + + console.log('tx', txHash); + return txHash; +} + +export async function checkSolEventAccountsClosure( + eventAccounts: PublicKey[] = createdEventAccounts, +) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const SwapEndpointIdl: any = await getSolanaSwapEndpointIdl(); + const cfSwapEndpointProgram = new anchor.Program(SwapEndpointIdl as SwapEndpoint); + const swapEndpointDataAccountAddress = new PublicKey( + getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'), + ); + + const maxRetries = 50; // 300 seconds + + for (let attempt = 0; attempt < maxRetries; attempt++) { + const swapEndpointDataAccount = + await cfSwapEndpointProgram.account.swapEndpointDataAccount.fetch( + swapEndpointDataAccountAddress, + ); + + if (swapEndpointDataAccount.openEventAccounts.length >= 10) { + await sleep(6000); + } else { + const onChainOpenedAccounts = swapEndpointDataAccount.openEventAccounts.map((element) => + element.toString(), + ); + for (const eventAccount of eventAccounts) { + if (!onChainOpenedAccounts.includes(eventAccount.toString())) { + const accountInfo = await getSolConnection().getAccountInfo(eventAccount); + if (accountInfo !== null) { + throw new Error('Event account still exists, should have been closed'); + } + } + } + return; + } + } + throw new Error('Timed out waiting for event accounts to be closed'); +} diff --git a/bouncer/shared/swapping.ts b/bouncer/shared/swapping.ts index c7e9520493..84f23783a6 100644 --- a/bouncer/shared/swapping.ts +++ b/bouncer/shared/swapping.ts @@ -3,7 +3,7 @@ import { Keypair, PublicKey } from '@solana/web3.js'; import Web3 from 'web3'; import { u8aToHex } from '@polkadot/util'; import { randomAsHex, randomAsNumber } from '../polkadot/util-crypto'; -import { performSwap } from '../shared/perform_swap'; +import { performSwap, performVaultSwap } from '../shared/perform_swap'; import { newAddress, chainFromAsset, @@ -16,7 +16,6 @@ import { } from '../shared/utils'; import { BtcAddressType } from '../shared/new_btc_address'; import { CcmDepositMetadata } from '../shared/new_swap'; -import { performVaultSwap } from './evm_vault_swap'; import { SwapContext, SwapStatus } from './swap_context'; enum SolidityType { diff --git a/bouncer/shared/utils.ts b/bouncer/shared/utils.ts index 39a147c904..ba60afe12d 100644 --- a/bouncer/shared/utils.ts +++ b/bouncer/shared/utils.ts @@ -50,6 +50,13 @@ export const evmChains = ['Ethereum', 'Arbitrum'] as Chain[]; export type Asset = SDKAsset; export type Chain = SDKChain; +export type VaultSwapParams = { + sourceAsset: Asset; + destAsset: Asset; + destAddress: string; + txHash: string; +}; + const isSDKAsset = (asset: Asset): asset is SDKAsset => asset in assetConstants; const isSDKChain = (chain: Chain): chain is SDKChain => chain in chainConstants; diff --git a/bouncer/tests/DCA_test.ts b/bouncer/tests/DCA_test.ts index 57b5d8c8d0..edf1dda287 100644 --- a/bouncer/tests/DCA_test.ts +++ b/bouncer/tests/DCA_test.ts @@ -11,9 +11,8 @@ import { send } from '../shared/send'; import { observeEvent, observeEvents } from '../shared/utils/substrate'; import { getBalance } from '../shared/get_balance'; import { ExecutableTest } from '../shared/executable_test'; -import { requestNewSwap } from '../shared/perform_swap'; +import { executeVaultSwap, requestNewSwap } from '../shared/perform_swap'; import { DcaParams, FillOrKillParamsX128 } from '../shared/new_swap'; -import { executeVaultSwap } from '../shared/evm_vault_swap'; /* eslint-disable @typescript-eslint/no-use-before-define */ export const testDCASwaps = new ExecutableTest('DCA-Swaps', main, 150); @@ -74,7 +73,7 @@ async function testDCASwap( await send(inputAsset, swapRequest.depositAddress, amount.toString()); testDCASwaps.log(`Sent ${amount} ${inputAsset} to ${swapRequest.depositAddress}`); } else { - const receipt = await executeVaultSwap( + const { txHash } = await executeVaultSwap( inputAsset, destAsset, destAddress, @@ -85,13 +84,13 @@ async function testDCASwap( dcaParams, ); - testDCASwaps.log(`Vault swap executed, tx hash: ${receipt.hash}`); + testDCASwaps.log(`Vault swap executed, tx hash: ${txHash}`); // Look after Swap Requested of data.origin.Vault.tx_hash swapRequestedHandle = observeSwapRequested( inputAsset, destAsset, - receipt.hash, + txHash, SwapRequestType.Regular, ); } diff --git a/bouncer/tests/all_concurrent_tests.ts b/bouncer/tests/all_concurrent_tests.ts index bca1bfa1f3..9c2dcad3a7 100755 --- a/bouncer/tests/all_concurrent_tests.ts +++ b/bouncer/tests/all_concurrent_tests.ts @@ -16,7 +16,7 @@ import { depositChannelCreation } from './request_swap_deposit_address_with_affi import { testDCASwaps } from './DCA_test'; import { testBrokerLevelScreening } from './broker_level_screening'; import { testBtcVaultSwap } from './btc_vault_swap'; -import { checkSolEventAccountsClosure } from '../shared/evm_vault_swap'; +import { checkSolEventAccountsClosure } from '../shared/sol_vault_swap'; async function runAllConcurrentTests() { // Specify the number of nodes via providing an argument to this script. diff --git a/bouncer/tests/all_swaps.ts b/bouncer/tests/all_swaps.ts index 55d203d827..b7bee979a0 100644 --- a/bouncer/tests/all_swaps.ts +++ b/bouncer/tests/all_swaps.ts @@ -1,10 +1,9 @@ import { InternalAsset as Asset, InternalAssets as Assets } from '@chainflip/cli'; -import { VaultSwapParams } from '../shared/evm_vault_swap'; import { ExecutableTest } from '../shared/executable_test'; import { SwapParams } from '../shared/perform_swap'; import { newCcmMetadata, testSwap, testVaultSwap } from '../shared/swapping'; import { btcAddressTypes } from '../shared/new_btc_address'; -import { ccmSupportedChains, chainFromAsset } from '../shared/utils'; +import { ccmSupportedChains, chainFromAsset, VaultSwapParams } from '../shared/utils'; /* eslint-disable @typescript-eslint/no-use-before-define */ export const testAllSwaps = new ExecutableTest('All-Swaps', main, 3000); diff --git a/bouncer/tests/fill_or_kill.ts b/bouncer/tests/fill_or_kill.ts index 84b40b2dfb..8620f2d0f7 100644 --- a/bouncer/tests/fill_or_kill.ts +++ b/bouncer/tests/fill_or_kill.ts @@ -9,13 +9,12 @@ import { observeSwapRequested, SwapRequestType, } from '../shared/utils'; -import { requestNewSwap } from '../shared/perform_swap'; +import { executeVaultSwap, requestNewSwap } from '../shared/perform_swap'; import { send } from '../shared/send'; import { getBalance } from '../shared/get_balance'; import { observeEvent } from '../shared/utils/substrate'; import { CcmDepositMetadata, FillOrKillParamsX128 } from '../shared/new_swap'; import { ExecutableTest } from '../shared/executable_test'; -import { executeVaultSwap } from '../shared/evm_vault_swap'; import { newCcmMetadata } from '../shared/swapping'; /* eslint-disable @typescript-eslint/no-use-before-define */ @@ -85,7 +84,7 @@ async function testMinPriceRefund(inputAsset: Asset, amount: number, swapViaVaul Math.random() < 0.5 ? ccmMetadata.ccmAdditionalData : undefined; } - const receipt = await executeVaultSwap( + const { txHash } = await executeVaultSwap( inputAsset, destAsset, destAddress, @@ -98,7 +97,7 @@ async function testMinPriceRefund(inputAsset: Asset, amount: number, swapViaVaul swapRequestedHandle = observeSwapRequested( inputAsset, destAsset, - receipt.hash, + txHash, SwapRequestType.Regular, ); } From 7f5ae333d397fd1c3ea59504dc416fa659e62c22 Mon Sep 17 00:00:00 2001 From: albert Date: Fri, 15 Nov 2024 10:42:48 +0100 Subject: [PATCH 46/62] chore: fix versioned decoding --- bouncer/shared/sol_vault_swap.ts | 4 ++-- engine/src/witness/common/cf_parameters.rs | 16 ++++++---------- .../src/witness/sol/program_swaps_witnessing.rs | 8 ++++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/bouncer/shared/sol_vault_swap.ts b/bouncer/shared/sol_vault_swap.ts index e10025c89a..94717cb49c 100644 --- a/bouncer/shared/sol_vault_swap.ts +++ b/bouncer/shared/sol_vault_swap.ts @@ -84,11 +84,11 @@ export async function executeSolVaultSwap( case Chains.Ethereum: case Chains.Arbitrum: cfParameters = - '0001000000040101010101010101010101010101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; + '0x0001000000000202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000' break; case Chains.Polkadot: cfParameters = - '0001000000010404040404040404040404040404040404040404040404040404040404040404000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; + '0x0001000000010404040404040404040404040404040404040404040404040404040404040404000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; break; // TODO: Not supporting BTC for now because the encoding is annoying. default: diff --git a/engine/src/witness/common/cf_parameters.rs b/engine/src/witness/common/cf_parameters.rs index e873f0d69a..5496e445df 100644 --- a/engine/src/witness/common/cf_parameters.rs +++ b/engine/src/witness/common/cf_parameters.rs @@ -37,12 +37,7 @@ mod tests { const MAX_CF_PARAM_LENGTH: u32 = MAX_CCM_ADDITIONAL_DATA_LENGTH + MAX_VAULT_SWAP_PARAMETERS_LENGTH; - const REFERENCE_EXPECTED_ENCODED: &[u8] = &[ - 0, 1, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 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, 0, 0, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 4, 0, 0, - ]; + const REFERENCE_EXPECTED_ENCODED_HEX: &str = "0001000000000202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000"; #[test] fn test_cf_parameters_max_length() { @@ -75,8 +70,9 @@ mod tests { }; let mut encoded = VersionedCfParameters::V0(cf_parameters).encode(); - - assert_eq!(encoded, REFERENCE_EXPECTED_ENCODED); + let expected_encoded: Vec = + hex::decode(REFERENCE_EXPECTED_ENCODED_HEX).expect("Decoding hex string failed"); + assert_eq!(encoded, expected_encoded); let ccm_cf_parameters = CfParameters { ccm_additional_data: CcmAdditionalData::default(), @@ -86,8 +82,8 @@ mod tests { encoded = VersionedCcmCfParameters::V0(ccm_cf_parameters).encode(); // Extra byte for the empty ccm metadata - let expected_encoded = [vec![0], Vec::from(REFERENCE_EXPECTED_ENCODED)].concat(); + let expected_encoded_with_metadata = [vec![0], expected_encoded.clone()].concat(); - assert_eq!(encoded, expected_encoded); + assert_eq!(encoded, expected_encoded_with_metadata); } } diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 4f3b026071..30c2038dc6 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -86,13 +86,13 @@ pub async fn get_program_swaps( let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { None => { - let CfParameters { ccm_additional_data: (), vault_swap_parameters } = - CfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding CfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + let VersionedCfParameters::V0(CfParameters { ccm_additional_data: (), vault_swap_parameters }) = + VersionedCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding VersionedCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; (None, vault_swap_parameters) }, Some(ccm_parameters) => { - let VersionedCfParameters::V0(CfParameters { ccm_additional_data, vault_swap_parameters }) = - VersionedCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding VersionedCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + let VersionedCcmCfParameters::V0(CfParameters { ccm_additional_data, vault_swap_parameters }) = + VersionedCcmCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding VersionedCcmCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; let deposit_metadata = Some(CcmDepositMetadata { source_chain: cf_primitives::ForeignChain::Solana, // TODO: Pass chain id from above? From 4033b59408830a2af72ec51f31a5f796df3613f4 Mon Sep 17 00:00:00 2001 From: albert Date: Fri, 15 Nov 2024 10:45:54 +0100 Subject: [PATCH 47/62] chore: fix bouncer lint --- bouncer/shared/sol_vault_swap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bouncer/shared/sol_vault_swap.ts b/bouncer/shared/sol_vault_swap.ts index 94717cb49c..5e7b8ca420 100644 --- a/bouncer/shared/sol_vault_swap.ts +++ b/bouncer/shared/sol_vault_swap.ts @@ -84,7 +84,7 @@ export async function executeSolVaultSwap( case Chains.Ethereum: case Chains.Arbitrum: cfParameters = - '0x0001000000000202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000' + '0x0001000000000202020202020202020202020202020202020202000000000000000000000000000000000000000000000000000000000000000000000303030303030303030303030303030303030303030303030303030303030303040000'; break; case Chains.Polkadot: cfParameters = From 0eee9b76875b7580d77df1e31fb04b535a225599 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Fri, 15 Nov 2024 14:25:22 +0100 Subject: [PATCH 48/62] feat: close accounts even for invalid swaps (#5405) * feat: close accounts even for invalid swaps * chore: remove unnecessary TODO * fix: - don't exit early if a single account is missing - handle missing accounts in rpc request method - more consistent error handling - tidy up imports - rpc error is error, not warning * fix: clippy * feat: rebase fix --------- Co-authored-by: albert Co-authored-by: Daniel --- .../witness/sol/program_swaps_witnessing.rs | 307 +++++++++++------- .../solana_vault_swap_accounts.rs | 14 +- 2 files changed, 201 insertions(+), 120 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 30c2038dc6..2d1738602a 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -1,13 +1,14 @@ -use super::super::common::cf_parameters::*; -use codec::Decode; -use std::collections::{BTreeSet, HashSet}; - -use crate::sol::{ - commitment_config::CommitmentConfig, - retry_rpc::{SolRetryRpcApi, SolRetryRpcClient}, - rpc_client_api::{RpcAccountInfoConfig, UiAccount, UiAccountData, UiAccountEncoding}, +use crate::{ + sol::{ + commitment_config::CommitmentConfig, + retry_rpc::{SolRetryRpcApi, SolRetryRpcClient}, + rpc_client_api::{RpcAccountInfoConfig, UiAccount, UiAccountData, UiAccountEncoding}, + }, + witness::common::cf_parameters::{ + CfParameters, VaultSwapParameters, VersionedCcmCfParameters, VersionedCfParameters, + }, }; -use anyhow::{anyhow, ensure}; +use anyhow::{anyhow, bail, ensure, Context}; use base64::Engine; use cf_chains::{ address::EncodedAddress, @@ -22,9 +23,11 @@ use cf_chains::{ }, CcmChannelMetadata, CcmDepositMetadata, ForeignChainAddress, }; +use codec::Decode; use futures::{stream, StreamExt, TryStreamExt}; use itertools::Itertools; use state_chain_runtime::chainflip::solana_elections::SolanaVaultSwapDetails; +use std::collections::{BTreeSet, HashSet}; use tracing::warn; const MAXIMUM_CONCURRENT_RPCS: usize = 16; @@ -48,7 +51,10 @@ pub async fn get_program_swaps( sc_closure_initiated_accounts: BTreeSet, usdc_token_mint_pubkey: SolAddress, ) -> Result< - (Vec<(VaultSwapAccountAndSender, SolanaVaultSwapDetails)>, Vec), + ( + Vec<(VaultSwapAccountAndSender, Option)>, + Vec, + ), anyhow::Error, > { let (new_program_swap_accounts, closed_accounts, slot) = get_changed_program_swap_accounts( @@ -69,96 +75,146 @@ pub async fn get_program_swaps( get_program_swap_event_accounts_data(sol_rpc, new_program_swap_accounts_chunk, slot) }) .buffered(MAXIMUM_CONCURRENT_RPCS) - .map_ok(|program_swap_account_data_chunk| { - stream::iter(program_swap_account_data_chunk.into_iter().filter_map( - |(account, program_swap_account_data)| match program_swap_account_data { - Some(data) => { - let from_asset = if let Some(token) = data.src_token { - if token == usdc_token_mint_pubkey.into() { - SolAsset::SolUsdc + .map_ok(stream::iter) + .try_flatten() + .filter_map(|response| { + futures::future::ready( + response + .inspect_err(|e| { + tracing::error!("Error querying for program swap account data: {e:?}"); + }) + .ok(), + ) + }) + .map( + |( + vault_swap_account, + SwapEvent { + creation_slot, + sender, + dst_chain, + dst_address, + dst_token, + amount, + src_token, + ccm_parameters, + cf_parameters, + }, + )| { + { + let vault_swap_details = move || { + let from_asset = + if let Some(token) = src_token { + if token == usdc_token_mint_pubkey.into() { + SolAsset::SolUsdc + } else { + bail!("Unsupported input token for the witnessed solana vault swap."); + } } else { - warn!("Unsupported output token for the witnessed solana vault swap, omitting the swap and the swap account."); - return None; - } - } else { - SolAsset::SolUsdc - }; + SolAsset::Sol + }; - let (deposit_metadata, vault_swap_parameters) = match data.ccm_parameters { + let ( + deposit_metadata, + VaultSwapParameters { + refund_params, + dca_params, + boost_fee, + broker_fee, + affiliate_fees, + }, + ) = match ccm_parameters { None => { - let VersionedCfParameters::V0(CfParameters { ccm_additional_data: (), vault_swap_parameters }) = - VersionedCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding VersionedCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; + let VersionedCfParameters::V0(CfParameters { + ccm_additional_data: (), + vault_swap_parameters, + }) = VersionedCfParameters::decode(&mut &cf_parameters[..]) + .map_err(|e| { + anyhow!("Error while decoding VersionedCfParameters for solana vault swap: {}.", e) + })?; (None, vault_swap_parameters) }, Some(ccm_parameters) => { - let VersionedCcmCfParameters::V0(CfParameters { ccm_additional_data, vault_swap_parameters }) = - VersionedCcmCfParameters::decode(&mut &data.cf_parameters[..]).map_err(|e| warn!("error while decoding VersionedCcmCfParameters for solana vault swap: {}. Omitting swap", e)).ok()?; - - let deposit_metadata = Some(CcmDepositMetadata { - source_chain: cf_primitives::ForeignChain::Solana, // TODO: Pass chain id from above? - source_address: Some(ForeignChainAddress::Sol(data.sender.into())), - channel_metadata: CcmChannelMetadata { - message: ccm_parameters.message - .to_vec() - .try_into() - .map_err(|_| anyhow!("Failed to deposit CCM: `message` too long.")).ok()?, - gas_budget: ccm_parameters.gas_amount.into(), - ccm_additional_data, - }, - }); - (deposit_metadata, vault_swap_parameters) - } + let VersionedCcmCfParameters::V0(CfParameters { + ccm_additional_data, + vault_swap_parameters + }) = VersionedCcmCfParameters::decode(&mut &cf_parameters[..]).map_err(|e| { + anyhow!("Error while decoding VersionedCcmCfParameters for solana vault swap: {}.", e) + }, + )?; + + ( + Some(CcmDepositMetadata { + source_chain: cf_primitives::ForeignChain::Solana, + source_address: Some(ForeignChainAddress::Sol( + sender.into(), + )), + channel_metadata: CcmChannelMetadata { + message: ccm_parameters + .message + .to_vec() + .try_into() + .map_err(|_| { + anyhow!( + "Failed to deposit CCM: `message` too long." + ) + })?, + gas_budget: ccm_parameters.gas_amount.into(), + ccm_additional_data, + }, + }), + vault_swap_parameters, + ) + }, }; - - Some(Ok::<_, anyhow::Error>((VaultSwapAccountAndSender { - vault_swap_account: account, - swap_sender: data.sender.into() - }, SolanaVaultSwapDetails { + Ok(SolanaVaultSwapDetails { from: from_asset, - deposit_amount: data.amount, - destination_address: EncodedAddress::from_chain_bytes(data.dst_chain.try_into().map_err(|e| warn!("error while parsing destination chain for solana vault swap:{}. Omitting swap", e)).ok()?, data.dst_address.to_vec()).map_err(|e| warn!("failed to decode the destination chain address for solana vault swap:{}. Omitting swap", e)).ok()?, - to: data.dst_token.try_into().map_err(|e| warn!("error while decoding destination token for solana vault swap: {}. Omitting swap", e)).ok()?, + deposit_amount: amount, + destination_address: EncodedAddress::from_chain_bytes( + dst_chain.try_into().map_err(|e| { + anyhow!("Error while parsing destination chain for solana vault swap:{}.", e) + })?, + dst_address.to_vec(), + ) + .map_err(|e| { + anyhow!("Failed to decode the destination address for solana vault swap:{}.", e) + })?, + to: dst_token.try_into().map_err(|e| { + anyhow!("Error while decoding destination token for solana vault swap: {}.", e) + })?, deposit_metadata, - swap_account: account, - creation_slot: data.creation_slot, - broker_fee: vault_swap_parameters.broker_fee, - affiliate_fees: vault_swap_parameters - .affiliate_fees + swap_account: vault_swap_account, + creation_slot, + broker_fee, + affiliate_fees: affiliate_fees .into_iter() .map(|entry| cf_primitives::Beneficiary { account: entry.affiliate.into(), bps: entry.fee.into() }) .collect_vec() .try_into() - .expect("runtime supports at least as many affiliates as we allow in cf_parameters encoding"), - refund_params: vault_swap_parameters.refund_params, - dca_params: vault_swap_parameters.dca_params, - boost_fee: vault_swap_parameters.boost_fee, - }))) - }, - - // It could happen that some account is closed between the queries. This should - // not happen because: - // 1. Accounts in `new_program_swap_accounts` can only be accounts that have - // newly been opened and they won't be closed until consensus is reached. - // 2. If due to rpc load management the get event accounts rpc is queried at a - // slot < get swap endpoint data rpc slot, the min_context_slot will prevent - // it from being executed before that. - // This could only happen if an engine is behind and were to see the account - // opened and closed between queries. That's not reallistic as it takes minutes - // for an account to be closed and even if it were to happen it's not - // problematic as we'd have reached consensus and the engine would just filter - // it out. - None => { - warn!("Event account not found for solana event account: {}", account); - None - }, - }, - )) - }) - .try_flatten() - .filter(|item| futures::future::ready(item.is_ok())) - .try_collect() - .await - .unwrap(); + .map_err(|_| { + anyhow!("runtime supports at least as many affiliates as we allow in cf_parameters encoding") + })?, + refund_params, + dca_params, + boost_fee, + }) + }; + ( + VaultSwapAccountAndSender { + vault_swap_account, + swap_sender: sender.into(), + }, + vault_swap_details() + .inspect_err(|e| { + warn!("Unable to derive swap details for account `{vault_swap_account}`: {e}") + }) + .ok(), + ) + } + }, + ) + .collect() + .await; Ok((new_swaps, closed_accounts)) @@ -260,10 +316,10 @@ async fn get_program_swap_event_accounts_data( sol_rpc: &SolRetryRpcClient, program_swap_event_accounts: Vec, min_context_slot: u64, -) -> Result)>, anyhow::Error> { - let accounts_info_response = sol_rpc +) -> anyhow::Result>> { + let account_infos = sol_rpc .get_multiple_accounts( - program_swap_event_accounts.as_slice(), + &program_swap_event_accounts[..], RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::Base64), data_slice: None, @@ -271,35 +327,58 @@ async fn get_program_swap_event_accounts_data( min_context_slot: Some(min_context_slot), }, ) - .await; - - let _slot = accounts_info_response.context.slot; - let accounts_info = accounts_info_response.value; + .await + .value; - ensure!(accounts_info.len() == program_swap_event_accounts.len()); + ensure!( + account_infos.len() == program_swap_event_accounts.len(), + "Number of queried accounts should match number of returned accounts." + ); - program_swap_event_accounts + Ok(program_swap_event_accounts .into_iter() - .zip(accounts_info.into_iter()) - .map(|(account, accounts_info)| match accounts_info { - Some(UiAccount { data: UiAccountData::Binary(base64_string, encoding), .. }) => { - if encoding != UiAccountEncoding::Base64 { - return Err(anyhow!("Data account encoding is not base64")); + .zip(account_infos.into_iter()) + .map(|(account, account_info)| { + Ok(( + account, + match account_info { + Some(UiAccount { + data: UiAccountData::Binary(base64_string, UiAccountEncoding::Base64), + .. + }) => { + let bytes = base64::engine::general_purpose::STANDARD + .decode(base64_string) + .map_err(|e| anyhow!("Failed to decode base64 string: {}", e))?; + + SwapEvent::check_and_deserialize(&bytes[..]) + .map_err(|e| anyhow!("Failed to deserialize data: {}", e)) + }, + Some(other) => Err(anyhow!( + "Expected UiAccountData::Binary(String, UiAccountEncoding::Base64), got {}", + match other.data { + UiAccountData::Binary(_, other) => format!("{:?}", other), + UiAccountData::Json(_) => "Json".to_string(), + UiAccountData::LegacyBinary(_) => "LegacyBinary".to_string(), + } + )), + // It could happen that some account is closed between the queries. This + // should not happen because: + // 1. Accounts in `new_program_swap_accounts` can only be accounts that have + // newly been opened and they won't be closed until consensus is reached. + // 2. If due to rpc load management the get event accounts rpc is queried at a + // slot < get swap endpoint data rpc slot, the min_context_slot will prevent + // it from being executed before that. + // This could only happen if an engine is behind and were to see the account + // opened and closed between queries. That's not realistic as it takes + // minutes for an account to be closed and even if it were to happen + // it's not problematic as we'd have reached consensus and the engine + // would just filter it out. + None => Err(anyhow!("Account does not exist.")), } - let bytes = base64::engine::general_purpose::STANDARD - .decode(base64_string) - .expect("Failed to decode base64 string"); - - let swap_event: SwapEvent = SwapEvent::check_and_deserialize(&bytes[..]) - .map_err(|e| anyhow!("Failed to deserialize data: {:?}", e))?; - - Ok((account, Some(swap_event))) - }, - Some(_) => - Err(anyhow!("Expected UiAccountData::Binary(String, UiAccountEncoding::Base64)")), - None => Ok((account, None)), + .context(format!("Error getting SwapEvent data for account `{account}`."))?, + )) }) - .collect() + .collect()) } #[cfg(test)] diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs index 91e1f71af0..dba191d281 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs @@ -58,7 +58,7 @@ impl BenchmarkValue for SolanaVaultSwapsKnownAccounts Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, Ord, PartialOrd, )] pub struct SolanaVaultSwapsVote { - pub new_accounts: BTreeSet<(Account, SwapDetails)>, + pub new_accounts: BTreeSet<(Account, Option)>, pub confirm_closed_accounts: BTreeSet, } @@ -68,10 +68,10 @@ impl Benchmark { fn benchmark_value() -> Self { Self { - new_accounts: BTreeSet::from([ + new_accounts: BTreeSet::from([( BenchmarkValue::benchmark_value(), - BenchmarkValue::benchmark_value(), - ]), + Some(BenchmarkValue::benchmark_value()), + )]), confirm_closed_accounts: BTreeSet::from([BenchmarkValue::benchmark_value()]), } } @@ -165,8 +165,10 @@ impl< let mut known_accounts = election_access.properties()?; election_access.delete(); known_accounts.witnessed_open_accounts.extend(consensus.new_accounts.iter().map( - |(account, swap_details)| { - Hook::initiate_vault_swap(swap_details.clone()); + |(account, maybe_swap_details)| { + if let Some(swap_details) = maybe_swap_details.as_ref() { + Hook::initiate_vault_swap(swap_details.clone()); + } account.clone() }, )); From e1312d13ebcf75c11e34fcd4b4a5883dea6a3041 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Wed, 20 Nov 2024 10:22:49 +0100 Subject: [PATCH 49/62] test: solana vault swap electoral system tests (#5382) * feat: tests * feat: refactor tests and new tests * feat: more test cases * chore: format * chore: comments * feat: more tests, addressed comments * chore: address comments * chore: address comment --- .../cf-elections/src/electoral_system.rs | 1 + .../src/electoral_systems/mocks.rs | 21 +- .../solana_vault_swap_accounts.rs | 4 +- .../src/electoral_systems/tests.rs | 1 + .../tests/solana_vault_swap_accounts.rs | 437 ++++++++++++++++++ 5 files changed, 459 insertions(+), 5 deletions(-) create mode 100644 state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs diff --git a/state-chain/pallets/cf-elections/src/electoral_system.rs b/state-chain/pallets/cf-elections/src/electoral_system.rs index 4ad624c090..115e2cf3e2 100644 --- a/state-chain/pallets/cf-elections/src/electoral_system.rs +++ b/state-chain/pallets/cf-elections/src/electoral_system.rs @@ -10,6 +10,7 @@ use crate::{ CorruptStorageError, ElectionIdentifier, }; +#[derive(Clone)] pub struct ConsensusVote { // If the validator hasn't voted, they will get a None. pub vote: Option<(VotePropertiesOf, ::Vote)>, diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs b/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs index 0ea98d014f..2f9b6a19c0 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs @@ -117,13 +117,17 @@ where // We may want to test initialisation of elections within on finalise, so *don't* want to // initialise an election in the utilities. pub fn build(self) -> TestContext { + let setup = self.clone(); + // We need to clear the storage at every build so if there are multiple test contexts used // within a single test they do not conflict. MockStorageAccess::clear_storage(); - MockStorageAccess::set_electoral_settings::(self.electoral_settings.clone()); + MockStorageAccess::set_electoral_settings::(setup.electoral_settings.clone()); + MockStorageAccess::set_unsynchronised_state::(setup.unsynchronised_state.clone()); + MockStorageAccess::set_unsynchronised_settings::(setup.unsynchronised_settings.clone()); - TestContext { setup: self.clone() } + TestContext { setup } } } @@ -136,8 +140,6 @@ impl TestContext { mut consensus_votes: ConsensusVotes, expected_consensus: Option, ) -> Self { - assert!(consensus_votes.num_authorities() > 0, "Cannot have zero authorities."); - use rand::seq::SliceRandom; consensus_votes.votes.shuffle(&mut rand::thread_rng()); @@ -189,6 +191,17 @@ impl TestContext { self } + pub fn expect_election_properties_only_election( + self, + expected_properties: ES::ElectionProperties, + ) -> Self { + assert_eq!( + MockStorageAccess::election_properties::(self.only_election_id()), + expected_properties + ); + self + } + /// Test the finalization of the election. /// /// `pre_finalize_checks` is a closure that is called with a read-only access to the electoral diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs index dba191d281..b96e7ba1a6 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs @@ -38,7 +38,9 @@ pub trait SolanaVaultSwapAccountsHook { pub type SolanaVaultSwapAccountsLastClosedAt = BlockNumber; -#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode)] +#[derive( + Clone, PartialEq, Eq, Debug, Serialize, Deserialize, TypeInfo, Encode, Decode, Default, +)] pub struct SolanaVaultSwapsKnownAccounts { pub witnessed_open_accounts: Vec, pub closure_initiated_accounts: BTreeSet, diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/tests.rs b/state-chain/pallets/cf-elections/src/electoral_systems/tests.rs index 84cdc6ad36..7fffd97d21 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/tests.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/tests.rs @@ -6,5 +6,6 @@ pub mod egress_success; pub mod liveness; pub mod monotonic_change; pub mod monotonic_median; +pub mod solana_vault_swap_accounts; pub mod unsafe_median; pub mod utils; diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs new file mode 100644 index 0000000000..598ec7be81 --- /dev/null +++ b/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs @@ -0,0 +1,437 @@ +use cf_chains::sol::{ + MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES, + MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, + NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES, +}; +use sp_std::collections::btree_set::BTreeSet; + +use super::{mocks::*, register_checks}; +use crate::{ + electoral_system::{ConsensusStatus, ConsensusVote, ConsensusVotes}, + electoral_systems::solana_vault_swap_accounts::{ + SolanaVaultSwapAccounts, SolanaVaultSwapAccountsHook, SolanaVaultSwapsKnownAccounts, + SolanaVaultSwapsVote, + }, +}; + +pub type Account = u64; +pub type SwapDetails = (); +pub type BlockNumber = u32; +pub type ValidatorId = (); + +thread_local! { + pub static CLOSE_ACCOUNTS_CALLED: std::cell::Cell = const { std::cell::Cell::new(0) }; + pub static INITIATE_VAULT_SWAP_CALLED: std::cell::Cell = const { std::cell::Cell::new(0) }; + pub static GET_NUMBER_OF_SOL_NONCES_CALLED: std::cell::Cell = const { std::cell::Cell::new(0) }; + pub static FAIL_CLOSE_ACCOUNTS: std::cell::Cell = const { std::cell::Cell::new(false) }; + pub static NO_OF_SOL_NONCES: std::cell::Cell = const { std::cell::Cell::new(10) }; +} + +struct MockHook; + +impl SolanaVaultSwapAccountsHook for MockHook { + fn close_accounts(_accounts: Vec) -> Result<(), ()> { + CLOSE_ACCOUNTS_CALLED.with(|hook_called| hook_called.set(hook_called.get() + 1)); + if FAIL_CLOSE_ACCOUNTS.with(|hook_called| hook_called.get()) { + Err(()) + } else { + Ok(()) + } + } + + fn initiate_vault_swap(_swap_details: SwapDetails) { + INITIATE_VAULT_SWAP_CALLED.with(|hook_called| hook_called.set(hook_called.get() + 1)); + } + + fn get_number_of_available_sol_nonce_accounts() -> usize { + GET_NUMBER_OF_SOL_NONCES_CALLED.with(|hook_called| hook_called.set(hook_called.get() + 1)); + NO_OF_SOL_NONCES.with(|hook_called| hook_called.get()) + } +} + +impl MockHook { + pub fn close_accounts_called() -> u8 { + CLOSE_ACCOUNTS_CALLED.with(|hook_called| hook_called.get()) + } + pub fn init_swap_called() -> u8 { + INITIATE_VAULT_SWAP_CALLED.with(|hook_called| hook_called.get()) + } + pub fn get_number_of_available_sol_nonce_accounts_called() -> u8 { + GET_NUMBER_OF_SOL_NONCES_CALLED.with(|hook_called| hook_called.get()) + } +} + +type MinimalVaultSwapAccounts = + SolanaVaultSwapAccounts; + +register_checks! { + MinimalVaultSwapAccounts { + only_one_election(_pre, post) { + assert_eq!(post.election_identifiers.len(), 1, "Only one election should exist."); + }, + initiate_vault_swap_hook_not_called(_pre, _post) { + assert_eq!(INITIATE_VAULT_SWAP_CALLED.with(|hook_called| hook_called.get()), 0, "Hook should have been called once so far!"); + }, + initiate_vault_swap_hook_called_twice(_pre, _post) { + assert_eq!(INITIATE_VAULT_SWAP_CALLED.with(|hook_called| hook_called.get()), 2, "Hook not called expected number of times"); + }, + initiate_vault_swap_hook_called_four_times(_pre,_post) { + assert_eq!(INITIATE_VAULT_SWAP_CALLED.with(|hook_called| hook_called.get()), 4, "Hook not called expected number of times"); + }, + initiate_vault_swap_hook_called_15_times(_pre, _post) { + assert_eq!(INITIATE_VAULT_SWAP_CALLED.with(|hook_called| hook_called.get()), 15, "Hook not called expected number of times"); + }, + close_accounts_hook_not_called(_pre, _post) { + assert_eq!(CLOSE_ACCOUNTS_CALLED.with(|hook_called| hook_called.get()), 0, "Hook should not have been called!"); + }, + close_accounts_hook_called_once(_pre, _post) { + assert_eq!(CLOSE_ACCOUNTS_CALLED.with(|hook_called| hook_called.get()), 1, "Hook not called expected number of times"); + }, + close_accounts_hook_called_twice(_pre, _post) { + assert_eq!(CLOSE_ACCOUNTS_CALLED.with(|hook_called| hook_called.get()), 2, "Hook not called expected number of times"); + }, + get_sol_nonces_hook_not_called(_pre, _post) { + assert_eq!(GET_NUMBER_OF_SOL_NONCES_CALLED.with(|hook_called| hook_called.get()), 0, "Hook should not have been called!"); + }, + get_sol_nonces_hook_called_once(_pre, _post) { + assert_eq!(GET_NUMBER_OF_SOL_NONCES_CALLED.with(|hook_called| hook_called.get()), 1, "Hook not called expected number of times"); + }, + get_sol_nonces_hook_called_twice(_pre, _post) { + assert_eq!(GET_NUMBER_OF_SOL_NONCES_CALLED.with(|hook_called| hook_called.get()), 2, "Hook not called expected number of times"); + }, + } +} + +pub const TEST_NUMBER_OF_ACCOUNTS: u64 = 15; + +#[test] +fn on_finalize_accounts_limit_reached() { + TestSetup::default() + .with_unsynchronised_state(0) + .build() + .test_on_finalize( + &0u32, + |_| { + assert_eq!( + MockHook::close_accounts_called(), + 0, + "Hook should not have been called!" + ); + assert_eq!(MockHook::init_swap_called(), 0, "Hook should not have been called!"); + assert_eq!( + MockHook::get_number_of_available_sol_nonce_accounts_called(), + 0, + "Hook should not have been called!" + ); + }, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_not_called(), + Check::::close_accounts_hook_not_called(), + Check::::get_sol_nonces_hook_not_called(), + ], + ) + .force_consensus_update(ConsensusStatus::Gained { + new: generate_votes_for_account_range(0..TEST_NUMBER_OF_ACCOUNTS), + most_recent: None, + }) + // account closure will be initiated since account limit is reached, even though time limit + // has not reached yet. + .test_on_finalize( + &1u32, + |_| {}, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_called_15_times(), + Check::::close_accounts_hook_called_once(), + Check::::get_sol_nonces_hook_called_once(), + ], + ); +} + +#[test] +fn on_finalize_time_limit_reached() { + TestSetup::default() + .with_unsynchronised_state(0) + .build() + .test_on_finalize( + &0u32, + |_| { + assert_eq!( + MockHook::close_accounts_called(), + 0, + "Hook should not have been called!" + ); + assert_eq!(MockHook::init_swap_called(), 0, "Hook should not have been called!"); + assert_eq!( + MockHook::get_number_of_available_sol_nonce_accounts_called(), + 0, + "Hook should not have been called!" + ); + }, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_not_called(), + Check::::close_accounts_hook_not_called(), + Check::::get_sol_nonces_hook_not_called(), + ], + ) + .force_consensus_update(ConsensusStatus::Gained { + new: generate_votes_for_account_range(0..2), + most_recent: None, + }) + // account closure will not initiate since we havent reached time or account limit + .test_on_finalize( + &0, + |_| {}, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_called_twice(), + Check::::close_accounts_hook_not_called(), + Check::::get_sol_nonces_hook_called_once(), + ], + ) + .force_consensus_update(ConsensusStatus::Gained { + new: generate_votes_for_account_range(2..4), + most_recent: None, + }) + // time limit reached. account closure initiated even though account number limit not + // reached + .test_on_finalize( + &MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, + |_| {}, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_called_four_times(), + Check::::close_accounts_hook_called_once(), + Check::::get_sol_nonces_hook_called_twice(), + ], + ); +} + +#[test] +fn on_finalize_close_accounts_error() { + let max_batch_size: u64 = MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES.try_into().unwrap(); + FAIL_CLOSE_ACCOUNTS.with(|hook_called| hook_called.set(true)); + TestSetup::default() + .with_unsynchronised_state(0) + .build() + .test_on_finalize( + &0u32, + |_| { + assert_eq!( + MockHook::close_accounts_called(), + 0, + "Hook should not have been called!" + ); + assert_eq!(MockHook::init_swap_called(), 0, "Hook should not have been called!"); + assert_eq!( + MockHook::get_number_of_available_sol_nonce_accounts_called(), + 0, + "Hook should not have been called!" + ); + }, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_not_called(), + Check::::close_accounts_hook_not_called(), + Check::::get_sol_nonces_hook_not_called(), + ], + ) + .force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: generate_votes_for_account_range(0..TEST_NUMBER_OF_ACCOUNTS), + }) + .test_on_finalize( + &1u32, + |_| {}, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_called_15_times(), + Check::::close_accounts_hook_called_once(), + Check::::get_sol_nonces_hook_called_once(), + ], + ) + .expect_election_properties_only_election(SolanaVaultSwapsKnownAccounts { + // if close_accounts errors, the accounts are pushed back into open accounts at the end + // of the vector. + witnessed_open_accounts: (max_batch_size..TEST_NUMBER_OF_ACCOUNTS) + .chain(0u64..max_batch_size) + .collect::>(), + closure_initiated_accounts: BTreeSet::new(), + }); +} + +#[test] +fn on_finalize_nonces_below_threshold() { + NO_OF_SOL_NONCES.with(|hook_called| { + hook_called.set(NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES - 1) + }); + TestSetup::default() + .with_unsynchronised_state(0) + .build() + .test_on_finalize( + &0u32, + |_| { + assert_eq!( + MockHook::close_accounts_called(), + 0, + "Hook should not have been called!" + ); + assert_eq!(MockHook::init_swap_called(), 0, "Hook should not have been called!"); + assert_eq!( + MockHook::get_number_of_available_sol_nonce_accounts_called(), + 0, + "Hook should not have been called!" + ); + }, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_not_called(), + Check::::close_accounts_hook_not_called(), + Check::::get_sol_nonces_hook_not_called(), + ], + ) + .force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: generate_votes_for_account_range(0..TEST_NUMBER_OF_ACCOUNTS), + }) + .test_on_finalize( + &1u32, + |_| {}, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_called_15_times(), + Check::::close_accounts_hook_not_called(), + Check::::get_sol_nonces_hook_called_once(), + ], + ) + .expect_election_properties_only_election(SolanaVaultSwapsKnownAccounts { + witnessed_open_accounts: (0..TEST_NUMBER_OF_ACCOUNTS).collect::>(), + closure_initiated_accounts: BTreeSet::new(), + }); +} + +pub const NEW_ACCOUNT_1: u64 = 1u64; +pub const NEW_ACCOUNT_2: u64 = 2u64; +pub const NEW_ACCOUNT_3: u64 = 3u64; + +pub const CLOSED_ACCOUNT_1: u64 = 4u64; +pub const CLOSED_ACCOUNT_2: u64 = 5u64; + +#[test] +fn test_consensus() { + TestSetup::::default() + .build_with_initial_election() + .expect_consensus( + generate_votes_specific_case([80, 80, 0, 0]), + Some(SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, ()), (NEW_ACCOUNT_2, ())]), + confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), + }), + ); + + TestSetup::::default() + .build_with_initial_election() + .expect_consensus( + generate_votes_specific_case([0, 80, 80, 80]), + Some(SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([ + (NEW_ACCOUNT_1, ()), + (NEW_ACCOUNT_2, ()), + (NEW_ACCOUNT_3, ()), + ]), + confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), + }), + ); + + TestSetup::::default() + .build_with_initial_election() + .expect_consensus( + generate_votes_specific_case([0, 0, 80, 80]), + Some(SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(NEW_ACCOUNT_3, ())]), + confirm_closed_accounts: BTreeSet::from([]), + }), + ); + + TestSetup::::default() + .build_with_initial_election() + .expect_consensus(ConsensusVotes { votes: vec![] }, None); + + TestSetup::::default() + .build_with_initial_election() + .expect_consensus(generate_vote_no_consensus(), None); +} + +fn generate_vote_no_consensus() -> ConsensusVotes { + let vote_1 = SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(1, ()), (2, ())]), + confirm_closed_accounts: BTreeSet::new(), + }; + + let vote_2 = SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(3, ()), (4, ())]), + confirm_closed_accounts: BTreeSet::new(), + }; + + ConsensusVotes { + votes: (0..80) + .map(|_| ConsensusVote { vote: Some(((), vote_1.clone())), validator_id: () }) + .chain( + (0..80) + .map(|_| ConsensusVote { vote: Some(((), vote_2.clone())), validator_id: () }), + ) + .collect::>(), + } +} + +fn generate_votes_specific_case( + no_of_each_vote: [usize; 4], +) -> ConsensusVotes { + let votes = [ + SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([ + (NEW_ACCOUNT_1, ()), + (NEW_ACCOUNT_2, ()), + (NEW_ACCOUNT_3, ()), + ]), + confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1, CLOSED_ACCOUNT_2]), + }, + SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, ()), (NEW_ACCOUNT_2, ())]), + confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), + }, + SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, ()), (NEW_ACCOUNT_3, ())]), + confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), + }, + SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(NEW_ACCOUNT_2, ()), (NEW_ACCOUNT_3, ())]), + confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_2]), + }, + ]; + ConsensusVotes { + votes: no_of_each_vote + .iter() + .enumerate() + .flat_map(|(i, &count)| { + let vote = votes[i].clone(); + std::iter::repeat_with(move || ConsensusVote { + vote: Some(((), vote.clone())), + validator_id: (), + }) + .take(count) + }) + .collect::>(), + } +} + +fn generate_votes_for_account_range( + r: std::ops::Range, +) -> SolanaVaultSwapsVote { + SolanaVaultSwapsVote { + new_accounts: r.map(|i| (i, ())).collect::>(), + confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1, CLOSED_ACCOUNT_2]), + } +} From 850eba7e2a8d04d63f6ac6a7f1fb48a819b08553 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Wed, 20 Nov 2024 10:59:16 +0100 Subject: [PATCH 50/62] fix: merge fixes --- .../tests/solana_vault_swap_accounts.rs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs index 598ec7be81..0f8e6c687d 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs @@ -326,7 +326,10 @@ fn test_consensus() { .expect_consensus( generate_votes_specific_case([80, 80, 0, 0]), Some(SolanaVaultSwapsVote { - new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, ()), (NEW_ACCOUNT_2, ())]), + new_accounts: BTreeSet::from([ + (NEW_ACCOUNT_1, Some(())), + (NEW_ACCOUNT_2, Some(())), + ]), confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), }), ); @@ -337,9 +340,9 @@ fn test_consensus() { generate_votes_specific_case([0, 80, 80, 80]), Some(SolanaVaultSwapsVote { new_accounts: BTreeSet::from([ - (NEW_ACCOUNT_1, ()), - (NEW_ACCOUNT_2, ()), - (NEW_ACCOUNT_3, ()), + (NEW_ACCOUNT_1, Some(())), + (NEW_ACCOUNT_2, Some(())), + (NEW_ACCOUNT_3, Some(())), ]), confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), }), @@ -350,7 +353,7 @@ fn test_consensus() { .expect_consensus( generate_votes_specific_case([0, 0, 80, 80]), Some(SolanaVaultSwapsVote { - new_accounts: BTreeSet::from([(NEW_ACCOUNT_3, ())]), + new_accounts: BTreeSet::from([(NEW_ACCOUNT_3, Some(()))]), confirm_closed_accounts: BTreeSet::from([]), }), ); @@ -366,12 +369,12 @@ fn test_consensus() { fn generate_vote_no_consensus() -> ConsensusVotes { let vote_1 = SolanaVaultSwapsVote { - new_accounts: BTreeSet::from([(1, ()), (2, ())]), + new_accounts: BTreeSet::from([(1, Some(())), (2, Some(()))]), confirm_closed_accounts: BTreeSet::new(), }; let vote_2 = SolanaVaultSwapsVote { - new_accounts: BTreeSet::from([(3, ()), (4, ())]), + new_accounts: BTreeSet::from([(3, Some(())), (4, Some(()))]), confirm_closed_accounts: BTreeSet::new(), }; @@ -392,22 +395,22 @@ fn generate_votes_specific_case( let votes = [ SolanaVaultSwapsVote { new_accounts: BTreeSet::from([ - (NEW_ACCOUNT_1, ()), - (NEW_ACCOUNT_2, ()), - (NEW_ACCOUNT_3, ()), + (NEW_ACCOUNT_1, Some(())), + (NEW_ACCOUNT_2, Some(())), + (NEW_ACCOUNT_3, Some(())), ]), confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1, CLOSED_ACCOUNT_2]), }, SolanaVaultSwapsVote { - new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, ()), (NEW_ACCOUNT_2, ())]), + new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, Some(())), (NEW_ACCOUNT_2, Some(()))]), confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), }, SolanaVaultSwapsVote { - new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, ()), (NEW_ACCOUNT_3, ())]), + new_accounts: BTreeSet::from([(NEW_ACCOUNT_1, Some(())), (NEW_ACCOUNT_3, Some(()))]), confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1]), }, SolanaVaultSwapsVote { - new_accounts: BTreeSet::from([(NEW_ACCOUNT_2, ()), (NEW_ACCOUNT_3, ())]), + new_accounts: BTreeSet::from([(NEW_ACCOUNT_2, Some(())), (NEW_ACCOUNT_3, Some(()))]), confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_2]), }, ]; @@ -431,7 +434,7 @@ fn generate_votes_for_account_range( r: std::ops::Range, ) -> SolanaVaultSwapsVote { SolanaVaultSwapsVote { - new_accounts: r.map(|i| (i, ())).collect::>(), + new_accounts: r.map(|i| (i, Some(()))).collect::>(), confirm_closed_accounts: BTreeSet::from([CLOSED_ACCOUNT_1, CLOSED_ACCOUNT_2]), } } From 21a1cf44322446d2fa25b6ebd63f1aebd47a7585 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Wed, 20 Nov 2024 11:25:52 +0100 Subject: [PATCH 51/62] feat: additional test for invalid swap case --- .../tests/solana_vault_swap_accounts.rs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs index 0f8e6c687d..0e72517a93 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/tests/solana_vault_swap_accounts.rs @@ -312,6 +312,53 @@ fn on_finalize_nonces_below_threshold() { }); } +#[test] +fn on_finalize_invalid_swap() { + TestSetup::default() + .with_unsynchronised_state(0) + .build() + .test_on_finalize( + &0u32, + |_| { + assert_eq!( + MockHook::close_accounts_called(), + 0, + "Hook should not have been called!" + ); + assert_eq!(MockHook::init_swap_called(), 0, "Hook should not have been called!"); + assert_eq!( + MockHook::get_number_of_available_sol_nonce_accounts_called(), + 0, + "Hook should not have been called!" + ); + }, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_not_called(), + Check::::close_accounts_hook_not_called(), + Check::::get_sol_nonces_hook_not_called(), + ], + ) + // we have a new account but it is an invalid swap + .force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: SolanaVaultSwapsVote { + new_accounts: BTreeSet::from([(0, None)]), + confirm_closed_accounts: BTreeSet::new(), + }, + }) + .test_on_finalize( + &MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS, + |_| {}, + vec![ + Check::::only_one_election(), + Check::::initiate_vault_swap_hook_not_called(), + Check::::close_accounts_hook_called_once(), + Check::::get_sol_nonces_hook_called_once(), + ], + ); +} + pub const NEW_ACCOUNT_1: u64 = 1u64; pub const NEW_ACCOUNT_2: u64 = 2u64; pub const NEW_ACCOUNT_3: u64 = 3u64; From 2ca01b02c7a3df53e6b7a925f78b94401528ce0d Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Wed, 20 Nov 2024 11:28:11 +0100 Subject: [PATCH 52/62] chore: minor --- .../src/electoral_systems/solana_vault_swap_accounts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs index b96e7ba1a6..ce92e88461 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/solana_vault_swap_accounts.rs @@ -275,7 +275,7 @@ impl< } } -pub fn count_votes( +fn count_votes( accounts: &BTreeSet, counts_accounts: &mut BTreeMap, count: &u32, From 7eeb355cd48a6732e461981687f87864cb63fd4b Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Wed, 20 Nov 2024 12:46:08 +0100 Subject: [PATCH 53/62] fix: remove expects --- engine/src/witness/sol/program_swaps_witnessing.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 2d1738602a..f13f181f2f 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -230,9 +230,7 @@ async fn get_changed_program_swap_accounts( swap_endpoint_data_account_address: SolAddress, ) -> Result<(Vec, Vec, u64), anyhow::Error> { let (_historical_number_event_accounts, open_event_accounts, slot) = - get_swap_endpoint_data(sol_rpc, swap_endpoint_data_account_address) - .await - .expect("Failed to get the event accounts"); + get_swap_endpoint_data(sol_rpc, swap_endpoint_data_account_address).await?; let new_program_swap_accounts: Vec<_> = open_event_accounts .iter() @@ -280,9 +278,7 @@ async fn get_swap_endpoint_data( if encoding != UiAccountEncoding::Base64 { return Err(anyhow!("Data account encoding is not base64")); } - let bytes = base64::engine::general_purpose::STANDARD - .decode(base64_string) - .expect("Failed to decode base64 string"); + let bytes = base64::engine::general_purpose::STANDARD.decode(base64_string)?; // 8 Discriminator + 16 Historical Number Event Accounts + 4 bytes vector length + data if bytes.len() < ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH + 20 { From 7027910934cfc50f19c96515e6752348700f3e03 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 20 Nov 2024 13:49:01 +0100 Subject: [PATCH 54/62] chore: clearer error message --- engine/src/witness/sol/program_swaps_witnessing.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index f13f181f2f..4ca9e5df24 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -350,11 +350,13 @@ async fn get_program_swap_event_accounts_data( .map_err(|e| anyhow!("Failed to deserialize data: {}", e)) }, Some(other) => Err(anyhow!( - "Expected UiAccountData::Binary(String, UiAccountEncoding::Base64), got {}", + "Expected UiAccountData::Binary(_, UiAccountEncoding::Base64), got {}", match other.data { - UiAccountData::Binary(_, other) => format!("{:?}", other), - UiAccountData::Json(_) => "Json".to_string(), - UiAccountData::LegacyBinary(_) => "LegacyBinary".to_string(), + UiAccountData::Binary(_, other) => + format!("UiAccountData::Binary(_, {:?})", other), + UiAccountData::Json(_) => "UiAccountData::Json(_)".to_string(), + UiAccountData::LegacyBinary(_) => + "UiAccountData::LegacyBinary(_)".to_string(), } )), // It could happen that some account is closed between the queries. This From be8c5a1b580c2a97736cd9d045d7bca2c64ed650 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 21 Nov 2024 01:53:19 +0100 Subject: [PATCH 55/62] feat: migration --- state-chain/pallets/cf-elections/src/lib.rs | 4 +- .../runtime/src/chainflip/solana_elections.rs | 2 +- state-chain/runtime/src/lib.rs | 10 +- state-chain/runtime/src/migrations.rs | 1 + .../solana_vault_swaps_migration.rs | 106 ++++++++++++++++++ 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs diff --git a/state-chain/pallets/cf-elections/src/lib.rs b/state-chain/pallets/cf-elections/src/lib.rs index 7b8774012c..d4e3bc0e1f 100644 --- a/state-chain/pallets/cf-elections/src/lib.rs +++ b/state-chain/pallets/cf-elections/src/lib.rs @@ -124,7 +124,7 @@ use frame_system::pallet_prelude::*; pub use pallet::*; -pub const PALLET_VERSION: StorageVersion = StorageVersion::new(1); +pub const PALLET_VERSION: StorageVersion = StorageVersion::new(2); pub use pallet::UniqueMonotonicIdentifier; @@ -492,7 +492,7 @@ pub mod pallet { /// Stores persistent state the electoral system needs. #[pallet::storage] - pub(crate) type ElectoralUnsynchronisedState, I: 'static = ()> = StorageValue< + pub type ElectoralUnsynchronisedState, I: 'static = ()> = StorageValue< _, ::ElectoralUnsynchronisedState, OptionQuery, diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 8963423bce..4d3749b5a8 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -84,7 +84,7 @@ pub fn initial_state( (), (), (), - 0, + 0u32, ), unsynchronised_settings: ( (), diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 31e9c19400..90c11ab230 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -67,7 +67,7 @@ use cf_traits::{ }; use codec::{alloc::string::ToString, Decode, Encode}; use core::ops::Range; -use frame_support::{derive_impl, instances::*}; +use frame_support::{derive_impl, instances::*, migrations::VersionedMigration}; pub use frame_system::Call as SystemCall; use pallet_cf_governance::GovCallHash; use pallet_cf_ingress_egress::{ @@ -1291,7 +1291,13 @@ type PalletMigrations = ( pallet_cf_cfe_interface::migrations::PalletMigration, ); -type MigrationsForV1_8 = (); +type MigrationsForV1_8 = VersionedMigration< + 1, + 2, + migrations::solana_vault_swaps_migration::SolanaVaultSwapsMigration, + pallet_cf_elections::Pallet, + DbWeight, +>; #[cfg(feature = "runtime-benchmarks")] #[macro_use] diff --git a/state-chain/runtime/src/migrations.rs b/state-chain/runtime/src/migrations.rs index 187dd5164c..b32758552b 100644 --- a/state-chain/runtime/src/migrations.rs +++ b/state-chain/runtime/src/migrations.rs @@ -2,3 +2,4 @@ pub mod housekeeping; pub mod reap_old_accounts; +pub mod solana_vault_swaps_migration; diff --git a/state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs b/state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs new file mode 100644 index 0000000000..539f97bfb2 --- /dev/null +++ b/state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs @@ -0,0 +1,106 @@ +use crate::*; +use chainflip::solana_elections::SolanaVaultSwapsSettings; +use frame_support::{pallet_prelude::Weight, storage::unhashed, traits::UncheckedOnRuntimeUpgrade}; + +use pallet_cf_elections::{ + Config, ElectoralSettings, ElectoralSystemRunner, ElectoralUnsynchronisedState, +}; +#[cfg(feature = "try-runtime")] +use sp_runtime::DispatchError; + +use cf_utilities::bs58_array; +use codec::{Decode, Encode}; + +pub struct SolanaVaultSwapsMigration; + +impl UncheckedOnRuntimeUpgrade for SolanaVaultSwapsMigration { + fn on_runtime_upgrade() -> Weight { + let mut raw_unsynchronised_state = unhashed::get_raw(&ElectoralUnsynchronisedState::< + Runtime, + SolanaInstance, + >::hashed_key()) + .unwrap(); + raw_unsynchronised_state.extend(0u32.encode()); + ElectoralUnsynchronisedState::::put(<>::ElectoralSystemRunner as ElectoralSystemRunner>::ElectoralUnsynchronisedState::decode(&mut &raw_unsynchronised_state[..]).unwrap()); + + let (usdc_token_mint_pubkey, swap_endpoint_data_account_address) = + match cf_runtime_utilities::genesis_hashes::genesis_hash::() { + cf_runtime_utilities::genesis_hashes::BERGHAIN => ( + SolAddress(bs58_array("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")), + SolAddress(bs58_array("5mFsKrqCH5v9Q9uF5o6qrsUi1GV2myuhc23NAi5YFs4M")), + ), + cf_runtime_utilities::genesis_hashes::PERSEVERANCE => ( + SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), + SolAddress(bs58_array("4hD7UM6rQtcqQWtzELvrafpmBYReVXvCpssB6qjY1Sg5")), + ), + cf_runtime_utilities::genesis_hashes::SISYPHOS => ( + SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), + SolAddress(bs58_array("mYabVW1uMXpGqwgHUBQu4Fg6GT9EMYUzYaGYbi3zgT7")), + ), + _ => ( + SolAddress(bs58_array("24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p")), + SolAddress(bs58_array("2tmtGLQcBd11BMiE9B1tAkQXwmPNgR79Meki2Eme4Ec9")), + ), + }; + + for key in ElectoralSettings::::iter_keys() { + let mut raw_storage_at_key = unhashed::get_raw(&ElectoralSettings::< + Runtime, + SolanaInstance, + >::hashed_key_for(key)) + .expect("We just got the keys directly from the storage"); + raw_storage_at_key.extend( + SolanaVaultSwapsSettings { + usdc_token_mint_pubkey, + swap_endpoint_data_account_address, + } + .encode(), + ); + ElectoralSettings::::insert(key, <>::ElectoralSystemRunner as ElectoralSystemRunner>::ElectoralSettings::decode(&mut &raw_storage_at_key[..]).unwrap()); + } + + Weight::zero() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + assert!(ElectoralUnsynchronisedState::::exists()); + assert!(ElectoralSettings::::iter_keys().next().is_some()); + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), DispatchError> { + let (.., last_block_number) = + ElectoralUnsynchronisedState::::get().unwrap(); + assert_eq!(last_block_number, 0u32); + for ( + .., + SolanaVaultSwapsSettings { usdc_token_mint_pubkey, swap_endpoint_data_account_address }, + ) in ElectoralSettings::::iter_values() + { + assert_eq!( + (usdc_token_mint_pubkey, swap_endpoint_data_account_address), + match cf_runtime_utilities::genesis_hashes::genesis_hash::() { + cf_runtime_utilities::genesis_hashes::BERGHAIN => ( + SolAddress(bs58_array("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")), + SolAddress(bs58_array("5mFsKrqCH5v9Q9uF5o6qrsUi1GV2myuhc23NAi5YFs4M")), + ), + cf_runtime_utilities::genesis_hashes::PERSEVERANCE => ( + SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), + SolAddress(bs58_array("4hD7UM6rQtcqQWtzELvrafpmBYReVXvCpssB6qjY1Sg5")), + ), + cf_runtime_utilities::genesis_hashes::SISYPHOS => ( + SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), + SolAddress(bs58_array("mYabVW1uMXpGqwgHUBQu4Fg6GT9EMYUzYaGYbi3zgT7")), + ), + _ => ( + SolAddress(bs58_array("24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p")), + SolAddress(bs58_array("2tmtGLQcBd11BMiE9B1tAkQXwmPNgR79Meki2Eme4Ec9")), + ), + } + ); + } + Ok(()) + } +} From aa607bd7c78cd149929e32b30b1fc65cf7ce1e8c Mon Sep 17 00:00:00 2001 From: albert Date: Thu, 21 Nov 2024 07:34:03 +0100 Subject: [PATCH 56/62] chore: test upgrade --- .github/workflows/ci-development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-development.yml b/.github/workflows/ci-development.yml index bc66706607..149542ba6e 100644 --- a/.github/workflows/ci-development.yml +++ b/.github/workflows/ci-development.yml @@ -79,7 +79,7 @@ jobs: uses: ./.github/workflows/upgrade-test.yml secrets: inherit with: - run-job: false + run-job: true publish: needs: [package] uses: ./.github/workflows/_30_publish.yml From 9bda26556d365c53fc289884d91b5374f8c4a6f5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 21 Nov 2024 10:29:48 +0100 Subject: [PATCH 57/62] chore: update migration versions --- state-chain/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 37b32613e9..33e78cf075 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -1293,8 +1293,8 @@ type PalletMigrations = ( ); type MigrationsForV1_8 = VersionedMigration< - 1, 2, + 3, migrations::solana_vault_swaps_migration::SolanaVaultSwapsMigration, pallet_cf_elections::Pallet, DbWeight, From 284e81e1fe1d3ba82ef99600e964f0805dd09a80 Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 21 Nov 2024 10:45:34 +0100 Subject: [PATCH 58/62] chore: update version --- state-chain/pallets/cf-elections/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state-chain/pallets/cf-elections/src/lib.rs b/state-chain/pallets/cf-elections/src/lib.rs index d4e3bc0e1f..bcbb182820 100644 --- a/state-chain/pallets/cf-elections/src/lib.rs +++ b/state-chain/pallets/cf-elections/src/lib.rs @@ -124,7 +124,7 @@ use frame_system::pallet_prelude::*; pub use pallet::*; -pub const PALLET_VERSION: StorageVersion = StorageVersion::new(2); +pub const PALLET_VERSION: StorageVersion = StorageVersion::new(3); pub use pallet::UniqueMonotonicIdentifier; From bafc496ce9b5648fcf651e75f916d3d4a18b6fff Mon Sep 17 00:00:00 2001 From: kylezs Date: Mon, 25 Nov 2024 16:56:35 +0100 Subject: [PATCH 59/62] fix: print pre and post upgrade logs in upgrade-test --- .github/workflows/upgrade-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/upgrade-test.yml b/.github/workflows/upgrade-test.yml index bbf637355b..618a017819 100644 --- a/.github/workflows/upgrade-test.yml +++ b/.github/workflows/upgrade-test.yml @@ -229,13 +229,13 @@ jobs: # In the case of a compatible upgrade, we don't expect any logs here continue-on-error: true run: | - cat /tmp/chainflip/*/start-all-engines-pre-upgrade.*log + cat /tmp/chainflip/*/chainflip-engine-pre-upgrade.*log - name: Print new post-upgrade chainflip-engine logs 🚗 if: always() continue-on-error: true run: | - cat /tmp/chainflip/*/start-all-engines-post-upgrade.*log + cat /tmp/chainflip/*/chainflip-engine-post-upgrade.*log - name: Print chainflip-node logs 📡 if: always() From f52d20cfe26e608e5d04a623f4881f9b99aff198 Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:09:54 +0100 Subject: [PATCH 60/62] Feat: Use TransactionInId for Solana Vault Swaps (#5442) * chore: update transcationInId and bouncer * chore: run CI * chore: add logging and decrease timeout * chore: not try runtime * chore: add more prints * chore: run only one Solana Vault swap * chore: use processed commitment * chore: use confirmed * chore: revert sendAndConfirmTransaction * chore: fix * chore: add print and extra swap * chore: remove unnecessary context transition * chore: log electoral settings * chore: readd missing line * chore: improve logging * chore: add fix to upgrade-test * chore: add missing SOLANA_PROGRAMS_VERSION * chore: replace nonces manually * chore: manually skip solana vault swaps * chore: revert logs * chore: run upgrade * chore: fix path * chore: refactor env * chore: restore files * chore: force initial sol nonces * chore: run upgrade ci * chore: fix reset --hard * chore: small refactor * chore: remove unnecessary reset * chore: revert files * chore: refactor --- .github/workflows/upgrade-test.yml | 16 ++++- bouncer/shared/force_sol_nonces.ts | 59 +++++++++++++++++++ bouncer/shared/perform_swap.ts | 36 ++++++++--- bouncer/shared/sol_vault_swap.ts | 27 +++++---- bouncer/shared/swap_context.ts | 16 +++-- bouncer/shared/utils.ts | 44 +++++++++++--- bouncer/tests/DCA_test.ts | 9 +-- bouncer/tests/broker_fee_collection.ts | 3 +- bouncer/tests/evm_deposits.ts | 5 +- bouncer/tests/fill_or_kill.ts | 9 ++- bouncer/tests/gaslimit_ccm.ts | 3 +- .../witness/sol/program_swaps_witnessing.rs | 4 -- .../runtime/src/chainflip/solana_elections.rs | 3 +- 13 files changed, 182 insertions(+), 52 deletions(-) create mode 100755 bouncer/shared/force_sol_nonces.ts diff --git a/.github/workflows/upgrade-test.yml b/.github/workflows/upgrade-test.yml index 618a017819..7db028d08a 100644 --- a/.github/workflows/upgrade-test.yml +++ b/.github/workflows/upgrade-test.yml @@ -23,6 +23,7 @@ on: env: FORCE_COLOR: 1 SOLANA_VERSION: v1.18.17 + SOLANA_PROGRAMS_VERSION: v1.0.0-swap-endpoint NODE_COUNT: "1-node" permissions: @@ -172,7 +173,20 @@ jobs: echo "/usr/lib after copy of .so files" ls -l /usr/lib touch ./localnet/.setup_complete + # TODO: This is a temporary fix to allow the localnet docker-compose.yml from an older commit to run the latest solana programs version. Remove after 1.8 is released. + sed -i 's|ghcr.io/chainflip-io/solana-localnet-ledger:v[0-9.]*|ghcr.io/chainflip-io/solana-localnet-ledger:${{ env.SOLANA_PROGRAMS_VERSION }}|g' localnet/docker-compose.yml ./localnet/manage.sh + git reset --hard + + # TODO: This is a temporary workaround to insert the 1.8 image nonces. Remove after 1.8 is released. + - name: Nonce workaround + if: inputs.run-job + run: | + git checkout ${{ github.sha }} + git rev-parse HEAD + cd bouncer + pnpm install --frozen-lockfile + ./shared/force_sol_nonces.ts - name: Run bouncer on upgrade-from version 🙅‍♂️ if: inputs.run-job @@ -183,8 +197,6 @@ jobs: cd bouncer pnpm install --frozen-lockfile ./run.sh - # TODO: Temporary to discard the fixes above - git reset --hard - name: Upgrade network 🚀 if: inputs.run-job diff --git a/bouncer/shared/force_sol_nonces.ts b/bouncer/shared/force_sol_nonces.ts new file mode 100755 index 0000000000..66f3009bca --- /dev/null +++ b/bouncer/shared/force_sol_nonces.ts @@ -0,0 +1,59 @@ +#!/usr/bin/env -S pnpm tsx + +import { PublicKey } from '@solana/web3.js'; +import { decodeSolAddress, runWithTimeoutAndExit } from '../shared/utils'; +import { submitGovernanceExtrinsic } from '../shared/cf_governance'; + +async function forceRecoverSolNonce(nonceAddress: string, nonceValue: string) { + await submitGovernanceExtrinsic(async (chainflip) => + chainflip.tx.environment.forceRecoverSolNonce( + decodeSolAddress(new PublicKey(nonceAddress).toBase58()), + decodeSolAddress(new PublicKey(nonceValue).toBase58()), + ), + ); +} + +async function main() { + await forceRecoverSolNonce( + '2cNMwUCF51djw2xAiiU54wz1WrU8uG4Q8Kp8nfEuwghw', + '8T217weMrePR8VqqiY1J6VQKn5GfDXDwTPuYekPffNTo', + ); + await forceRecoverSolNonce( + 'HVG21SovGzMBJDB9AQNuWb6XYq4dDZ6yUwCbRUuFnYDo', + 'Hvg5WgDgdhcex1TsJW8PiPqcxUizLitoEmXcCShmXVWJ', + ); + await forceRecoverSolNonce( + 'HDYArziNzyuNMrK89igisLrXFe78ti8cvkcxfx4qdU2p', + '8vM4M9MWoYZE7YDGhpUhoetabY8dwaz4AcDR9hbCHd7u', + ); + await forceRecoverSolNonce( + 'HLPsNyxBqfq2tLE31v6RiViLp2dTXtJRgHgsWgNDRPs2', + '3fWpjCEzbHNU8qQD8YqoE5PfFahHNp4nwVVgJzxwTZya', + ); + await forceRecoverSolNonce( + 'GKMP63TqzbueWTrFYjRwMNkAyTHpQ54notRbAbMDmePM', + '96CDysvpx87Cd4TnxsMajFA9cKwFid1tMUFWnQWnifpJ', + ); + await forceRecoverSolNonce( + 'EpmHm2aSPsB5ZZcDjqDhQ86h1BV32GFCbGSMuC58Y2tn', + 'BjY7LRovNVwGEh5BGcfK4bZcjVan4YzuyFqcBwraG9Bj', + ); + await forceRecoverSolNonce( + '9yBZNMrLrtspj4M7bEf2X6tqbqHxD2vNETw8qSdvJHMa', + 'Hb35byiENfrMFwznb5TUxdAWN52dV81tWYWT3N99VRWr', + ); + await forceRecoverSolNonce( + 'J9dT7asYJFGS68NdgDCYjzU2Wi8uBoBusSHN1Z6JLWna', + '4TbbvCow8yHxnzdMT22gUt3JvHwAF8dbscBCLRezmpCY', + ); + await forceRecoverSolNonce( + 'GUMpVpQFNYJvSbyTtUarZVL7UDUgErKzDTSVJhekUX55', + 'FooctpZoHqoSjDE983JTJRyovN5Py6PZiiubd53gLFMv', + ); + await forceRecoverSolNonce( + 'AUiHYbzH7qLZSkb3u7nAqtvqC7e41sEzgWjBEvXrpfGv', + 'HCNp5KwKadPNiPs3nY1DtVcDfFkuUE2uUBsBueZbnkWc', + ); +} + +await runWithTimeoutAndExit(main(), 60); diff --git a/bouncer/shared/perform_swap.ts b/bouncer/shared/perform_swap.ts index 8a537e8a4e..b387ff49ee 100644 --- a/bouncer/shared/perform_swap.ts +++ b/bouncer/shared/perform_swap.ts @@ -19,6 +19,8 @@ import { getSolWhaleKeyPair, decodeSolAddress, VaultSwapParams, + TransactionOriginId, + TransactionOrigin, } from '../shared/utils'; import { CcmDepositMetadata } from '../shared/new_swap'; import { SwapContext, SwapStatus } from './swap_context'; @@ -136,7 +138,7 @@ export async function doPerformSwap( const swapRequestedHandle = observeSwapRequested( sourceAsset, destAsset, - channelId, + { type: TransactionOrigin.DepositChannel, channelId }, messageMetadata ? SwapRequestType.Ccm : SwapRequestType.Regular, ); @@ -252,7 +254,7 @@ export async function executeVaultSwap( dcaParams?: DcaParams, ) { let sourceAddress: string; - let txHash: string; + let transactionId: TransactionOriginId; const srcChain = chainFromAsset(sourceAsset); @@ -276,7 +278,7 @@ export async function executeVaultSwap( dcaParams, wallet, ); - txHash = receipt.hash; + transactionId = { type: TransactionOrigin.VaultSwapEvm, txHash: receipt.hash }; sourceAddress = wallet.address.toLowerCase(); } else { // Temporary until we implement the Solana encoding in the SDK/BrokerApi @@ -285,11 +287,20 @@ export async function executeVaultSwap( 'BoostFeeBps, FillOrKillParams and DcaParams are not supported for Solana vault swaps for now', ); } - txHash = await executeSolVaultSwap(sourceAsset, destAsset, destAddress, messageMetadata); + const { slot, accountAddress } = await executeSolVaultSwap( + sourceAsset, + destAsset, + destAddress, + messageMetadata, + ); + transactionId = { + type: TransactionOrigin.VaultSwapSolana, + addressAndSlot: [decodeSolAddress(accountAddress.toBase58()), slot], + }; sourceAddress = decodeSolAddress(getSolWhaleKeyPair().publicKey.toBase58()); } - return { txHash, sourceAddress }; + return { transactionId, sourceAddress }; } export async function performVaultSwap( @@ -316,7 +327,7 @@ export async function performVaultSwap( } try { - const { txHash, sourceAddress } = await executeVaultSwap( + const { transactionId, sourceAddress } = await executeVaultSwap( sourceAsset, destAsset, destAddress, @@ -326,7 +337,16 @@ export async function performVaultSwap( fillOrKillParams, dcaParams, ); - swapContext?.updateStatus(swapTag, SwapStatus.VaultContractExecuted); + swapContext?.updateStatus(swapTag, SwapStatus.VaultSwapInitiated); + + await observeSwapRequested( + sourceAsset, + destAsset, + transactionId, + messageMetadata ? SwapRequestType.Ccm : SwapRequestType.Regular, + ); + + swapContext?.updateStatus(swapTag, SwapStatus.VaultSwapScheduled); const ccmEventEmitted = messageMetadata ? observeCcmReceived(sourceAsset, destAsset, destAddress, messageMetadata, sourceAddress) @@ -344,7 +364,7 @@ export async function performVaultSwap( sourceAsset, destAsset, destAddress, - txHash, + transactionId, }; } catch (err) { console.error('err:', err); diff --git a/bouncer/shared/sol_vault_swap.ts b/bouncer/shared/sol_vault_swap.ts index 5e7b8ca420..4a252b2394 100644 --- a/bouncer/shared/sol_vault_swap.ts +++ b/bouncer/shared/sol_vault_swap.ts @@ -1,7 +1,7 @@ import * as anchor from '@coral-xyz/anchor'; import { InternalAsset as Asset, Chains, assetConstants } from '@chainflip/cli'; -import { PublicKey, sendAndConfirmTransaction, Keypair } from '@solana/web3.js'; +import { PublicKey, Keypair, sendAndConfirmTransaction } from '@solana/web3.js'; import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { getContractAddress, @@ -24,6 +24,10 @@ import { getSolanaSwapEndpointIdl, getSolanaVaultIdl } from './contract_interfac // @ts-expect-error workaround because of anchor issue const { BN } = anchor.default; +// Using AnchorProvider runs into issues so instead we store the wallet in id.json and then +// set the ANCHOR_WALLET env. Depending on how the SDK is implemented we can remove this. +process.env.ANCHOR_WALLET = 'shared/solana_keypair.json'; + const createdEventAccounts: PublicKey[] = []; // Temporary before the SDK implements this. @@ -42,10 +46,6 @@ export async function executeSolVaultSwap( ); const whaleKeypair = getSolWhaleKeyPair(); - // Using AnchorProvider runs into issues so instead we store the wallet in id.json and then - // set the ANCHOR_WALLET env. Depending on how the SDK is implemented we can remove this. - process.env.ANCHOR_WALLET = 'shared/solana_keypair.json'; - const connection = getSolConnection(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const VaultIdl: any = await getSolanaVaultIdl(); @@ -162,13 +162,18 @@ export async function executeSolVaultSwap( }) .signers([whaleKeypair, newEventAccountKeypair]) .transaction(); - const txHash = await sendAndConfirmTransaction(connection, tx, [ - whaleKeypair, - newEventAccountKeypair, - ]); + const txHash = await sendAndConfirmTransaction( + connection, + tx, + [whaleKeypair, newEventAccountKeypair], + { commitment: 'confirmed' }, + ); - console.log('tx', txHash); - return txHash; + const transactionData = await connection.getTransaction(txHash, { commitment: 'confirmed' }); + if (transactionData === null) { + throw new Error('Solana TransactionData is empty'); + } + return { txHash, slot: transactionData!.slot, accountAddress: newEventAccountKeypair.publicKey }; } export async function checkSolEventAccountsClosure( diff --git a/bouncer/shared/swap_context.ts b/bouncer/shared/swap_context.ts index 5825547225..a11afc174a 100644 --- a/bouncer/shared/swap_context.ts +++ b/bouncer/shared/swap_context.ts @@ -3,7 +3,8 @@ import assert from 'assert'; export enum SwapStatus { Initiated, Funded, - VaultContractExecuted, + VaultSwapInitiated, + VaultSwapScheduled, SwapScheduled, Success, Failure, @@ -35,16 +36,23 @@ export class SwapContext { ); break; } - case SwapStatus.VaultContractExecuted: { + case SwapStatus.VaultSwapInitiated: { assert( currentStatus === SwapStatus.Initiated, `Unexpected status transition for ${tag}. Transitioning from ${currentStatus} to ${status}`, ); break; } + case SwapStatus.VaultSwapScheduled: { + assert( + currentStatus === SwapStatus.VaultSwapInitiated, + `Unexpected status transition for ${tag}. Transitioning from ${currentStatus} to ${status}`, + ); + break; + } case SwapStatus.SwapScheduled: { assert( - currentStatus === SwapStatus.VaultContractExecuted || currentStatus === SwapStatus.Funded, + currentStatus === SwapStatus.Funded, `Unexpected status transition for ${tag}. Transitioning from ${currentStatus} to ${status}`, ); break; @@ -52,7 +60,7 @@ export class SwapContext { case SwapStatus.Success: { assert( currentStatus === SwapStatus.SwapScheduled || - currentStatus === SwapStatus.VaultContractExecuted, + currentStatus === SwapStatus.VaultSwapScheduled, `Unexpected status transition for ${tag}. Transitioning from ${currentStatus} to ${status}`, ); break; diff --git a/bouncer/shared/utils.ts b/bouncer/shared/utils.ts index 331533a1ae..c44a43f787 100644 --- a/bouncer/shared/utils.ts +++ b/bouncer/shared/utils.ts @@ -54,7 +54,7 @@ export type VaultSwapParams = { sourceAsset: Asset; destAsset: Asset; destAddress: string; - txHash: string; + transactionId: TransactionOriginId; }; const isSDKAsset = (asset: Asset): asset is SDKAsset => asset in assetConstants; @@ -495,18 +495,50 @@ export enum SwapRequestType { IngressEgressFee = 'IngressEgressFee', } +export enum TransactionOrigin { + DepositChannel = 'DepositChannel', + VaultSwapEvm = 'VaultSwapEvm', + VaultSwapSolana = 'VaultSwapSolana', +} + +export type TransactionOriginId = + | { type: TransactionOrigin.DepositChannel; channelId: number } + | { type: TransactionOrigin.VaultSwapEvm; txHash: string } + | { type: TransactionOrigin.VaultSwapSolana; addressAndSlot: [string, number] }; + function checkRequestTypeMatches(actual: object | string, expected: SwapRequestType) { if (typeof actual === 'object') { return expected in actual; } - return expected === actual; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function checkTransactionInMatches(actual: any, expected: TransactionOriginId): boolean { + if ('DepositChannel' in actual) { + return ( + expected.type === TransactionOrigin.DepositChannel && + Number(actual.DepositChannel.channelId.replaceAll(',', '')) === expected.channelId + ); + } + if ('Vault' in actual) { + return ( + ('Evm' in actual.Vault.txId && + expected.type === TransactionOrigin.VaultSwapEvm && + actual.Vault.txId.Evm === expected.txHash) || + ('Solana' in actual.Vault.txId && + expected.type === TransactionOrigin.VaultSwapSolana && + actual.Vault.txId.Solana[1].replaceAll(',', '') === expected.addressAndSlot[1].toString() && + actual.Vault.txId.Solana[0].toString() === expected.addressAndSlot[0].toString()) + ); + } + throw new Error(`Unsupported transaction origin type ${actual}`); +} + export async function observeSwapRequested( sourceAsset: Asset, destAsset: Asset, - id: number | string, + id: TransactionOriginId, swapRequestType: SwapRequestType, ) { // need to await this to prevent the chainflip api from being disposed prematurely @@ -515,11 +547,7 @@ export async function observeSwapRequested( const data = event.data; if (typeof data.origin === 'object') { - const channelMatches = - (typeof id === 'number' && - 'DepositChannel' in data.origin && - Number(data.origin.DepositChannel.channelId.replaceAll(',', '')) === id) || - (typeof id === 'string' && 'Vault' in data.origin && data.origin.Vault.txId.Evm === id); + const channelMatches = checkTransactionInMatches(data.origin, id); const sourceAssetMatches = sourceAsset === (data.inputAsset as Asset); const destAssetMatches = destAsset === (data.outputAsset as Asset); const requestTypeMatches = checkRequestTypeMatches(data.requestType, swapRequestType); diff --git a/bouncer/tests/DCA_test.ts b/bouncer/tests/DCA_test.ts index edf1dda287..e4282f766f 100644 --- a/bouncer/tests/DCA_test.ts +++ b/bouncer/tests/DCA_test.ts @@ -6,6 +6,7 @@ import { observeBalanceIncrease, observeSwapRequested, SwapRequestType, + TransactionOrigin, } from '../shared/utils'; import { send } from '../shared/send'; import { observeEvent, observeEvents } from '../shared/utils/substrate'; @@ -65,7 +66,7 @@ async function testDCASwap( swapRequestedHandle = observeSwapRequested( inputAsset, destAsset, - depositChannelId, + { type: TransactionOrigin.DepositChannel, channelId: depositChannelId }, SwapRequestType.Regular, ); @@ -73,7 +74,7 @@ async function testDCASwap( await send(inputAsset, swapRequest.depositAddress, amount.toString()); testDCASwaps.log(`Sent ${amount} ${inputAsset} to ${swapRequest.depositAddress}`); } else { - const { txHash } = await executeVaultSwap( + const { transactionId } = await executeVaultSwap( inputAsset, destAsset, destAddress, @@ -84,13 +85,13 @@ async function testDCASwap( dcaParams, ); - testDCASwaps.log(`Vault swap executed, tx hash: ${txHash}`); + testDCASwaps.log(`Vault swap executed, tx id: ${transactionId}`); // Look after Swap Requested of data.origin.Vault.tx_hash swapRequestedHandle = observeSwapRequested( inputAsset, destAsset, - txHash, + transactionId, SwapRequestType.Regular, ); } diff --git a/bouncer/tests/broker_fee_collection.ts b/bouncer/tests/broker_fee_collection.ts index 594948f3ff..54b7a814a1 100644 --- a/bouncer/tests/broker_fee_collection.ts +++ b/bouncer/tests/broker_fee_collection.ts @@ -15,6 +15,7 @@ import { amountToFineAmountBigInt, SwapRequestType, observeSwapRequested, + TransactionOrigin, } from '../shared/utils'; import { getBalance } from '../shared/get_balance'; import { getChainflipApi, observeEvent } from '../shared/utils/substrate'; @@ -120,7 +121,7 @@ async function testBrokerFees(inputAsset: Asset, seed?: string): Promise { const swapRequestedHandle = observeSwapRequested( inputAsset, destAsset, - channelId, + { type: TransactionOrigin.DepositChannel, channelId }, SwapRequestType.Regular, ); diff --git a/bouncer/tests/evm_deposits.ts b/bouncer/tests/evm_deposits.ts index f046016904..a3104cfb95 100644 --- a/bouncer/tests/evm_deposits.ts +++ b/bouncer/tests/evm_deposits.ts @@ -16,6 +16,7 @@ import { assetContractId, observeSwapRequested, SwapRequestType, + TransactionOrigin, } from '../shared/utils'; import { signAndSendTxEvm } from '../shared/send_evm'; import { getCFTesterAbi } from '../shared/contract_interfaces'; @@ -152,7 +153,7 @@ async function testDoubleDeposit(sourceAsset: Asset, destAsset: Asset) { const swapRequestedHandle = observeSwapRequested( sourceAsset, destAsset, - swapParams.channelId, + { type: TransactionOrigin.DepositChannel, channelId: swapParams.channelId }, SwapRequestType.Regular, ); @@ -166,7 +167,7 @@ async function testDoubleDeposit(sourceAsset: Asset, destAsset: Asset) { const swapRequestedHandle = observeSwapRequested( sourceAsset, destAsset, - swapParams.channelId, + { type: TransactionOrigin.DepositChannel, channelId: swapParams.channelId }, SwapRequestType.Regular, ); diff --git a/bouncer/tests/fill_or_kill.ts b/bouncer/tests/fill_or_kill.ts index 8620f2d0f7..525663cd9c 100644 --- a/bouncer/tests/fill_or_kill.ts +++ b/bouncer/tests/fill_or_kill.ts @@ -8,6 +8,7 @@ import { observeBalanceIncrease, observeSwapRequested, SwapRequestType, + TransactionOrigin, } from '../shared/utils'; import { executeVaultSwap, requestNewSwap } from '../shared/perform_swap'; import { send } from '../shared/send'; @@ -59,12 +60,10 @@ async function testMinPriceRefund(inputAsset: Asset, amount: number, swapViaVaul refundParameters, ); const depositAddress = swapRequest.depositAddress; - const depositChannelId = swapRequest.channelId; - swapRequestedHandle = observeSwapRequested( inputAsset, destAsset, - depositChannelId, + { type: TransactionOrigin.DepositChannel, channelId: swapRequest.channelId }, SwapRequestType.Regular, ); @@ -84,7 +83,7 @@ async function testMinPriceRefund(inputAsset: Asset, amount: number, swapViaVaul Math.random() < 0.5 ? ccmMetadata.ccmAdditionalData : undefined; } - const { txHash } = await executeVaultSwap( + const { transactionId } = await executeVaultSwap( inputAsset, destAsset, destAddress, @@ -97,7 +96,7 @@ async function testMinPriceRefund(inputAsset: Asset, amount: number, swapViaVaul swapRequestedHandle = observeSwapRequested( inputAsset, destAsset, - txHash, + transactionId, SwapRequestType.Regular, ); } diff --git a/bouncer/tests/gaslimit_ccm.ts b/bouncer/tests/gaslimit_ccm.ts index 3bccd06174..1f38819410 100644 --- a/bouncer/tests/gaslimit_ccm.ts +++ b/bouncer/tests/gaslimit_ccm.ts @@ -14,6 +14,7 @@ import { sleep, SwapRequestType, SwapType, + TransactionOrigin, } from '../shared/utils'; import { requestNewSwap } from '../shared/perform_swap'; import { send } from '../shared/send'; @@ -151,7 +152,7 @@ async function trackGasLimitSwap( const swapRequestedHandle = observeSwapRequested( sourceAsset, destAsset, - channelId, + { type: TransactionOrigin.DepositChannel, channelId }, SwapRequestType.Ccm, ); await send(sourceAsset, depositAddress); diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 4ca9e5df24..0990696156 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -217,10 +217,6 @@ pub async fn get_program_swaps( .await; Ok((new_swaps, closed_accounts)) - - // TODO: When submitting data we could technically submit the slot when the SwapEvent was - // queried for the new opened accounts. However, it's just easier to submit the slot when the - // SwapEndpointDataAccount was queried for both closed accounts and new opened accounts. } async fn get_changed_program_swap_accounts( diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 4d3749b5a8..2db0e38335 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -520,7 +520,6 @@ pub struct SolanaVaultSwapDetails { pub deposit_amount: SolAmount, pub destination_address: EncodedAddress, pub deposit_metadata: Option, - // TODO: swap_account and creation_slot will be pulled into TransactionInId type (PRO-1760) pub swap_account: SolAddress, pub creation_slot: u64, pub broker_fee: Beneficiary, @@ -566,7 +565,7 @@ impl swap_details.to, swap_details.destination_address, swap_details.deposit_metadata, - Default::default(), // TODO txHash PRO-1760 + (swap_details.swap_account, swap_details.creation_slot), (), swap_details.broker_fee, swap_details.affiliate_fees, From 0f0746c17d7f292d644f5c088782396cf87c7cf0 Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 26 Nov 2024 14:24:21 +0100 Subject: [PATCH 61/62] chore: update affiliate_fee type --- engine/src/witness/sol/program_swaps_witnessing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index 0990696156..a41c4bee07 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -188,7 +188,7 @@ pub async fn get_program_swaps( broker_fee, affiliate_fees: affiliate_fees .into_iter() - .map(|entry| cf_primitives::Beneficiary { account: entry.affiliate.into(), bps: entry.fee.into() }) + .map(|entry| cf_primitives::Beneficiary { account: entry.affiliate, bps: entry.fee.into() }) .collect_vec() .try_into() .map_err(|_| { From bcabca31729cfdb8feb50c8b2d09c7ffa9eeabcf Mon Sep 17 00:00:00 2001 From: albert Date: Tue, 26 Nov 2024 15:28:09 +0100 Subject: [PATCH 62/62] chore: restore upgrade flag --- .github/workflows/ci-development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-development.yml b/.github/workflows/ci-development.yml index 149542ba6e..bc66706607 100644 --- a/.github/workflows/ci-development.yml +++ b/.github/workflows/ci-development.yml @@ -79,7 +79,7 @@ jobs: uses: ./.github/workflows/upgrade-test.yml secrets: inherit with: - run-job: true + run-job: false publish: needs: [package] uses: ./.github/workflows/_30_publish.yml