From 6f330f7f2f2359b327d5dd4912494208174bfe94 Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:06:29 +0100 Subject: [PATCH 01/20] chore: derive from_token_account if not passed (#5604) * chore: derive from_token_account if not passed * chore: fix * chore. lint --- bouncer/shared/sol_vault_swap.ts | 10 +--------- .../runtime/src/chainflip/vault_swaps.rs | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/bouncer/shared/sol_vault_swap.ts b/bouncer/shared/sol_vault_swap.ts index 61bf203a315..d3937aaab0f 100644 --- a/bouncer/shared/sol_vault_swap.ts +++ b/bouncer/shared/sol_vault_swap.ts @@ -9,7 +9,6 @@ import { Transaction, AccountMeta, } from '@solana/web3.js'; -import { getAssociatedTokenAddressSync } from '@solana/spl-token'; import BigNumber from 'bignumber.js'; import { getContractAddress, @@ -108,14 +107,7 @@ export async function executeSolVaultSwap( event_data_account: decodeSolAddress(newEventAccountKeypair.publicKey.toBase58()), input_amount: '0x' + new BigNumber(amountToSwap).toString(16), refund_parameters: refundParams, - from_token_account: - srcAsset === 'Sol' - ? undefined - : getAssociatedTokenAddressSync( - new PublicKey(getContractAddress('Solana', 'SolUsdc')), - whaleKeypair.publicKey, - false, - ).toString(), + from_token_account: undefined, }; const vaultSwapDetails = (await chainflip.rpc( diff --git a/state-chain/runtime/src/chainflip/vault_swaps.rs b/state-chain/runtime/src/chainflip/vault_swaps.rs index b16fd526971..9620f959317 100644 --- a/state-chain/runtime/src/chainflip/vault_swaps.rs +++ b/state-chain/runtime/src/chainflip/vault_swaps.rs @@ -15,7 +15,8 @@ use cf_chains::{ cf_parameters::build_cf_parameters, evm::api::{EvmCall, EvmEnvironmentProvider}, sol::{ - api::SolanaEnvironment, instruction_builder::SolanaInstructionBuilder, SolAmount, SolPubkey, + api::SolanaEnvironment, instruction_builder::SolanaInstructionBuilder, + sol_tx_core::address_derivation::derive_associated_token_account, SolAmount, SolPubkey, }, Arbitrum, CcmChannelMetadata, ChannelRefundParametersEncoded, Ethereum, ForeignChain, }; @@ -282,10 +283,18 @@ pub fn solana_vault_swap( ) .map_err(|_| "Failed to derive supported token account")?; - let from_token_account = SolPubkey::try_from( - from_token_account.ok_or("From token account is required for SolUsdc swaps")?, - ) - .map_err(|_| "Invalid Solana Address: from_token_account")?; + let from_token_account = match from_token_account { + Some(token_account) => SolPubkey::try_from(token_account) + .map_err(|_| "Failed to decode the source token account")?, + // Defaulting to the user's associated token account + None => derive_associated_token_account( + from.into(), + api_environment.usdc_token_mint_pubkey, + ) + .map_err(|_| "Failed to derive the associated token account")? + .address + .into(), + }; SolanaInstructionBuilder::x_swap_usdc( api_environment, From 7e2955b4c0feb26a5aba706b91a2f316476aa593 Mon Sep 17 00:00:00 2001 From: Janislav Date: Tue, 4 Feb 2025 12:35:39 +0100 Subject: [PATCH 02/20] test: added check of swap requests and egress pipelines (#5606) * test: added explixit check of swap requests * chore: removed egress check * chore: using the correct Mock --- .../cf-ingress-egress/src/tests/screening.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/state-chain/pallets/cf-ingress-egress/src/tests/screening.rs b/state-chain/pallets/cf-ingress-egress/src/tests/screening.rs index 6e3f9a66231..b33b301d6bc 100644 --- a/state-chain/pallets/cf-ingress-egress/src/tests/screening.rs +++ b/state-chain/pallets/cf-ingress-egress/src/tests/screening.rs @@ -18,8 +18,10 @@ use cf_chains::{ }; use cf_traits::{ - mocks::account_role_registry::MockAccountRoleRegistry, AccountRoleRegistry, BalanceApi, - DepositApi, + mocks::{ + account_role_registry::MockAccountRoleRegistry, swap_request_api::MockSwapRequestHandler, + }, + AccountRoleRegistry, BalanceApi, DepositApi, }; use cf_primitives::{chains::assets::btc, Beneficiaries, ChannelId}; @@ -150,6 +152,7 @@ fn process_marked_transaction_and_expect_refund() { ); assert_eq!(ScheduledTransactionsForRejection::::decode_len(), Some(1)); + assert!(MockSwapRequestHandler::::get_swap_requests().is_empty()); }); } @@ -200,6 +203,8 @@ fn finalize_boosted_tx_if_marked_after_prewitness() { .. }) ); + + assert!(MockSwapRequestHandler::::get_swap_requests().len() == 1); }); } @@ -256,6 +261,8 @@ fn reject_tx_if_marked_before_prewitness() { block_height: _, }) ); + + assert!(MockSwapRequestHandler::::get_swap_requests().is_empty()); }); } @@ -385,6 +392,8 @@ fn send_funds_back_after_they_have_been_rejected() { deposit_details, }); + assert_eq!(MockEgressBroadcaster::get_pending_api_calls().len(), 0); + IngressEgress::on_finalize(1); assert_eq!(ScheduledTransactionsForRejection::::decode_len(), None); @@ -396,6 +405,8 @@ fn send_funds_back_after_they_have_been_rejected() { tx_id: _, }) ); + + assert_eq!(MockEgressBroadcaster::get_pending_api_calls().len(), 1); }); } @@ -451,5 +462,7 @@ fn can_report_between_prewitness_and_witness_if_tx_was_not_boosted() { block_height: _, }) ); + + assert!(MockSwapRequestHandler::::get_swap_requests().is_empty()); }); } From 1ffa4a90c00f14de4e478646c253194212cf4617 Mon Sep 17 00:00:00 2001 From: Maxim Urschumzew Date: Tue, 4 Feb 2025 14:50:54 +0100 Subject: [PATCH 03/20] feat: `VoterApi::vote` now returns an Option. (#5589) * feat: `VoterApi::vote` now returns an Option. The voter api can now signal that it doesn't have anything to vote for by returning `Ok(None)`. Previously, in such cases we had to return `Err(..)`, which lead to the RetrierClient scheduling an exponential backoff. * nit: change loglevel to `debug` in case of no vote. * fix: import `debug` macro. --- engine/src/elections.rs | 7 ++-- engine/src/elections/voter_api.rs | 10 +++--- engine/src/witness/sol.rs | 57 +++++++++++++++++-------------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/engine/src/elections.rs b/engine/src/elections.rs index d3961948ff8..8f97a309e1e 100644 --- a/engine/src/elections.rs +++ b/engine/src/elections.rs @@ -22,7 +22,7 @@ use std::{ collections::{BTreeMap, HashMap}, sync::Arc, }; -use tracing::{error, info, warn}; +use tracing::{debug, error, info, warn}; use voter_api::CompositeVoterApi; const MAXIMUM_CONCURRENT_FILTER_REQUESTS: usize = 16; @@ -160,7 +160,7 @@ where }, let (election_identifier, result_vote) = vote_tasks.next_or_pending() => { match result_vote { - Ok(vote) => { + Ok(Some(vote)) => { info!("Voting task for election: '{:?}' succeeded.", election_identifier); // Create the partial_vote early so that SharedData can be provided as soon as the vote has been generated, rather than only after it is submitted. let partial_vote = VoteStorageOf::<>::ElectoralSystemRunner>::vote_into_partial_vote(&vote, |shared_data| { @@ -176,6 +176,9 @@ where pending_submissions.insert(election_identifier, (partial_vote, vote)); }, + Ok(None) => { + debug!("Voting task for election '{:?}' returned 'None' (nothing to submit).", election_identifier); + }, Err(error) => { warn!("Voting task for election '{:?}' failed with error: '{:?}'.", election_identifier, error); } diff --git a/engine/src/elections/voter_api.rs b/engine/src/elections/voter_api.rs index ae863c9b8c5..2281ebe0477 100644 --- a/engine/src/elections/voter_api.rs +++ b/engine/src/elections/voter_api.rs @@ -16,7 +16,7 @@ pub trait VoterApi { &self, settings: ::ElectoralSettings, properties: ::ElectionProperties, - ) -> Result, anyhow::Error>; + ) -> Result>, anyhow::Error>; } pub struct CompositeVoter { @@ -41,7 +41,7 @@ pub trait CompositeVoterApi { &self, settings: ::ElectoralSettings, properties: ::ElectionProperties, - ) -> Result, anyhow::Error>; + ) -> Result>, anyhow::Error>; } // TODO Combine this into the composite macro PRO-1736 @@ -55,7 +55,9 @@ macro_rules! generate_voter_api_tuple_impls { settings: as ElectoralSystemTypes>::ElectoralSettings, properties: as ElectoralSystemTypes>::ElectionProperties, ) -> Result< - < as ElectoralSystemTypes>::VoteStorage as VoteStorage>::Vote, + Option< + < as ElectoralSystemTypes>::VoteStorage as VoteStorage>::Vote + >, anyhow::Error, > { use vote_storage::composite::$module::CompositeVote; @@ -69,7 +71,7 @@ macro_rules! generate_voter_api_tuple_impls { $voter.vote( $electoral_system, properties, - ).await.map(CompositeVote::$electoral_system) + ).await.map(|x| x.map(CompositeVote::$electoral_system)) }, )* } diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index 8499b28123d..fc70f963128 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -53,10 +53,10 @@ impl VoterApi for SolanaBlockHeightTrackingVoter { &self, _settings: ::ElectoralSettings, _properties: ::ElectionProperties, - ) -> Result, anyhow::Error> { + ) -> Result>, anyhow::Error> { let slot = self.client.get_slot(CommitmentConfig::finalized()).await; CHAIN_TRACKING.set(&[cf_chains::Solana::NAME], Into::::into(slot)); - Ok(slot) + Ok(Some(slot)) } } @@ -71,7 +71,7 @@ impl VoterApi for SolanaIngressTrackingVoter { &self, settings: ::ElectoralSettings, properties: ::ElectionProperties, - ) -> Result, anyhow::Error> { + ) -> Result>, anyhow::Error> { sol_deposits::get_channel_ingress_amounts( &self.client, settings.vault_program, @@ -82,6 +82,7 @@ impl VoterApi for SolanaIngressTrackingVoter { .and_then(|vote| { vote.try_into().map_err(|_| anyhow::anyhow!("Too many channels in election")) }) + .map(Option::Some) } } @@ -96,7 +97,7 @@ impl VoterApi for SolanaNonceTrackingVoter { &self, _settings: ::ElectoralSettings, properties: ::ElectionProperties, - ) -> Result, anyhow::Error> { + ) -> Result>, anyhow::Error> { let (nonce_account, previous_nonce, previous_slot) = properties; let nonce_and_slot = @@ -105,8 +106,10 @@ impl VoterApi for SolanaNonceTrackingVoter { .map(|(nonce, slot)| MonotonicChangeVote { value: nonce, block: slot }); // If the nonce is not found, we default to the previous nonce and slot. // The `MonotonicChange` electoral system ensure this vote is filtered. - Ok(nonce_and_slot - .unwrap_or(MonotonicChangeVote { value: previous_nonce, block: previous_slot })) + Ok(Some( + nonce_and_slot + .unwrap_or(MonotonicChangeVote { value: previous_nonce, block: previous_slot }), + )) } } @@ -121,13 +124,14 @@ impl VoterApi for SolanaEgressWitnessingVoter { &self, _settings: ::ElectoralSettings, signature: ::ElectionProperties, - ) -> Result, anyhow::Error> { + ) -> Result>, anyhow::Error> { egress_witnessing::get_finalized_fee_and_success_status(&self.client, signature) .await .map(|(tx_fee, transaction_successful)| TransactionSuccessDetails { tx_fee, transaction_successful, }) + .map(Option::Some) } } @@ -142,23 +146,25 @@ impl VoterApi for SolanaLivenessVoter { &self, _settings: ::ElectoralSettings, slot: ::ElectionProperties, - ) -> Result, anyhow::Error> { - Ok(SolHash::from_str( - &self - .client - .get_block( - slot, - RpcBlockConfig { - transaction_details: Some(TransactionDetails::None), - rewards: Some(false), - max_supported_transaction_version: Some(0), - ..Default::default() - }, - ) - .await - .blockhash, - ) - .map_err(|e| anyhow::anyhow!("Failed to convert blockhash String to SolHash: {e}"))?) + ) -> Result>, anyhow::Error> { + Ok(Some( + SolHash::from_str( + &self + .client + .get_block( + slot, + RpcBlockConfig { + transaction_details: Some(TransactionDetails::None), + rewards: Some(false), + max_supported_transaction_version: Some(0), + ..Default::default() + }, + ) + .await + .blockhash, + ) + .map_err(|e| anyhow::anyhow!("Failed to convert blockhash String to SolHash: {e}"))?, + )) } } @@ -173,7 +179,7 @@ impl VoterApi for SolanaVaultSwapsVoter { &self, settings: ::ElectoralSettings, properties: ::ElectionProperties, - ) -> Result, anyhow::Error> { + ) -> Result>, anyhow::Error> { program_swaps_witnessing::get_program_swaps( &self.client, settings.swap_endpoint_data_account_address, @@ -190,6 +196,7 @@ impl VoterApi for SolanaVaultSwapsVoter { new_accounts: new_accounts.into_iter().collect::>(), confirm_closed_accounts: confirm_closed_accounts.into_iter().collect::>(), }) + .map(Option::Some) } } From e5effe1c3d66a17ac2097280fecb1da0a04cbd1c Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:03:34 +0100 Subject: [PATCH 04/20] chore: optimize Solana CCM Checker (#5608) * add destination address to the ccm checker * chore: add duplication test with destination address * chore: lint --------- Co-authored-by: Roy Yang --- state-chain/chains/src/address.rs | 7 +- state-chain/chains/src/ccm_checker.rs | 211 ++++++++++-------- state-chain/chains/src/sol/api.rs | 1 + .../pallets/cf-ingress-egress/src/lib.rs | 9 +- state-chain/pallets/cf-swapping/src/lib.rs | 23 +- state-chain/runtime/src/lib.rs | 2 +- 6 files changed, 152 insertions(+), 101 deletions(-) diff --git a/state-chain/chains/src/address.rs b/state-chain/chains/src/address.rs index d9e616e38b0..10647ef30aa 100644 --- a/state-chain/chains/src/address.rs +++ b/state-chain/chains/src/address.rs @@ -147,6 +147,12 @@ impl TryFrom for SolPubkey { } } +impl From for EncodedAddress { + fn from(from: SolAddress) -> EncodedAddress { + EncodedAddress::Sol(from.0) + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum AddressError { InvalidAddress, @@ -185,7 +191,6 @@ impl TryFrom for ScriptPubkey { } } } - pub trait IntoForeignChainAddress { fn into_foreign_chain_address(self) -> ForeignChainAddress; } diff --git a/state-chain/chains/src/ccm_checker.rs b/state-chain/chains/src/ccm_checker.rs index 42e4e7c39ef..81a08dab1f4 100644 --- a/state-chain/chains/src/ccm_checker.rs +++ b/state-chain/chains/src/ccm_checker.rs @@ -1,4 +1,5 @@ use crate::{ + address::EncodedAddress, sol::{SolAsset, SolCcmAccounts, SolPubkey, MAX_CCM_BYTES_SOL, MAX_CCM_BYTES_USDC}, CcmChannelMetadata, }; @@ -18,6 +19,7 @@ pub enum CcmValidityError { CcmIsTooLong, CcmAdditionalDataContainsInvalidAccounts, RedundantDataSupplied, + InvalidDestinationAddress, } impl From for DispatchError { fn from(value: CcmValidityError) -> Self { @@ -29,6 +31,8 @@ impl From for DispatchError { "Invalid Ccm: additional data contains invalid accounts".into(), CcmValidityError::RedundantDataSupplied => "Invalid Ccm: Additional data supplied but they will not be used".into(), + CcmValidityError::InvalidDestinationAddress => + "Invalid Ccm: Destination address is not compatible with the target Chain.".into(), } } } @@ -37,6 +41,7 @@ pub trait CcmValidityCheck { fn check_and_decode( _ccm: &CcmChannelMetadata, _egress_asset: cf_primitives::Asset, + _destination: EncodedAddress, ) -> Result { Ok(DecodedCcmAdditionalData::NotRequired) } @@ -62,8 +67,12 @@ impl CcmValidityCheck for CcmValidityChecker { fn check_and_decode( ccm: &CcmChannelMetadata, egress_asset: Asset, + destination: EncodedAddress, ) -> Result { if ForeignChain::from(egress_asset) == ForeignChain::Solana { + let destination_address = SolPubkey::try_from(destination) + .map_err(|_| CcmValidityError::InvalidDestinationAddress)?; + let asset: SolAsset = egress_asset .try_into() .expect("Only Solana chain's asset will be checked. This conversion must succeed."); @@ -88,16 +97,14 @@ impl CcmValidityCheck for CcmValidityChecker { // Therefore we want to allow for duplicated accounts, both duplicated // within the additional accounts and with our accounts. Then we can // calculate the length accordingly. - - let mut seen_addresses = BTreeSet::new(); - - // From the Chainflip accounts included in the transaction, aggKey, DataAccount, - // NonceAccount and some others are irrelevant to the user anyway. The only - // relevant ones not included here are environment ones (ReceiverNative for - // SOL, receiverTokenAccount and USDC MINT for SolUsdc) but that'd just be - // a minor improvement. - seen_addresses.insert(SYSTEM_PROGRAM_ID); - seen_addresses.insert(SYS_VAR_INSTRUCTIONS); + // The Chainflip accounts are anyway irrelevant to the user except for a + // few that are acounted for here. The only relevant is the token + let mut seen_addresses = BTreeSet::from_iter([ + SYSTEM_PROGRAM_ID, + SYS_VAR_INSTRUCTIONS, + destination_address.into(), + ccm_accounts.cf_receiver.pubkey.into(), + ]); if asset == SolAsset::SolUsdc { seen_addresses.insert(TOKEN_PROGRAM_ID); @@ -159,11 +166,17 @@ mod test { use super::*; use crate::sol::{sol_tx_core::sol_test_values, SolCcmAddress, SolPubkey, MAX_CCM_BYTES_SOL}; + pub const DEST_ADDR: EncodedAddress = EncodedAddress::Sol([0x00; 32]); + pub const MOCK_ADDR: SolPubkey = SolPubkey([0x01; 32]); + pub const CF_RECEIVER_ADDR: SolPubkey = SolPubkey([0xff; 32]); + pub const FALLBACK_ADDR: SolPubkey = SolPubkey([0xf0; 32]); + pub const INVALID_DEST_ADDR: EncodedAddress = EncodedAddress::Eth([0x00; 20]); + #[test] fn can_verify_valid_ccm() { let ccm = sol_test_values::ccm_parameter().channel_metadata; assert_eq!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Sol), + CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, DEST_ADDR), Ok(DecodedCcmAdditionalData::Solana(VersionedSolanaCcmAdditionalData::V0( sol_test_values::ccm_accounts() ))) @@ -179,7 +192,7 @@ mod test { }; assert_err!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Sol), + CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, DEST_ADDR), CcmValidityError::CannotDecodeCcmAdditionalData ); } @@ -190,38 +203,35 @@ mod test { message: vec![0x01; MAX_CCM_BYTES_SOL].try_into().unwrap(), gas_budget: 0, ccm_additional_data: VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, additional_accounts: vec![], - fallback_address: SolPubkey([0xf0; 32]), + fallback_address: FALLBACK_ADDR, }) .encode() .try_into() .unwrap(), }; - assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::Sol)); + assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::Sol, DEST_ADDR)); // Length check for Sol let mut invalid_ccm = ccm(); invalid_ccm.message = vec![0x01; MAX_CCM_BYTES_SOL + 1].try_into().unwrap(); assert_err!( - CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::Sol), + CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::Sol, DEST_ADDR), CcmValidityError::CcmIsTooLong ); let mut invalid_ccm = ccm(); invalid_ccm.ccm_additional_data = VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - additional_accounts: vec![SolCcmAddress { - pubkey: SolPubkey([0x01; 32]), - is_writable: true, - }], - fallback_address: SolPubkey([0xf0; 32]), + cf_receiver: SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + additional_accounts: vec![SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }], + fallback_address: FALLBACK_ADDR, }) .encode() .try_into() .unwrap(); assert_err!( - CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::Sol), + CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::Sol, DEST_ADDR), CcmValidityError::CcmIsTooLong ); } @@ -232,38 +242,35 @@ mod test { message: vec![0x01; MAX_CCM_BYTES_USDC].try_into().unwrap(), gas_budget: 0, ccm_additional_data: VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - fallback_address: SolPubkey([0xf0; 32]), + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, + fallback_address: FALLBACK_ADDR, additional_accounts: vec![], }) .encode() .try_into() .unwrap(), }; - assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::SolUsdc)); + assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::SolUsdc, DEST_ADDR)); // Length check for SolUsdc let mut invalid_ccm = ccm(); invalid_ccm.message = vec![0x01; MAX_CCM_BYTES_USDC + 1].try_into().unwrap(); assert_err!( - CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::SolUsdc), + CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::SolUsdc, DEST_ADDR), CcmValidityError::CcmIsTooLong ); let mut invalid_ccm = ccm(); invalid_ccm.ccm_additional_data = VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - additional_accounts: vec![SolCcmAddress { - pubkey: SolPubkey([0x01; 32]), - is_writable: true, - }], - fallback_address: SolPubkey([0xf0; 32]), + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, + additional_accounts: vec![SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }], + fallback_address: FALLBACK_ADDR, }) .encode() .try_into() .unwrap(); assert_err!( - CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::SolUsdc), + CcmValidityChecker::check_and_decode(&invalid_ccm, Asset::SolUsdc, DEST_ADDR), CcmValidityError::CcmIsTooLong ); } @@ -273,23 +280,23 @@ mod test { let ccm = sol_test_values::ccm_parameter().channel_metadata; // Ok for Solana Chain - assert_ok!(CcmValidityChecker::check_and_decode(&ccm, Asset::Sol)); + assert_ok!(CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, DEST_ADDR)); // Fails for non-solana chains assert_err!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Btc), + CcmValidityChecker::check_and_decode(&ccm, Asset::Btc, DEST_ADDR), CcmValidityError::RedundantDataSupplied, ); assert_err!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Dot), + CcmValidityChecker::check_and_decode(&ccm, Asset::Dot, DEST_ADDR), CcmValidityError::RedundantDataSupplied, ); assert_err!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Eth), + CcmValidityChecker::check_and_decode(&ccm, Asset::Eth, DEST_ADDR), CcmValidityError::RedundantDataSupplied, ); assert_err!( - CcmValidityChecker::check_and_decode(&ccm, Asset::ArbEth), + CcmValidityChecker::check_and_decode(&ccm, Asset::ArbEth, DEST_ADDR), CcmValidityError::RedundantDataSupplied, ); } @@ -301,43 +308,43 @@ mod test { // Only fails for Solana chain. ccm.message = [0x00; MAX_CCM_BYTES_SOL + 1].to_vec().try_into().unwrap(); assert_err!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Sol), + CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, DEST_ADDR), CcmValidityError::CcmIsTooLong ); ccm.message = [0x00; MAX_CCM_BYTES_USDC + 1].to_vec().try_into().unwrap(); assert_err!( - CcmValidityChecker::check_and_decode(&ccm, Asset::SolUsdc), + CcmValidityChecker::check_and_decode(&ccm, Asset::SolUsdc, DEST_ADDR), CcmValidityError::CcmIsTooLong ); // Always valid on other chains. ccm.ccm_additional_data.clear(); assert_ok!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Eth), + CcmValidityChecker::check_and_decode(&ccm, Asset::Eth, DEST_ADDR), DecodedCcmAdditionalData::NotRequired ); assert_ok!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Btc), + CcmValidityChecker::check_and_decode(&ccm, Asset::Btc, DEST_ADDR), DecodedCcmAdditionalData::NotRequired ); assert_ok!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Flip), + CcmValidityChecker::check_and_decode(&ccm, Asset::Flip, DEST_ADDR), DecodedCcmAdditionalData::NotRequired ); assert_ok!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Usdt), + CcmValidityChecker::check_and_decode(&ccm, Asset::Usdt, DEST_ADDR), DecodedCcmAdditionalData::NotRequired ); assert_ok!( - CcmValidityChecker::check_and_decode(&ccm, Asset::Usdc), + CcmValidityChecker::check_and_decode(&ccm, Asset::Usdc, DEST_ADDR), DecodedCcmAdditionalData::NotRequired ); assert_ok!( - CcmValidityChecker::check_and_decode(&ccm, Asset::ArbUsdc), + CcmValidityChecker::check_and_decode(&ccm, Asset::ArbUsdc, DEST_ADDR), DecodedCcmAdditionalData::NotRequired ); assert_ok!( - CcmValidityChecker::check_and_decode(&ccm, Asset::ArbEth), + CcmValidityChecker::check_and_decode(&ccm, Asset::ArbEth, DEST_ADDR), DecodedCcmAdditionalData::NotRequired ); } @@ -355,10 +362,10 @@ mod test { is_writable: true, }, additional_accounts: vec![ - SolCcmAddress { pubkey: crate::sol::SolPubkey([0x01; 32]), is_writable: false }, - SolCcmAddress { pubkey: crate::sol::SolPubkey([0x02; 32]), is_writable: false }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: false }, + SolCcmAddress { pubkey: SolPubkey([0x02; 32]), is_writable: false }, ], - fallback_address: SolPubkey([0xf0; 32]), + fallback_address: FALLBACK_ADDR, }; assert_err!( check_ccm_for_blacklisted_accounts(&ccm_accounts, blacklisted_accounts()), @@ -366,10 +373,7 @@ mod test { ); let ccm_accounts = SolCcmAccounts { - cf_receiver: SolCcmAddress { - pubkey: crate::sol::SolPubkey([0x01; 32]), - is_writable: true, - }, + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, additional_accounts: vec![ SolCcmAddress { pubkey: sol_test_values::TOKEN_VAULT_PDA_ACCOUNT.into(), @@ -377,7 +381,7 @@ mod test { }, SolCcmAddress { pubkey: crate::sol::SolPubkey([0x02; 32]), is_writable: false }, ], - fallback_address: SolPubkey([0xf0; 32]), + fallback_address: FALLBACK_ADDR, }; assert_err!( check_ccm_for_blacklisted_accounts(&ccm_accounts, blacklisted_accounts()), @@ -391,10 +395,10 @@ mod test { is_writable: true, }, additional_accounts: vec![ - SolCcmAddress { pubkey: crate::sol::SolPubkey([0x01; 32]), is_writable: false }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: false }, SolCcmAddress { pubkey: crate::sol::SolPubkey([0x02; 32]), is_writable: false }, ], - fallback_address: SolPubkey([0xf0; 32]), + fallback_address: FALLBACK_ADDR, }; assert_err!( check_ccm_for_blacklisted_accounts(&ccm_accounts, blacklisted_accounts()), @@ -402,15 +406,12 @@ mod test { ); let ccm_accounts = SolCcmAccounts { - cf_receiver: SolCcmAddress { - pubkey: crate::sol::SolPubkey([0x01; 32]), - is_writable: true, - }, + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, additional_accounts: vec![ SolCcmAddress { pubkey: sol_test_values::agg_key().into(), is_writable: false }, SolCcmAddress { pubkey: crate::sol::SolPubkey([0x02; 32]), is_writable: false }, ], - fallback_address: SolPubkey([0xf0; 32]), + fallback_address: FALLBACK_ADDR, }; assert_err!( check_ccm_for_blacklisted_accounts(&ccm_accounts, blacklisted_accounts()), @@ -423,20 +424,47 @@ mod test { message: vec![0x01; MAX_CCM_BYTES_SOL - 36].try_into().unwrap(), gas_budget: 0, ccm_additional_data: VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - fallback_address: SolPubkey([0xf0; 32]), + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, + fallback_address: FALLBACK_ADDR, additional_accounts: vec![ SolCcmAddress { pubkey: SYSTEM_PROGRAM_ID.into(), is_writable: false }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, ], }) .encode() .try_into() .unwrap(), }; - assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::Sol)); + assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::Sol, DEST_ADDR)); + } + #[test] + fn can_check_length_duplicated_with_destination_address() { + let ccm = || CcmChannelMetadata { + message: vec![0x01; MAX_CCM_BYTES_SOL - 36].try_into().unwrap(), + gas_budget: 0, + ccm_additional_data: VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, + fallback_address: FALLBACK_ADDR, + additional_accounts: vec![ + SolCcmAddress { pubkey: SYSTEM_PROGRAM_ID.into(), is_writable: false }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { + pubkey: SolPubkey::try_from(DEST_ADDR).unwrap(), + is_writable: true, + }, + SolCcmAddress { + pubkey: SolPubkey::try_from(DEST_ADDR).unwrap(), + is_writable: true, + }, + ], + }) + .encode() + .try_into() + .unwrap(), + }; + assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::Sol, DEST_ADDR)); } #[test] fn can_check_length_native_duplicated_fail() { @@ -444,14 +472,14 @@ mod test { message: vec![0x01; MAX_CCM_BYTES_SOL - 68].try_into().unwrap(), gas_budget: 0, ccm_additional_data: VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - fallback_address: SolPubkey([0xf0; 32]), + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, + fallback_address: FALLBACK_ADDR, additional_accounts: vec![ SolCcmAddress { pubkey: SYSTEM_PROGRAM_ID.into(), is_writable: false }, SolCcmAddress { pubkey: TOKEN_PROGRAM_ID.into(), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, ], }) .encode() @@ -459,7 +487,7 @@ mod test { .unwrap(), }; assert_err!( - CcmValidityChecker::check_and_decode(&invalid_ccm(), Asset::Sol), + CcmValidityChecker::check_and_decode(&invalid_ccm(), Asset::Sol, DEST_ADDR), CcmValidityError::CcmIsTooLong ); } @@ -469,21 +497,21 @@ mod test { message: vec![0x01; MAX_CCM_BYTES_USDC - 37].try_into().unwrap(), gas_budget: 0, ccm_additional_data: VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - fallback_address: SolPubkey([0xf0; 32]), + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, + fallback_address: FALLBACK_ADDR, additional_accounts: vec![ SolCcmAddress { pubkey: SYSTEM_PROGRAM_ID.into(), is_writable: false }, SolCcmAddress { pubkey: TOKEN_PROGRAM_ID.into(), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, ], }) .encode() .try_into() .unwrap(), }; - assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::SolUsdc)); + assert_ok!(CcmValidityChecker::check_and_decode(&ccm(), Asset::SolUsdc, DEST_ADDR)); } #[test] fn can_check_length_usdc_duplicated_fail() { @@ -491,14 +519,14 @@ mod test { message: vec![0x01; MAX_CCM_BYTES_USDC - 36].try_into().unwrap(), gas_budget: 0, ccm_additional_data: VersionedSolanaCcmAdditionalData::V0(SolCcmAccounts { - cf_receiver: SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - fallback_address: SolPubkey([0xf0; 32]), + cf_receiver: SolCcmAddress { pubkey: CF_RECEIVER_ADDR, is_writable: true }, + fallback_address: FALLBACK_ADDR, additional_accounts: vec![ SolCcmAddress { pubkey: SYSTEM_PROGRAM_ID.into(), is_writable: false }, SolCcmAddress { pubkey: TOKEN_PROGRAM_ID.into(), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, - SolCcmAddress { pubkey: SolPubkey([0x01; 32]), is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, + SolCcmAddress { pubkey: MOCK_ADDR, is_writable: true }, ], }) .encode() @@ -506,8 +534,17 @@ mod test { .unwrap(), }; assert_err!( - CcmValidityChecker::check_and_decode(&invalid_ccm(), Asset::SolUsdc), + CcmValidityChecker::check_and_decode(&invalid_ccm(), Asset::SolUsdc, DEST_ADDR), CcmValidityError::CcmIsTooLong ); } + + #[test] + fn can_verify_destination_address() { + let ccm = sol_test_values::ccm_parameter().channel_metadata; + assert_eq!( + CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, INVALID_DEST_ADDR), + Err(CcmValidityError::InvalidDestinationAddress) + ); + } } diff --git a/state-chain/chains/src/sol/api.rs b/state-chain/chains/src/sol/api.rs index a69e4ad956a..f2a59d40881 100644 --- a/state-chain/chains/src/sol/api.rs +++ b/state-chain/chains/src/sol/api.rs @@ -364,6 +364,7 @@ impl SolanaApi { .expect("This is parsed from bounded vec, therefore the size must fit"), }, transfer_param.asset.into(), + transfer_param.to.into(), ) .map_err(SolanaTransactionBuildingError::InvalidCcm)?; diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index 5fc61bdc27e..357aa02ff00 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -2202,8 +2202,12 @@ impl, I: 'static> Pallet { }; if let Some(metadata) = deposit_metadata.clone() { - if T::CcmValidityChecker::check_and_decode(&metadata.channel_metadata, output_asset) - .is_err() + if T::CcmValidityChecker::check_and_decode( + &metadata.channel_metadata, + output_asset, + destination_address, + ) + .is_err() { log::warn!("Failed to process vault swap due to invalid CCM metadata"); return; @@ -2501,6 +2505,7 @@ impl, I: 'static> Pallet { if T::CcmValidityChecker::check_and_decode( &metadata.channel_metadata, destination_asset, + destination_address, ) .is_err() { diff --git a/state-chain/pallets/cf-swapping/src/lib.rs b/state-chain/pallets/cf-swapping/src/lib.rs index 8aca9614ed3..a9ace7fb553 100644 --- a/state-chain/pallets/cf-swapping/src/lib.rs +++ b/state-chain/pallets/cf-swapping/src/lib.rs @@ -1119,16 +1119,19 @@ pub mod pallet { let destination_chain: ForeignChain = destination_asset.into(); ensure!(destination_chain.ccm_support(), Error::::CcmUnsupportedForTargetChain); - let _ = T::CcmValidityChecker::check_and_decode(ccm, destination_asset).map_err( - |e| { - log::warn!( - "Failed to open channel due to invalid CCM. Broker: {:?}, Error: {:?}", - broker, - e - ); - Error::::InvalidCcm - }, - )?; + let _ = T::CcmValidityChecker::check_and_decode( + ccm, + destination_asset, + destination_address.clone(), + ) + .map_err(|e| { + log::warn!( + "Failed to open channel due to invalid CCM. Broker: {:?}, Error: {:?}", + broker, + e + ); + Error::::InvalidCcm + })?; } let (channel_id, deposit_address, expiry_height, channel_opening_fee) = diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index ddb47b11364..54d71c3b4be 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -2282,7 +2282,7 @@ impl_runtime_apis! { } // Ensure CCM message is valid - match CcmValidityChecker::check_and_decode(ccm, destination_asset) + match CcmValidityChecker::check_and_decode(ccm, destination_asset, destination_address.clone()) { Ok(DecodedCcmAdditionalData::Solana(VersionedSolanaCcmAdditionalData::V0(ccm_accounts))) => { // Ensure the CCM parameters do not contain blacklisted accounts. From 2863abf636079334fb77d1aea5f4c4f4ff6db262 Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:42:50 +0100 Subject: [PATCH 05/20] fix: ensure lp account is registered for transfers (#5605) * fix: ensure lp account is registered for transfers * fix: bouncer test * fix: bouncer lint --- bouncer/tests/lp_api_test.ts | 10 ++++++++++ state-chain/pallets/cf-lp/src/lib.rs | 7 +++++++ state-chain/pallets/cf-lp/src/tests.rs | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/bouncer/tests/lp_api_test.ts b/bouncer/tests/lp_api_test.ts index dd54f779352..cda327939f2 100644 --- a/bouncer/tests/lp_api_test.ts +++ b/bouncer/tests/lp_api_test.ts @@ -12,6 +12,9 @@ import { assetDecimals, stateChainAssetFromAsset, Chain, + handleSubstrateError, + shortChainFromAsset, + newAddress, } from '../shared/utils'; import { lpApiRpc } from '../shared/json_rpc'; import { depositLiquidity } from '../shared/deposit_liquidity'; @@ -151,6 +154,13 @@ async function testTransferAsset() { const sourceLpAccount = keyring.createFromUri('//LP_API'); const destinationLpAccount = keyring.createFromUri('//LP_2'); + // Destination account needs a refund address too. + const chain = shortChainFromAsset(testAsset); + const refundAddress = await newAddress(testAsset, '//LP_2'); + await chainflip.tx.liquidityProvider + .registerLiquidityRefundAddress({ [chain]: refundAddress }) + .signAndSend(destinationLpAccount, { nonce: -1 }, handleSubstrateError(chainflip)); + const oldBalanceSource = await getLpBalance(sourceLpAccount.address); const oldBalanceDestination = await getLpBalance(destinationLpAccount.address); diff --git a/state-chain/pallets/cf-lp/src/lib.rs b/state-chain/pallets/cf-lp/src/lib.rs index 83bfe2c24c3..20221c2e5d1 100644 --- a/state-chain/pallets/cf-lp/src/lib.rs +++ b/state-chain/pallets/cf-lp/src/lib.rs @@ -357,6 +357,13 @@ impl Pallet { ), Error::::DestinationAccountNotLiquidityProvider ); + ensure!( + LiquidityRefundAddress::::contains_key( + &destination_account, + ForeignChain::from(asset) + ), + Error::::NoLiquidityRefundAddressRegistered + ); // Sweep earned fees T::PoolApi::sweep(&account_id)?; diff --git a/state-chain/pallets/cf-lp/src/tests.rs b/state-chain/pallets/cf-lp/src/tests.rs index 5c47121a8fe..7b0b27497fb 100644 --- a/state-chain/pallets/cf-lp/src/tests.rs +++ b/state-chain/pallets/cf-lp/src/tests.rs @@ -5,7 +5,7 @@ use cf_primitives::{AccountId, Asset, AssetAmount, ForeignChain}; use cf_test_utilities::assert_events_match; use cf_traits::{AccountRoleRegistry, BalanceApi, Chainflip, SetSafeMode}; -use frame_support::{assert_noop, assert_ok, error::BadOrigin, traits::OriginTrait}; +use frame_support::{assert_err, assert_noop, assert_ok, error::BadOrigin, traits::OriginTrait}; use sp_runtime::AccountId32; #[test] @@ -88,6 +88,21 @@ fn liquidity_providers_can_move_assets_internally() { Error::::CannotTransferToOriginAccount ); + assert_err!( + LiquidityProvider::transfer_asset( + RuntimeOrigin::signed((LP_ACCOUNT).into()), + TRANSFER_AMOUNT, + Asset::Eth, + AccountId::from(LP_ACCOUNT_2), + ), + Error::::NoLiquidityRefundAddressRegistered + ); + + assert_ok!(LiquidityProvider::register_liquidity_refund_address( + RuntimeOrigin::signed(LP_ACCOUNT_2.into()), + EncodedAddress::Eth(Default::default()) + )); + assert_ok!(LiquidityProvider::transfer_asset( RuntimeOrigin::signed((LP_ACCOUNT).into()), TRANSFER_AMOUNT, From 76a76b3cabbaa886be648e581d011b5a74617e67 Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:24:05 +0100 Subject: [PATCH 06/20] fix: use chain-specific account type for refund params (#5560) * fix: use chain-specific account type for refund params * fix: bouncer tests * fix: update unit tests --- .../src/witnessing/state_chain.rs | 13 +++--- bouncer/tests/vault_swap_fee_collection.ts | 2 +- engine/src/witness/arb.rs | 4 +- engine/src/witness/btc/vault_swaps.rs | 10 ++--- engine/src/witness/eth.rs | 4 +- engine/src/witness/evm/vault.rs | 19 ++++---- .../cf-integration-tests/src/solana.rs | 6 +-- .../cf-integration-tests/src/swapping.rs | 6 +-- state-chain/chains/src/cf_parameters.rs | 43 ++++++++++++------- state-chain/chains/src/evm/api/vault_swaps.rs | 11 ++--- .../src/evm/api/vault_swaps/x_call_native.rs | 6 +-- .../src/evm/api/vault_swaps/x_call_token.rs | 12 +++--- .../src/evm/api/vault_swaps/x_swap_native.rs | 4 +- .../src/evm/api/vault_swaps/x_swap_token.rs | 4 +- .../chains/src/sol/instruction_builder.rs | 21 ++++----- .../cf-ingress-egress/src/benchmarking.rs | 7 ++- .../pallets/cf-ingress-egress/src/lib.rs | 20 ++++++--- .../pallets/cf-ingress-egress/src/tests.rs | 8 ++-- .../cf-ingress-egress/src/tests/boost.rs | 8 ++-- .../runtime/src/chainflip/solana_elections.rs | 4 +- .../runtime/src/chainflip/vault_swaps.rs | 12 ++++-- state-chain/runtime/src/lib.rs | 16 +------ 22 files changed, 131 insertions(+), 109 deletions(-) diff --git a/api/bin/chainflip-ingress-egress-tracker/src/witnessing/state_chain.rs b/api/bin/chainflip-ingress-egress-tracker/src/witnessing/state_chain.rs index 992c8dc76f8..00d3e511187 100644 --- a/api/bin/chainflip-ingress-egress-tracker/src/witnessing/state_chain.rs +++ b/api/bin/chainflip-ingress-egress-tracker/src/witnessing/state_chain.rs @@ -3,7 +3,7 @@ use crate::{ utils::get_broadcast_id, }; use cf_chains::{ - address::EncodedAddress, + address::{EncodedAddress, IntoForeignChainAddress}, dot::{PolkadotExtrinsicIndex, PolkadotTransactionId}, evm::{SchnorrVerificationComponents, H256}, instances::ChainInstanceFor, @@ -288,7 +288,11 @@ where .collect::>>() .try_into() .expect("We collect into the same Affiliates type we started with, so the Vec bound is the same."), - refund_params: self.refund_params.map(|params| params.map_address(|a| TrackerAddress::from(a.to_encoded_address(network)))), + refund_params: self.refund_params.map( + |params| params.map_address( + |a| TrackerAddress::from(a.into_foreign_chain_address().to_encoded_address(network)) + ) + ), dca_params: self.dca_params, max_boost_fee: self.boost_fee, } @@ -887,7 +891,6 @@ mod tests { #[tokio::test] async fn test_handle_vault_deposit_calls() { - use cf_chains::ChannelRefundParametersDecoded; chainflip_api::use_chainflip_account_id_encoding(); let (eth_address, _) = parse_eth_address("0x541f563237A309B3A61E33BDf07a8930Bdba8D99"); let affiliate_short_id = AffiliateShortId::from(69); @@ -942,8 +945,8 @@ mod tests { bps: 10 }]) .unwrap(), - refund_params: Some(ChannelRefundParametersDecoded { - refund_address: ForeignChainAddress::Eth(eth_address), + refund_params: Some(ChannelRefundParameters { + refund_address: eth_address, retry_duration: Default::default(), min_price: Default::default(), }), diff --git a/bouncer/tests/vault_swap_fee_collection.ts b/bouncer/tests/vault_swap_fee_collection.ts index 24445962fdc..6f923d2e51c 100644 --- a/bouncer/tests/vault_swap_fee_collection.ts +++ b/bouncer/tests/vault_swap_fee_collection.ts @@ -129,7 +129,7 @@ async function testFeeCollection(inputAsset: Asset): Promise<[KeyringPair, strin testVaultSwapFeeCollection.debugLog('Earned affiliate fees after:', earnedAffiliateFeesAfter); assert( earnedBrokerFeesAfter > earnedBrokerFeesBefore, - `No increase in earned broker fees after ${inputAsset} swap`, + `No increase in earned broker fees after ${tag}(${inputAsset} -> ${destAsset}) vault swap: ${{ account: broker.address, commissionBps }}, ${earnedBrokerFeesBefore} -> ${earnedBrokerFeesAfter}`, ); assert( earnedAffiliateFeesAfter > earnedAffiliateFeesBefore, diff --git a/engine/src/witness/arb.rs b/engine/src/witness/arb.rs index 6050c63e73e..b762ce14db3 100644 --- a/engine/src/witness/arb.rs +++ b/engine/src/witness/arb.rs @@ -192,7 +192,9 @@ impl super::evm::vault::IngressCallBuilder for ArbCallBuilder { destination_address: EncodedAddress, deposit_metadata: Option, tx_id: H256, - vault_swap_parameters: Option, + vault_swap_parameters: Option< + VaultSwapParameters<::ChainAccount>, + >, ) -> state_chain_runtime::RuntimeCall { let deposit = vault_deposit_witness!( source_asset, diff --git a/engine/src/witness/btc/vault_swaps.rs b/engine/src/witness/btc/vault_swaps.rs index bb5a72a133b..ff98041324c 100644 --- a/engine/src/witness/btc/vault_swaps.rs +++ b/engine/src/witness/btc/vault_swaps.rs @@ -6,7 +6,7 @@ use cf_chains::{ deposit_address::DepositAddress, vault_swap_encoding::UtxoEncodedData, ScriptPubkey, Utxo, UtxoId, }, - ChannelRefundParametersDecoded, ForeignChainAddress, + ChannelRefundParameters, }; use cf_primitives::{AccountId, Beneficiary, ChannelId, DcaParameters}; use cf_utilities::SliceToArray; @@ -159,9 +159,9 @@ pub fn try_extract_vault_swap_witness( .collect_vec() .try_into() .expect("runtime supports at least as many affiliates as we allow in UTXO encoding"), - refund_params: Some(ChannelRefundParametersDecoded { + refund_params: Some(ChannelRefundParameters { retry_duration: data.parameters.retry_duration.into(), - refund_address: ForeignChainAddress::Btc(refund_address), + refund_address, min_price, }), dca_params: Some(DcaParameters { @@ -326,9 +326,9 @@ mod tests { }), affiliate_fees: bounded_vec![MOCK_SWAP_PARAMS.parameters.affiliates[0].into()], deposit_metadata: None, - refund_params: Some(ChannelRefundParametersDecoded { + refund_params: Some(ChannelRefundParameters { retry_duration: MOCK_SWAP_PARAMS.parameters.retry_duration.into(), - refund_address: ForeignChainAddress::Btc(refund_pubkey), + refund_address: refund_pubkey, min_price: sqrt_price_to_price(bounded_sqrt_price( MOCK_SWAP_PARAMS.parameters.min_output_amount.into(), DEPOSIT_AMOUNT.into(), diff --git a/engine/src/witness/eth.rs b/engine/src/witness/eth.rs index fd3cef8daea..c5e289ba8a3 100644 --- a/engine/src/witness/eth.rs +++ b/engine/src/witness/eth.rs @@ -241,7 +241,9 @@ impl super::evm::vault::IngressCallBuilder for EthCallBuilder { destination_address: EncodedAddress, deposit_metadata: Option, tx_id: H256, - vault_swap_parameters: Option, + vault_swap_parameters: Option< + VaultSwapParameters<::ChainAccount>, + >, ) -> state_chain_runtime::RuntimeCall { let deposit = vault_deposit_witness!( source_asset, diff --git a/engine/src/witness/evm/vault.rs b/engine/src/witness/evm/vault.rs index 639105caa08..4923161b497 100644 --- a/engine/src/witness/evm/vault.rs +++ b/engine/src/witness/evm/vault.rs @@ -27,14 +27,15 @@ use state_chain_runtime::{EthereumInstance, Runtime, RuntimeCall}; abigen!(Vault, "$CF_ETH_CONTRACT_ABI_ROOT/$CF_ETH_CONTRACT_ABI_TAG/IVault.json"); -fn decode_cf_parameters( +fn decode_cf_parameters( cf_parameters: &[u8], block_height: u64, -) -> (Option, CcmData) +) -> (Option>, CcmData) where + RefundAddress: Decode, CcmData: Default + Decode, { - if let Ok(decoded) = VersionedCfParameters::::decode(&mut &cf_parameters[..]) { + if let Ok(decoded) = VersionedCfParameters::decode(&mut &cf_parameters[..]) { match decoded { VersionedCfParameters::V0(CfParameters { ccm_additional_data, @@ -86,8 +87,8 @@ where sender: _, cf_parameters, }) => { - let vault_swap_parameters = - decode_cf_parameters::<()>(&cf_parameters[..], block_height).0; + let (vault_swap_parameters, ()) = + decode_cf_parameters(&cf_parameters[..], block_height); Some(CallBuilder::vault_swap_request( block_height, @@ -109,8 +110,8 @@ where sender: _, cf_parameters, }) => { - let vault_swap_parameters = - decode_cf_parameters::<()>(&cf_parameters[..], block_height).0; + let (vault_swap_parameters, ()) = + decode_cf_parameters(&cf_parameters[..], block_height); Some(CallBuilder::vault_swap_request( block_height, @@ -287,7 +288,9 @@ pub trait IngressCallBuilder { destination_address: EncodedAddress, deposit_metadata: Option, tx_hash: H256, - vault_swap_parameters: Option, + vault_swap_parameters: Option< + VaultSwapParameters<::ChainAccount>, + >, ) -> state_chain_runtime::RuntimeCall; fn vault_transfer_failed( diff --git a/state-chain/cf-integration-tests/src/solana.rs b/state-chain/cf-integration-tests/src/solana.rs index 72f1b9fe9ce..9f4a05d3e73 100644 --- a/state-chain/cf-integration-tests/src/solana.rs +++ b/state-chain/cf-integration-tests/src/solana.rs @@ -14,7 +14,7 @@ use cf_chains::{ SolAddress, SolApiEnvironment, SolCcmAccounts, SolCcmAddress, SolHash, SolPubkey, SolanaCrypto, }, - CcmChannelMetadata, CcmDepositMetadata, Chain, ChannelRefundParametersDecoded, + CcmChannelMetadata, CcmDepositMetadata, Chain, ChannelRefundParameters, ExecutexSwapAndCallError, ForeignChainAddress, RequiresSignatureRefresh, SetAggKeyWithAggKey, SetAggKeyWithAggKeyError, Solana, SwapOrigin, TransactionBuilder, }; @@ -58,9 +58,9 @@ const BOB: AccountId = AccountId::new([0x44; 32]); const DEPOSIT_AMOUNT: u64 = 5_000_000_000u64; // 5 Sol const FALLBACK_ADDRESS: SolAddress = SolAddress([0xf0; 32]); -const REFUND_PARAMS: ChannelRefundParametersDecoded = ChannelRefundParametersDecoded { +const REFUND_PARAMS: ChannelRefundParameters = ChannelRefundParameters { retry_duration: 0, - refund_address: ForeignChainAddress::Sol(FALLBACK_ADDRESS), + refund_address: FALLBACK_ADDRESS, min_price: sp_core::U256::zero(), }; diff --git a/state-chain/cf-integration-tests/src/swapping.rs b/state-chain/cf-integration-tests/src/swapping.rs index a9e43fa1240..3156835a7c1 100644 --- a/state-chain/cf-integration-tests/src/swapping.rs +++ b/state-chain/cf-integration-tests/src/swapping.rs @@ -18,7 +18,7 @@ use cf_chains::{ assets::eth::Asset as EthAsset, eth::{api::EthereumApi, EthereumTrackedData}, evm::DepositDetails, - CcmChannelMetadata, CcmDepositMetadata, Chain, ChainState, ChannelRefundParametersDecoded, + CcmChannelMetadata, CcmDepositMetadata, Chain, ChainState, ChannelRefundParameters, DefaultRetryPolicy, Ethereum, ExecutexSwapAndCall, ForeignChain, ForeignChainAddress, RetryPolicy, SwapOrigin, TransactionBuilder, TransferAssetParams, }; @@ -53,9 +53,9 @@ use state_chain_runtime::{ const DORIS: AccountId = AccountId::new([0x11; 32]); const ZION: AccountId = AccountId::new([0x22; 32]); -const ETH_REFUND_PARAMS: ChannelRefundParametersDecoded = ChannelRefundParametersDecoded { +const ETH_REFUND_PARAMS: ChannelRefundParameters = ChannelRefundParameters { retry_duration: 5, - refund_address: ForeignChainAddress::Eth(sp_core::H160([100u8; 20])), + refund_address: sp_core::H160([100u8; 20]), min_price: sp_core::U256::zero(), }; diff --git a/state-chain/chains/src/cf_parameters.rs b/state-chain/chains/src/cf_parameters.rs index 2fbb1b1761b..837483086c8 100644 --- a/state-chain/chains/src/cf_parameters.rs +++ b/state-chain/chains/src/cf_parameters.rs @@ -1,4 +1,4 @@ -use crate::{CcmAdditionalData, CcmChannelMetadata, ChannelRefundParametersDecoded}; +use crate::{CcmAdditionalData, CcmChannelMetadata, Chain, ChannelRefundParameters}; use cf_primitives::{ AccountId, AffiliateAndFee, BasisPoints, Beneficiary, DcaParameters, MAX_AFFILIATES, }; @@ -8,21 +8,25 @@ use sp_core::ConstU32; use sp_runtime::{BoundedVec, Vec}; #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Debug)] -pub enum VersionedCfParameters { - V0(CfParameters), +pub enum VersionedCfParameters { + V0(CfParameters), } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Debug)] -pub struct CfParameters { +pub struct CfParameters { /// CCMs may require additional data (e.g. CCMs to Solana requires a list of addresses). pub ccm_additional_data: CcmData, - pub vault_swap_parameters: VaultSwapParameters, + pub vault_swap_parameters: VaultSwapParameters, } -pub type VersionedCcmCfParameters = VersionedCfParameters; +pub type VersionedCcmCfParameters = + VersionedCfParameters; -impl CfParameters { - pub fn with_ccm_data(cf_parameter: CfParameters<()>, data: CcmAdditionalData) -> Self { +impl CfParameters { + pub fn with_ccm_data( + cf_parameter: CfParameters, + data: CcmAdditionalData, + ) -> Self { CfParameters { ccm_additional_data: data, vault_swap_parameters: cf_parameter.vault_swap_parameters, @@ -31,8 +35,8 @@ impl CfParameters { } #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Debug)] -pub struct VaultSwapParameters { - pub refund_params: ChannelRefundParametersDecoded, +pub struct VaultSwapParameters { + pub refund_params: ChannelRefundParameters, pub dca_params: Option, pub boost_fee: u8, pub broker_fee: Beneficiary, @@ -42,8 +46,8 @@ pub struct VaultSwapParameters { /// Provide a function that builds and encodes `cf_parameters`. /// The return type is encoded Vec, which circumvents the difference in return types depending /// on if CCM data is available. -pub fn build_cf_parameters( - refund_parameters: ChannelRefundParametersDecoded, +pub fn build_cf_parameters( + refund_parameters: ChannelRefundParameters, dca_parameters: Option, boost_fee: u8, broker_id: AccountId, @@ -88,12 +92,19 @@ mod tests { #[test] fn test_cf_parameters_max_length() { + // Pessimistic assumption of some chain with 64 bytes of account data. + #[derive(Encode, Decode, MaxEncodedLen)] + struct MaxAccountLength([u8; 64]); assert!( - MAX_VAULT_SWAP_PARAMETERS_LENGTH as usize >= VaultSwapParameters::max_encoded_len() + MAX_VAULT_SWAP_PARAMETERS_LENGTH as usize >= + VaultSwapParameters::::max_encoded_len() ); - assert!(MAX_CF_PARAM_LENGTH as usize >= CfParameters::<()>::max_encoded_len()); assert!( - MAX_VAULT_SWAP_PARAMETERS_LENGTH as usize >= VaultSwapParameters::max_encoded_len() + MAX_CF_PARAM_LENGTH as usize >= CfParameters::::max_encoded_len() + ); + assert!( + MAX_VAULT_SWAP_PARAMETERS_LENGTH as usize >= + VaultSwapParameters::::max_encoded_len() ); } @@ -111,7 +122,7 @@ mod tests { affiliate_fees: sp_core::bounded_vec![], }; - let cf_parameters = CfParameters::<()> { + let cf_parameters = CfParameters { ccm_additional_data: (), vault_swap_parameters: vault_swap_parameters.clone(), }; diff --git a/state-chain/chains/src/evm/api/vault_swaps.rs b/state-chain/chains/src/evm/api/vault_swaps.rs index e0b0605c014..9a78f7267a7 100644 --- a/state-chain/chains/src/evm/api/vault_swaps.rs +++ b/state-chain/chains/src/evm/api/vault_swaps.rs @@ -7,16 +7,17 @@ pub mod x_swap_token; #[cfg(test)] pub mod test_utils { use crate::{ - cf_parameters::*, CcmChannelMetadata, ChannelRefundParameters, ForeignChainAddress, + cf_parameters::*, eth::Address as EthAddress, CcmChannelMetadata, ChannelRefundParameters, }; use cf_primitives::{ - AccountId, AffiliateAndFee, AffiliateShortId, Beneficiary, DcaParameters, MAX_AFFILIATES, + chains::Ethereum, AccountId, AffiliateAndFee, AffiliateShortId, Beneficiary, DcaParameters, + MAX_AFFILIATES, }; use frame_support::pallet_prelude::ConstU32; use sp_runtime::BoundedVec; - pub fn refund_address() -> ForeignChainAddress { - ForeignChainAddress::Eth([0xF0; 20].into()) + pub fn refund_address() -> EthAddress { + [0xF0; 20].into() } pub fn dca_parameter() -> DcaParameters { DcaParameters { number_of_chunks: 10u32, chunk_interval: 5u32 } @@ -40,7 +41,7 @@ pub mod test_utils { pub const BROKER_FEE: u8 = 150u8; pub fn dummy_cf_parameter(with_ccm: bool) -> Vec { - build_cf_parameters( + build_cf_parameters::( ChannelRefundParameters { retry_duration: 1u32, refund_address: refund_address(), diff --git a/state-chain/chains/src/evm/api/vault_swaps/x_call_native.rs b/state-chain/chains/src/evm/api/vault_swaps/x_call_native.rs index c813e18288b..562426527ca 100644 --- a/state-chain/chains/src/evm/api/vault_swaps/x_call_native.rs +++ b/state-chain/chains/src/evm/api/vault_swaps/x_call_native.rs @@ -140,13 +140,13 @@ mod test { 255, 255, 255, 255, 255, 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, 7, 0, 1, 2, 3, 4, 5, 6, 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, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 16, 17, 34, 51, - 68, 1, 0, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 16, 17, 34, 51, + 68, 1, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 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, 1, 10, 0, 0, 0, 5, 0, 0, 0, 100, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 1, - 0, 4, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 4, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); } diff --git a/state-chain/chains/src/evm/api/vault_swaps/x_call_token.rs b/state-chain/chains/src/evm/api/vault_swaps/x_call_token.rs index a301433dea0..337356c0f45 100644 --- a/state-chain/chains/src/evm/api/vault_swaps/x_call_token.rs +++ b/state-chain/chains/src/evm/api/vault_swaps/x_call_token.rs @@ -164,13 +164,13 @@ mod test { 255, 255, 255, 255, 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, 7, 0, 1, 2, 3, 4, 5, 6, 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, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 0, 16, 17, 34, 51, 68, - 1, 0, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 240, 240, 240, 240, 240, 240, 240, 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, 1, 10, 0, 0, 0, 5, 0, 0, 0, 100, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 16, 17, 34, 51, 68, + 1, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 240, 240, 240, 240, 240, 240, 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, 1, 10, 0, 0, 0, 5, 0, 0, 0, 100, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, - 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 1, - 0, 4, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 1, 0, 4, + 1, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); } diff --git a/state-chain/chains/src/evm/api/vault_swaps/x_swap_native.rs b/state-chain/chains/src/evm/api/vault_swaps/x_swap_native.rs index 1e25f240ffb..12a6f494df9 100644 --- a/state-chain/chains/src/evm/api/vault_swaps/x_swap_native.rs +++ b/state-chain/chains/src/evm/api/vault_swaps/x_swap_native.rs @@ -114,13 +114,13 @@ mod test { 32, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, 105, 0, 1, 0, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 0, 0, 104, 0, 1, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 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, 1, 10, 0, 0, 0, 5, 0, 0, 0, 100, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 1, 0, 4, 1, 10, 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 ] ); } diff --git a/state-chain/chains/src/evm/api/vault_swaps/x_swap_token.rs b/state-chain/chains/src/evm/api/vault_swaps/x_swap_token.rs index 6679c7005d9..55d4793bd64 100644 --- a/state-chain/chains/src/evm/api/vault_swaps/x_swap_token.rs +++ b/state-chain/chains/src/evm/api/vault_swaps/x_swap_token.rs @@ -143,13 +143,13 @@ mod test { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 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, - 105, 0, 1, 0, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, + 104, 0, 1, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 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, 1, 10, 0, 0, 0, 5, 0, 0, 0, 100, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 1, 0, 4, 1, 10, 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 ] ); } diff --git a/state-chain/chains/src/sol/instruction_builder.rs b/state-chain/chains/src/sol/instruction_builder.rs index 2eb44029c81..abd3344194c 100644 --- a/state-chain/chains/src/sol/instruction_builder.rs +++ b/state-chain/chains/src/sol/instruction_builder.rs @@ -105,10 +105,11 @@ mod test { signing_key::SolSigningKey, sol_tx_core::sol_test_values::*, SolAddress, SolHash, SolLegacyMessage, SolLegacyTransaction, }, - ChannelRefundParametersDecoded, ForeignChainAddress, + ChannelRefundParameters, }; use cf_primitives::{ - AccountId, AffiliateAndFee, AffiliateShortId, BasisPoints, DcaParameters, MAX_AFFILIATES, + chains::Solana, AccountId, AffiliateAndFee, AffiliateShortId, BasisPoints, DcaParameters, + MAX_AFFILIATES, }; use sol_prim::consts::{const_address, MAX_TRANSACTION_LENGTH}; use sp_core::ConstU32; @@ -162,10 +163,10 @@ mod test { )) } - fn channel_refund_parameters() -> ChannelRefundParametersDecoded { - ChannelRefundParametersDecoded { + fn channel_refund_parameters() -> ChannelRefundParameters { + ChannelRefundParameters { min_price: sp_core::U256::default(), - refund_address: ForeignChainAddress::Sol(DESTINATION_ADDRESS_SOL), + refund_address: DESTINATION_ADDRESS_SOL, retry_duration: 10u32, } } @@ -184,7 +185,7 @@ mod test { } fn cf_parameter(with_ccm: bool) -> Vec { - build_cf_parameters( + build_cf_parameters::( channel_refund_parameters(), Some(dca_parameters()), BOOST_FEE, @@ -232,7 +233,7 @@ mod test { FROM.into(), ); - let expected_serialized_tx = hex_literal::hex!("02fd3e32179e2999e2ae3bda58c9e0dee61bcf2a67674dd3f0fe58bd0c161eba05c2680605235cd00b7b8d2dea51201457cee297e0c0b6ef338baa9592bab72209ad58557eab2fd47c1713c850a45fffde67c2d323876df46307d36fd50997a1f9448f6d98b76aaec44b3143518e86868115375a985b5867c4aae991eebf15f40f02000307cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b0000000000000000000000000000000000000000000000000000000000000000010506060300010204ac01a3265ce2f3698dc4d2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000077000000000a000000049e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214").to_vec(); + let expected_serialized_tx = hex_literal::hex!("021d485a1e6df1d3b4dcee7f4c1442443d61c33ba047556e5461cfa14fdccf7eb4b16b1bfad0428704b01dc85e46ac29f19a8f4e82620c6c6cc87cc1a1e5fb1908baa3bed94d223389107f6fff96ce9b85d646ab64f95394f6d4be7455f27571030edc6746ed42c9b8a16abdfd6efc4fbc7e1df5c8f9fc24e5b7ff3ccd8ca0ca0c02000307cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b0000000000000000000000000000000000000000000000000000000000000000010506060300010204ab01a3265ce2f3698dc4d2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000076000000000a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = @@ -263,7 +264,7 @@ mod test { FROM.into(), ); - let expected_serialized_tx = hex_literal::hex!("02b0f0eb7b671ca7af132904f3f349accba018d93f90c2e73a8ea566e88a9bb3590a72ee8ccdbc8e308807e8487839a7a89e1920c4f15f1941ef760a3e07596b029a3fc1c1ad1d27a5fc227808bfcfbb447294aa7b5dbf156aeb2092a5745f0c7e4d8580a8757be3223dfbdbdd0cc60937418b597d229774b6ec6622a9adee0a0e02000307cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b0000000000000000000000000000000000000000000000000000000000000000010506060300010204ae02a3265ce2f3698dc4d20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0a00000001040000007c1d0f070000000000000000dd000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a000000049e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214").to_vec(); + let expected_serialized_tx = hex_literal::hex!("026b0befc47440952c815d5c94691b3ffab988b83e02ee74731b420cf1e572d8f06774330f43a7639528aa004435e10ec49accee16bd3cb73733a502d61fb10e07d4b8cbf900562ab1f613d70be1b28142c869c3d47bfacb6742dd50fa61e6820ce4ec8f10b2a2f330fdd0a351935fecd850323123223af2acb8bbed4b7e86970102000307cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b0000000000000000000000000000000000000000000000000000000000000000010506060300010204ad02a3265ce2f3698dc4d20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0a00000001040000007c1d0f070000000000000000dc000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = @@ -304,7 +305,7 @@ mod test { FROM.into(), ); - let expected_serialized_tx = hex_literal::hex!("025bf721731dad87d86d866ce5d5ae67464837ea82ed143c1b7938b8c80cfa21648ffa8b3f3b6e4c6ffbb98e882a731ba6515aa82ce424c8be77a6868b71d4e903baa3dbe690708ccb8ede793064c778f714cc692789a631ad0ae2a53ee5fa4bc23db51709c77056f76e8818359d4079d1add6ed568b073a4369e8e790d64034070200060bcf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d227fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8ae91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee871ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b000000000000000000000000000000000000000000000000000000000000000001080a0a040003010209060705ad014532fc63e55377ebd2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000077000000000a000000049e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406").to_vec(); + let expected_serialized_tx = hex_literal::hex!("02d546874a4fc16d7c369ea5eb86a3344c3bdeda58b0721de0c2c5a372b455d565232421d5ceabb4782dd2fb23cac7812ef1836032b65afc823094b56d01500201bcc4d183f10ae173e8f7b1fe9fcd8fc2297b4aab8ed58a5d220877dbbbeecedaab2a1d913e1ad6870245e8ed9b4ee0ea1e2f4cb948bf0143defede95c83aa8040200060bcf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d227fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8ae91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee871ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b000000000000000000000000000000000000000000000000000000000000000001080a0a040003010209060705ac014532fc63e55377ebd2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000076000000000a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = @@ -345,7 +346,7 @@ mod test { FROM.into(), ); - let expected_serialized_tx = hex_literal::hex!("02f67f0e322569f5497d6703f08d43ddaa84b617589d4dffb1825f50bd7ba23835ac6b00074a49f2c9324ea4652a8e335394cc290d472bc52e61671c6851109605d89e6841c5499e0f30744855f8f516fd3794a4a5c2f34c4fbbfc02505b3c1a8af193e4e66218d59795d9038816fc95d35ba960f2ba13bd65e8e77a745a74540c0200060bcf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d227fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8ae91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee871ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b000000000000000000000000000000000000000000000000000000000000000001080a0a040003010209060705af024532fc63e55377ebd20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0900000001040000007c1d0f070000000000000000dd000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a000000049e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406").to_vec(); + let expected_serialized_tx = hex_literal::hex!("0283f23209bc3d33c8e7fc33d4541de8fb6172f28d6d73212d1c7630491c0605a9051648fe23da7870a180649a5fd509af4dd0a5a4b4563ca2a5bf8386ecb9fe053160493fa3dd5274744ec6c42b5c6152ef58aff96c2c1230fefc06a6a89fde864db41185571b1d2740d7639ff64a53b35db561d0ee75a68fa9f8268f5807fd060200060bcf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d227fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8ae91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee871ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b000000000000000000000000000000000000000000000000000000000000000001080a0a040003010209060705ae024532fc63e55377ebd20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0900000001040000007c1d0f070000000000000000dc000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = diff --git a/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs b/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs index d75054cd07e..5d21a8c26a6 100644 --- a/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs +++ b/state-chain/pallets/cf-ingress-egress/src/benchmarking.rs @@ -3,7 +3,6 @@ use super::*; use crate::{BoostStatus, DisabledEgressAssets}; use cf_chains::{ - address::EncodedAddress, benchmarking_value::{BenchmarkValue, BenchmarkValueExtended}, DepositChannel, }; @@ -310,15 +309,15 @@ mod benchmarks { input_asset: BenchmarkValue::benchmark_value(), output_asset: Asset::Eth, deposit_amount: 1_000u32.into(), - destination_address: EncodedAddress::benchmark_value(), + destination_address: BenchmarkValue::benchmark_value(), deposit_metadata: Some(deposit_metadata), tx_id: TransactionInIdFor::::benchmark_value(), deposit_details: BenchmarkValue::benchmark_value(), broker_fee: None, affiliate_fees: Default::default(), - refund_params: Some(ChannelRefundParametersDecoded { + refund_params: Some(ChannelRefundParameters { retry_duration: Default::default(), - refund_address: ForeignChainAddress::Eth(Default::default()), + refund_address: BenchmarkValue::benchmark_value(), min_price: Default::default(), }), dca_params: None, diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index 357aa02ff00..903a2917d7b 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -28,9 +28,10 @@ use cf_chains::{ assets::any::GetChainAssetMap, ccm_checker::CcmValidityCheck, AllBatch, AllBatchError, CcmAdditionalData, CcmChannelMetadata, CcmDepositMetadata, CcmMessage, - Chain, ChainCrypto, ChannelLifecycleHooks, ChannelRefundParametersDecoded, ConsolidateCall, - DepositChannel, DepositDetailsToTransactionInId, DepositOriginType, ExecutexSwapAndCall, - FetchAssetParams, ForeignChainAddress, IntoTransactionInIdForAnyChain, RejectCall, SwapOrigin, + Chain, ChainCrypto, ChannelLifecycleHooks, ChannelRefundParameters, + ChannelRefundParametersDecoded, ConsolidateCall, DepositChannel, + DepositDetailsToTransactionInId, DepositOriginType, ExecutexSwapAndCall, FetchAssetParams, + ForeignChainAddress, IntoTransactionInIdForAnyChain, RejectCall, SwapOrigin, TransferAssetParams, }; use cf_primitives::{ @@ -406,12 +407,15 @@ pub mod pallet { pub deposit_amount: ::ChainAmount, pub deposit_details: ::DepositDetails, pub output_asset: Asset, + // Note we use EncodedAddress here rather than eg. ForeignChainAddress because this + // value can be populated by the submitter of the vault deposit and is not verified + // in the engine, so we need to verify on-chain. pub destination_address: EncodedAddress, pub deposit_metadata: Option, pub tx_id: TransactionInIdFor, pub broker_fee: Option>, pub affiliate_fees: Affiliates, - pub refund_params: Option, + pub refund_params: Option>>, pub dca_params: Option, pub boost_fee: BasisPoints, } @@ -465,7 +469,7 @@ pub mod pallet { destination_address: ForeignChainAddress, broker_fees: Beneficiaries, channel_metadata: Option, - refund_params: Option, + refund_params: Option>, dca_params: Option, }, LiquidityProvision { @@ -2237,7 +2241,8 @@ impl, I: 'static> Pallet { destination_asset: output_asset, destination_address: destination_address_internal, broker_fees, - refund_params, + refund_params: refund_params + .map(|params| params.map_address(|address| address.into_foreign_chain_address())), dca_params, channel_metadata, }; @@ -2547,7 +2552,8 @@ impl, I: 'static> Pallet { destination_address: destination_address_internal, broker_fees, channel_metadata: channel_metadata.clone(), - refund_params: refund_params.clone(), + refund_params: refund_params + .map(|params| params.map_address(|address| address.into_foreign_chain_address())), dca_params: dca_params.clone(), }; diff --git a/state-chain/pallets/cf-ingress-egress/src/tests.rs b/state-chain/pallets/cf-ingress-egress/src/tests.rs index c7da4b9bf40..b1beeb6c818 100644 --- a/state-chain/pallets/cf-ingress-egress/src/tests.rs +++ b/state-chain/pallets/cf-ingress-egress/src/tests.rs @@ -17,7 +17,7 @@ use cf_chains::{ btc::{BitcoinNetwork, ScriptPubkey}, evm::{DepositDetails, EvmFetchId, H256}, mocks::MockEthereum, - CcmChannelMetadata, ChannelRefundParametersDecoded, DepositChannel, DepositOriginType, + CcmChannelMetadata, ChannelRefundParameters, DepositChannel, DepositOriginType, ExecutexSwapAndCall, SwapOrigin, TransactionInIdForAnyChain, TransferAssetParams, }; use cf_primitives::{ @@ -56,9 +56,9 @@ const BOB_ETH_ADDRESS: EthereumAddress = H160([101u8; 20]); const ETH_ETH: EthAsset = EthAsset::Eth; const ETH_FLIP: EthAsset = EthAsset::Flip; const DEFAULT_DEPOSIT_AMOUNT: u128 = 1_000; -const ETH_REFUND_PARAMS: ChannelRefundParametersDecoded = ChannelRefundParametersDecoded { +const ETH_REFUND_PARAMS: ChannelRefundParameters = ChannelRefundParameters { retry_duration: 0, - refund_address: ForeignChainAddress::Eth(ALICE_ETH_ADDRESS), + refund_address: ALICE_ETH_ADDRESS, min_price: sp_core::U256::zero(), }; @@ -1808,7 +1808,7 @@ fn submit_vault_swap_request( deposit_details: DepositDetails, broker_fee: Beneficiary, affiliate_fees: Affiliates, - refund_params: ChannelRefundParametersDecoded, + refund_params: ChannelRefundParameters, dca_params: Option, boost_fee: BasisPoints, ) -> DispatchResult { diff --git a/state-chain/pallets/cf-ingress-egress/src/tests/boost.rs b/state-chain/pallets/cf-ingress-egress/src/tests/boost.rs index 94aeafa7c04..44062ac3879 100644 --- a/state-chain/pallets/cf-ingress-egress/src/tests/boost.rs +++ b/state-chain/pallets/cf-ingress-egress/src/tests/boost.rs @@ -1,6 +1,6 @@ use super::*; -use cf_chains::{ChannelRefundParametersDecoded, DepositOriginType, FeeEstimationApi}; +use cf_chains::{DepositOriginType, FeeEstimationApi}; use cf_primitives::{AssetAmount, BasisPoints, PrewitnessedDepositId, SwapRequestId}; use cf_test_utilities::assert_event_sequence; use cf_traits::{ @@ -1150,6 +1150,8 @@ fn taking_network_fee_from_boost_fee() { mod vault_swaps { + use cf_chains::ChannelRefundParameters; + use crate::BoostedVaultTransactions; use super::*; @@ -1200,9 +1202,9 @@ mod vault_swaps { tx_id, broker_fee: Some(Beneficiary { account: BROKER, bps: 5 }), affiliate_fees: Default::default(), - refund_params: Some(ChannelRefundParametersDecoded { + refund_params: Some(ChannelRefundParameters { retry_duration: 2, - refund_address: ForeignChainAddress::Eth([2; 20].into()), + refund_address: [2; 20].into(), min_price: Default::default(), }), dca_params: None, diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index b42c8fcc10e..25f26c52335 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::{ compute_units_costs::MIN_COMPUTE_PRICE, SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, SolanaCrypto, }, - CcmDepositMetadata, Chain, ChannelRefundParametersDecoded, FeeEstimationApi, + CcmDepositMetadata, Chain, ChannelRefundParameters, FeeEstimationApi, FetchAndCloseSolanaVaultSwapAccounts, ForeignChain, Solana, }; use cf_primitives::{AffiliateShortId, Affiliates, Beneficiary, DcaParameters}; @@ -495,7 +495,7 @@ pub struct SolanaVaultSwapDetails { pub swap_account: SolAddress, pub creation_slot: u64, pub broker_fee: Beneficiary, - pub refund_params: ChannelRefundParametersDecoded, + pub refund_params: ChannelRefundParameters, pub dca_params: Option, pub boost_fee: u8, pub affiliate_fees: Affiliates, diff --git a/state-chain/runtime/src/chainflip/vault_swaps.rs b/state-chain/runtime/src/chainflip/vault_swaps.rs index 9620f959317..50876c0f672 100644 --- a/state-chain/runtime/src/chainflip/vault_swaps.rs +++ b/state-chain/runtime/src/chainflip/vault_swaps.rs @@ -18,7 +18,7 @@ use cf_chains::{ api::SolanaEnvironment, instruction_builder::SolanaInstructionBuilder, sol_tx_core::address_derivation::derive_associated_token_account, SolAmount, SolPubkey, }, - Arbitrum, CcmChannelMetadata, ChannelRefundParametersEncoded, Ethereum, ForeignChain, + Arbitrum, CcmChannelMetadata, ChannelRefundParametersEncoded, Ethereum, ForeignChain, Solana, }; use cf_primitives::{ AffiliateAndFee, Affiliates, Asset, AssetAmount, BasisPoints, DcaParameters, SWAP_DELAY_BLOCKS, @@ -114,13 +114,18 @@ pub fn evm_vault_swap( ) -> Result, DispatchErrorWithMessage> { let refund_params = refund_params.try_map_address(|addr| { ChainAddressConverter::try_from_encoded_address(addr) + .and_then(|addr| addr.try_into().map_err(|_| ())) .map_err(|_| "Invalid refund address".into()) })?; let processed_affiliate_fees = to_affiliate_and_fees(&broker_id, affiliate_fees)? .try_into() .map_err(|_| "Too many affiliates.")?; - let cf_parameters = build_cf_parameters( + let cf_parameters = match ForeignChain::from(source_asset) { + ForeignChain::Ethereum => build_cf_parameters::, + ForeignChain::Arbitrum => build_cf_parameters::, + _ => Err(DispatchErrorWithMessage::from("Unsupported source chain for EVM vault swap"))?, + }( refund_params, dca_parameters, boost_fee, @@ -246,13 +251,14 @@ pub fn solana_vault_swap( let from = SolPubkey::try_from(from).map_err(|_| "Invalid Solana Address: from")?; let refund_parameters = refund_parameters.try_map_address(|addr| { ChainAddressConverter::try_from_encoded_address(addr) + .and_then(|addr| addr.try_into().map_err(|_| ())) .map_err(|_| "Invalid refund address".into()) })?; let event_data_account = SolPubkey::try_from(event_data_account) .map_err(|_| "Invalid Solana Address: event_data_account")?; let input_amount = SolAmount::try_from(input_amount).map_err(|_| "Input amount exceeded MAX")?; - let cf_parameters = build_cf_parameters( + let cf_parameters = build_cf_parameters::( refund_parameters, dca_parameters, boost_fee, diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 54d71c3b4be..39358579496 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -2335,21 +2335,7 @@ impl_runtime_apis! { ( ForeignChain::Ethereum, VaultSwapExtraParametersEncoded::Ethereum(extra_params) - ) => { - crate::chainflip::vault_swaps::evm_vault_swap( - broker_id, - source_asset, - extra_params.input_amount, - destination_asset, - destination_address, - broker_commission, - extra_params.refund_parameters, - boost_fee, - affiliate_fees, - dca_parameters, - channel_metadata, - ) - }, + )| ( ForeignChain::Arbitrum, VaultSwapExtraParametersEncoded::Arbitrum(extra_params) From 1c5548f1ee8367290a387152433022772b02c32e Mon Sep 17 00:00:00 2001 From: kylezs Date: Wed, 5 Feb 2025 17:14:42 +0100 Subject: [PATCH 07/20] fix: no gaps in root btc source (#5609) * test: add strictly monotonic test case * fix: ensure no btc blocks skipped at source * chore: clippy * chore: consistent naming * fix: should yield due to hash not matching only if height is same as before * chore: remove unnecessary as_mut() --- engine/src/witness/btc/source.rs | 83 +++++++++++++++++-- .../common/chain_source/strictly_monotonic.rs | 3 +- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/engine/src/witness/btc/source.rs b/engine/src/witness/btc/source.rs index 890bdfbc9ef..a6c8e0078b0 100644 --- a/engine/src/witness/btc/source.rs +++ b/engine/src/witness/btc/source.rs @@ -1,6 +1,7 @@ use std::time::Duration; use bitcoin::BlockHash; +use cf_chains::btc; use cf_utilities::make_periodic_tick; use futures_util::stream; @@ -25,6 +26,12 @@ impl BtcSource { const POLL_INTERVAL: Duration = Duration::from_secs(5); +pub struct BtcSourceState { + last_block_yielded_hash: BlockHash, + last_block_yielded_index: btc::BlockNumber, + best_known_block_index: btc::BlockNumber, +} + #[async_trait::async_trait] impl ChainSource for BtcSource where @@ -40,13 +47,71 @@ where ) -> (BoxChainStream<'_, Self::Index, Self::Hash, Self::Data>, Self::Client) { ( Box::pin(stream::unfold( - (self.client.clone(), None, make_periodic_tick(POLL_INTERVAL, true)), - |(client, last_block_hash_yielded, mut tick)| async move { + ( + self.client.clone(), + Option::::None, + make_periodic_tick(POLL_INTERVAL, true), + ), + |(client, mut stream_state, mut tick)| async move { loop { + // We don't want to wait for the tick if we have backfilling to do, so we do + // it here before awaiting the tick. + if let Some(state) = &stream_state { + if state.best_known_block_index > state.last_block_yielded_index { + tracing::debug!( + "Backfilling BTC source from index {} to {}", + state.last_block_yielded_index, + state.best_known_block_index, + ); + let header = client + .header_at_index( + state.last_block_yielded_index.saturating_add(1), + ) + .await; + return Some(( + header, + ( + client, + Some(BtcSourceState { + last_block_yielded_hash: header.hash, + last_block_yielded_index: header.index, + best_known_block_index: state.best_known_block_index, + }), + tick, + ), + )); + } + } + tick.tick().await; let best_block_header = client.best_block_header().await; - if last_block_hash_yielded != Some(best_block_header.hash) { + + let yield_new_best_header: bool = match &mut stream_state { + Some(state) + // We want to immediately yield the new best header if we've reorged on the same block + // or it's the next block we expect + if (state.last_block_yielded_index == best_block_header.height && + state.last_block_yielded_hash != best_block_header.hash) || + state.last_block_yielded_index.saturating_add(1) == best_block_header.height => + true, + // If we don't yet have state (we're initialising), then we want to + // yield the new best header immediately + None => true, + // If we've progressed by more than one block, then we need to backfill + Some(state) + if state.last_block_yielded_index < best_block_header.height => + { + // Update the state for the next iteration to backfill + state.best_known_block_index = best_block_header.height; + false + }, + // do nothing, just loop again. + _ => false, + }; + + if yield_new_best_header { + // Yield the new best header immediately return Some(( Header { index: best_block_header.height, @@ -54,8 +119,16 @@ where parent_hash: best_block_header.previous_block_hash, data: (), }, - (client, Some(best_block_header.hash), tick), - )) + ( + client, + Some(BtcSourceState { + last_block_yielded_hash: best_block_header.hash, + last_block_yielded_index: best_block_header.height, + best_known_block_index: best_block_header.height, + }), + tick, + ), + )); } } }, diff --git a/engine/src/witness/common/chain_source/strictly_monotonic.rs b/engine/src/witness/common/chain_source/strictly_monotonic.rs index e5341d06393..11e39cfe246 100644 --- a/engine/src/witness/common/chain_source/strictly_monotonic.rs +++ b/engine/src/witness/common/chain_source/strictly_monotonic.rs @@ -83,6 +83,7 @@ mod test { Header { index: 4u32, hash: (), parent_hash: Some(()), data: () }, Header { index: 3u32, hash: (), parent_hash: Some(()), data: () }, Header { index: 2u32, hash: (), parent_hash: Some(()), data: () }, + Header { index: 10u32, hash: (), parent_hash: Some(()), data: () }, ]), last_output: None } @@ -90,7 +91,7 @@ mod test { .await .into_iter() .map(|header| header.index), - [4, 5, 6] + [4, 5, 6, 10] )); } } From 4d911fbd35770c94e0d0368f2741305556f62f3f Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Thu, 6 Feb 2025 12:26:17 +0100 Subject: [PATCH 08/20] fix: solana elections efficiency improvements (#5585) * fix: only submit liveness when haven't yet submitted * fix: solana efficiency with old delta based ingress (#5580) * fix: use is_vote_needed to filter redundant deposit votes * fix: keep processing other votes if one vote refers to a nonexistent election. * feat: refresh delta_based_ingress elections (i.e., update ElectionProperties) whenever ingressed amount changes. This ensures that engines only vote when they witness a change in the channel balance. * fix: Take the UnsynchronisedStateMap into account for generating election properties. Other changes: - inline code for recreating elections because we need the electoral_access. - re-create election instead of refreshing because we want to delete old votes. * fix: choose consensus amount based on amount not on block height. * refactor: Create `new_properties` immediately instead of recreating it from election state. This gets rid of all unwraps and uses less code. We now collect new_properties as we go through the channels. We also make sure to delete entries from new_properties if a channel is closed. * nit: delete election before recreating it, instead of other way round. This way there is always only one election for a given channel. * smaller fixes: - revert the `is_vote_needed` behaviour to always return true. We don't need to filter votes here because the election properties are going to be up-to-date always. - change log level for new logs to debug. - Remove subtraction in match case where the sol ingressed amount decreases. Instead use same logging as on main. * chore: Clone properties instead of reading them again from storage. This also moves getting the election_access out of the if branches. --------- Co-authored-by: Daniel * fix: merge issues * feat: failing test * fix: new delta based ingress passing tests * fix: clippy * refactor: use immutable new_pending_totals * fix: improve tests for channel closure * fix: bugs: - change back to behaviour prioritising old consensus - don't log on account closure (equal amount, higher block) - close account even with pending total * fix: refresh properties on pending ingress * Update state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs Co-authored-by: kylezs * fix: comments and naming --------- Co-authored-by: kylezs Co-authored-by: Maxim Urschumzew Co-authored-by: kylezs --- .../blockchain/delta_based_ingress.rs | 202 +++-- .../src/electoral_systems/liveness.rs | 4 +- .../src/electoral_systems/mocks.rs | 3 +- .../tests/delta_based_ingress.rs | 832 ++++++++++++------ state-chain/pallets/cf-elections/src/lib.rs | 14 +- 5 files changed, 708 insertions(+), 347 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs b/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs index eb8592c18eb..db8756ffd70 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs @@ -5,6 +5,7 @@ use crate::{ }, vote_storage, CorruptStorageError, ElectionIdentifier, }; +use cf_runtime_utilities::log_or_panic; use cf_traits::IngressSink; use cf_utilities::success_threshold_from_share_count; use codec::{Decode, Encode, MaxEncodedLen}; @@ -52,6 +53,7 @@ where Sink: IngressSink + 'static, Settings: Parameter + Member + MaybeSerializeDeserialize + Eq, ::Account: Ord, + ::Amount: Default, ValidatorId: Member + Parameter + Ord + MaybeSerializeDeserialize, { pub fn open_channel + 'static>( @@ -100,6 +102,7 @@ where Sink: IngressSink + 'static, Settings: Parameter + Member + MaybeSerializeDeserialize + Eq, ::Account: Ord, + ::Amount: Default, ValidatorId: Member + Parameter + Ord + MaybeSerializeDeserialize, { type ValidatorId = ValidatorId; @@ -143,6 +146,7 @@ where Sink: IngressSink + 'static, Settings: Parameter + Member + MaybeSerializeDeserialize + Eq, ::Account: Ord, + ::Amount: Default, ValidatorId: Member + Parameter + Ord + MaybeSerializeDeserialize, { fn is_vote_desired>( @@ -153,14 +157,10 @@ where } fn is_vote_needed( - (_, current_partial_vote, _): ( - VotePropertiesOf, - PartialVoteOf, - AuthorityVoteOf, - ), - (proposed_partial_vote, _): (PartialVoteOf, VoteOf), + (_, _, _): (VotePropertiesOf, PartialVoteOf, AuthorityVoteOf), + (_, proposed_vote): (PartialVoteOf, VoteOf), ) -> bool { - current_partial_vote != proposed_partial_vote + !proposed_vote.is_empty() } fn generate_vote_properties( @@ -176,7 +176,7 @@ where chain_tracking: &Self::OnFinalizeContext, ) -> Result { for election_identifier in election_identifiers { - let (mut channels, mut pending_ingress_totals, option_consensus) = { + let (properties, pending_ingress_totals, option_consensus) = { let election_access = ElectoralAccess::election_mut(election_identifier); ( election_access.properties()?, @@ -185,13 +185,15 @@ where ) }; - let mut closed_channels = Vec::new(); - for (account, (details, _)) in &channels { + let mut new_properties = properties.clone(); + let mut new_pending_ingress_totals = + ::ElectionState::default(); + for (account, (details, _)) in &properties { // We currently split the ingressed amount into two parts: - // 1. The consensus amount that is *before* chain tracking. i.e. Chain tracking is - // *ahead*. - // 2. The pending amount that is *after* chain tracking. i.e. Chain tracking is - // *behind*. + // 1. The consensus amount with a block number *earlier* than chain tracking. i.e. + // Chain tracking is *ahead*, deposit witnessing is lagging. + // 2. The pending amount that with a block number *later* than chain tracking. i.e. + // Chain tracking is *lagging*, deposit witnessing is ahead. // The engines currently do not necessarily agree on a particular value at the point // of an election because of the inability to query for data at // a particular block height on Solana. Thus, there are two approaches: @@ -204,41 +206,78 @@ where // can send the smallest unit of Solana in a stream to the victim's deposit channel, // delaying the victim's deposit until the attacker stops their stream. - let ( - option_ingress_total_before_chain_tracking, - option_ingress_total_after_chain_tracking, - ) = match option_consensus.as_ref().and_then(|consensus| consensus.get(account)) { - None => (None, None), - Some(consensus_ingress_total) => { - if consensus_ingress_total.block_number <= *chain_tracking { - (Some(*consensus_ingress_total), None) + let (ready_total, future_total) = match ( + option_consensus.as_ref().and_then(|consensus| consensus.get(account)), + pending_ingress_totals.get(account), + ) { + (None, None) => (None, None), + (Some(total), None) | (None, Some(total)) => { + if total.block_number <= *chain_tracking { + (Some(total), None) } else { - match pending_ingress_totals.remove(account) { - None => (None, Some(*consensus_ingress_total)), - Some(pending_ingress_total) => { - if pending_ingress_total.block_number < - consensus_ingress_total.block_number && - pending_ingress_total.amount < - consensus_ingress_total.amount - { - if pending_ingress_total.block_number <= *chain_tracking { - ( - Some(pending_ingress_total), - Some(*consensus_ingress_total), - ) - } else { - (None, Some(pending_ingress_total)) - } - } else { - (None, Some(*consensus_ingress_total)) - } - }, + (None, Some(total)) + } + }, + (Some(new_consensus), Some(old_consensus)) => { + if new_consensus.block_number <= old_consensus.block_number { + // Not sure if this is possible, but can't exclude it either. Can + // indicate a re-org or misbehaving rpcs. Ignore the previous + // amount. + if *chain_tracking >= new_consensus.block_number { + (Some(new_consensus), None) + } else { + (None, Some(new_consensus)) + } + } else { + // In this branch we handle the 'happy' case where block numbers are + // monotonically increasing. + if *chain_tracking >= new_consensus.block_number { + // Chain tracking has progressed beyond the latest ingress block so + // we can ignore any previously pending amounts. + (Some(new_consensus), None) + } else if *chain_tracking >= old_consensus.block_number { + // Chain tracking has progressed beyond the previous deposit block + // but not the latest. We can confirm the previous amount, the + // latest will become pending, as long as the amounts are different. + debug_assert!(new_consensus.amount >= old_consensus.amount); + // NOTE: We can be sure of this because the block numbers cannot + // decrease and, if they were equal, we would have entered the + // initial condition. + debug_assert!( + new_consensus.block_number > old_consensus.block_number + ); + + if new_consensus.amount >= old_consensus.amount { + // Note: balance can be equal on channel closure. + (Some(old_consensus), Some(new_consensus)) + } else { + log_or_panic!( + "Consensus {:?} balance for Solana deposit channel `{:?}` decreased from {:?} to {:?}", + details.asset, + account, + old_consensus.amount, + new_consensus.amount + ); + (Some(old_consensus), None) + } + } else { + // Chain tracking has not progressed beyond the previous deposit + // block. We can confirm neither the previous nor the latest amount. + // We don't update the pending consensus amount: this is to defend + // against a malicious actor streaming small amounts, which + // would otherwise delay the deposit. + if new_consensus.amount == old_consensus.amount { + // If amounts are the same it could be account closure. + (None, Some(new_consensus)) + } else { + (None, Some(old_consensus)) + } } } }, }; - if let Some(ingress_total) = option_ingress_total_before_chain_tracking { + if let Some(ready_total) = ready_total { let previous_amount = ElectoralAccess::unsynchronised_state_map(&( account.clone(), details.asset, @@ -246,61 +285,70 @@ where .map_or(Zero::zero(), |previous_total_ingressed| { previous_total_ingressed.amount }); - match previous_amount.cmp(&ingress_total.amount) { + match previous_amount.cmp(&ready_total.amount) { Ordering::Less => { Sink::on_ingress( account.clone(), details.asset, - ingress_total.amount - previous_amount, - ingress_total.block_number, + ready_total.amount - previous_amount, + ready_total.block_number, (), ); ElectoralAccess::set_unsynchronised_state_map( (account.clone(), details.asset), - Some(ingress_total), + Some(*ready_total), + ); + new_properties.entry(account.clone()).and_modify( + |(_details, total)| { + *total = *ready_total; + }, ); }, Ordering::Greater => { - log::warn!("Deposit channels on Solana chain has reverted! Account: {:?}, Asset: {:?}, Previous ingressed total: {:?}, new ingressed total: {:?}", account, details.asset, previous_amount, ingress_total.amount); + log::error!( + "Finalized {:?} balance for Solana deposit channel `{:?}` decreased from {:?} to {:?}", + details.asset, + account, + previous_amount, + ready_total.amount + ); }, Ordering::Equal => (), } - if ingress_total.block_number >= details.close_block { + // Note: we only check for close in this branch to guarantee that both the + // channel state and chain tracking have caught up to the close block, and all + // confirmed deposits have been ingressed. + if ready_total.block_number >= details.close_block { Sink::on_channel_closed(account.clone()); - closed_channels.push(account.clone()); + new_properties.remove(account); } } - if let Some(ingress_total_after_chain_tracking) = - option_ingress_total_after_chain_tracking - { - pending_ingress_totals - .insert(account.clone(), ingress_total_after_chain_tracking); + if let Some(future_total) = future_total { + new_properties.entry(account.clone()).and_modify(|(_details, total)| { + *total = *future_total; + }); + new_pending_ingress_totals.insert(account.clone(), *future_total); } } - let mut election_access = ElectoralAccess::election_mut(election_identifier); - if !closed_channels.is_empty() { - for closed_channel in closed_channels { - pending_ingress_totals.remove(&closed_channel); - channels.remove(&closed_channel); - } - - if channels.is_empty() { - election_access.delete(); - } else { - election_access.set_state(pending_ingress_totals)?; - election_access.refresh( - // This value is meaningless. We increment as it is required to use a new - // higher value to refresh the election. - election_identifier - .extra() - .checked_add(1) - .ok_or_else(CorruptStorageError::new)?, - channels, - )?; - } + let election_access = ElectoralAccess::election_mut(election_identifier); + + if new_properties.is_empty() { + // Note: it's possible that there are still some remaining pending totals, but if + // the channel is expired, we need to close it, otherwise an attacker could keep it + // open indeifitely by streaming small deposits. + election_access.delete(); + } else if new_properties != properties { + log::debug!("recreate delta based ingress election: recreate since properties changed from: {properties:?}, to: {new_properties:?}"); + election_access.delete(); + ElectoralAccess::new_election( + Default::default(), + new_properties, + new_pending_ingress_totals, + )?; } else { - election_access.set_state(pending_ingress_totals)?; + log::debug!("recreate delta based ingress election: keeping old because properties didn't change: {properties:?}"); + election_access.set_state(new_pending_ingress_totals)?; } } diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/liveness.rs b/state-chain/pallets/cf-elections/src/electoral_systems/liveness.rs index 8249bc7fee6..54a25d0f471 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/liveness.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/liveness.rs @@ -93,9 +93,9 @@ impl< fn is_vote_desired>( _election_access: &ElectionAccess, - _current_vote: Option<(VotePropertiesOf, AuthorityVoteOf)>, + current_vote: Option<(VotePropertiesOf, AuthorityVoteOf)>, ) -> Result { - Ok(true) + Ok(current_vote.is_none()) } fn on_finalize + 'static>( 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 1f50861ba78..a41a4858eeb 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs @@ -420,7 +420,8 @@ register_checks! { ); assert!( post_finalize.election_identifiers.is_empty(), - "Expected no elections after finalization.", + "Expected no elections after finalization, got: {:?}.", + post_finalize.election_identifiers, ); }, } diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs b/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs index 48733cffad8..9f67366aaea 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs @@ -59,8 +59,36 @@ struct DepositChannel { pub close_block: BlockNumber, } +const DEFAULT_CHANNEL_ACCOUNT: u32 = 1234; +const DEFAULT_CHANNEL_OPEN_BLOCK: BlockNumber = 100; +const DEFAULT_CHANNEL_CLOSE_BLOCK: BlockNumber = 200; +const DEFAULT_CHANNEL: DepositChannel = DepositChannel { + account: DEFAULT_CHANNEL_ACCOUNT, + asset: Asset::Sol, + total_ingressed: 0, + block_number: DEFAULT_CHANNEL_OPEN_BLOCK, + close_block: DEFAULT_CHANNEL_CLOSE_BLOCK, +}; + +impl Default for DepositChannel { + fn default() -> Self { + DEFAULT_CHANNEL + } +} + +impl DepositChannel { + pub fn open(&self) { + assert_ok!(DeltaBasedIngress::open_channel::>( + TestContext::::identifiers(), + self.account, + self.asset, + self.close_block + )); + } +} + fn to_state( - channels: Vec, + channels: impl IntoIterator, ) -> BTreeMap> { channels .into_iter() @@ -77,7 +105,7 @@ fn to_state( } fn to_state_map( - channels: Vec, + channels: impl IntoIterator, ) -> BTreeMap<(AccountId, Asset), ChannelTotalIngressedFor> { channels .into_iter() @@ -94,7 +122,7 @@ fn to_state_map( } fn to_properties( - channels: Vec, + channels: impl IntoIterator, ) -> BTreeMap< AccountId, (OpenChannelDetailsFor, ChannelTotalIngressedFor), @@ -116,102 +144,113 @@ fn to_properties( .collect::>() } -fn initial_channel_state() -> Vec { - vec![ - DepositChannel { - account: 1u32, - asset: Asset::Sol, - total_ingressed: 0u64, - block_number: 0u64, - close_block: 1_000u64, - }, - DepositChannel { - account: 2u32, - asset: Asset::SolUsdc, - total_ingressed: 0u64, - block_number: 0u64, - close_block: 2_000u64, - }, - ] -} -fn channel_state_ingressed() -> Vec { - vec![ - DepositChannel { - account: 1u32, - asset: Asset::Sol, - total_ingressed: 1_000u64, - block_number: 700u64, - close_block: 1_000u64, - }, - DepositChannel { - account: 2u32, - asset: Asset::SolUsdc, - total_ingressed: 2_000u64, - block_number: 800u64, - close_block: 2_000u64, - }, - ] -} +const INITIAL_CHANNEL_STATE: [DepositChannel; 2] = [ + DepositChannel { + account: 1u32, + asset: Asset::Sol, + total_ingressed: 0u64, + block_number: 0u64, + close_block: 1_000u64, + }, + DepositChannel { + account: 2u32, + asset: Asset::SolUsdc, + total_ingressed: 0u64, + block_number: 0u64, + close_block: 2_000u64, + }, +]; +const CHANNEL_STATE_INGRESSED: [DepositChannel; 2] = [ + DepositChannel { + account: 1u32, + asset: Asset::Sol, + total_ingressed: 1_000u64, + block_number: 700u64, + close_block: 1_000u64, + }, + DepositChannel { + account: 2u32, + asset: Asset::SolUsdc, + total_ingressed: 2_000u64, + block_number: 800u64, + close_block: 2_000u64, + }, +]; -fn channel_state_final() -> Vec { - vec![ - DepositChannel { - account: 1u32, - asset: Asset::Sol, - total_ingressed: 4_000u64, - block_number: 900u64, - close_block: 1_000u64, - }, - DepositChannel { - account: 2u32, - asset: Asset::SolUsdc, - total_ingressed: 6_000u64, - block_number: 900u64, - close_block: 2_000u64, - }, - ] -} +const CHANNEL_STATE_FINAL: [DepositChannel; 2] = [ + DepositChannel { + account: 1u32, + asset: Asset::Sol, + total_ingressed: 4_000u64, + block_number: 900u64, + close_block: 1_000u64, + }, + DepositChannel { + account: 2u32, + asset: Asset::SolUsdc, + total_ingressed: 6_000u64, + block_number: 900u64, + close_block: 2_000u64, + }, +]; -fn channel_state_closed() -> Vec { - vec![ - DepositChannel { - account: 1u32, - asset: Asset::Sol, - total_ingressed: 4_000u64, - block_number: 1_000u64, - close_block: 1_000u64, - }, - DepositChannel { - account: 2u32, - asset: Asset::SolUsdc, - total_ingressed: 6_000u64, - block_number: 2_000u64, - close_block: 2_000u64, - }, - ] -} +const CHANNEL_STATE_CLOSED: [DepositChannel; 2] = [ + DepositChannel { + account: 1u32, + asset: Asset::Sol, + total_ingressed: 4_000u64, + block_number: 1_000u64, + close_block: 1_000u64, + }, + DepositChannel { + account: 2u32, + asset: Asset::SolUsdc, + total_ingressed: 6_000u64, + block_number: 2_000u64, + close_block: 2_000u64, + }, +]; fn with_default_setup() -> TestSetup { - let initial_elections = initial_channel_state(); TestSetup::<_>::default() .with_initial_election_state( 1u32, - to_properties(initial_elections.clone()), - to_state(initial_elections.clone()), + to_properties(INITIAL_CHANNEL_STATE), + to_state(INITIAL_CHANNEL_STATE), + ) + .with_initial_state_map(to_state_map(INITIAL_CHANNEL_STATE).into_iter().collect::>()) +} + +impl TestContext { + #[track_caller] + fn assert_state_update( + self, + chain_tracking: &BlockNumber, + channels: impl IntoIterator, + expected_state: impl IntoIterator, + ) -> Self { + self.force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: to_state(channels), + }) + .test_on_finalize( + chain_tracking, + |_| (), + [Check::ingressed(vec![]), Check::ended_at_state(to_state(expected_state))], ) - .with_initial_state_map(to_state_map(initial_elections).into_iter().collect::>()) + } } register_checks! { SimpleDeltaBasedIngress { - started_at_state_map_state(pre_finalize, _post, state: Vec) { + started_at_state_map_state(pre_finalize, _post, state: impl Clone + IntoIterator + 'static) { assert_eq!( pre_finalize.unsynchronised_state_map, to_state_map(state), "Expected state map incorrect before finalization." ); }, - ended_at_state_map_state(_pre, post_finalize, state: Vec) { + ended_at_state_map_state(_pre, post_finalize, state: impl Clone + IntoIterator + 'static) { assert_eq!( post_finalize.unsynchronised_state_map, to_state_map(state), @@ -219,29 +258,49 @@ register_checks! { ); }, ended_at_state(_pre, post, election_state: BTreeMap>) { - assert_eq!(*post.election_state.get(post.election_identifiers[0].unique_monotonic()).unwrap(), election_state, "Expected election state incorrect."); - }, - ended_at_empty_state(_pre, post) { - assert_eq!(post.election_state.len(), 0, "Expected State to be empty, but it's not."); + assert_eq!( + *post.election_state.get(post.election_identifiers[0].unique_monotonic()).unwrap(), + election_state, + "Expected election state incorrect. Expected {:?}, got: {:?}", + election_state, + *post.election_state.get(post.election_identifiers[0].unique_monotonic()).unwrap() + ); }, ingressed(_pre, _post, expected_ingressed: Vec<(AccountId, Asset, Amount)>) { AMOUNT_INGRESSED.with(|ingresses| { assert_eq!( ingresses.clone().into_inner(), expected_ingressed, - "Amount ingressed incorrect." + "Unexpected ingresses. Expected {:?}, got {:?}", expected_ingressed, ingresses.clone().into_inner() ); }); }, - channel_closed(_pre, _post, expected_closed_channels: Vec) { + channels_closed_matches(_pre, _post, expected_closed_channels: Vec) { CHANNELS_CLOSED.with(|channels| { - assert_eq!( - channels.clone().into_inner(), - expected_closed_channels, - "Channels closed incorrect." + assert!( + *channels.borrow() == expected_closed_channels, + "Channels closed incorrect: expected {:?}, got {:?}", expected_closed_channels, channels.clone().into_inner() + ); + }); + }, + channel_closed_once(_pre, _post, expected_closed: AccountId) { + CHANNELS_CLOSED.with(|channels| { + assert!( + channels.borrow().iter().filter(|c| **c == expected_closed).count() == 1, + "Channels closed incorrect: expected {:?}, got {:?}", expected_closed, channels.clone().into_inner() ); }); }, + channel_not_closed(_pre, _post, expected_closed: AccountId) { + CHANNELS_CLOSED.with(|channels| { + assert!( + !channels.borrow().contains(&expected_closed), + "Expected {:?} to be open, but is contained in closed channels: {:?}", + expected_closed, + channels.clone().into_inner() + ); + }); + } } } @@ -252,14 +311,14 @@ fn trigger_ingress_on_consensus() { .build_with_initial_election() .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_ingressed()), + new: to_state(CHANNEL_STATE_INGRESSED), }) .test_on_finalize( &ingressed_block, |_| (), vec![ - Check::started_at_state_map_state(initial_channel_state()), - Check::ended_at_state_map_state(channel_state_ingressed()), + Check::started_at_state_map_state(INITIAL_CHANNEL_STATE), + Check::ended_at_state_map_state(CHANNEL_STATE_INGRESSED), Check::ingressed(vec![ (1u32, Asset::Sol, 1_000u64), (2u32, Asset::SolUsdc, 2_000u64), @@ -268,14 +327,14 @@ fn trigger_ingress_on_consensus() { ) .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_final()), + new: to_state(CHANNEL_STATE_FINAL), }) .test_on_finalize( &ingressed_block, |_| (), vec![ - Check::started_at_state_map_state(channel_state_ingressed()), - Check::ended_at_state_map_state(channel_state_final()), + Check::started_at_state_map_state(CHANNEL_STATE_INGRESSED), + Check::ended_at_state_map_state(CHANNEL_STATE_FINAL), Check::ingressed(vec![ (1u32, Asset::Sol, 1_000u64), (2u32, Asset::SolUsdc, 2_000u64), @@ -288,66 +347,215 @@ fn trigger_ingress_on_consensus() { #[test] fn only_trigger_ingress_on_witnessed_blocks() { - let ingress_block = channel_state_ingressed() + let ingress_block = CHANNEL_STATE_INGRESSED .into_iter() .map(|channel| channel.block_number) .collect::>(); with_default_setup() .build_with_initial_election() - .force_consensus_update(ConsensusStatus::Gained { - most_recent: None, - new: to_state(channel_state_ingressed()), - }) - .test_on_finalize( + .assert_state_update( &(ingress_block[0] - 1), - |_| (), - vec![Check::ended_at_state(to_state(channel_state_ingressed()))], + CHANNEL_STATE_INGRESSED, + CHANNEL_STATE_INGRESSED, ) .test_on_finalize( &(ingress_block[1] - 1), |_| (), vec![ - Check::started_at_state_map_state(initial_channel_state()), - Check::ended_at_state(to_state(channel_state_ingressed())), - Check::ingressed(vec![(1u32, Asset::Sol, 1_000u64)]), + Check::started_at_state_map_state(INITIAL_CHANNEL_STATE), + Check::ended_at_state(to_state([CHANNEL_STATE_INGRESSED[1]])), + Check::ingressed(vec![( + CHANNEL_STATE_INGRESSED[0].account, + CHANNEL_STATE_INGRESSED[0].asset, + CHANNEL_STATE_INGRESSED[0].total_ingressed, + )]), ], ) .test_on_finalize( &ingress_block[1], |_| (), vec![ - Check::ended_at_state_map_state(channel_state_ingressed()), - Check::ingressed(vec![ - (1u32, Asset::Sol, 1_000u64), - (2u32, Asset::SolUsdc, 2_000u64), - ]), - Check::ended_at_state(to_state(channel_state_ingressed())), + Check::ended_at_state_map_state(CHANNEL_STATE_INGRESSED), + Check::ingressed( + CHANNEL_STATE_INGRESSED + .iter() + .map(|channel| (channel.account, channel.asset, channel.total_ingressed)) + .collect::>(), + ), + Check::ended_at_state(Default::default()), ], ); } -#[test] -fn can_close_channels() { - let channel_close_block = channel_state_closed() - .into_iter() - .map(|channel| channel.close_block) - .collect::>(); - with_default_setup() - .build_with_initial_election() - .force_consensus_update(ConsensusStatus::Gained { - most_recent: None, - new: to_state(channel_state_closed()), - }) - .test_on_finalize(&channel_close_block[0], |_| (), vec![Check::channel_closed(vec![1u32])]) +mod channel_closure { + use super::*; + const DEPOSIT_BLOCK: BlockNumber = DEFAULT_CHANNEL_OPEN_BLOCK + 10; + const DEPOSIT_AMOUNT: u64 = 500; + + #[test] + fn can_close_channels() { + fn check_closure( + ctx: TestContext, + channel: DepositChannel, + ) -> TestContext { + ctx.force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: [( + channel.account, + ChannelTotalIngressed { + amount: channel.total_ingressed, + block_number: channel.close_block, + }, + )] + .into_iter() + .collect(), + }) + .test_on_finalize( + &channel.close_block, + |_| (), + vec![Check::channel_closed_once(channel.account), Check::ingressed(vec![])], + ) + } + + let channels = [ + DepositChannel { account: 1u32, ..Default::default() }, + DepositChannel { + account: 2u32, + close_block: DEFAULT_CHANNEL_CLOSE_BLOCK + 100, + ..Default::default() + }, + ]; + let test_ctx = with_default_setup() + .build() + .then(|| channels.iter().for_each(|channel| channel.open())); + let test_ctx = check_closure(test_ctx, channels[0]); + let _test_ctx = check_closure(test_ctx, channels[1]); + } + + fn setup_close_after_deposits( + chain_tracking_lagging: bool, + ) -> TestContext { + let setup = with_default_setup() + .build() + .then(|| DEFAULT_CHANNEL.open()) + .force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: [( + DEFAULT_CHANNEL_ACCOUNT, + ChannelTotalIngressed { amount: DEPOSIT_AMOUNT, block_number: DEPOSIT_BLOCK }, + )] + .into_iter() + .collect(), + }); + + if chain_tracking_lagging { + setup + // Chain tracking is lagging, nothing should be ingressed. + .test_on_finalize( + &{ DEPOSIT_BLOCK - 1 }, + |_| (), + vec![ + Check::ingressed(vec![]), + Check::ended_at_state( + [( + DEFAULT_CHANNEL_ACCOUNT, + ChannelTotalIngressed { + amount: DEPOSIT_AMOUNT, + block_number: DEPOSIT_BLOCK, + }, + )] + .into_iter() + .collect(), + ), + ], + ) + } else { + setup + // Chain tracking is caught up, deposit is ingressed. + .test_on_finalize( + &DEPOSIT_BLOCK, + |_| (), + vec![ + Check::ingressed(vec![( + DEFAULT_CHANNEL_ACCOUNT, + Asset::Sol, + DEPOSIT_AMOUNT, + )]), + Check::ended_at_state([].into_iter().collect()), + ], + ) + } + // Engines reach consensus on account closure: total balance is unchanged, block number is + // the close block. .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_closed()), + new: [( + DEFAULT_CHANNEL_ACCOUNT, + ChannelTotalIngressed { + amount: DEPOSIT_AMOUNT, + block_number: DEFAULT_CHANNEL_CLOSE_BLOCK, + }, + )] + .into_iter() + .collect(), }) - .test_on_finalize( - &channel_close_block[1], - |_| (), - vec![Check::channel_closed(vec![1u32, 2u32])], - ); + } + + #[test] + fn close_after_deposit() { + setup_close_after_deposits(false) + // Chain tracking reaches close block, channel is closed. + .test_on_finalize( + &DEFAULT_CHANNEL_CLOSE_BLOCK, + |_| (), + vec![ + Check::channel_closed_once(DEFAULT_CHANNEL_ACCOUNT), + Check::ingressed(vec![(DEFAULT_CHANNEL_ACCOUNT, Asset::Sol, DEPOSIT_AMOUNT)]), + Check::all_elections_deleted(), + ], + ); + } + + #[test] + fn close_after_deposit_lagging() { + setup_close_after_deposits(true) + // Chain tracking reaches close block, channel is closed. + .test_on_finalize( + &DEFAULT_CHANNEL_CLOSE_BLOCK, + |_| (), + vec![ + Check::channel_closed_once(DEFAULT_CHANNEL_ACCOUNT), + Check::ingressed(vec![(DEFAULT_CHANNEL_ACCOUNT, Asset::Sol, DEPOSIT_AMOUNT)]), + Check::all_elections_deleted(), + ], + ); + } + + // Same as above, except tracking catches up to a block between the deposit and close block. + #[test] + fn close_after_deposit_lagging_recovered() { + setup_close_after_deposits(true) + // Chain tracking catches up, deposit is ingressed, channel not yet closed. + .test_on_finalize( + &{ DEFAULT_CHANNEL_CLOSE_BLOCK - 1 }, + |_| (), + vec![ + Check::channel_not_closed(DEFAULT_CHANNEL_ACCOUNT), + Check::ingressed(vec![(DEFAULT_CHANNEL_ACCOUNT, Asset::Sol, DEPOSIT_AMOUNT)]), + Check::election_id_incremented(), + ], + ) + // Chain tracking reaches close block, channel is closed. + .test_on_finalize( + &DEFAULT_CHANNEL_CLOSE_BLOCK, + |_| (), + vec![ + Check::channel_closed_once(DEFAULT_CHANNEL_ACCOUNT), + Check::ingressed(vec![(DEFAULT_CHANNEL_ACCOUNT, Asset::Sol, DEPOSIT_AMOUNT)]), + Check::all_elections_deleted(), + ], + ); + } } #[test] @@ -386,14 +594,14 @@ fn test_deposit_channel_recycling() { }, ]; - let initial_close_block = channel_state_closed()[1].close_block; + let initial_close_block = CHANNEL_STATE_CLOSED[1].close_block; let recycled_same_asset_close_block = channel_state_recycled_same_asset[0].close_block; let recycled_diff_asset_close_block = channel_state_recycled_different_asset[0].close_block; with_default_setup() .build() .then(|| { - for deposit_channel in initial_channel_state() { + for deposit_channel in INITIAL_CHANNEL_STATE { assert_ok!(DeltaBasedIngress::open_channel::>( TestContext::::identifiers(), deposit_channel.account, @@ -404,18 +612,18 @@ fn test_deposit_channel_recycling() { }) .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_closed()), + new: to_state(CHANNEL_STATE_CLOSED), }) .test_on_finalize( &initial_close_block, |_| (), vec![ - Check::ended_at_state_map_state(channel_state_closed()), + Check::ended_at_state_map_state(CHANNEL_STATE_CLOSED), Check::ingressed(vec![ (1u32, Asset::Sol, 4_000u64), (2u32, Asset::SolUsdc, 6_000u64), ]), - Check::channel_closed(vec![1u32, 2u32]), + Check::channels_closed_matches(vec![1u32, 2u32]), ], ) .then(|| { @@ -446,7 +654,7 @@ fn test_deposit_channel_recycling() { (1u32, Asset::Sol, 16_000u64), (2u32, Asset::SolUsdc, 24_000u64), ]), - Check::channel_closed(vec![1u32, 2u32, 1u32, 2u32]), + Check::channels_closed_matches(vec![1u32, 2u32, 1u32, 2u32]), ], ) .then(|| { @@ -472,8 +680,7 @@ fn test_deposit_channel_recycling() { Check::ended_at_state_map_state( [channel_state_recycled_different_asset, channel_state_recycled_same_asset] .into_iter() - .flatten() - .collect(), + .flatten(), ), Check::ingressed(vec![ (1u32, Asset::Sol, 4_000u64), @@ -484,74 +691,64 @@ fn test_deposit_channel_recycling() { (1u32, Asset::SolUsdc, 100_000u64), (2u32, Asset::Sol, 200_000u64), ]), - Check::channel_closed(vec![1u32, 2u32, 1u32, 2u32, 1u32, 2u32]), + Check::channels_closed_matches(vec![1u32, 2u32, 1u32, 2u32, 1u32, 2u32]), ], ); } #[test] fn do_nothing_on_revert() { - let channel_state_reverted = vec![ + const CHANNEL_STATE_REVERTED: [DepositChannel; 2] = [ DepositChannel { - account: 1u32, - asset: Asset::Sol, - total_ingressed: 500u64, - block_number: 950u64, - close_block: 1_000u64, + total_ingressed: CHANNEL_STATE_INGRESSED[0].total_ingressed - 500u64, + ..CHANNEL_STATE_INGRESSED[0] }, DepositChannel { - account: 2u32, - asset: Asset::SolUsdc, - total_ingressed: 500u64, - block_number: 950u64, - close_block: 2_000u64, + total_ingressed: CHANNEL_STATE_INGRESSED[1].total_ingressed - 1_500u64, + ..CHANNEL_STATE_INGRESSED[1] }, ]; - let ingress_block = channel_state_ingressed()[1].block_number; - let revert_block = channel_state_reverted[0].block_number; - let close_block = channel_state_reverted[1].close_block; + let total_ingress = CHANNEL_STATE_INGRESSED + .iter() + .map(|channel| (channel.account, channel.asset, channel.total_ingressed)) + .collect::>(); with_default_setup() - .build_with_initial_election() + .build() + .then(|| CHANNEL_STATE_INGRESSED.iter().for_each(|channel| channel.open())) .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_ingressed()), + new: to_state(CHANNEL_STATE_INGRESSED), }) .test_on_finalize( - &ingress_block, + &CHANNEL_STATE_INGRESSED[1].block_number, |_| (), vec![ - Check::ended_at_state_map_state(channel_state_ingressed()), - Check::ingressed(vec![ - (1u32, Asset::Sol, 1_000u64), - (2u32, Asset::SolUsdc, 2_000u64), - ]), + Check::ingressed(total_ingress.clone()), + Check::ended_at_state_map_state(CHANNEL_STATE_INGRESSED), ], ) .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_reverted), + new: to_state(CHANNEL_STATE_REVERTED), }) .test_on_finalize( - &revert_block, + &CHANNEL_STATE_REVERTED[0].block_number, |_| (), vec![ // No new ingress is expected. - Check::ingressed(vec![ - (1u32, Asset::Sol, 1_000u64), - (2u32, Asset::SolUsdc, 2_000u64), - ]), + Check::ingressed(total_ingress.clone()), ], ) .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_closed()), + new: to_state(CHANNEL_STATE_CLOSED), }) .test_on_finalize( - &close_block, + &CHANNEL_STATE_CLOSED[1].close_block, |_| (), vec![ - Check::channel_closed(vec![1u32, 2u32]), + Check::channels_closed_matches(vec![1u32, 2u32]), Check::ingressed(vec![ (1u32, Asset::Sol, 1_000u64), (2u32, Asset::SolUsdc, 2_000u64), @@ -583,15 +780,12 @@ fn test_open_channel_with_existing_election() { }, ]; - let combined_state_closed = [channel_state_closed(), additional_channels.clone()] - .into_iter() - .flatten() - .collect::>(); + let combined_state_closed = CHANNEL_STATE_CLOSED.into_iter().chain(additional_channels.clone()); with_default_setup() .build() .then(|| { - for deposit_channel in initial_channel_state() { + for deposit_channel in INITIAL_CHANNEL_STATE { assert_ok!(DeltaBasedIngress::open_channel::>( TestContext::::identifiers(), deposit_channel.account, @@ -602,13 +796,13 @@ fn test_open_channel_with_existing_election() { }) .force_consensus_update(ConsensusStatus::Gained { most_recent: None, - new: to_state(channel_state_ingressed()), + new: to_state(CHANNEL_STATE_INGRESSED), }) .test_on_finalize( - &channel_state_ingressed()[1].block_number, + &CHANNEL_STATE_INGRESSED[1].block_number, |_| (), vec![ - Check::ended_at_state_map_state(channel_state_ingressed()), + Check::ended_at_state_map_state(CHANNEL_STATE_INGRESSED), Check::ingressed(vec![ (1u32, Asset::Sol, 1_000u64), (2u32, Asset::SolUsdc, 2_000u64), @@ -645,7 +839,7 @@ fn test_open_channel_with_existing_election() { (3u32, Asset::Sol, 5_000u64), (4u32, Asset::SolUsdc, 6_000u64), ]), - Check::channel_closed(vec![1u32, 2u32, 3u32, 4u32]), + Check::channels_closed_matches(vec![1u32, 2u32, 3u32, 4u32]), ], ); } @@ -691,115 +885,223 @@ fn start_new_election_if_too_many_channels_in_current_election() { #[test] fn pending_ingresses_update_with_consensus() { + const CHAIN_TRACKING: BlockNumber = 1_000; let deposit_channel_pending = DepositChannel { account: 1u32, asset: Asset::Sol, total_ingressed: 1_000u64, - block_number: 500u64, - close_block: 1_000u64, + block_number: CHAIN_TRACKING + 10, + close_block: BlockNumber::MAX, // we're not testing closing here }; - let deposit_channel_pending_updated_amount = DepositChannel { - account: 1u32, - asset: Asset::Sol, - total_ingressed: 999u64, - block_number: 500u64, - close_block: 1_000u64, + let deposit_channel_pending_lower_amount = DepositChannel { + total_ingressed: deposit_channel_pending.total_ingressed - 1, + ..deposit_channel_pending }; - let deposit_channel_pending_updated_block = DepositChannel { - account: 1u32, - asset: Asset::Sol, - total_ingressed: 1_000u64, - block_number: 499u64, - close_block: 1_000u64, + let deposit_channel_pending_higher_amount = DepositChannel { + total_ingressed: deposit_channel_pending.total_ingressed + 1, + ..deposit_channel_pending }; - let deposit_channel_pending_consensus = DepositChannel { - account: 1u32, - asset: Asset::Sol, + let deposit_channel_pending_lower_block = DepositChannel { + block_number: deposit_channel_pending.block_number - 1, + ..deposit_channel_pending + }; + let deposit_channel_pending_higher_block = DepositChannel { + block_number: deposit_channel_pending.block_number + 1, + ..deposit_channel_pending + }; + let deposit_channel_with_next_deposit = DepositChannel { total_ingressed: 2_500u64, - block_number: 1_000u64, - close_block: 1_000u64, + block_number: deposit_channel_pending.block_number + 10, + ..deposit_channel_pending }; - with_default_setup() + + let test = with_default_setup() .build() - .then(|| { - assert_ok!(DeltaBasedIngress::open_channel::>( - TestContext::::identifiers(), - deposit_channel_pending.account, - deposit_channel_pending.asset, - deposit_channel_pending.close_block - )); - }) - .force_consensus_update(ConsensusStatus::Gained { - most_recent: None, - new: to_state(vec![deposit_channel_pending]), - }) - .test_on_finalize( - &(deposit_channel_pending.block_number - 10), - |_| (), - vec![ - Check::ingressed(vec![]), - Check::ended_at_state(to_state(vec![deposit_channel_pending])), - ], + .then(|| deposit_channel_pending.open()) + .assert_state_update(&CHAIN_TRACKING, [deposit_channel_pending], [deposit_channel_pending]) + .assert_state_update( + &{ CHAIN_TRACKING + 1 }, + [deposit_channel_pending_lower_amount], + [deposit_channel_pending_lower_amount], ) - .force_consensus_update(ConsensusStatus::Gained { - most_recent: None, - new: to_state(vec![deposit_channel_pending_updated_amount]), - }) - // If consensus amount or block is less than current pending, pending is updated to the new - // consensus. - .test_on_finalize( - &(deposit_channel_pending.block_number - 10), - |_| (), - vec![ - Check::ingressed(vec![]), - Check::ended_at_state(to_state(vec![deposit_channel_pending_updated_amount])), - ], + .assert_state_update( + &{ CHAIN_TRACKING + 2 }, + [deposit_channel_pending_higher_amount], + [deposit_channel_pending_higher_amount], ) - // Same applies if block number is less than currently pending. - .force_consensus_update(ConsensusStatus::Gained { - most_recent: None, - new: to_state(vec![deposit_channel_pending_updated_block]), - }) - .test_on_finalize( - &(deposit_channel_pending.block_number - 10), - |_| (), - vec![ - Check::ingressed(vec![]), - Check::ended_at_state(to_state(vec![deposit_channel_pending_updated_block])), - ], + .assert_state_update( + &{ CHAIN_TRACKING + 3 }, + [deposit_channel_pending_lower_block], + [deposit_channel_pending_lower_block], ) - // If neither amount nor block is less, consensus is ignored until Pending ingress is - // processed first. - .force_consensus_update(ConsensusStatus::Gained { - most_recent: None, - new: to_state(vec![deposit_channel_pending_consensus]), - }) + .assert_state_update( + &{ CHAIN_TRACKING + 4 }, + [deposit_channel_pending_higher_block], + [deposit_channel_pending_higher_block], + ) + // Trying to push the state to a different amount at a higher block will have no effect. + .assert_state_update( + &{ CHAIN_TRACKING + 5 }, + [deposit_channel_with_next_deposit], + [deposit_channel_pending_higher_block], + ); + + test + // Once chain tracking advances past the latest consensus value, we process the first + // deposit. .test_on_finalize( - &(deposit_channel_pending.block_number - 10), + &(deposit_channel_pending_higher_block.block_number), |_| (), vec![ - Check::ingressed(vec![]), - // Ignore the latest consensus until the pending ingress is processed. - Check::ended_at_state(to_state(vec![deposit_channel_pending_updated_block])), + Check::ingressed(vec![( + deposit_channel_pending.account, + deposit_channel_pending.asset, + deposit_channel_pending.total_ingressed, + )]), + Check::election_id_incremented(), + Check::ended_at_state(to_state(vec![deposit_channel_with_next_deposit])), ], ) - // Process the Pending ingress, then set the Consensus as Pending ingress. + // After the deposit, the latest consensus value will have been promoted to the pending + // state. .test_on_finalize( - &(deposit_channel_pending.block_number), + &{ deposit_channel_pending_higher_block.block_number + 1 }, |_| (), vec![ - Check::ingressed(vec![(1u32, Asset::Sol, 1_000u64)]), - Check::ended_at_state(to_state(vec![deposit_channel_pending_consensus])), + Check::ingressed(vec![( + deposit_channel_pending.account, + deposit_channel_pending.asset, + deposit_channel_pending.total_ingressed, + )]), + Check::ended_at_state(to_state(vec![deposit_channel_with_next_deposit])), ], ) - // Process the final pending ingress. + // Once chain tracking advances past the block of the next deposit, we process it too. .test_on_finalize( - &(deposit_channel_pending_consensus.block_number), + &(deposit_channel_with_next_deposit.block_number), |_| (), vec![ - Check::ingressed(vec![(1u32, Asset::Sol, 1_000u64), (1u32, Asset::Sol, 1_500u64)]), - Check::channel_closed(vec![1u32]), - Check::ended_at_empty_state(), + Check::ingressed(vec![ + ( + deposit_channel_pending.account, + deposit_channel_pending.asset, + deposit_channel_pending.total_ingressed, + ), + ( + deposit_channel_pending.account, + deposit_channel_pending.asset, + deposit_channel_with_next_deposit.total_ingressed - + deposit_channel_pending.total_ingressed, + ), + ]), + Check::ended_at_state(Default::default()), ], ); } + +mod multiple_deposits { + use super::*; + + const DEPOSIT_ADDRESS: u32 = 1; + const TOTAL_1: ChannelTotalIngressed = + ChannelTotalIngressed { amount: 1000, block_number: 10 }; + const TOTAL_2: ChannelTotalIngressed = + ChannelTotalIngressed { amount: 1500, block_number: 20 }; + + #[test] + fn multiple_deposits_result_in_multiple_ingresses() { + // Case 1: Two deposits, and three finality checks: + // - Check 1: Chain tracking has not reached the block of the first deposit. + // - Check 2: Chain tracking has not reached the block of the second deposit, but has passed + // the first. + // - Check 3: Chain tracking has reached the block of the second deposit. + with_default_setup() + .build() + .then(|| { + DepositChannel { + account: DEPOSIT_ADDRESS, + asset: Asset::Sol, + close_block: 100, + total_ingressed: 0, + block_number: 0, + } + .open(); + }) + .force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: BTreeMap::from_iter([(DEPOSIT_ADDRESS, TOTAL_1)]), + }) + // Before chain tracking reaches the ingress block, nothing should be ingressed. + .test_on_finalize( + &{ TOTAL_1.block_number - 1 }, + |_| {}, + [Check::ingressed(vec![]), Check::election_id_incremented()], + ) + // Simulate a second deposit at a later block. + .force_consensus_update(ConsensusStatus::Gained { + most_recent: Some(BTreeMap::from_iter([(DEPOSIT_ADDRESS, TOTAL_1)])), + new: BTreeMap::from_iter([(DEPOSIT_ADDRESS, TOTAL_2)]), + }) + // Finalize with chain tracking at a block between the two deposits. Only the first + // should be ingressed. + .test_on_finalize( + &{ TOTAL_2.block_number - 1 }, + |_| {}, + [ + Check::ingressed(vec![(DEPOSIT_ADDRESS, Asset::Sol, TOTAL_1.amount)]), + Check::election_id_incremented(), + ], + ) + // Finalize with chain tracking at the block of the second deposit. Both should be + // ingressed. + .test_on_finalize( + &TOTAL_2.block_number, + |_| {}, + [Check::ingressed(vec![ + (DEPOSIT_ADDRESS, Asset::Sol, TOTAL_1.amount), + (DEPOSIT_ADDRESS, Asset::Sol, TOTAL_2.amount - TOTAL_1.amount), + ])], + ); + } + + #[test] + fn multiple_deposits_result_in_single_deposit() { + // Case 2: Two deposits and three finality checks: + // - Check 1: Chain tracking has not reached the block of the first deposit. + // - Check 2: Chain tracking has still not reached the block of the first deposit. + // - Check 3: Chain tracking has reached the block of the second deposit. + with_default_setup() + .build() + .then(|| { + DepositChannel { + account: DEPOSIT_ADDRESS, + asset: Asset::Sol, + close_block: 100, + total_ingressed: 0, + block_number: 0, + } + .open(); + }) + .force_consensus_update(ConsensusStatus::Gained { + most_recent: None, + new: BTreeMap::from_iter([(DEPOSIT_ADDRESS, TOTAL_1)]), + }) + // Before chain tracking reaches the ingress block, nothing should be ingressed. + .test_on_finalize(&{ TOTAL_1.block_number - 1 }, |_| {}, [Check::ingressed(vec![])]) + // Simulate a second deposit at a later block. + .force_consensus_update(ConsensusStatus::Gained { + most_recent: Some(BTreeMap::from_iter([(DEPOSIT_ADDRESS, TOTAL_1)])), + new: BTreeMap::from_iter([(DEPOSIT_ADDRESS, TOTAL_2)]), + }) + // Finalize with chain tracking at a block before the first deposit. Nothing should be + // ingressed. + .test_on_finalize(&{ TOTAL_1.block_number - 1 }, |_| {}, [Check::ingressed(vec![])]) + // Finalize with chain tracking at the block of the second deposit. Both should be + // ingressed as a single deposit. + .test_on_finalize( + &TOTAL_2.block_number, + |_| {}, + [Check::ingressed(vec![(DEPOSIT_ADDRESS, Asset::Sol, TOTAL_2.amount)])], + ); + } +} diff --git a/state-chain/pallets/cf-elections/src/lib.rs b/state-chain/pallets/cf-elections/src/lib.rs index cb25fa53136..0044d878417 100644 --- a/state-chain/pallets/cf-elections/src/lib.rs +++ b/state-chain/pallets/cf-elections/src/lib.rs @@ -378,6 +378,8 @@ pub mod pallet { /// Not all vote data was cleared. *You should continue clearing votes until you receive /// the AllVotesCleared event*. AllVotesNotCleared, + /// Received vote for an unknown election + UnknownElection(ElectionIdentifierOf), } #[derive(CloneNoBound, PartialEqNoBound, EqNoBound)] @@ -1227,8 +1229,16 @@ pub mod pallet { ); for (election_identifier, authority_vote) in authority_votes { - let unique_monotonic_identifier = - Self::ensure_election_exists(election_identifier)?; + // if an identifier refers to a non existent election, skip this vote, + // but continue processing others. + let unique_monotonic_identifier = if let Ok(unique_monotonic_identifier) = + Self::ensure_election_exists(election_identifier) + { + unique_monotonic_identifier + } else { + Self::deposit_event(Event::UnknownElection(election_identifier)); + continue; + }; let (partial_vote, option_vote) = match authority_vote { AuthorityVote::PartialVote(partial_vote) => (partial_vote, None), From c23279a9bc8d8be31c2eb5ba913150531951014d Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:59:48 +0100 Subject: [PATCH 09/20] chore: bump old engine version to 1.7.9 (#5611) * chore: bump old engine version to 1.7.9 * fix: tomls too --- engine-runner-bin/Cargo.toml | 4 ++-- engine-runner-bin/src/main.rs | 2 +- engine-upgrade-utils/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine-runner-bin/Cargo.toml b/engine-runner-bin/Cargo.toml index 51105c66764..95f05122243 100644 --- a/engine-runner-bin/Cargo.toml +++ b/engine-runner-bin/Cargo.toml @@ -31,10 +31,10 @@ assets = [ # The old version gets put into target/release/deps by the package github actions workflow. # It downloads the correct version from the releases page. [ - "target/release/libchainflip_engine_v1_7_8.so", + "target/release/libchainflip_engine_v1_7_9.so", # This is the path where the engine dylib is searched for on linux. # As set in the build.rs file. - "usr/lib/chainflip-engine/libchainflip_engine_v1_7_8.so", + "usr/lib/chainflip-engine/libchainflip_engine_v1_7_9.so", "755", ], ] diff --git a/engine-runner-bin/src/main.rs b/engine-runner-bin/src/main.rs index 85d2250aa81..13e1c2abb6b 100644 --- a/engine-runner-bin/src/main.rs +++ b/engine-runner-bin/src/main.rs @@ -3,7 +3,7 @@ use engine_upgrade_utils::{CStrArray, NEW_VERSION, OLD_VERSION}; // Declare the entrypoints into each version of the engine mod old { - #[engine_proc_macros::link_engine_library_version("1.7.8")] + #[engine_proc_macros::link_engine_library_version("1.7.9")] extern "C" { pub fn cfe_entrypoint( c_args: engine_upgrade_utils::CStrArray, diff --git a/engine-upgrade-utils/src/lib.rs b/engine-upgrade-utils/src/lib.rs index a3733a04b33..c78aa03c72f 100644 --- a/engine-upgrade-utils/src/lib.rs +++ b/engine-upgrade-utils/src/lib.rs @@ -10,7 +10,7 @@ pub mod build_helpers; // rest of the places the version needs changing on build using the build scripts in each of the // relevant crates. // Should also check that the compatibility function below `args_compatible_with_old` is correct. -pub const OLD_VERSION: &str = "1.7.8"; +pub const OLD_VERSION: &str = "1.7.9"; pub const NEW_VERSION: &str = "1.8.0"; pub const ENGINE_LIB_PREFIX: &str = "chainflip_engine_v"; From d87032bdc353b579f6cfd97f6a55f64336fd7d67 Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:37:51 +0100 Subject: [PATCH 10/20] chore: temporarily disable Swap-After-Disconnection bouncer test (#5613) --- bouncer/full_bouncer.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bouncer/full_bouncer.sh b/bouncer/full_bouncer.sh index 642e40a1eec..ea877b79b0d 100755 --- a/bouncer/full_bouncer.sh +++ b/bouncer/full_bouncer.sh @@ -12,6 +12,7 @@ echo "Running full bouncer 🧪" if [[ $LOCALNET == false ]]; then echo "🤫 Skipping tests that require localnet" else - echo "🚀 Running tests that require localnet" - ./commands/run_test.ts "Swap-After-Disconnection" + echo "🤫 Skipping tests that require localnet until they are fixed." + # echo "🚀 Running tests that require localnet" + # ./commands/run_test.ts "Swap-After-Disconnection" fi From 725283a8a8aa70938927e43a5b7da2748a62d7fd Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:14:35 +0100 Subject: [PATCH 11/20] chore: reduce voter logging in engine (#5584) * chore: reduce voter logging in engine * chore: skip self on instrumentation --------- Co-authored-by: kylezs --- engine/src/elections.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/engine/src/elections.rs b/engine/src/elections.rs index 8f97a309e1e..ceb5eefedba 100644 --- a/engine/src/elections.rs +++ b/engine/src/elections.rs @@ -83,6 +83,7 @@ where } } + #[tracing::instrument(name = "voter-task", skip(self))] async fn reset_and_continuously_vote(&self) -> Result<(), anyhow::Error> { let mut rng = rand::rngs::OsRng; let latest_unfinalized_block = self.state_chain_client.latest_unfinalized_block(); @@ -149,7 +150,7 @@ where let state_chain_client = &self.state_chain_client; async move { for (election_identifier, _) in votes.iter() { - info!("Submitting vote for election: '{:?}'", election_identifier); + debug!("Submitting vote for election: '{:?}'", election_identifier); } // TODO: Use block hash you got this vote tasks details from as the based of the mortal of the extrinsic state_chain_client.submit_signed_extrinsic(pallet_cf_elections::Call::::vote { @@ -161,7 +162,7 @@ where let (election_identifier, result_vote) = vote_tasks.next_or_pending() => { match result_vote { Ok(Some(vote)) => { - info!("Voting task for election: '{:?}' succeeded.", election_identifier); + debug!("Voting task for election: '{:?}' succeeded.", election_identifier); // Create the partial_vote early so that SharedData can be provided as soon as the vote has been generated, rather than only after it is submitted. let partial_vote = VoteStorageOf::<>::ElectoralSystemRunner>::vote_into_partial_vote(&vote, |shared_data| { let shared_data_hash = SharedDataHash::of(&shared_data); @@ -198,7 +199,7 @@ where for (election_identifier, election_data) in electoral_data.current_elections { if election_data.is_vote_desired { if !vote_tasks.contains_key(&election_identifier) { - info!("Voting task for election: '{:?}' initiate.", election_identifier); + debug!("Voting task for election: '{:?}' initiated.", election_identifier); vote_tasks.insert( election_identifier, Box::pin(self.voter.request_with_limit( @@ -217,7 +218,7 @@ where )) ); } else { - info!("Voting task for election: '{:?}' not initiated as a task is already running for that election.", election_identifier); + debug!("Voting task for election: '{:?}' not initiated as a task is already running for that election.", election_identifier); } } } From e0c0a2d632b5a97c4f9808ba5eb4c0eac94e0ffe Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:25:02 +0100 Subject: [PATCH 12/20] chore: increase solana ledger size (#5614) chore: increase ledger size to 100000 --- localnet/init/scripts/start-solana.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localnet/init/scripts/start-solana.sh b/localnet/init/scripts/start-solana.sh index c8f7e352194..e1f3d77c9ba 100755 --- a/localnet/init/scripts/start-solana.sh +++ b/localnet/init/scripts/start-solana.sh @@ -4,4 +4,4 @@ set -e DATETIME=$(date '+%Y-%m-%d_%H-%M-%S') export RUST_LOG=solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=info,solana_bpf_loader=debug,solana_rbpf=debug export RUST_BACKTRACE=full -solana-test-validator --limit-ledger-size 30000 --ledger /tmp/solana/test-ledger > /tmp/solana/solana.$DATETIME.log 2>&1 & +solana-test-validator --limit-ledger-size 100000 --ledger /tmp/solana/test-ledger > /tmp/solana/solana.$DATETIME.log 2>&1 & From 1a135bd462e5a8d56de667baa2a9f9de40f25d2d Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:03:49 +0100 Subject: [PATCH 13/20] fix: make naming more consistent for request_swap_parameter_encoding (#5615) * fix: make naming more consistent for request_swap_parameter_encoding * Update state-chain/custom-rpc/src/lib.rs --- api/bin/chainflip-broker-api/src/main.rs | 2 +- bouncer/shared/btc_vault_swap.ts | 2 +- bouncer/shared/evm_vault_swap.ts | 2 +- bouncer/shared/sol_vault_swap.ts | 2 +- state-chain/custom-rpc/src/lib.rs | 8 ++++---- state-chain/runtime/src/lib.rs | 2 +- state-chain/runtime/src/runtime_apis.rs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/bin/chainflip-broker-api/src/main.rs b/api/bin/chainflip-broker-api/src/main.rs index be3fbd5de2e..62cd7cc8795 100644 --- a/api/bin/chainflip-broker-api/src/main.rs +++ b/api/bin/chainflip-broker-api/src/main.rs @@ -232,7 +232,7 @@ impl RpcServer for RpcServerImpl { Ok(self .api .raw_client() - .cf_get_vault_swap_details( + .cf_request_swap_parameter_encoding( self.api.state_chain_client.account_id(), source_asset, destination_asset, diff --git a/bouncer/shared/btc_vault_swap.ts b/bouncer/shared/btc_vault_swap.ts index c6803788cd1..6c653e97120 100644 --- a/bouncer/shared/btc_vault_swap.ts +++ b/bouncer/shared/btc_vault_swap.ts @@ -50,7 +50,7 @@ export async function buildAndSendBtcVaultSwap( }; const BtcVaultSwapDetails = (await chainflip.rpc( - `cf_get_vault_swap_details`, + `cf_request_swap_parameter_encoding`, brokerFees.account, { chain: 'Bitcoin', asset: stateChainAssetFromAsset('Btc') }, { chain: chainFromAsset(destinationAsset), asset: stateChainAssetFromAsset(destinationAsset) }, diff --git a/bouncer/shared/evm_vault_swap.ts b/bouncer/shared/evm_vault_swap.ts index a08892ac9f6..7aef188b5ae 100644 --- a/bouncer/shared/evm_vault_swap.ts +++ b/bouncer/shared/evm_vault_swap.ts @@ -88,7 +88,7 @@ export async function executeEvmVaultSwap( }; const vaultSwapDetails = (await chainflip.rpc( - `cf_get_vault_swap_details`, + `cf_request_swap_parameter_encoding`, brokerAddress, { chain: srcChain, asset: stateChainAssetFromAsset(sourceAsset) }, { chain: destChain, asset: stateChainAssetFromAsset(destAsset) }, diff --git a/bouncer/shared/sol_vault_swap.ts b/bouncer/shared/sol_vault_swap.ts index d3937aaab0f..941065d6d02 100644 --- a/bouncer/shared/sol_vault_swap.ts +++ b/bouncer/shared/sol_vault_swap.ts @@ -111,7 +111,7 @@ export async function executeSolVaultSwap( }; const vaultSwapDetails = (await chainflip.rpc( - `cf_get_vault_swap_details`, + `cf_request_swap_parameter_encoding`, brokerFees.account, { chain: chainFromAsset(srcAsset), asset: stateChainAssetFromAsset(srcAsset) }, { chain: chainFromAsset(destAsset), asset: stateChainAssetFromAsset(destAsset) }, diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index a92466f2ed7..a7c4d266711 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -993,8 +993,8 @@ pub trait CustomApi { at: Option, ) -> RpcResult<()>; - #[method(name = "get_vault_swap_details")] - fn cf_get_vault_swap_details( + #[method(name = "request_swap_parameter_encoding")] + fn cf_request_swap_parameter_encoding( &self, broker: state_chain_runtime::AccountId, source_asset: Asset, @@ -1870,7 +1870,7 @@ where self.with_runtime_api(at, |api, hash| api.cf_filter_votes(hash, validator, proposed_votes)) } - fn cf_get_vault_swap_details( + fn cf_request_swap_parameter_encoding( &self, broker: state_chain_runtime::AccountId, source_asset: Asset, @@ -1886,7 +1886,7 @@ where ) -> RpcResult> { self.with_runtime_api(at, |api, hash| { Ok::<_, CfApiError>( - api.cf_get_vault_swap_details( + api.cf_request_swap_parameter_encoding( hash, broker, source_asset, diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 39358579496..bbf378032cf 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -2231,7 +2231,7 @@ impl_runtime_apis! { pallet_cf_swapping::Pallet::::validate_refund_params(retry_duration).map_err(Into::into) } - fn cf_get_vault_swap_details( + fn cf_request_swap_parameter_encoding( broker_id: AccountId, source_asset: Asset, destination_asset: Asset, diff --git a/state-chain/runtime/src/runtime_apis.rs b/state-chain/runtime/src/runtime_apis.rs index 5f42c4be066..dc0817d5b87 100644 --- a/state-chain/runtime/src/runtime_apis.rs +++ b/state-chain/runtime/src/runtime_apis.rs @@ -449,7 +449,7 @@ decl_runtime_apis!( fn cf_validate_refund_params( retry_duration: BlockNumber, ) -> Result<(), DispatchErrorWithMessage>; - fn cf_get_vault_swap_details( + fn cf_request_swap_parameter_encoding( broker: AccountId32, source_asset: Asset, destination_asset: Asset, From 2875da9f19c49ccea33402994087bbc2b88c6e79 Mon Sep 17 00:00:00 2001 From: kylezs Date: Fri, 7 Feb 2025 13:09:06 +0100 Subject: [PATCH 14/20] fix: swap after disconnect polkadot bouncer issue (#5616) * fix: localnet assethub fixes * chore: use polkadot2 as backup on localnet * fix: remove DOT ingress swap after disconnect test * fix: reenable tests * fix:remove polkadot1 --------- Co-authored-by: Daniel --- bouncer/full_bouncer.sh | 5 ++--- bouncer/tests/swap_after_temp_disconnecting_chains.ts | 3 +-- localnet/common.sh | 2 +- localnet/docker-compose.yml | 9 +++++++-- localnet/init/config/Settings.toml | 6 +++--- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/bouncer/full_bouncer.sh b/bouncer/full_bouncer.sh index ea877b79b0d..642e40a1eec 100755 --- a/bouncer/full_bouncer.sh +++ b/bouncer/full_bouncer.sh @@ -12,7 +12,6 @@ echo "Running full bouncer 🧪" if [[ $LOCALNET == false ]]; then echo "🤫 Skipping tests that require localnet" else - echo "🤫 Skipping tests that require localnet until they are fixed." - # echo "🚀 Running tests that require localnet" - # ./commands/run_test.ts "Swap-After-Disconnection" + echo "🚀 Running tests that require localnet" + ./commands/run_test.ts "Swap-After-Disconnection" fi diff --git a/bouncer/tests/swap_after_temp_disconnecting_chains.ts b/bouncer/tests/swap_after_temp_disconnecting_chains.ts index 4677121dc53..6e2f010ec36 100755 --- a/bouncer/tests/swap_after_temp_disconnecting_chains.ts +++ b/bouncer/tests/swap_after_temp_disconnecting_chains.ts @@ -14,7 +14,7 @@ export const testSwapAfterDisconnection = new ExecutableTest( // Testing a swap after temporarily disconnecting external nodes async function main() { const networkName = 'chainflip-localnet_default'; - const allExternalNodes = ['bitcoin', 'geth', 'polkadot1']; + const allExternalNodes = ['bitcoin', 'geth']; await Promise.all( allExternalNodes.map((container) => disconnectContainerFromNetwork(container, networkName)), @@ -27,7 +27,6 @@ async function main() { ); await Promise.all([ - testSwap('Dot', 'Btc', undefined, undefined, testSwapAfterDisconnection.swapContext), testSwap('Btc', 'Flip', undefined, undefined, testSwapAfterDisconnection.swapContext), testSwap('Eth', 'Usdc', undefined, undefined, testSwapAfterDisconnection.swapContext), ]); diff --git a/localnet/common.sh b/localnet/common.sh index d5d247c39b9..95792117b2d 100644 --- a/localnet/common.sh +++ b/localnet/common.sh @@ -246,7 +246,7 @@ yeet() { logs() { echo "🤖 Which service would you like to tail?" - select SERVICE in node engine broker lp polkadot geth bitcoin solana poster sequencer staker debug redis all ingress-egress-tracker; do + select SERVICE in node engine broker lp polkadot1 polkadot2 assethub geth bitcoin solana poster sequencer staker debug redis all ingress-egress-tracker; do if [[ $SERVICE == "all" ]]; then $DOCKER_COMPOSE_CMD -f localnet/docker-compose.yml -p "chainflip-localnet" logs --follow tail -f $CHAINFLIP_BASE_PATH/*/chainflip-*.log diff --git a/localnet/docker-compose.yml b/localnet/docker-compose.yml index a4d98a5bc64..3e5ec0f5022 100644 --- a/localnet/docker-compose.yml +++ b/localnet/docker-compose.yml @@ -154,6 +154,11 @@ services: - --name=PolkaDocker2 - --wasmtime-instantiation-strategy=recreate-instance-copy-on-write - --unsafe-force-node-key-generation + # We use polkadot2 as a backup node for DOT. + - --rpc-cors=all + - --rpc-external + - --rpc-methods=unsafe + - --rpc-max-connections=100000 # - --bootnodes=/ip4/127.0.0.1/tcp/30433 ports: - 9948:9944 @@ -165,7 +170,7 @@ services: "curl", "-H 'Content-Type: application/json;'", "-d '{\"id\":1, \"jsonrpc\":\"2.0\", \"method\": \"chain_getBlockHash\", \"params\" : [0]}'", - "http://localhost:9947" + "http://localhost:9948" ] interval: 10s timeout: 5s @@ -209,7 +214,7 @@ services: "curl", "-H 'Content-Type: application/json;'", "-d '{\"id\":1, \"jsonrpc\":\"2.0\", \"method\": \"chain_getBlockHash\", \"params\" : [0]}'", - "http://localhost:9947" + "http://localhost:9955" ] interval: 10s timeout: 5s diff --git a/localnet/init/config/Settings.toml b/localnet/init/config/Settings.toml index f04e655b0f6..81d462931cd 100644 --- a/localnet/init/config/Settings.toml +++ b/localnet/init/config/Settings.toml @@ -28,9 +28,9 @@ ws_endpoint = "ws://localhost:9947" http_endpoint = "http://localhost:9947" # optional -#[dot.backup_rpc] -#ws_endpoint = "ws://localhost:8000" -#http_endpoint = "http://localhost:8000" +[dot.backup_rpc] +ws_endpoint = "ws://localhost:9948" +http_endpoint = "http://localhost:9948" [btc.rpc] http_endpoint = "http://localhost:8332" From c9767626ed1ca6244d78a18582c70985615a316f Mon Sep 17 00:00:00 2001 From: dandanlen <3168260+dandanlen@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:57:19 +0100 Subject: [PATCH 15/20] fix: bump versions, remove old migrations and organise remaining ones (#5619) * chore: Bump versions and remove deprecated code. * fix: remove old migrations and organise remaining ones * chore: bump runtime spec version to 190 --- .github/workflows/upgrade-test.yml | 12 - Cargo.lock | 20 +- api/bin/chainflip-broker-api/Cargo.toml | 2 +- api/bin/chainflip-cli/Cargo.toml | 7 +- api/bin/chainflip-lp-api/Cargo.toml | 2 +- api/lib/Cargo.toml | 4 +- engine-dylib/Cargo.toml | 4 +- engine-proc-macros/Cargo.toml | 2 +- engine-runner-bin/Cargo.toml | 10 +- engine-runner-bin/src/main.rs | 4 +- engine-upgrade-utils/src/lib.rs | 4 +- engine/Cargo.toml | 2 +- state-chain/custom-rpc/src/lib.rs | 4 +- state-chain/custom-rpc/src/monitoring.rs | 42 +- state-chain/node/Cargo.toml | 2 +- .../pallets/cf-environment/src/migrations.rs | 15 +- .../pallets/cf-funding/src/migrations.rs | 18 - .../pallets/cf-ingress-egress/src/lib.rs | 34 +- .../cf-ingress-egress/src/migrations.rs | 32 +- .../deposit_channel_details_migration.rs | 156 ------- .../rename_scheduled_tx_for_reject.rs | 72 --- .../scheduled_egress_ccm_migration.rs | 110 ----- .../pallets/cf-swapping/src/migrations.rs | 16 +- .../swap_and_swap_request_migration.rs | 208 --------- .../pallets/cf-validator/src/migrations.rs | 15 +- .../src/migrations/rename_blocks_per_epoch.rs | 66 --- state-chain/runtime/Cargo.toml | 2 +- state-chain/runtime/src/lib.rs | 88 +--- state-chain/runtime/src/migrations.rs | 7 - .../src/migrations/api_calls_gas_migration.rs | 174 -------- .../arbitrum_chain_tracking_migration.rs | 116 ----- .../runtime/src/migrations/housekeeping.rs | 14 +- .../{ => housekeeping}/reap_old_accounts.rs | 0 .../solana_remove_unused_channels_state.rs | 0 .../remove_fee_tracking_migration.rs | 416 ------------------ .../solana_transaction_data_migration.rs | 88 ---- .../solana_vault_swaps_migration.rs | 106 ----- state-chain/runtime/src/monitoring_apis.rs | 44 -- 38 files changed, 74 insertions(+), 1844 deletions(-) delete mode 100644 state-chain/pallets/cf-ingress-egress/src/migrations/deposit_channel_details_migration.rs delete mode 100644 state-chain/pallets/cf-ingress-egress/src/migrations/rename_scheduled_tx_for_reject.rs delete mode 100644 state-chain/pallets/cf-ingress-egress/src/migrations/scheduled_egress_ccm_migration.rs delete mode 100644 state-chain/pallets/cf-swapping/src/migrations/swap_and_swap_request_migration.rs delete mode 100644 state-chain/pallets/cf-validator/src/migrations/rename_blocks_per_epoch.rs delete mode 100644 state-chain/runtime/src/migrations/api_calls_gas_migration.rs delete mode 100644 state-chain/runtime/src/migrations/arbitrum_chain_tracking_migration.rs rename state-chain/runtime/src/migrations/{ => housekeeping}/reap_old_accounts.rs (100%) rename state-chain/runtime/src/migrations/{ => housekeeping}/solana_remove_unused_channels_state.rs (100%) delete mode 100644 state-chain/runtime/src/migrations/remove_fee_tracking_migration.rs delete mode 100644 state-chain/runtime/src/migrations/solana_transaction_data_migration.rs delete mode 100644 state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs diff --git a/.github/workflows/upgrade-test.yml b/.github/workflows/upgrade-test.yml index 981b78e2667..05868f94909 100644 --- a/.github/workflows/upgrade-test.yml +++ b/.github/workflows/upgrade-test.yml @@ -173,21 +173,9 @@ 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 id: pre-upgrade-bouncer diff --git a/Cargo.lock b/Cargo.lock index 860a9755f34..0c02a2fccba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1342,7 +1342,7 @@ dependencies = [ [[package]] name = "cf-engine-dylib" -version = "1.8.0" +version = "1.9.0" dependencies = [ "chainflip-engine", "engine-proc-macros", @@ -1582,7 +1582,7 @@ dependencies = [ [[package]] name = "chainflip-api" -version = "1.8.0" +version = "1.9.0" dependencies = [ "anyhow", "async-trait", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "chainflip-broker-api" -version = "1.8.0" +version = "1.9.0" dependencies = [ "anyhow", "cf-chains", @@ -1653,7 +1653,7 @@ dependencies = [ [[package]] name = "chainflip-cli" -version = "1.8.0" +version = "1.9.0" dependencies = [ "anyhow", "bigdecimal", @@ -1674,7 +1674,7 @@ dependencies = [ [[package]] name = "chainflip-engine" -version = "1.8.0" +version = "1.9.0" dependencies = [ "anyhow", "async-broadcast", @@ -1814,7 +1814,7 @@ dependencies = [ [[package]] name = "chainflip-lp-api" -version = "1.8.0" +version = "1.9.0" dependencies = [ "anyhow", "cf-primitives", @@ -1841,7 +1841,7 @@ dependencies = [ [[package]] name = "chainflip-node" -version = "1.8.0" +version = "1.9.0" dependencies = [ "cf-chains", "cf-primitives", @@ -3268,7 +3268,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "engine-proc-macros" -version = "1.8.0" +version = "1.9.0" dependencies = [ "engine-upgrade-utils", "proc-macro2", @@ -3278,7 +3278,7 @@ dependencies = [ [[package]] name = "engine-runner" -version = "1.8.0" +version = "1.9.0" dependencies = [ "anyhow", "assert_cmd", @@ -13269,7 +13269,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "state-chain-runtime" -version = "1.8.0" +version = "1.9.0" dependencies = [ "bitvec", "cf-amm", diff --git a/api/bin/chainflip-broker-api/Cargo.toml b/api/bin/chainflip-broker-api/Cargo.toml index 6778ad49bda..01b63c8e2fa 100644 --- a/api/bin/chainflip-broker-api/Cargo.toml +++ b/api/bin/chainflip-broker-api/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Chainflip team "] name = "chainflip-broker-api" -version = "1.8.0" +version = "1.9.0" edition = "2021" [package.metadata.deb] diff --git a/api/bin/chainflip-cli/Cargo.toml b/api/bin/chainflip-cli/Cargo.toml index 9af9a736823..e3621158199 100644 --- a/api/bin/chainflip-cli/Cargo.toml +++ b/api/bin/chainflip-cli/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Chainflip team "] edition = "2021" build = "build.rs" name = "chainflip-cli" -version = "1.8.0" +version = "1.9.0" [lints] workspace = true @@ -15,7 +15,10 @@ clap = { workspace = true, features = ["derive", "env"] } config = { workspace = true } futures = { workspace = true } hex = { workspace = true, default-features = true, features = ["serde"] } -serde = { workspace = true, default-features = true, features = ["derive", "rc"] } +serde = { workspace = true, default-features = true, features = [ + "derive", + "rc", +] } tokio = { workspace = true, features = ["full"] } serde_json = { workspace = true } diff --git a/api/bin/chainflip-lp-api/Cargo.toml b/api/bin/chainflip-lp-api/Cargo.toml index a35ca3f8bff..11ac5f93938 100644 --- a/api/bin/chainflip-lp-api/Cargo.toml +++ b/api/bin/chainflip-lp-api/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Chainflip team "] name = "chainflip-lp-api" -version = "1.8.0" +version = "1.9.0" edition = "2021" [package.metadata.deb] diff --git a/api/lib/Cargo.toml b/api/lib/Cargo.toml index 5239ee5ed2e..69efe66b8f8 100644 --- a/api/lib/Cargo.toml +++ b/api/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chainflip-api" -version = "1.8.0" +version = "1.9.0" edition = "2021" [lints] @@ -12,7 +12,7 @@ async-trait = { workspace = true } bs58 = { workspace = true, default-features = true } ed25519-dalek = { workspace = true } futures = { workspace = true } -hex = { workspace = true, default-features = true} +hex = { workspace = true, default-features = true } hmac-sha512 = { workspace = true } libsecp256k1 = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } diff --git a/engine-dylib/Cargo.toml b/engine-dylib/Cargo.toml index 7d4212447d0..4c5953ed058 100644 --- a/engine-dylib/Cargo.toml +++ b/engine-dylib/Cargo.toml @@ -3,11 +3,11 @@ authors = ["Chainflip team "] build = "build.rs" edition = "2021" name = "cf-engine-dylib" -version = "1.8.0" +version = "1.9.0" [lib] crate-type = ["cdylib"] -name = "chainflip_engine_v1_8_0" +name = "chainflip_engine_v1_9_0" path = "src/lib.rs" [dependencies] diff --git a/engine-proc-macros/Cargo.toml b/engine-proc-macros/Cargo.toml index 8b4e1f011b0..2fd13356cca 100644 --- a/engine-proc-macros/Cargo.toml +++ b/engine-proc-macros/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" name = "engine-proc-macros" # The version here is the version that will be used for the generated code, and therefore will be the # suffix of the generated engine entrypoint. TODO: Fix this. -version = "1.8.0" +version = "1.9.0" [lib] proc-macro = true diff --git a/engine-runner-bin/Cargo.toml b/engine-runner-bin/Cargo.toml index 95f05122243..c34a76fab1e 100644 --- a/engine-runner-bin/Cargo.toml +++ b/engine-runner-bin/Cargo.toml @@ -2,7 +2,7 @@ name = "engine-runner" description = "The central runner for the chainflip engine, it requires two shared library versions to run." # NB: When updating this version, you must update the debian assets appropriately too. -version = "1.8.0" +version = "1.9.0" authors = ["Chainflip team "] build = "build.rs" edition = "2021" @@ -22,19 +22,19 @@ assets = [ # to specify this. We do this in the `chainflip-engine.service` files, so the user does not need to set it # manually. [ - "target/release/libchainflip_engine_v1_8_0.so", + "target/release/libchainflip_engine_v1_9_0.so", # This is the path where the engine dylib is searched for on linux. # As set in the build.rs file. - "usr/lib/chainflip-engine/libchainflip_engine_v1_8_0.so", + "usr/lib/chainflip-engine/libchainflip_engine_v1_9_0.so", "755", ], # The old version gets put into target/release/deps by the package github actions workflow. # It downloads the correct version from the releases page. [ - "target/release/libchainflip_engine_v1_7_9.so", + "target/release/libchainflip_engine_v1_8_0.so", # This is the path where the engine dylib is searched for on linux. # As set in the build.rs file. - "usr/lib/chainflip-engine/libchainflip_engine_v1_7_9.so", + "usr/lib/chainflip-engine/libchainflip_engine_v1_8_0.so", "755", ], ] diff --git a/engine-runner-bin/src/main.rs b/engine-runner-bin/src/main.rs index 13e1c2abb6b..5b1103420f0 100644 --- a/engine-runner-bin/src/main.rs +++ b/engine-runner-bin/src/main.rs @@ -3,7 +3,7 @@ use engine_upgrade_utils::{CStrArray, NEW_VERSION, OLD_VERSION}; // Declare the entrypoints into each version of the engine mod old { - #[engine_proc_macros::link_engine_library_version("1.7.9")] + #[engine_proc_macros::link_engine_library_version("1.8.0")] extern "C" { pub fn cfe_entrypoint( c_args: engine_upgrade_utils::CStrArray, @@ -13,7 +13,7 @@ mod old { } mod new { - #[engine_proc_macros::link_engine_library_version("1.8.0")] + #[engine_proc_macros::link_engine_library_version("1.9.0")] extern "C" { fn cfe_entrypoint( c_args: engine_upgrade_utils::CStrArray, diff --git a/engine-upgrade-utils/src/lib.rs b/engine-upgrade-utils/src/lib.rs index c78aa03c72f..206da9c587a 100644 --- a/engine-upgrade-utils/src/lib.rs +++ b/engine-upgrade-utils/src/lib.rs @@ -10,8 +10,8 @@ pub mod build_helpers; // rest of the places the version needs changing on build using the build scripts in each of the // relevant crates. // Should also check that the compatibility function below `args_compatible_with_old` is correct. -pub const OLD_VERSION: &str = "1.7.9"; -pub const NEW_VERSION: &str = "1.8.0"; +pub const OLD_VERSION: &str = "1.8.0"; +pub const NEW_VERSION: &str = "1.9.0"; pub const ENGINE_LIB_PREFIX: &str = "chainflip_engine_v"; pub const ENGINE_ENTRYPOINT_PREFIX: &str = "cfe_entrypoint_v"; diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 88122cd52f7..4631d3d2f50 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Chainflip team "] build = "build.rs" edition = "2021" name = "chainflip-engine" -version = "1.8.0" +version = "1.9.0" [lib] crate-type = ["lib"] diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index a7c4d266711..394e6d454d1 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -1,4 +1,4 @@ -use crate::{boost_pool_rpc::BoostPoolFeesRpc, monitoring::RpcEpochStateV2}; +use crate::{boost_pool_rpc::BoostPoolFeesRpc, monitoring::RpcEpochState}; use boost_pool_rpc::BoostPoolDetailsRpc; use cf_amm::{ common::{PoolPairsMap, Side}, @@ -109,7 +109,7 @@ impl From for RpcFlipSupply { pub struct RpcMonitoringData { pub external_chains_height: ExternalChainsBlockHeight, pub btc_utxos: BtcUtxos, - pub epoch: RpcEpochStateV2, + pub epoch: RpcEpochState, pub pending_redemptions: RpcRedemptionsInfo, pub pending_broadcasts: PendingBroadcasts, pub pending_tss: PendingTssCeremonies, diff --git a/state-chain/custom-rpc/src/monitoring.rs b/state-chain/custom-rpc/src/monitoring.rs index 2641769a42c..554e0d43a08 100644 --- a/state-chain/custom-rpc/src/monitoring.rs +++ b/state-chain/custom-rpc/src/monitoring.rs @@ -5,7 +5,6 @@ use cf_utilities::rpc::NumberOrHex; use jsonrpsee::proc_macros::rpc; use sc_client_api::{BlockchainEvents, HeaderBackend}; use serde::{Deserialize, Serialize}; -use sp_api::ApiExt; use sp_core::{bounded_vec::BoundedVec, ConstU32}; use state_chain_runtime::{ self, @@ -15,7 +14,6 @@ use state_chain_runtime::{ LastRuntimeUpgradeInfo, MonitoringRuntimeApi, OpenDepositChannels, PendingBroadcasts, PendingTssCeremonies, RedemptionsInfo, SolanaNonces, }, - Block, }; impl From for RpcEpochState { fn from(rotation_state: EpochState) -> Self { @@ -37,25 +35,6 @@ pub struct RpcEpochState { pub rotation_phase: String, } -// Temporary struct to hold the deprecated blocks_per_epoch field. -// Can be deleted after v1.7 is released (meaning: after the version is bumped to 1.8). -#[derive(Serialize, Deserialize, Clone)] -pub struct RpcEpochStateV2 { - #[deprecated( - since = "1.8.0", - note = "This field is deprecated and will be removed in v1.8. Use blocks_per_epoch instead." - )] - blocks_per_epoch: u32, - #[serde(flatten)] - epoch_state: RpcEpochState, -} - -impl From for RpcEpochStateV2 { - fn from(epoch_state: EpochState) -> Self { - Self { blocks_per_epoch: epoch_state.epoch_duration, epoch_state: epoch_state.into() } - } -} - #[rpc(server, client, namespace = "cf_monitoring")] pub trait MonitoringApi { #[method(name = "authorities")] @@ -75,7 +54,7 @@ pub trait MonitoringApi { at: Option, ) -> RpcResult>; #[method(name = "epoch_state")] - fn cf_epoch_state(&self, at: Option) -> RpcResult; + fn cf_epoch_state(&self, at: Option) -> RpcResult; #[method(name = "redemptions")] fn cf_redemptions(&self, at: Option) -> RpcResult; #[method(name = "pending_broadcasts")] @@ -145,7 +124,7 @@ where cf_btc_utxos() -> BtcUtxos, cf_dot_aggkey() -> PolkadotAccountId, cf_suspended_validators() -> Vec<(Offence, u32)>, - cf_epoch_state() -> RpcEpochStateV2 [map: RpcEpochStateV2::from], + cf_epoch_state() -> RpcEpochState [map: Into::into], cf_redemptions() -> RedemptionsInfo, cf_pending_broadcasts_count() -> PendingBroadcasts, cf_pending_tss_ceremonies_count() -> PendingTssCeremonies, @@ -155,7 +134,8 @@ where cf_rotation_broadcast_ids() -> ActivateKeysBroadcastIds, cf_sol_nonces() -> SolanaNonces, cf_sol_aggkey() -> SolAddress, - cf_sol_onchain_key() -> SolAddress + cf_sol_onchain_key() -> SolAddress, + cf_monitoring_data() -> RpcMonitoringData [map: Into::into], } fn cf_fee_imbalance( @@ -166,20 +146,6 @@ where .map(|imbalance| imbalance.map(|i| (*i).into())) } - fn cf_monitoring_data( - &self, - at: Option, - ) -> RpcResult { - self.with_runtime_api(at, |api, hash| { - if api.api_version::>(hash).unwrap().unwrap() < 2 { - let old_result = api.cf_monitoring_data_before_version_2(hash)?; - Ok(old_result.into()) - } else { - api.cf_monitoring_data(hash) - } - }) - .map(Into::into) - } fn cf_accounts_info( &self, accounts: BoundedVec>, diff --git a/state-chain/node/Cargo.toml b/state-chain/node/Cargo.toml index 15ebac0c55c..91aca0e5b72 100644 --- a/state-chain/node/Cargo.toml +++ b/state-chain/node/Cargo.toml @@ -8,7 +8,7 @@ license = "" name = "chainflip-node" publish = false repository = "https://github.com/chainflip-io/chainflip-backend" -version = "1.8.0" +version = "1.9.0" [[bin]] name = "chainflip-node" diff --git a/state-chain/pallets/cf-environment/src/migrations.rs b/state-chain/pallets/cf-environment/src/migrations.rs index 3c7a2aedf79..9ecc856b6e2 100644 --- a/state-chain/pallets/cf-environment/src/migrations.rs +++ b/state-chain/pallets/cf-environment/src/migrations.rs @@ -2,7 +2,7 @@ use crate::{Config, Pallet}; #[cfg(feature = "try-runtime")] use crate::{CurrentReleaseVersion, Get}; use cf_runtime_utilities::PlaceholderMigration; -use frame_support::{migrations::VersionedMigration, traits::OnRuntimeUpgrade}; +use frame_support::traits::OnRuntimeUpgrade; #[cfg(feature = "try-runtime")] use frame_support::{pallet_prelude::DispatchError, sp_runtime}; #[cfg(feature = "try-runtime")] @@ -11,6 +11,8 @@ use sp_std::vec::Vec; mod sol_api_environment; pub use sol_api_environment::SolApiEnvironmentMigration; +// NOTE: Do not remove this. This is used to update the on-chain version for CFE compatibility +// checks. pub struct VersionUpdate(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for VersionUpdate { @@ -36,16 +38,7 @@ impl OnRuntimeUpgrade for VersionUpdate { } // Migration for Updating Solana's Api Environments. -pub type PalletMigration = ( - VersionedMigration< - 12, - 13, - SolApiEnvironmentMigration, - Pallet, - ::DbWeight, - >, - PlaceholderMigration<13, Pallet>, -); +pub type PalletMigration = (PlaceholderMigration<13, Pallet>,); #[cfg(test)] mod tests { diff --git a/state-chain/pallets/cf-funding/src/migrations.rs b/state-chain/pallets/cf-funding/src/migrations.rs index 5f0b07bcae5..d4c1e5f11ed 100644 --- a/state-chain/pallets/cf-funding/src/migrations.rs +++ b/state-chain/pallets/cf-funding/src/migrations.rs @@ -2,21 +2,3 @@ use crate::Pallet; use cf_runtime_utilities::PlaceholderMigration; pub type PalletMigration = PlaceholderMigration<4, Pallet>; - -pub mod active_bidders_migration { - pub const APPLY_AT_FUNDING_STORAGE_VERSION: u16 = 4; -} - -pub mod old { - use crate::*; - use frame_support::{pallet_prelude::ValueQuery, Blake2_128Concat}; - - #[frame_support::storage_alias] - pub type ActiveBidder = StorageMap< - Pallet, - Blake2_128Concat, - ::AccountId, - bool, - ValueQuery, - >; -} diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index 903a2917d7b..507b93e773e 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -38,7 +38,7 @@ use cf_primitives::{ AccountRole, AffiliateShortId, Affiliates, Asset, AssetAmount, BasisPoints, Beneficiaries, Beneficiary, BoostPoolTier, BroadcastId, ChannelId, DcaParameters, EgressCounter, EgressId, EpochIndex, ForeignChain, GasAmount, PrewitnessedDepositId, SwapRequestId, - ThresholdSignatureRequestId, TransactionHash, SECONDS_PER_BLOCK, + ThresholdSignatureRequestId, SECONDS_PER_BLOCK, }; use cf_runtime_utilities::log_or_panic; use cf_traits::{ @@ -1431,38 +1431,6 @@ pub mod pallet { Ok(()) } - // TODO: remove these deprecated calls after runtime version 1.8 - #[pallet::call_index(10)] - #[pallet::weight(T::WeightInfo::vault_swap_request())] - pub fn vault_swap_request_deprecated( - origin: OriginFor, - _from: Asset, - _to: Asset, - _deposit_amount: AssetAmount, - _destination_address: EncodedAddress, - _tx_hash: TransactionHash, - ) -> DispatchResult { - T::EnsureWitnessed::ensure_origin(origin)?; - - Err(DispatchError::Other("deprecated")) - } - - #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::vault_swap_request())] - pub fn vault_ccm_swap_request_deprecated( - origin: OriginFor, - _source_asset: Asset, - _deposit_amount: AssetAmount, - _destination_asset: Asset, - _destination_address: EncodedAddress, - _deposit_metadata: CcmDepositMetadata, - _tx_hash: TransactionHash, - ) -> DispatchResult { - T::EnsureWitnessed::ensure_origin(origin)?; - - Err(DispatchError::Other("deprecated")) - } - #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::mark_transaction_for_rejection())] pub fn mark_transaction_for_rejection( diff --git a/state-chain/pallets/cf-ingress-egress/src/migrations.rs b/state-chain/pallets/cf-ingress-egress/src/migrations.rs index f1ce3ba3805..8e7091555b4 100644 --- a/state-chain/pallets/cf-ingress-egress/src/migrations.rs +++ b/state-chain/pallets/cf-ingress-egress/src/migrations.rs @@ -1,32 +1,4 @@ -use cf_runtime_utilities::PlaceholderMigration; -use frame_support::migrations::VersionedMigration; - use crate::Pallet; -pub mod deposit_channel_details_migration; -pub mod rename_scheduled_tx_for_reject; -pub mod scheduled_egress_ccm_migration; +use cf_runtime_utilities::PlaceholderMigration; -pub type PalletMigration = ( - VersionedMigration< - 17, - 18, - deposit_channel_details_migration::DepositChannelDetailsMigration, - Pallet, - ::DbWeight, - >, - VersionedMigration< - 18, - 19, - scheduled_egress_ccm_migration::ScheduledEgressCcmMigration, - Pallet, - ::DbWeight, - >, - VersionedMigration< - 19, - 20, - rename_scheduled_tx_for_reject::RenameScheduledTxForReject, - Pallet, - ::DbWeight, - >, - PlaceholderMigration<20, Pallet>, -); +pub type PalletMigration = (PlaceholderMigration<20, Pallet>,); diff --git a/state-chain/pallets/cf-ingress-egress/src/migrations/deposit_channel_details_migration.rs b/state-chain/pallets/cf-ingress-egress/src/migrations/deposit_channel_details_migration.rs deleted file mode 100644 index addd8111079..00000000000 --- a/state-chain/pallets/cf-ingress-egress/src/migrations/deposit_channel_details_migration.rs +++ /dev/null @@ -1,156 +0,0 @@ -use frame_support::traits::UncheckedOnRuntimeUpgrade; - -use crate::{Config, DepositChannelDetails}; -use cf_chains::CcmMessage; - -use crate::*; -use frame_support::pallet_prelude::Weight; -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; - -use codec::{Decode, Encode}; - -pub mod old { - use super::*; - use crate::BoostStatus; - use cf_chains::{ChannelRefundParametersDecoded, DepositChannel, ForeignChainAddress}; - use cf_primitives::Beneficiaries; - use frame_support::{pallet_prelude::OptionQuery, Twox64Concat}; - - const MAX_CCM_MSG_LENGTH: u32 = 10_000; - const MAX_CCM_CF_PARAM_LENGTH: u32 = 1_000; - - type CcmMessage = BoundedVec>; - type CcmCfParameters = BoundedVec>; - - #[derive(PartialEq, Eq, Encode, Decode)] - pub struct DepositChannelDetails, I: 'static> { - pub owner: T::AccountId, - pub deposit_channel: DepositChannel, - pub opened_at: TargetChainBlockNumber, - pub expires_at: TargetChainBlockNumber, - pub action: ChannelAction, - pub boost_fee: BasisPoints, - pub boost_status: BoostStatus>, - } - - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub struct CcmChannelMetadata { - pub message: CcmMessage, - pub gas_budget: AssetAmount, - pub cf_parameters: CcmCfParameters, - } - - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub enum ChannelAction { - Swap { - destination_asset: Asset, - destination_address: ForeignChainAddress, - broker_fees: Beneficiaries, - refund_params: Option, - dca_params: Option, - }, - LiquidityProvision { - lp_account: AccountId, - refund_address: Option, - }, - CcmTransfer { - destination_asset: Asset, - destination_address: ForeignChainAddress, - broker_fees: Beneficiaries, - channel_metadata: CcmChannelMetadata, - refund_params: Option, - dca_params: Option, - }, - } - - #[frame_support::storage_alias] - pub type DepositChannelLookup, I: 'static> = StorageMap< - Pallet, - Twox64Concat, - TargetChainAccount, - DepositChannelDetails, - OptionQuery, - >; -} - -pub struct DepositChannelDetailsMigration, I: 'static = ()>(PhantomData<(T, I)>); - -impl, I: 'static> UncheckedOnRuntimeUpgrade for DepositChannelDetailsMigration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - Ok((old::DepositChannelLookup::::iter_keys().count() as u64).encode()) - } - - fn on_runtime_upgrade() -> Weight { - crate::DepositChannelLookup::::translate_values::, _>( - |old_deposit_channel_details| { - let action = match old_deposit_channel_details.action { - old::ChannelAction::LiquidityProvision { lp_account, refund_address } => - ChannelAction::LiquidityProvision { lp_account, refund_address }, - old::ChannelAction::Swap { - destination_asset, - destination_address, - broker_fees, - refund_params, - dca_params, - } => ChannelAction::Swap { - destination_asset, - destination_address, - broker_fees, - channel_metadata: None, - refund_params, - dca_params, - }, - old::ChannelAction::CcmTransfer { - destination_asset, - destination_address, - broker_fees, - channel_metadata, - refund_params, - dca_params, - } => ChannelAction::Swap { - destination_asset, - destination_address, - broker_fees, - channel_metadata: Some(crate::CcmChannelMetadata { - message: CcmMessage::try_from(channel_metadata.message.into_inner()) - .unwrap_or_default(), - gas_budget: channel_metadata.gas_budget, - ccm_additional_data: crate::CcmAdditionalData::try_from( - channel_metadata.cf_parameters.into_inner(), - ) - .unwrap_or_default(), - }), - refund_params, - dca_params, - }, - }; - - Some(DepositChannelDetails:: { - owner: old_deposit_channel_details.owner, - deposit_channel: old_deposit_channel_details.deposit_channel, - opened_at: old_deposit_channel_details.opened_at, - expires_at: old_deposit_channel_details.expires_at, - action, - boost_fee: old_deposit_channel_details.boost_fee, - boost_status: old_deposit_channel_details.boost_status, - }) - }, - ); - - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), DispatchError> { - let pre_deposit_channel_lookup_count = ::decode(&mut state.as_slice()) - .map_err(|_| DispatchError::from("Failed to decode state"))?; - - let post_deposit_channel_lookup_count = - crate::DepositChannelLookup::::iter().count() as u64; - - assert_eq!(pre_deposit_channel_lookup_count, post_deposit_channel_lookup_count); - Ok(()) - } -} diff --git a/state-chain/pallets/cf-ingress-egress/src/migrations/rename_scheduled_tx_for_reject.rs b/state-chain/pallets/cf-ingress-egress/src/migrations/rename_scheduled_tx_for_reject.rs deleted file mode 100644 index 1bc4eb899a7..00000000000 --- a/state-chain/pallets/cf-ingress-egress/src/migrations/rename_scheduled_tx_for_reject.rs +++ /dev/null @@ -1,72 +0,0 @@ -use frame_support::traits::UncheckedOnRuntimeUpgrade; - -use crate::*; -use frame_support::pallet_prelude::Weight; -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; - -pub mod old { - use super::*; - - #[frame_support::storage_alias] - pub type ScheduledTxForReject, I: 'static> = - StorageValue, Vec>, ValueQuery>; -} - -pub struct RenameScheduledTxForReject, I: 'static = ()>(PhantomData<(T, I)>); - -impl, I: 'static> UncheckedOnRuntimeUpgrade for RenameScheduledTxForReject { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - let count = old::ScheduledTxForReject::::get().len() as u64; - Ok(count.encode()) - } - - fn on_runtime_upgrade() -> Weight { - crate::ScheduledTransactionsForRejection::::put( - old::ScheduledTxForReject::::take(), - ); - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), DispatchError> { - let pre_upgrade_count = ::decode(&mut state.as_slice()) - .map_err(|_| DispatchError::from("Failed to decode state"))?; - - let post_upgrade_count = - crate::ScheduledTransactionsForRejection::::get().len() as u64; - - assert_eq!(pre_upgrade_count, post_upgrade_count); - Ok(()) - } -} - -#[cfg(test)] -mod migration_tests { - use crate::mock_btc::Test; - - use self::mock_btc::new_test_ext; - - use super::*; - - #[test] - fn test_migration() { - new_test_ext().execute_with(|| { - old::ScheduledTxForReject::::put::< - sp_runtime::Vec>, - >(vec![]); - assert_eq!(old::ScheduledTxForReject::::get(), vec![]); - - #[cfg(feature = "try-runtime")] - let state: Vec = RenameScheduledTxForReject::::pre_upgrade().unwrap(); - - RenameScheduledTxForReject::::on_runtime_upgrade(); - - #[cfg(feature = "try-runtime")] - RenameScheduledTxForReject::::post_upgrade(state).unwrap(); - - assert_eq!(ScheduledTransactionsForRejection::::get(), vec![]); - }); - } -} diff --git a/state-chain/pallets/cf-ingress-egress/src/migrations/scheduled_egress_ccm_migration.rs b/state-chain/pallets/cf-ingress-egress/src/migrations/scheduled_egress_ccm_migration.rs deleted file mode 100644 index 23adf746f7f..00000000000 --- a/state-chain/pallets/cf-ingress-egress/src/migrations/scheduled_egress_ccm_migration.rs +++ /dev/null @@ -1,110 +0,0 @@ -use frame_support::traits::UncheckedOnRuntimeUpgrade; - -use crate::{Config, CrossChainMessage}; - -use crate::*; -use frame_support::pallet_prelude::Weight; -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; - -use codec::{Decode, Encode}; - -pub mod old { - use cf_chains::ForeignChainAddress; - - use super::*; - - const MAX_CCM_MSG_LENGTH: u32 = 10_000; - const MAX_CCM_CF_PARAM_LENGTH: u32 = 1_000; - - type CcmMessage = BoundedVec>; - type CcmCfParameters = BoundedVec>; - - #[derive(PartialEq, Eq, Encode, Decode)] - pub struct CrossChainMessage { - pub egress_id: EgressId, - pub asset: C::ChainAsset, - pub amount: C::ChainAmount, - pub destination_address: C::ChainAccount, - pub message: CcmMessage, - pub source_chain: ForeignChain, - pub source_address: Option, - pub cf_parameters: CcmCfParameters, - pub gas_budget: C::ChainAmount, - } - - #[frame_support::storage_alias] - pub type ScheduledEgressCcm, I: 'static> = StorageValue< - Pallet, - Vec>::TargetChain>>, - ValueQuery, - >; -} - -pub struct ScheduledEgressCcmMigration, I: 'static = ()>(PhantomData<(T, I)>); - -impl, I: 'static> UncheckedOnRuntimeUpgrade for ScheduledEgressCcmMigration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - let count = old::ScheduledEgressCcm::::get().len() as u64; - Ok(count.encode()) - } - - fn on_runtime_upgrade() -> Weight { - let _ = crate::ScheduledEgressCcm::::translate::< - Vec>, - _, - >(|old_cross_chain_messages| { - match old_cross_chain_messages { - None => None, - Some(old_cross_chain_messages) => { - let mut new_cross_chain_messages = - Vec::with_capacity(old_cross_chain_messages.len()); - for old_cross_chain_message in old_cross_chain_messages { - let destination_chain: ForeignChain = - (old_cross_chain_message.asset).into(); - new_cross_chain_messages.push(CrossChainMessage { - egress_id: old_cross_chain_message.egress_id, - asset: old_cross_chain_message.asset, - amount: old_cross_chain_message.amount, - destination_address: old_cross_chain_message.destination_address, - message: CcmMessage::try_from( - old_cross_chain_message.message.into_inner(), - ) - .unwrap_or_default(), - source_chain: old_cross_chain_message.source_chain, - source_address: old_cross_chain_message.source_address, - ccm_additional_data: CcmAdditionalData::try_from( - old_cross_chain_message.cf_parameters.into_inner(), - ) - .unwrap_or_default(), - // Using reasonable values for gas budget egress. We can't just - // use the ingress gas budget because it's has a different meaning. - gas_budget: match destination_chain { - cf_chains::ForeignChain::Ethereum => 500_000_u128, - cf_chains::ForeignChain::Arbitrum => 1_500_000_u128, - cf_chains::ForeignChain::Solana => 600_000_u128, - // This should not be possible but no need to error out. - _ => 0_u128, - }, - }); - } - Some(new_cross_chain_messages) - }, - } - }); - - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), DispatchError> { - let pre_scheduled_egress_ccm_count = ::decode(&mut state.as_slice()) - .map_err(|_| DispatchError::from("Failed to decode state"))?; - - let post_scheduled_egress_ccm_count = crate::ScheduledEgressCcm::::get().len() as u64; - - assert_eq!(pre_scheduled_egress_ccm_count, post_scheduled_egress_ccm_count); - Ok(()) - } -} diff --git a/state-chain/pallets/cf-swapping/src/migrations.rs b/state-chain/pallets/cf-swapping/src/migrations.rs index 40cf95076d5..7e5c18a0082 100644 --- a/state-chain/pallets/cf-swapping/src/migrations.rs +++ b/state-chain/pallets/cf-swapping/src/migrations.rs @@ -1,16 +1,4 @@ -use cf_runtime_utilities::PlaceholderMigration; -use frame_support::migrations::VersionedMigration; - use crate::Pallet; -pub mod swap_and_swap_request_migration; +use cf_runtime_utilities::PlaceholderMigration; -pub type PalletMigration = ( - VersionedMigration< - 6, - 7, - swap_and_swap_request_migration::Migration, - Pallet, - ::DbWeight, - >, - PlaceholderMigration<7, Pallet>, -); +pub type PalletMigration = (PlaceholderMigration<7, Pallet>,); diff --git a/state-chain/pallets/cf-swapping/src/migrations/swap_and_swap_request_migration.rs b/state-chain/pallets/cf-swapping/src/migrations/swap_and_swap_request_migration.rs deleted file mode 100644 index eaba2afb866..00000000000 --- a/state-chain/pallets/cf-swapping/src/migrations/swap_and_swap_request_migration.rs +++ /dev/null @@ -1,208 +0,0 @@ -use frame_support::traits::UncheckedOnRuntimeUpgrade; - -use cf_chains::{CcmAdditionalData, CcmMessage}; - -use crate::Config; - -use crate::*; -use frame_support::pallet_prelude::Weight; -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; - -use codec::{Decode, Encode}; - -pub mod old { - use super::*; - use cf_chains::{ChannelRefundParametersDecoded, ForeignChainAddress}; - use cf_primitives::{Asset, AssetAmount, Beneficiaries, SwapId}; - use frame_support::Twox64Concat; - - const MAX_CCM_MSG_LENGTH: u32 = 10_000; - const MAX_CCM_CF_PARAM_LENGTH: u32 = 1_000; - - type CcmMessage = BoundedVec>; - type CcmCfParameters = BoundedVec>; - - #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen)] - pub struct CcmChannelMetadata { - pub message: CcmMessage, - pub gas_budget: AssetAmount, - pub cf_parameters: CcmCfParameters, - } - - #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] - pub struct CcmDepositMetadataGeneric
{ - pub channel_metadata: CcmChannelMetadata, - pub source_chain: ForeignChain, - pub source_address: Option
, - } - - pub type CcmDepositMetadata = CcmDepositMetadataGeneric; - - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub enum GasSwapState { - OutputReady { gas_budget: AssetAmount }, - Scheduled { gas_swap_id: SwapId }, - ToBeScheduled { gas_budget: AssetAmount, other_gas_asset: Asset }, - } - - #[allow(clippy::large_enum_variant)] - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub struct CcmState { - pub gas_swap_state: GasSwapState, - pub ccm_deposit_metadata: CcmDepositMetadata, - } - - #[allow(clippy::large_enum_variant)] - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub enum SwapRequestState { - UserSwap { - ccm: Option, - output_address: ForeignChainAddress, - dca_state: DcaState, - broker_fees: Beneficiaries, - }, - NetworkFee, - IngressEgressFee, - } - - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub struct SwapRequest { - pub id: SwapRequestId, - pub input_asset: Asset, - pub output_asset: Asset, - pub refund_params: Option, - pub state: SwapRequestState, - } - - #[frame_support::storage_alias] - pub type SwapRequests = - StorageMap, Twox64Concat, SwapRequestId, SwapRequest>; - - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub enum FeeType { - NetworkFee, - BrokerFee(Beneficiaries), - } - - #[derive(Clone, PartialEq, Eq, Encode, Decode)] - pub struct Swap { - pub swap_id: SwapId, - pub swap_request_id: SwapRequestId, - pub from: Asset, - pub to: Asset, - pub input_amount: AssetAmount, - pub fees: Vec>, - pub refund_params: Option, - } - - #[frame_support::storage_alias] - pub type SwapQueue = - StorageMap, Twox64Concat, BlockNumberFor, Vec>, ValueQuery>; -} - -pub struct Migration(PhantomData); - -impl UncheckedOnRuntimeUpgrade for Migration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - let swap_request_count = old::SwapRequests::::iter().count() as u64; - let scheduled_swaps_count: u64 = - old::SwapQueue::::iter().map(|(_, swaps)| swaps.len() as u64).sum(); - Ok((swap_request_count, scheduled_swaps_count).encode()) - } - - fn on_runtime_upgrade() -> Weight { - crate::SwapRequests::::translate_values::, _>(|old_swap_requests| { - Some(SwapRequest { - id: old_swap_requests.id, - input_asset: old_swap_requests.input_asset, - output_asset: old_swap_requests.output_asset, - refund_params: old_swap_requests.refund_params, - state: match old_swap_requests.state { - old::SwapRequestState::UserSwap { - ccm, - output_address, - dca_state, - broker_fees, - } => SwapRequestState::UserSwap { - ccm_deposit_metadata: ccm.map(|old_ccm_state| CcmDepositMetadata { - channel_metadata: CcmChannelMetadata { - message: CcmMessage::try_from( - old_ccm_state - .ccm_deposit_metadata - .channel_metadata - .message - .into_inner(), - ) - .unwrap_or_default(), - gas_budget: old_ccm_state - .ccm_deposit_metadata - .channel_metadata - .gas_budget, - ccm_additional_data: CcmAdditionalData::try_from( - old_ccm_state - .ccm_deposit_metadata - .channel_metadata - .cf_parameters - .into_inner(), - ) - .unwrap_or_default(), - }, - source_chain: old_ccm_state.ccm_deposit_metadata.source_chain, - source_address: old_ccm_state.ccm_deposit_metadata.source_address, - }), - output_address, - dca_state, - broker_fees, - }, - old::SwapRequestState::NetworkFee => SwapRequestState::NetworkFee, - old::SwapRequestState::IngressEgressFee => SwapRequestState::IngressEgressFee, - }, - }) - }); - - crate::SwapQueue::::translate_values::>, _>(|old_swaps| { - Some( - old_swaps - .into_iter() - .map(|swap| Swap { - swap_id: swap.swap_id, - swap_request_id: swap.swap_request_id, - from: swap.from, - to: swap.to, - input_amount: swap.input_amount, - fees: swap - .fees - .into_iter() - .map(|fee| match fee { - old::FeeType::NetworkFee => - FeeType::NetworkFee { min_fee_enforced: false }, - old::FeeType::BrokerFee(beneficiaries) => - FeeType::BrokerFee(beneficiaries), - }) - .collect(), - refund_params: swap.refund_params, - }) - .collect(), - ) - }); - - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), DispatchError> { - let (pre_swap_request_count, pre_scheduled_swap_count) = - <(u64, u64)>::decode(&mut state.as_slice()) - .map_err(|_| DispatchError::from("Failed to decode state"))?; - - let post_swap_request_count = crate::SwapRequests::::iter().count() as u64; - let post_scheduled_swaps_count: u64 = - SwapQueue::::iter().map(|(_, swaps)| swaps.len() as u64).sum(); - - assert_eq!(pre_swap_request_count, post_swap_request_count); - assert_eq!(pre_scheduled_swap_count, post_scheduled_swaps_count); - Ok(()) - } -} diff --git a/state-chain/pallets/cf-validator/src/migrations.rs b/state-chain/pallets/cf-validator/src/migrations.rs index 6198e5a7cd7..48054595815 100644 --- a/state-chain/pallets/cf-validator/src/migrations.rs +++ b/state-chain/pallets/cf-validator/src/migrations.rs @@ -1,16 +1,3 @@ use crate::Pallet; use cf_runtime_utilities::PlaceholderMigration; -use frame_support::migrations::VersionedMigration; - -mod rename_blocks_per_epoch; - -pub type PalletMigration = ( - VersionedMigration< - 4, - 5, - rename_blocks_per_epoch::BlocksPerEpochMigration, - Pallet, - ::DbWeight, - >, - PlaceholderMigration<5, Pallet>, -); +pub type PalletMigration = (PlaceholderMigration<5, Pallet>,); diff --git a/state-chain/pallets/cf-validator/src/migrations/rename_blocks_per_epoch.rs b/state-chain/pallets/cf-validator/src/migrations/rename_blocks_per_epoch.rs deleted file mode 100644 index 0075f8e3f51..00000000000 --- a/state-chain/pallets/cf-validator/src/migrations/rename_blocks_per_epoch.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::*; -use frame_support::{pallet_prelude::Weight, traits::UncheckedOnRuntimeUpgrade}; - -#[cfg(feature = "try-runtime")] -use codec::{Decode, Encode}; -#[cfg(feature = "try-runtime")] -use frame_support::sp_runtime::DispatchError; - -pub mod old { - use super::*; - - #[frame_support::storage_alias] - pub type BlocksPerEpoch = StorageValue, BlockNumberFor, ValueQuery>; -} - -pub struct BlocksPerEpochMigration(sp_std::marker::PhantomData); - -// Rename BlocksPerEpoch -> EpochDuration -impl UncheckedOnRuntimeUpgrade for BlocksPerEpochMigration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - Ok(old::BlocksPerEpoch::::get().encode()) - } - - fn on_runtime_upgrade() -> Weight { - EpochDuration::::put(old::BlocksPerEpoch::::take()); - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), DispatchError> { - assert_eq!( - EpochDuration::::get(), - BlockNumberFor::::decode(&mut &state[..]).unwrap() - ); - assert!(!old::BlocksPerEpoch::::exists()); - Ok(()) - } -} - -#[cfg(test)] -mod migration_tests { - use crate::mock::Test; - - use self::mock::new_test_ext; - - use super::*; - - #[test] - fn test_migration() { - new_test_ext().execute_with(|| { - old::BlocksPerEpoch::::put(100); - assert_ne!(EpochDuration::::get(), 100); - - #[cfg(feature = "try-runtime")] - let state: Vec = BlocksPerEpochMigration::::pre_upgrade().unwrap(); - - BlocksPerEpochMigration::::on_runtime_upgrade(); - - #[cfg(feature = "try-runtime")] - BlocksPerEpochMigration::::post_upgrade(state).unwrap(); - - assert_eq!(EpochDuration::::get(), 100); - }); - } -} diff --git a/state-chain/runtime/Cargo.toml b/state-chain/runtime/Cargo.toml index c8d33b7757e..5c5c9268dfb 100644 --- a/state-chain/runtime/Cargo.toml +++ b/state-chain/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "state-chain-runtime" -version = "1.8.0" +version = "1.9.0" authors = ["Chainflip Team "] edition = "2021" homepage = "https://chainflip.io" diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index bbf378032cf..dcf74bd11fc 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -19,11 +19,10 @@ use crate::{ }, Offence, }, - migrations::solana_transaction_data_migration::NoopUpgrade, monitoring_apis::{ ActivateKeysBroadcastIds, AuthoritiesInfo, BtcUtxos, EpochState, ExternalChainsBlockHeight, - FeeImbalance, FlipSupply, LastRuntimeUpgradeInfo, MonitoringDataV2, OpenDepositChannels, - PendingBroadcasts, PendingTssCeremonies, RedemptionsInfo, SolanaNonces, + FeeImbalance, FlipSupply, LastRuntimeUpgradeInfo, OpenDepositChannels, PendingBroadcasts, + PendingTssCeremonies, RedemptionsInfo, SolanaNonces, }, runtime_apis::{ runtime_decl_for_custom_runtime_api::CustomRuntimeApi, AuctionState, BoostPoolDepth, @@ -69,8 +68,9 @@ use cf_traits::{ }; use codec::{alloc::string::ToString, Decode, Encode}; use core::ops::Range; -use frame_support::{derive_impl, instances::*, migrations::VersionedMigration}; +use frame_support::{derive_impl, instances::*}; pub use frame_system::Call as SystemCall; +use monitoring_apis::MonitoringDataV2; use pallet_cf_governance::GovCallHash; use pallet_cf_ingress_egress::{ ChannelAction, DepositWitness, IngressOrEgress, OwedAmount, TargetChainAsset, @@ -212,7 +212,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("chainflip-node"), impl_name: create_runtime_str!("chainflip-node"), authoring_version: 1, - spec_version: 180, + spec_version: 190, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 13, @@ -1240,6 +1240,8 @@ pub type PalletExecutionOrder = ( /// - The VersionUpdate migration. Don't remove this. /// - Individual pallet migrations. Don't remove these unless there's a good reason. Prefer to /// disable these at the pallet level (ie. set it to () or PhantomData). +/// - Housekeeping migrations. Don't remove this - check individual housekeeping migrations and +/// remove them when they are no longer needed. /// - Release-specific migrations: remove these if they are no longer needed. type AllMigrations = ( // This ClearEvents should only be run at the start of all migrations. This is in case another @@ -1249,11 +1251,8 @@ type AllMigrations = ( // UPGRADE pallet_cf_environment::migrations::VersionUpdate, PalletMigrations, - MigrationsForV1_8, migrations::housekeeping::Migration, - // Can be removed once Solana address re-use is activated. - migrations::solana_remove_unused_channels_state::SolanaRemoveUnusedChannelsState, - migrations::reap_old_accounts::Migration, + MigrationsForV1_9, ); /// All the pallet-specific migrations and migrations that depend on pallet migration order. Do not @@ -1296,6 +1295,7 @@ type PalletMigrations = ( pallet_cf_cfe_interface::migrations::PalletMigration, ); +#[allow(unused)] macro_rules! instanced_migrations { ( module: $module:ident, @@ -1319,7 +1319,7 @@ macro_rules! instanced_migrations { VersionedMigration< $from, $to, - NoopUpgrade, + (), $module::Pallet, DbWeight, >, @@ -1328,71 +1328,7 @@ macro_rules! instanced_migrations { } } -type MigrationsForV1_8 = ( - VersionedMigration< - 2, - 3, - migrations::solana_vault_swaps_migration::SolanaVaultSwapsMigration, - pallet_cf_elections::Pallet, - DbWeight, - >, - // Only the Solana Transaction type has changed - instanced_migrations! { - module: pallet_cf_broadcast, - migration: migrations::solana_transaction_data_migration::SolanaTransactionDataMigration, - from: 10, - to: 11, - include_instances: [ - SolanaInstance - ], - exclude_instances: [ - EthereumInstance, - PolkadotInstance, - BitcoinInstance, - ArbitrumInstance, - ], - }, - instanced_migrations! { - module: pallet_cf_chain_tracking, - migration: migrations::arbitrum_chain_tracking_migration::ArbitrumChainTrackingMigration, - from: 3, - to: 4, - include_instances: [ - ArbitrumInstance - ], - exclude_instances: [ - EthereumInstance, - PolkadotInstance, - BitcoinInstance, - SolanaInstance, - ], - }, - instanced_migrations! { - module: pallet_cf_broadcast, - migration: migrations::api_calls_gas_migration::EthApiCallsGasMigration, - from: 11, - to: 12, - include_instances: [ - EthereumInstance, - ArbitrumInstance - ], - exclude_instances: [ - PolkadotInstance, - BitcoinInstance, - SolanaInstance, - ], - }, - instanced_migrations! { - module: pallet_cf_elections, - migration: migrations::remove_fee_tracking_migration::RemoveFeeTrackingMigration, - from: 3, - to: 4, - include_instances: [ - SolanaInstance - ], - exclude_instances: [], - }, -); +type MigrationsForV1_9 = (); #[cfg(feature = "runtime-benchmarks")] #[macro_use] @@ -2631,7 +2567,7 @@ impl_runtime_apis! { SolanaBroadcaster::current_on_chain_key().unwrap_or_default() } fn cf_monitoring_data() -> MonitoringDataV2 { - MonitoringDataV2{ + MonitoringDataV2 { external_chains_height: Self::cf_external_chains_block_height(), btc_utxos: Self::cf_btc_utxos(), epoch: Self::cf_epoch_state(), diff --git a/state-chain/runtime/src/migrations.rs b/state-chain/runtime/src/migrations.rs index 2977b3c58cd..c79a5429760 100644 --- a/state-chain/runtime/src/migrations.rs +++ b/state-chain/runtime/src/migrations.rs @@ -1,10 +1,3 @@ //! Chainflip runtime storage migrations. -pub mod api_calls_gas_migration; -pub mod arbitrum_chain_tracking_migration; pub mod housekeeping; -pub mod reap_old_accounts; -pub mod remove_fee_tracking_migration; -pub mod solana_remove_unused_channels_state; -pub mod solana_transaction_data_migration; -pub mod solana_vault_swaps_migration; diff --git a/state-chain/runtime/src/migrations/api_calls_gas_migration.rs b/state-chain/runtime/src/migrations/api_calls_gas_migration.rs deleted file mode 100644 index 30aa951a3eb..00000000000 --- a/state-chain/runtime/src/migrations/api_calls_gas_migration.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::*; -use cf_chains::{ - eth::api::{register_redemption, update_flip_supply}, - evm::api::{ - all_batch, common::EncodableTransferAssetParams, execute_x_swap_and_call, - set_agg_key_with_agg_key, set_comm_key_with_agg_key, set_gov_key_with_agg_key, - transfer_fallback, EvmTransactionBuilder, - }, -}; -use frame_support::traits::UncheckedOnRuntimeUpgrade; -use sp_std::marker::PhantomData; - -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; -#[cfg(feature = "try-runtime")] -use sp_std::{vec, vec::Vec}; - -pub mod old { - - use super::*; - use cf_chains::{evm::api::EvmTransactionBuilder, Chain}; - use codec::{Decode, Encode}; - use frame_support::{CloneNoBound, DebugNoBound, EqNoBound, Never, PartialEqNoBound}; - use scale_info::TypeInfo; - use sp_core::RuntimeDebug; - - #[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug, PartialEq, Eq)] - pub struct ExecutexSwapAndCall { - pub transfer_param: EncodableTransferAssetParams, - pub source_chain: u32, - pub source_address: Vec, - pub gas_budget: ::ChainAmount, - pub message: Vec, - } - - #[derive(CloneNoBound, DebugNoBound, PartialEqNoBound, EqNoBound, Encode, Decode, TypeInfo)] - #[scale_info(skip_type_params(Environment))] - pub enum EthereumApi { - SetAggKeyWithAggKey(EvmTransactionBuilder), - RegisterRedemption(EvmTransactionBuilder), - UpdateFlipSupply(EvmTransactionBuilder), - SetGovKeyWithAggKey(EvmTransactionBuilder), - SetCommKeyWithAggKey( - EvmTransactionBuilder, - ), - AllBatch(EvmTransactionBuilder), - ExecutexSwapAndCall(EvmTransactionBuilder), - TransferFallback(EvmTransactionBuilder), - #[doc(hidden)] - #[codec(skip)] - _Phantom(PhantomData, Never), - } - - #[derive(CloneNoBound, DebugNoBound, PartialEqNoBound, EqNoBound, Encode, Decode, TypeInfo)] - #[scale_info(skip_type_params(Environment))] - pub enum ArbitrumApi { - SetAggKeyWithAggKey(EvmTransactionBuilder), - AllBatch(EvmTransactionBuilder), - ExecutexSwapAndCall(EvmTransactionBuilder), - TransferFallback(EvmTransactionBuilder), - #[doc(hidden)] - #[codec(skip)] - _Phantom(PhantomData, Never), - } -} - -fn evm_tx_builder_fn(evm_tx_builder: EvmTransactionBuilder) -> EvmTransactionBuilder { - EvmTransactionBuilder { - signer_and_sig_data: evm_tx_builder.signer_and_sig_data, - replay_protection: evm_tx_builder.replay_protection, - call: evm_tx_builder.call, - } -} - -fn evm_tx_builder_execute_x_swap( - evm_tx_builder: EvmTransactionBuilder, - is_arbitrum: bool, -) -> EvmTransactionBuilder { - EvmTransactionBuilder { - signer_and_sig_data: evm_tx_builder.signer_and_sig_data, - replay_protection: evm_tx_builder.replay_protection, - call: execute_x_swap_and_call::ExecutexSwapAndCall { - transfer_param: evm_tx_builder.call.transfer_param, - source_chain: evm_tx_builder.call.source_chain, - source_address: evm_tx_builder.call.source_address, - // Setting a reasonable default for gas budget - gas_budget: match is_arbitrum { - true => 1_500_000_u128, - false => 300_000_u128, - }, - message: evm_tx_builder.call.message, - }, - } -} - -pub struct EthApiCallsGasMigration; - -impl UncheckedOnRuntimeUpgrade for EthApiCallsGasMigration { - fn on_runtime_upgrade() -> frame_support::weights::Weight { - pallet_cf_broadcast::PendingApiCalls::::translate( - |_broadcast_id, old_apicall: old::EthereumApi| { - Some(match old_apicall { - old::EthereumApi::SetAggKeyWithAggKey(evm_tx_builder) => - EthereumApi::SetAggKeyWithAggKey(evm_tx_builder_fn(evm_tx_builder)), - old::EthereumApi::RegisterRedemption(evm_tx_builder) => - EthereumApi::RegisterRedemption(evm_tx_builder_fn(evm_tx_builder)), - old::EthereumApi::UpdateFlipSupply(evm_tx_builder) => - EthereumApi::UpdateFlipSupply(evm_tx_builder_fn(evm_tx_builder)), - old::EthereumApi::SetGovKeyWithAggKey(evm_tx_builder) => - EthereumApi::SetGovKeyWithAggKey(evm_tx_builder_fn(evm_tx_builder)), - old::EthereumApi::SetCommKeyWithAggKey(evm_tx_builder) => - EthereumApi::SetCommKeyWithAggKey(evm_tx_builder_fn(evm_tx_builder)), - old::EthereumApi::AllBatch(evm_tx_builder) => - EthereumApi::AllBatch(evm_tx_builder_fn(evm_tx_builder)), - old::EthereumApi::ExecutexSwapAndCall(evm_tx_builder) => - EthereumApi::ExecutexSwapAndCall(evm_tx_builder_execute_x_swap( - evm_tx_builder, - false, - )), - old::EthereumApi::TransferFallback(evm_tx_builder) => - EthereumApi::TransferFallback(evm_tx_builder_fn(evm_tx_builder)), - old::EthereumApi::_Phantom(..) => unreachable!(), - }) - }, - ); - - Weight::zero() - } -} - -pub struct ArbApiCallsGasMigration; - -impl UncheckedOnRuntimeUpgrade for ArbApiCallsGasMigration { - fn on_runtime_upgrade() -> frame_support::weights::Weight { - pallet_cf_broadcast::PendingApiCalls::::translate( - |_broadcast_id, old_apicall: old::ArbitrumApi| { - Some(match old_apicall { - old::ArbitrumApi::SetAggKeyWithAggKey(evm_tx_builder) => - ArbitrumApi::SetAggKeyWithAggKey(evm_tx_builder_fn(evm_tx_builder)), - old::ArbitrumApi::AllBatch(evm_tx_builder) => - ArbitrumApi::AllBatch(evm_tx_builder_fn(evm_tx_builder)), - old::ArbitrumApi::ExecutexSwapAndCall(evm_tx_builder) => - ArbitrumApi::ExecutexSwapAndCall(evm_tx_builder_execute_x_swap( - evm_tx_builder, - true, - )), - old::ArbitrumApi::TransferFallback(evm_tx_builder) => - ArbitrumApi::TransferFallback(evm_tx_builder_fn(evm_tx_builder)), - old::ArbitrumApi::_Phantom(..) => unreachable!(), - }) - }, - ); - - Weight::zero() - } -} - -pub struct NoOpMigration; - -impl UncheckedOnRuntimeUpgrade for NoOpMigration { - fn on_runtime_upgrade() -> frame_support::weights::Weight { - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - Ok(vec![]) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), DispatchError> { - Ok(()) - } -} diff --git a/state-chain/runtime/src/migrations/arbitrum_chain_tracking_migration.rs b/state-chain/runtime/src/migrations/arbitrum_chain_tracking_migration.rs deleted file mode 100644 index b65da0afc91..00000000000 --- a/state-chain/runtime/src/migrations/arbitrum_chain_tracking_migration.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::Runtime; -use cf_chains::{ - arb::ArbitrumTrackedData, instances::ArbitrumInstance, Arbitrum, Chain, ChainState, -}; -use frame_support::{traits::UncheckedOnRuntimeUpgrade, weights::Weight}; -use serde::{Deserialize, Serialize}; -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; -#[cfg(feature = "try-runtime")] -use sp_std::prelude::*; - -// Define the old data structure in a temporary module -mod old { - use super::*; - use frame_support::{pallet_prelude::*, sp_runtime::FixedU64}; - - #[derive( - Copy, - Clone, - RuntimeDebug, - PartialEq, - Eq, - Encode, - Decode, - MaxEncodedLen, - TypeInfo, - Serialize, - Deserialize, - )] - #[codec(mel_bound())] - pub struct ArbitrumTrackedData { - pub base_fee: ::ChainAmount, - pub gas_limit_multiplier: FixedU64, - } - - #[derive( - PartialEqNoBound, - EqNoBound, - CloneNoBound, - Encode, - Decode, - TypeInfo, - MaxEncodedLen, - DebugNoBound, - Serialize, - Deserialize, - )] - pub struct ChainState { - pub block_height: ::ChainBlockNumber, - pub tracked_data: ArbitrumTrackedData, - } - - #[frame_support::storage_alias] - pub type CurrentChainState> = - StorageValue, ChainState>; -} - -pub struct ArbitrumChainTrackingMigration; - -impl UncheckedOnRuntimeUpgrade for ArbitrumChainTrackingMigration { - fn on_runtime_upgrade() -> frame_support::weights::Weight { - use frame_support::assert_ok; - - // Perform the state translation from the old to the new format - assert_ok!( - pallet_cf_chain_tracking::CurrentChainState::::translate::< - old::ChainState, - _, - >(|maybe_old_state| { - let (block_height, base_fee) = maybe_old_state - .map(|state| (state.block_height, state.tracked_data.base_fee)) - .unwrap_or((Default::default(), Default::default())); - Some(ChainState { - block_height, - tracked_data: ArbitrumTrackedData { base_fee, l1_base_fee_estimate: 1_u128 }, - }) - }) - ); - - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - // Pre-checks to ensure the migration is needed - if old::CurrentChainState::::get().is_none() { - return Err(DispatchError::Other( - "CurrentChainState for ArbitrumInstance does not exist", - )); - } - Ok(vec![]) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), DispatchError> { - // Post-checks to verify the migration was successful - let new_state = - pallet_cf_chain_tracking::CurrentChainState::::get().ok_or( - { - DispatchError::Other( - "CurrentChainState for ArbitrumInstance is missing after migration", - ) - }, - )?; - assert_eq!(new_state.tracked_data.l1_base_fee_estimate, 1_u128); - - Ok(()) - } -} - -pub struct NoOpMigration; -impl UncheckedOnRuntimeUpgrade for NoOpMigration { - fn on_runtime_upgrade() -> Weight { - Default::default() - } -} diff --git a/state-chain/runtime/src/migrations/housekeeping.rs b/state-chain/runtime/src/migrations/housekeeping.rs index 9802fbf1257..97860cd1b36 100644 --- a/state-chain/runtime/src/migrations/housekeeping.rs +++ b/state-chain/runtime/src/migrations/housekeeping.rs @@ -6,9 +6,19 @@ use sp_runtime::DispatchError; #[cfg(feature = "try-runtime")] use sp_std::vec::Vec; -pub struct Migration; +pub mod reap_old_accounts; +pub mod solana_remove_unused_channels_state; -impl OnRuntimeUpgrade for Migration { +pub type Migration = ( + NetworkSpecificHousekeeping, + reap_old_accounts::Migration, + // Can be removed once Solana address re-use is activated. + solana_remove_unused_channels_state::SolanaRemoveUnusedChannelsState, +); + +pub struct NetworkSpecificHousekeeping; + +impl OnRuntimeUpgrade for NetworkSpecificHousekeeping { fn on_runtime_upgrade() -> Weight { match genesis_hashes::genesis_hash::() { genesis_hashes::BERGHAIN => { diff --git a/state-chain/runtime/src/migrations/reap_old_accounts.rs b/state-chain/runtime/src/migrations/housekeeping/reap_old_accounts.rs similarity index 100% rename from state-chain/runtime/src/migrations/reap_old_accounts.rs rename to state-chain/runtime/src/migrations/housekeeping/reap_old_accounts.rs diff --git a/state-chain/runtime/src/migrations/solana_remove_unused_channels_state.rs b/state-chain/runtime/src/migrations/housekeeping/solana_remove_unused_channels_state.rs similarity index 100% rename from state-chain/runtime/src/migrations/solana_remove_unused_channels_state.rs rename to state-chain/runtime/src/migrations/housekeeping/solana_remove_unused_channels_state.rs diff --git a/state-chain/runtime/src/migrations/remove_fee_tracking_migration.rs b/state-chain/runtime/src/migrations/remove_fee_tracking_migration.rs deleted file mode 100644 index 76a9b6a2aab..00000000000 --- a/state-chain/runtime/src/migrations/remove_fee_tracking_migration.rs +++ /dev/null @@ -1,416 +0,0 @@ -use crate::*; -use frame_support::{pallet_prelude::Weight, storage::unhashed, traits::UncheckedOnRuntimeUpgrade}; - -use pallet_cf_elections::{ - self, BitmapComponents, ElectionConsensusHistory, ElectionConsensusHistoryUpToDate, - ElectionProperties, ElectionState, ElectoralSettings, ElectoralSystemTypes, - ElectoralUnsynchronisedState, ElectoralUnsynchronisedStateMap, IndividualComponents, - SharedData, SharedDataReferenceCount, -}; - -use chainflip::solana_elections::SolanaElectoralSystemRunner; - -use pallet_cf_elections::UniqueMonotonicIdentifier; - -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; - -mod old { - use super::*; - use cf_chains::sol::SolAmount; - use chainflip::solana_elections::{ - SolanaBlockHeightTracking, SolanaEgressWitnessing, SolanaIngressTracking, SolanaLiveness, - SolanaNonceTracking, SolanaVaultSwapTracking, - }; - use frame_support::{pallet_prelude::OptionQuery, Twox64Concat}; - use pallet_cf_elections::{ - electoral_systems::composite::tuple_6_impls, ConsensusHistory, ElectionIdentifier, - }; - use sp_runtime::FixedU128; - - #[derive(Debug, PartialEq, Eq, Encode, Decode, Clone)] - pub struct SolanaFeeUnsynchronisedSettings { - fee_multiplier: FixedU128, - } - - #[frame_support::storage_alias] - pub type ElectoralUnsynchronisedSettings = StorageValue< - SolanaElections, - ((), SolanaFeeUnsynchronisedSettings, (), (), (), (), ()), - OptionQuery, - >; - - #[frame_support::storage_alias] - pub type ElectoralUnsynchronisedState = StorageValue< - SolanaElections, - ( - ::ElectoralUnsynchronisedState, - SolAmount, - (), - ::ElectoralUnsynchronisedState, - ::ElectoralUnsynchronisedState, - ::ElectoralUnsynchronisedState, - ::ElectoralUnsynchronisedState, - ), - OptionQuery, - >; - - #[frame_support::storage_alias] - pub type ElectoralSettings = StorageMap< - SolanaElections, - Twox64Concat, - UniqueMonotonicIdentifier, - ( - ::ElectoralSettings, - (), - ::ElectoralSettings, - ::ElectoralSettings, - ::ElectoralSettings, - ::ElectoralSettings, - ::ElectoralSettings, - ), - OptionQuery, - >; - - #[frame_support::storage_alias] - pub type ElectionProperties = StorageMap< - SolanaElections, - Twox64Concat, - // old election identifier - ElectionIdentifier, - CompositeElectionProperties, - OptionQuery, - >; - - #[frame_support::storage_alias] - pub type ElectionState = StorageMap< - SolanaElections, - Twox64Concat, - UniqueMonotonicIdentifier, - CompositeElectionState, - OptionQuery, - >; - - #[frame_support::storage_alias] - pub type ElectoralUnsynchronisedStateMap = StorageMap< - SolanaElections, - Twox64Concat, - CompositeElectoralUnsynchronisedStateMapKey, - CompositeElectoralUnsynchronisedStateMapValue, - OptionQuery, - >; - - #[frame_support::storage_alias] - pub type ElectionConsensusHistory = StorageMap< - SolanaElections, - Twox64Concat, - UniqueMonotonicIdentifier, - ConsensusHistory, - OptionQuery, - >; - - macro_rules! define_composite_enum { - ($name:ident, $type:ident, $BType:ty) => { - #[derive(Debug, PartialEq, Eq, Encode, Decode, Clone)] - pub enum $name { - A(::$type), - // B was fee tracking - B($BType), - C(::$type), - D(::$type), - EE(::$type), - FF(::$type), - GG(::$type), - } - - paste::paste! { - #[allow(non_snake_case)] - pub fn []( - enum_name: $name, - ) -> Option<::$type> { - match enum_name { - $name::A(a) => Some(tuple_6_impls::$name::A(a)), - $name::B(_b) => None, - $name::C(c) => Some(tuple_6_impls::$name::B(c)), - $name::D(d) => Some(tuple_6_impls::$name::C(d)), - $name::EE(ee) => Some(tuple_6_impls::$name::D(ee)), - $name::FF(ff) => Some(tuple_6_impls::$name::EE(ff)), - $name::GG(gg) => Some(tuple_6_impls::$name::FF(gg)), - } - } - } - }; - } - - define_composite_enum!(CompositeElectionState, ElectionState, ()); - define_composite_enum!(CompositeElectionProperties, ElectionProperties, ()); - define_composite_enum!( - CompositeElectoralUnsynchronisedStateMapKey, - ElectoralUnsynchronisedStateMapKey, - () - ); - define_composite_enum!( - CompositeElectoralUnsynchronisedStateMapValue, - ElectoralUnsynchronisedStateMapValue, - () - ); - define_composite_enum!(CompositeElectionIdentifierExtra, ElectionIdentifierExtra, ()); - define_composite_enum!(CompositeConsensus, Consensus, SolAmount); - - pub fn unwrap_composite_consensus_history( - consensus_history: ConsensusHistory, - ) -> Option::Consensus>> - { - old::translate_composite_enum_CompositeConsensus(consensus_history.most_recent).map( - |consensus| ConsensusHistory { - most_recent: consensus, - lost_since: consensus_history.lost_since, - }, - ) - } -} - -pub struct RemoveFeeTrackingMigration; - -impl UncheckedOnRuntimeUpgrade for RemoveFeeTrackingMigration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - let pre_upgrade_properties_len = old::ElectionProperties::iter().count() as u32; - - let pre_upgrade_election_state_len = old::ElectionState::iter().count() as u32; - - let pre_upgrade_map_len = old::ElectoralUnsynchronisedStateMap::iter().count() as u32; - - let mut pre_upgrade_data = Vec::new(); - pre_upgrade_data.extend(pre_upgrade_properties_len.encode()); - pre_upgrade_data.extend(pre_upgrade_election_state_len.encode()); - pre_upgrade_data.extend(pre_upgrade_map_len.encode()); - - Ok(pre_upgrade_data) - } - - fn on_runtime_upgrade() -> Weight { - log::info!("Starting remove fee tracking migration"); - - // Only fee tracking had a value, so the rest can be deleted, and will default to () - old::ElectoralUnsynchronisedSettings::take(); - - log::info!("Cleared electoral unsynchronised settings"); - - // ElectoralUnsynchronisedState - if let Some(state) = old::ElectoralUnsynchronisedState::take() { - let (a, _deleted_b_fee, (), (), (), (), gg) = state; - log::info!("Migrating electoral unsynchronised state {:?}", state); - - ElectoralUnsynchronisedState::::put((a, (), (), (), (), gg)); - log::info!("Inserted new electoral unsynchronised state"); - } - - // ElectoralUnsynchronisedStateMap - let mut new_state_map = Vec::new(); - for (key, value) in old::ElectoralUnsynchronisedStateMap::iter() { - if let (Some(key), Some(value)) = ( - old::translate_composite_enum_CompositeElectoralUnsynchronisedStateMapKey(key), - old::translate_composite_enum_CompositeElectoralUnsynchronisedStateMapValue(value), - ) { - new_state_map.push((key, value)) - } - } - log::info!("Iterated over old state map"); - - if !new_state_map.is_empty() { - let _ = old::ElectoralUnsynchronisedStateMap::clear(u32::MAX, None); - log::info!("Adding new state map with {:?} items", new_state_map.len()); - for (key, value) in new_state_map { - ElectoralUnsynchronisedStateMap::::insert(key, value); - } - } - - // ElectoralSettings - let open_election_identifiers = old::ElectoralSettings::iter_keys(); - let mut new_settings = Vec::new(); - log::info!("Iterating over old settings"); - for electoral_settings_id in open_election_identifiers { - if let Some((a, _b_deleted, c, d, ee, ff, gg)) = - old::ElectoralSettings::get(electoral_settings_id) - { - new_settings.push((electoral_settings_id, (a, c, d, ee, ff, gg))); - } - } - log::info!("Iterated over old settings"); - - if !new_settings.is_empty() { - log::info!("Adding new settings with {:?} items", new_settings.len()); - let _ = old::ElectoralSettings::clear(u32::MAX, None); - log::info!("Cleared old electoral settings"); - for (electoral_settings_id, settings) in new_settings { - ElectoralSettings::::insert( - electoral_settings_id, - settings, - ); - } - } - - // Properties - let election_properties = old::ElectionProperties::iter().collect::>(); - let mut new_election_properties = Vec::new(); - - log::info!("Old election properties: {:?}", election_properties); - - for (election_identifier, _props) in election_properties { - let key = old::ElectionProperties::hashed_key_for(election_identifier.clone()); - - log::info!("Migrating election properties {:?}", key); - - let raw_storage_at_key = - unhashed::get_raw(&key).expect("We just got the keys directly from the storage"); - - let props = - old::CompositeElectionProperties::decode(&mut &raw_storage_at_key[..]).unwrap(); - - log::info!("Migrating election properties, decoded {:?}", props); - - if let Some(props) = old::translate_composite_enum_CompositeElectionProperties(props) { - let old_extra = election_identifier.extra(); - log::info!("Migrating election properties, old extra {:?}", old_extra); - if let Some(new_extra) = - old::translate_composite_enum_CompositeElectionIdentifierExtra( - old_extra.clone(), - ) { - log::info!("Migrating election properties, new extra {:?}", new_extra); - new_election_properties - .push((election_identifier.with_extra(new_extra), props)); - } - } - } - - if !new_election_properties.is_empty() { - log::info!( - "Adding new election properties with {:?} items", - new_election_properties.len() - ); - - let _ = old::ElectionProperties::clear(u32::MAX, None); - log::info!("Cleared old election properties"); - - for (election_identifier, props) in new_election_properties { - log::info!("Inserting new election properties {:?}", election_identifier); - ElectionProperties::::insert(election_identifier, props); - } - } - - // Election state - let election_state = old::ElectionState::iter().collect::>(); - log::info!("Old election state: {:?}", election_state); - let mut new_election_state = Vec::new(); - for (election_identifier, _state) in election_state { - let raw_storage_at_key = - unhashed::get_raw(&old::ElectionState::hashed_key_for(election_identifier)) - .expect("We just got the keys directly from the storage"); - - let state = old::CompositeElectionState::decode(&mut &raw_storage_at_key[..]).unwrap(); - - if let Some(state) = old::translate_composite_enum_CompositeElectionState(state) { - new_election_state.push((election_identifier, state)); - } - } - - if !new_election_state.is_empty() { - log::info!("Adding new election state with {:?} items", new_election_state.len()); - let _ = old::ElectionState::clear(u32::MAX, None); - for (election_identifier, state) in new_election_state { - ElectionState::::insert(election_identifier, state); - } - } - - // Composite consensus - let election_consensus_history = old::ElectionConsensusHistory::iter().collect::>(); - let mut new_election_consensus_history = Vec::new(); - for (election_identifier, consensus) in election_consensus_history { - if let Some(consensus) = old::unwrap_composite_consensus_history(consensus) { - new_election_consensus_history.push((election_identifier, consensus)); - } - } - - if !new_election_consensus_history.is_empty() { - log::info!( - "Adding new election consensus history with {:?} items", - new_election_consensus_history.len() - ); - let _ = old::ElectionConsensusHistory::clear(u32::MAX, None); - for (election_identifier, consensus) in new_election_consensus_history { - ElectionConsensusHistory::::insert( - election_identifier, - consensus, - ); - } - } - - // The engines will re-fill these votes. - let _ = SharedDataReferenceCount::::clear(u32::MAX, None); - let _ = SharedData::::clear(u32::MAX, None); - let _ = BitmapComponents::::clear(u32::MAX, None); - let _ = IndividualComponents::::clear(u32::MAX, None); - let _ = ElectionConsensusHistoryUpToDate::::clear(u32::MAX, None); - - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), DispatchError> { - let pre_upgrade_properties_len = u32::decode(&mut &state[..4]).unwrap(); - let pre_upgrade_election_state_len = u32::decode(&mut &state[4..8]).unwrap(); - let pre_upgrade_map_len = u32::decode(&mut &state[8..12]).unwrap(); - - // Check lengths are correct: - let properties_len = ElectionProperties::::iter().count() as u32; - let election_state_len = ElectionState::::iter().count() as u32; - let map_len = - ElectoralUnsynchronisedStateMap::::iter().count() as u32; - - assert!( - // There's always going to be one fee election removed, so the properties length should - // be one less than the pre-upgrade length. - properties_len == pre_upgrade_properties_len.saturating_sub(1) || - // for idempotency - properties_len == pre_upgrade_properties_len - ); - assert!( - // There's always going to be one fee election removed, so the election state length - // should be one less than the pre-upgrade length. - election_state_len == pre_upgrade_election_state_len.saturating_sub(1) || - // for idempotency - election_state_len == pre_upgrade_election_state_len - ); - assert_eq!(map_len, pre_upgrade_map_len); - - use pallet_cf_elections::ElectionIdentifierOf; - - let open_election_identifiers = ElectoralSettings::::iter_keys(); - for electoral_settings_id in open_election_identifiers { - // should decode - let settings: Option< - ::ElectoralSettings, - > = ElectoralSettings::::get(electoral_settings_id); - assert!(settings.is_some()); - } - - // check properties decoded correctly - let _props: Vec<( - ElectionIdentifierOf, - ::ElectionProperties, - )> = ElectionProperties::::iter().collect::>(); - - assert!(SharedDataReferenceCount::::iter_keys() - .next() - .is_none()); - assert!(SharedData::::iter_keys().next().is_none()); - assert!(BitmapComponents::::iter_keys().next().is_none()); - assert!(IndividualComponents::::iter_keys().next().is_none()); - assert!(ElectionConsensusHistoryUpToDate::::iter_keys() - .next() - .is_none()); - - Ok(()) - } -} diff --git a/state-chain/runtime/src/migrations/solana_transaction_data_migration.rs b/state-chain/runtime/src/migrations/solana_transaction_data_migration.rs deleted file mode 100644 index f1382e7302c..00000000000 --- a/state-chain/runtime/src/migrations/solana_transaction_data_migration.rs +++ /dev/null @@ -1,88 +0,0 @@ -use frame_support::traits::UncheckedOnRuntimeUpgrade; -use pallet_cf_broadcast::BroadcastData; - -use crate::*; -use frame_support::pallet_prelude::Weight; -#[cfg(feature = "try-runtime")] -use sp_runtime::DispatchError; - -use cf_chains::sol::SolanaTransactionData; -use codec::{Decode, Encode}; - -pub mod old { - use cf_chains::sol::{SolLegacyMessage, SolSignature}; - use cf_primitives::BroadcastId; - use frame_support::{pallet_prelude::OptionQuery, Twox64Concat}; - - use super::*; - - #[derive(PartialEq, Eq, Encode, Decode)] - pub struct SolanaTransactionData { - pub serialized_transaction: Vec, - } - - #[derive(PartialEq, Eq, Encode, Decode)] - pub struct SolanaBroadcastData { - pub broadcast_id: BroadcastId, - pub transaction_payload: SolanaTransactionData, - pub threshold_signature_payload: SolLegacyMessage, - pub transaction_out_id: SolSignature, - pub nominee: Option<::AccountId>, - } - - #[frame_support::storage_alias] - pub type AwaitingBroadcast = - StorageMap; -} - -pub struct SolanaTransactionDataMigration; - -impl UncheckedOnRuntimeUpgrade for SolanaTransactionDataMigration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, DispatchError> { - Ok((old::AwaitingBroadcast::iter().count() as u64).encode()) - } - - fn on_runtime_upgrade() -> Weight { - pallet_cf_broadcast::AwaitingBroadcast::::translate_values::< - old::SolanaBroadcastData, - _, - >(|old_sol_broadcast_data| { - Some(BroadcastData:: { - broadcast_id: old_sol_broadcast_data.broadcast_id, - transaction_payload: SolanaTransactionData { - serialized_transaction: old_sol_broadcast_data - .transaction_payload - .serialized_transaction, - skip_preflight: true, - }, - threshold_signature_payload: old_sol_broadcast_data.threshold_signature_payload, - transaction_out_id: old_sol_broadcast_data.transaction_out_id, - nominee: old_sol_broadcast_data.nominee, - }) - }); - - Weight::zero() - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), DispatchError> { - let pre_awaiting_broadcast_count = ::decode(&mut state.as_slice()) - .map_err(|_| DispatchError::from("Failed to decode state"))?; - - let post_awaiting_broadcast_count = - pallet_cf_broadcast::AwaitingBroadcast::::iter().count() - as u64; - - assert_eq!(pre_awaiting_broadcast_count, post_awaiting_broadcast_count); - Ok(()) - } -} - -pub struct NoopUpgrade; - -impl UncheckedOnRuntimeUpgrade for NoopUpgrade { - fn on_runtime_upgrade() -> Weight { - Weight::zero() - } -} diff --git a/state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs b/state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs deleted file mode 100644 index 1e46ba05bb4..00000000000 --- a/state-chain/runtime/src/migrations/solana_vault_swaps_migration.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::*; -use chainflip::solana_elections::SolanaVaultSwapsSettings; -use frame_support::{pallet_prelude::Weight, storage::unhashed, traits::UncheckedOnRuntimeUpgrade}; - -use pallet_cf_elections::{ - electoral_system::ElectoralSystemTypes, Config, ElectoralSettings, 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 ElectoralSystemTypes>::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("FmAcjWaRFUxGWBfGT7G3CzcFeJFsewQ4KPJVG4f6fcob")), - ), - cf_runtime_utilities::genesis_hashes::PERSEVERANCE => ( - SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), - SolAddress(bs58_array("12MYcNumSQCn81yKRfrk5P5ThM5ivkLiZda979hhKJDR")), - ), - cf_runtime_utilities::genesis_hashes::SISYPHOS => ( - SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), - SolAddress(bs58_array("EXeku7Q9AiAXBdH7cUHw2ue3okhrofvDZR7EBE1BVQZu")), - ), - _ => ( - 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 ElectoralSystemTypes>::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("FmAcjWaRFUxGWBfGT7G3CzcFeJFsewQ4KPJVG4f6fcob")), - ), - cf_runtime_utilities::genesis_hashes::PERSEVERANCE => ( - SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), - SolAddress(bs58_array("12MYcNumSQCn81yKRfrk5P5ThM5ivkLiZda979hhKJDR")), - ), - cf_runtime_utilities::genesis_hashes::SISYPHOS => ( - SolAddress(bs58_array("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU")), - SolAddress(bs58_array("EXeku7Q9AiAXBdH7cUHw2ue3okhrofvDZR7EBE1BVQZu")), - ), - _ => ( - SolAddress(bs58_array("24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p")), - SolAddress(bs58_array("2tmtGLQcBd11BMiE9B1tAkQXwmPNgR79Meki2Eme4Ec9")), - ), - } - ); - } - Ok(()) - } -} diff --git a/state-chain/runtime/src/monitoring_apis.rs b/state-chain/runtime/src/monitoring_apis.rs index 5a45aec1627..a57facdf37c 100644 --- a/state-chain/runtime/src/monitoring_apis.rs +++ b/state-chain/runtime/src/monitoring_apis.rs @@ -147,48 +147,6 @@ pub struct MonitoringDataV2 { pub activating_key_broadcast_ids: ActivateKeysBroadcastIds, } -#[derive(Serialize, Deserialize, Encode, Decode, Eq, PartialEq, TypeInfo, Debug, Clone)] -pub struct MonitoringData { - pub external_chains_height: ExternalChainsBlockHeight, - pub btc_utxos: BtcUtxos, - pub epoch: EpochState, - pub pending_redemptions: RedemptionsInfo, - pub pending_broadcasts: PendingBroadcasts, - pub pending_tss: PendingTssCeremonies, - pub open_deposit_channels: OpenDepositChannels, - pub fee_imbalance: FeeImbalance, - pub authorities: AuthoritiesInfo, - pub build_version: LastRuntimeUpgradeInfo, - pub suspended_validators: Vec<(Offence, u32)>, - pub pending_swaps: u32, - pub dot_aggkey: PolkadotAccountId, - pub flip_supply: FlipSupply, -} - -impl From for MonitoringDataV2 { - fn from(monitoring_data: MonitoringData) -> Self { - Self { - epoch: monitoring_data.epoch, - pending_redemptions: monitoring_data.pending_redemptions, - fee_imbalance: monitoring_data.fee_imbalance.map(|i| *i), - external_chains_height: monitoring_data.external_chains_height, - btc_utxos: monitoring_data.btc_utxos, - pending_broadcasts: monitoring_data.pending_broadcasts, - pending_tss: monitoring_data.pending_tss, - open_deposit_channels: monitoring_data.open_deposit_channels, - authorities: monitoring_data.authorities, - build_version: monitoring_data.build_version, - suspended_validators: monitoring_data.suspended_validators, - pending_swaps: monitoring_data.pending_swaps, - dot_aggkey: monitoring_data.dot_aggkey, - flip_supply: monitoring_data.flip_supply, - sol_aggkey: Default::default(), - sol_onchain_key: Default::default(), - sol_nonces: Default::default(), - activating_key_broadcast_ids: Default::default(), - } - } -} decl_runtime_apis!( #[api_version(2)] pub trait MonitoringRuntimeApi { @@ -209,8 +167,6 @@ decl_runtime_apis!( fn cf_sol_nonces() -> SolanaNonces; fn cf_sol_aggkey() -> SolAddress; fn cf_sol_onchain_key() -> SolAddress; - #[changed_in(2)] - fn cf_monitoring_data() -> MonitoringData; fn cf_monitoring_data() -> MonitoringDataV2; fn cf_accounts_info( accounts: BoundedVec>, From 4b289c06e31a0baf99a813a036c661567bb31bc1 Mon Sep 17 00:00:00 2001 From: kylezs Date: Mon, 10 Feb 2025 17:21:01 +0100 Subject: [PATCH 16/20] chore: bump elections version (#5624) --- 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 0044d878417..1e51050241a 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(4); +pub const PALLET_VERSION: StorageVersion = StorageVersion::new(5); pub use pallet::UniqueMonotonicIdentifier; From c060e23271ba4fe1374a8fb5be7ec92a5578b2e6 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Tue, 11 Feb 2025 10:17:37 +1300 Subject: [PATCH 17/20] feat: Add base for versioned solana transaction (#5599) * Added versioned transaction and versioned message support * WIP added tests for versioned transaction for simple native transfer * Added unit test for Versioned transactions with Address lookup table * Moved (almost) everything in sol_tx_core into sol-prim Moved ALT related stuff to its own file. * Improved where sol core types are imported from. --------- Co-authored-by: Daniel --- Cargo.lock | 7 +- .../witness/sol/program_swaps_witnessing.rs | 13 +- engine/src/witness/sol/sol_deposits.rs | 16 +- foreign-chains/solana/sol-prim/Cargo.toml | 5 + .../sol-prim/src}/address_derivation.rs | 126 ++- foreign-chains/solana/sol-prim/src/alt.rs | 190 ++++ foreign-chains/solana/sol-prim/src/consts.rs | 11 + .../solana/sol-prim/src/instructions.rs | 4 + .../instructions}/bpf_loader_instructions.rs | 5 +- .../src/instructions}/compute_budget.rs | 7 +- .../src/instructions}/program_instructions.rs | 15 +- .../src/instructions}/token_instructions.rs | 13 +- foreign-chains/solana/sol-prim/src/lib.rs | 80 +- foreign-chains/solana/sol-prim/src/signer.rs | 9 + .../solana/sol-prim/src/tests/address.rs | 4 + .../solana/sol-prim/src/tests/digest.rs | 1 + .../solana/sol-prim/src/tests/pda.rs | 18 +- .../solana/sol-prim/src/tests/signature.rs | 4 +- .../solana/sol-prim/src/transaction.rs | 876 ++++++++++++++++++ state-chain/chains/Cargo.toml | 5 +- state-chain/chains/src/ccm_checker.rs | 12 +- state-chain/chains/src/lib.rs | 22 +- state-chain/chains/src/sol.rs | 19 +- state-chain/chains/src/sol/api.rs | 23 +- .../chains/src/sol/instruction_builder.rs | 18 +- state-chain/chains/src/sol/sol_tx_core.rs | 13 +- .../chains/src/sol/sol_tx_core/transaction.rs | 304 ------ .../chains/src/sol/{sol_tx_core => }/tests.rs | 94 +- .../chains/src/sol/transaction_builder.rs | 16 +- state-chain/runtime/Cargo.toml | 3 - .../src/chainflip/address_derivation/sol.rs | 6 +- .../runtime/src/chainflip/solana_elections.rs | 2 +- 32 files changed, 1432 insertions(+), 509 deletions(-) rename {state-chain/chains/src/sol/sol_tx_core => foreign-chains/solana/sol-prim/src}/address_derivation.rs (60%) create mode 100644 foreign-chains/solana/sol-prim/src/alt.rs create mode 100644 foreign-chains/solana/sol-prim/src/instructions.rs rename {state-chain/chains/src/sol/sol_tx_core => foreign-chains/solana/sol-prim/src/instructions}/bpf_loader_instructions.rs (98%) rename {state-chain/chains/src/sol/sol_tx_core => foreign-chains/solana/sol-prim/src/instructions}/compute_budget.rs (96%) rename {state-chain/chains/src/sol/sol_tx_core => foreign-chains/solana/sol-prim/src/instructions}/program_instructions.rs (98%) rename {state-chain/chains/src/sol/sol_tx_core => foreign-chains/solana/sol-prim/src/instructions}/token_instructions.rs (91%) create mode 100644 foreign-chains/solana/sol-prim/src/transaction.rs delete mode 100644 state-chain/chains/src/sol/sol_tx_core/transaction.rs rename state-chain/chains/src/sol/{sol_tx_core => }/tests.rs (92%) diff --git a/Cargo.lock b/Cargo.lock index 0c02a2fccba..de4b1d6a3b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1313,7 +1313,6 @@ dependencies = [ "ethereum-types", "frame-support", "generic-array 1.2.0", - "heck 0.5.0", "hex", "hex-literal", "itertools 0.13.0", @@ -2789,7 +2788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1145d32e826a7748b69ee8fc62d3e6355ff7f1051df53141e7048162fc90481b" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.96", ] [[package]] @@ -12206,15 +12205,18 @@ dependencies = [ "bincode 2.0.0-rc.3", "borsh", "bs58 0.5.1", + "cf-primitives", "curve25519-dalek 2.1.3", "digest 0.10.7", "ed25519-dalek", "generic-array 1.2.0", + "heck 0.5.0", "parity-scale-codec", "scale-info", "serde", "serde_json", "sha2 0.10.8", + "sha2-const", "sp-core 34.0.0", "sp-std 14.0.0 (git+https://github.com/chainflip-io/polkadot-sdk.git?tag=chainflip-substrate-1.15.2%2B2)", "thiserror 1.0.69", @@ -13324,7 +13326,6 @@ dependencies = [ "paste", "scale-info", "serde", - "sol-prim", "sp-api", "sp-block-builder", "sp-consensus-aura", diff --git a/engine/src/witness/sol/program_swaps_witnessing.rs b/engine/src/witness/sol/program_swaps_witnessing.rs index d0d634f27ec..c8a19d909a1 100644 --- a/engine/src/witness/sol/program_swaps_witnessing.rs +++ b/engine/src/witness/sol/program_swaps_witnessing.rs @@ -11,19 +11,16 @@ use cf_chains::{ cf_parameters::{ CfParameters, VaultSwapParameters, VersionedCcmCfParameters, VersionedCfParameters, }, - sol::{ - api::VaultSwapAccountAndSender, - sol_tx_core::program_instructions::{ - swap_endpoints::types::{SwapEndpointDataAccount, SwapEvent}, - ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH, - }, - SolAddress, - }, + sol::{api::VaultSwapAccountAndSender, SolAddress}, CcmChannelMetadata, CcmDepositMetadata, ForeignChainAddress, }; use codec::Decode; use futures::{stream, StreamExt, TryStreamExt}; use itertools::Itertools; +use sol_prim::program_instructions::{ + swap_endpoints::types::{SwapEndpointDataAccount, SwapEvent}, + ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH, +}; use state_chain_runtime::chainflip::solana_elections::SolanaVaultSwapDetails; use std::collections::{BTreeSet, HashSet}; use tracing::warn; diff --git a/engine/src/witness/sol/sol_deposits.rs b/engine/src/witness/sol/sol_deposits.rs index 3e16fdb0cbd..e419314a12f 100644 --- a/engine/src/witness/sol/sol_deposits.rs +++ b/engine/src/witness/sol/sol_deposits.rs @@ -1,20 +1,18 @@ use anyhow::ensure; use base64::Engine; -use cf_chains::sol::{ - sol_tx_core::{ - address_derivation::{derive_associated_token_account, derive_fetch_account}, - program_instructions::{ - types::DepositChannelHistoricalFetch, ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH, - }, - }, - SolAddress, SolAmount, -}; +use cf_chains::sol::{SolAddress, SolAmount}; use cf_primitives::chains::assets::sol::Asset; use futures::{stream, StreamExt, TryStreamExt}; use pallet_cf_elections::electoral_systems::blockchain::delta_based_ingress::{ ChannelTotalIngressed, ChannelTotalIngressedFor, OpenChannelDetailsFor, }; use serde_json::Value; +use sol_prim::{ + address_derivation::{derive_associated_token_account, derive_fetch_account}, + program_instructions::{ + types::DepositChannelHistoricalFetch, ANCHOR_PROGRAM_DISCRIMINATOR_LENGTH, + }, +}; use sp_runtime::SaturatedConversion; use state_chain_runtime::SolanaIngressEgress; use std::{collections::BTreeMap, str::FromStr}; diff --git a/foreign-chains/solana/sol-prim/Cargo.toml b/foreign-chains/solana/sol-prim/Cargo.toml index 1f9862b773d..94b88570a9b 100644 --- a/foreign-chains/solana/sol-prim/Cargo.toml +++ b/foreign-chains/solana/sol-prim/Cargo.toml @@ -26,12 +26,15 @@ bs58 = { workspace = true, optional = true } digest = { workspace = true, optional = true } serde = { workspace = true, optional = true, features = ["derive"] } sha2 = { workspace = true, optional = true } +sha2-const = { workspace = true } thiserror = { workspace = true, optional = true } codec = { workspace = true, optional = true, features = ["derive"] } scale-info = { workspace = true, optional = true, features = ["derive"] } +heck = { workspace = true } sp-std = { workspace = true } sp-core = { workspace = true } +cf-primitives = { workspace = true } [dev-dependencies] serde_json = { workspace = true } @@ -59,4 +62,6 @@ std = [ "bincode/std", "sp-std/std", "sp-core/std", + "cf-primitives/std", ] +runtime-integration-tests = ["std", "ed25519-dalek/rand_core"] \ No newline at end of file diff --git a/state-chain/chains/src/sol/sol_tx_core/address_derivation.rs b/foreign-chains/solana/sol-prim/src/address_derivation.rs similarity index 60% rename from state-chain/chains/src/sol/sol_tx_core/address_derivation.rs rename to foreign-chains/solana/sol-prim/src/address_derivation.rs index fe91ccee1fb..810e8bba60e 100644 --- a/state-chain/chains/src/sol/sol_tx_core/address_derivation.rs +++ b/foreign-chains/solana/sol-prim/src/address_derivation.rs @@ -1,11 +1,12 @@ //! Provides a interface to deriving addresses for the purposes of DepositChannels //! as well as deriving ATA for token operations (such as fetch or transfer). +use crate::{ + consts::{ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID}, + pda::{Pda as DerivedAddressBuilder, PdaError}, + Address, PdaAndBump, +}; use cf_primitives::ChannelId; -use sol_prim::PdaAndBump; - -use crate::sol::{AddressDerivationError, DerivedAddressBuilder, SolAddress}; -use sol_prim::consts::{ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID}; // Prefix seeds used in the Vault program to prevent seed collisions const DEPOSIT_CHANNEL_PREFIX_SEED: &[u8] = b"channel"; @@ -16,8 +17,8 @@ const SWAP_ENDPOINT_NATIVE_VAULT_SEED: &[u8] = b"swap_endpoint_native_vault"; /// Derive address for a given channel ID pub fn derive_deposit_address( channel_id: ChannelId, - vault_program: SolAddress, -) -> Result { + vault_program: Address, +) -> Result { let seed = channel_id.to_le_bytes(); DerivedAddressBuilder::from_address(vault_program)? .chain_seed(DEPOSIT_CHANNEL_PREFIX_SEED)? @@ -27,9 +28,9 @@ pub fn derive_deposit_address( /// Derive a Associated Token Account (ATA) of a main target account. pub fn derive_associated_token_account( - target: SolAddress, - mint_pubkey: SolAddress, -) -> Result { + target: Address, + mint_pubkey: Address, +) -> Result { DerivedAddressBuilder::from_address(ASSOCIATED_TOKEN_PROGRAM_ID)? .chain_seed(target)? .chain_seed(TOKEN_PROGRAM_ID)? @@ -39,9 +40,9 @@ pub fn derive_associated_token_account( /// Derive a fetch account from the vault program key and a deposit channel address used as a seed. pub fn derive_fetch_account( - deposit_channel_address: SolAddress, - vault_program: SolAddress, -) -> Result { + deposit_channel_address: Address, + vault_program: Address, +) -> Result { DerivedAddressBuilder::from_address(vault_program)? .chain_seed(HISTORICAL_FETCH_PREFIX_SEED)? .chain_seed(deposit_channel_address)? @@ -50,9 +51,9 @@ pub fn derive_fetch_account( /// Derive the token supported account required for vault swaps. pub fn derive_token_supported_account( - vault_program: SolAddress, - mint_pubkey: SolAddress, -) -> Result { + vault_program: Address, + mint_pubkey: Address, +) -> Result { DerivedAddressBuilder::from_address(vault_program)? .chain_seed(SUPPORTED_TOKEN_PREFIX_SEED)? .chain_seed(mint_pubkey)? @@ -61,8 +62,8 @@ pub fn derive_token_supported_account( /// Derive the Swap Endpoint's native vault account where SOL is stored before fetching. pub fn derive_swap_endpoint_native_vault_account( - swap_endpoint_program: SolAddress, -) -> Result { + swap_endpoint_program: Address, +) -> Result { DerivedAddressBuilder::from_address(swap_endpoint_program)? .chain_seed(SWAP_ENDPOINT_NATIVE_VAULT_SEED)? .finish() @@ -70,30 +71,32 @@ pub fn derive_swap_endpoint_native_vault_account( /// Derive an address from our Vault program key. Produces an Address and a bump. #[cfg(test)] -fn derive_address( - seed: impl AsRef<[u8]>, - vault_program: SolAddress, -) -> Result { +fn derive_address(seed: impl AsRef<[u8]>, vault_program: Address) -> Result { DerivedAddressBuilder::from_address(vault_program)?.chain_seed(seed)?.finish() } #[cfg(test)] mod tests { use super::*; - use crate::sol::sol_tx_core::sol_test_values; + use crate::consts::const_address; use std::str::FromStr; + const USDC_TOKEN_MINT_PUB_KEY: Address = + const_address("24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p"); + const VAULT_PROGRAM: Address = const_address("8inHGLHXegST3EPLcpisQe9D1hDT9r7DJjS395L3yuYf"); + const SWAP_ENDPOINT_PROGRAM: Address = + const_address("35uYgHdfZQT4kHkaaXQ6ZdCkK5LFrsk43btTLbGCRCNT"); + #[test] fn derive_associated_token_account_on_curve() { let wallet_address = - SolAddress::from_str("HfasueN6RNPjSM6rKGH5dga6kS2oUF8siGH3m4MXPURp").unwrap(); - let mint_pubkey = sol_test_values::USDC_TOKEN_MINT_PUB_KEY; + Address::from_str("HfasueN6RNPjSM6rKGH5dga6kS2oUF8siGH3m4MXPURp").unwrap(); + let mint_pubkey = USDC_TOKEN_MINT_PUB_KEY; assert_eq!( derive_associated_token_account(wallet_address, mint_pubkey).unwrap(), PdaAndBump { - address: SolAddress::from_str("BeRexE9vZSdQMNg65PAnhy3rRPUxF6oWsxyNegYxySZD") - .unwrap(), + address: Address::from_str("BeRexE9vZSdQMNg65PAnhy3rRPUxF6oWsxyNegYxySZD").unwrap(), bump: 253u8 } ); @@ -102,14 +105,13 @@ mod tests { #[test] fn derive_associated_token_account_off_curve() { let pda_address = - SolAddress::from_str("9j17hjg8wR2uFxJAJDAFahwsgTCNx35sc5qXSxDmuuF6").unwrap(); - let mint_pubkey = sol_test_values::USDC_TOKEN_MINT_PUB_KEY; + Address::from_str("9j17hjg8wR2uFxJAJDAFahwsgTCNx35sc5qXSxDmuuF6").unwrap(); + let mint_pubkey = USDC_TOKEN_MINT_PUB_KEY; assert_eq!( derive_associated_token_account(pda_address, mint_pubkey).unwrap(), PdaAndBump { - address: SolAddress::from_str("DUjCLckPi4g7QAwBEwuFL1whpgY6L9fxwXnqbWvS2pcW") - .unwrap(), + address: Address::from_str("DUjCLckPi4g7QAwBEwuFL1whpgY6L9fxwXnqbWvS2pcW").unwrap(), bump: 251u8 } ); @@ -120,21 +122,19 @@ mod tests { let channel_0_seed = 0u64.to_le_bytes(); let channel_1_seed = 1u64.to_le_bytes(); - let vault_program = sol_test_values::VAULT_PROGRAM; + let vault_program = VAULT_PROGRAM; assert_eq!( derive_address(channel_0_seed, vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("JDtAzKWKzQJCiHCfK4PU7qYuE4wChxuqfDqQhRbv6kwX") - .unwrap(), + address: Address::from_str("JDtAzKWKzQJCiHCfK4PU7qYuE4wChxuqfDqQhRbv6kwX").unwrap(), bump: 254u8 } ); assert_eq!( derive_address(channel_1_seed, vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("32qRitYeor2v7Rb3M2iL8PHkoyqhcoCCqYuWCNKqstN7") - .unwrap(), + address: Address::from_str("32qRitYeor2v7Rb3M2iL8PHkoyqhcoCCqYuWCNKqstN7").unwrap(), bump: 255u8 } ); @@ -142,8 +142,7 @@ mod tests { assert_eq!( derive_address([11u8, 12u8, 13u8, 55u8], vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("XFmi41e1L9t732KoZdmzMSVige3SjjzsLzk1rW4rhwP") - .unwrap(), + address: Address::from_str("XFmi41e1L9t732KoZdmzMSVige3SjjzsLzk1rW4rhwP").unwrap(), bump: 255u8 } ); @@ -151,8 +150,7 @@ mod tests { assert_eq!( derive_address([1], vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("5N72J9YQKpky5yFnrWWpFcBQsWpFMK4rW6b2Ue3YmYcu") - .unwrap(), + address: Address::from_str("5N72J9YQKpky5yFnrWWpFcBQsWpFMK4rW6b2Ue3YmYcu").unwrap(), bump: 255u8 } ); @@ -160,8 +158,7 @@ mod tests { assert_eq!( derive_address([1, 2], vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("6PkQHEp18NgEDS5ydkgivU4pzTV6sYmoEaHvbbv4un73") - .unwrap(), + address: Address::from_str("6PkQHEp18NgEDS5ydkgivU4pzTV6sYmoEaHvbbv4un73").unwrap(), bump: 255u8 } ); @@ -169,12 +166,11 @@ mod tests { #[test] fn can_derive_deposit_address_native() { - let vault_program = sol_test_values::VAULT_PROGRAM; + let vault_program = VAULT_PROGRAM; assert_eq!( derive_deposit_address(0u64, vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("5mP7x1r66PC62PFxXTiEEJVd2Guddc3vWEAkhgWxXehm") - .unwrap(), + address: Address::from_str("5mP7x1r66PC62PFxXTiEEJVd2Guddc3vWEAkhgWxXehm").unwrap(), bump: 255u8 }, ); @@ -182,22 +178,20 @@ mod tests { assert_eq!( derive_deposit_address(1u64, vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("AXjJtvtRra2d8zp8429eQiuuCQeUKBKcSFUXHvQJYRGb") - .unwrap(), + address: Address::from_str("AXjJtvtRra2d8zp8429eQiuuCQeUKBKcSFUXHvQJYRGb").unwrap(), bump: 255u8 }, ); } #[test] fn can_derive_deposit_address_token() { - let vault_program = sol_test_values::VAULT_PROGRAM; - let token_mint_pubkey = sol_test_values::USDC_TOKEN_MINT_PUB_KEY; + let vault_program = VAULT_PROGRAM; + let token_mint_pubkey = USDC_TOKEN_MINT_PUB_KEY; let derived_account_0 = derive_deposit_address(0u64, vault_program).unwrap(); assert_eq!( derive_associated_token_account(derived_account_0.address, token_mint_pubkey).unwrap(), PdaAndBump { - address: SolAddress::from_str("5WXnwDp1AA4QZqi3CJEx7HGjTPBj9h42pLwCRuV7AmGs") - .unwrap(), + address: Address::from_str("5WXnwDp1AA4QZqi3CJEx7HGjTPBj9h42pLwCRuV7AmGs").unwrap(), bump: 255u8 } ); @@ -206,8 +200,7 @@ mod tests { assert_eq!( derive_associated_token_account(derived_account_1.address, token_mint_pubkey).unwrap(), PdaAndBump { - address: SolAddress::from_str("2og5SNyC3RG8D5sKhKqRpFnPbtBCC7kPBDNzAxbwLct4") - .unwrap(), + address: Address::from_str("2og5SNyC3RG8D5sKhKqRpFnPbtBCC7kPBDNzAxbwLct4").unwrap(), bump: 251u8 } ); @@ -216,34 +209,33 @@ mod tests { #[test] fn test_sol_derive_fetch_account() { let fetch_account = derive_fetch_account( - SolAddress::from_str("HAMxiXdEJxiBHabZAUm8PSLvWQM2GHi5PArVZvUCeDab").unwrap(), - SolAddress::from_str("8inHGLHXegST3EPLcpisQe9D1hDT9r7DJjS395L3yuYf").unwrap(), + Address::from_str("HAMxiXdEJxiBHabZAUm8PSLvWQM2GHi5PArVZvUCeDab").unwrap(), + Address::from_str("8inHGLHXegST3EPLcpisQe9D1hDT9r7DJjS395L3yuYf").unwrap(), ) .unwrap() .address; assert_eq!( fetch_account, - SolAddress::from_str("9c2CYdB21rMHViEr1KKTUNVPpLKMKv4mV8iWDitofvhH").unwrap() + Address::from_str("9c2CYdB21rMHViEr1KKTUNVPpLKMKv4mV8iWDitofvhH").unwrap() ); } #[test] fn can_derive_fetch_account_native() { - let vault_program = sol_test_values::VAULT_PROGRAM; + let vault_program = VAULT_PROGRAM; let deposit_channel = derive_deposit_address(0u64, vault_program).unwrap().address; assert_eq!( derive_fetch_account(deposit_channel, vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("4oeSeUiNcbd2CK6jRs5b5hHKE5so4nxzNWPsHMoTSAm3") - .unwrap(), + address: Address::from_str("4oeSeUiNcbd2CK6jRs5b5hHKE5so4nxzNWPsHMoTSAm3").unwrap(), bump: 253u8 }, ); } #[test] fn can_derive_fetch_account_token() { - let vault_program = sol_test_values::VAULT_PROGRAM; - let token_mint_pubkey = sol_test_values::USDC_TOKEN_MINT_PUB_KEY; + let vault_program = VAULT_PROGRAM; + let token_mint_pubkey = USDC_TOKEN_MINT_PUB_KEY; let deposit_channel = derive_deposit_address(0u64, vault_program).unwrap().address; let deposit_channel_ata = derive_associated_token_account(deposit_channel, token_mint_pubkey) @@ -253,8 +245,7 @@ mod tests { assert_eq!( derive_fetch_account(deposit_channel_ata, vault_program).unwrap(), PdaAndBump { - address: SolAddress::from_str("CkGQUU19izDobt5NLGmj2h6DBMFRkmj6WN6onNtQVwzn") - .unwrap(), + address: Address::from_str("CkGQUU19izDobt5NLGmj2h6DBMFRkmj6WN6onNtQVwzn").unwrap(), bump: 255u8 } ); @@ -262,29 +253,28 @@ mod tests { #[test] fn can_derive_token_support_account() { - let vault_program = sol_test_values::VAULT_PROGRAM; - let token_mint_pubkey = sol_test_values::USDC_TOKEN_MINT_PUB_KEY; + let vault_program = VAULT_PROGRAM; + let token_mint_pubkey = USDC_TOKEN_MINT_PUB_KEY; let usdc_support_account = derive_token_supported_account(vault_program, token_mint_pubkey) .unwrap() .address; assert_eq!( usdc_support_account, - SolAddress::from_str("9nJKeYP6yUriVUp9moYZHYAFmo3cCRpc2NMZ7tCMsGF6").unwrap() + Address::from_str("9nJKeYP6yUriVUp9moYZHYAFmo3cCRpc2NMZ7tCMsGF6").unwrap() ); } #[test] fn can_derive_swap_endpoint_native_vault_account() { - let swap_endpoint_program = sol_test_values::SWAP_ENDPOINT_PROGRAM; + let swap_endpoint_program = SWAP_ENDPOINT_PROGRAM; let swap_endpoint_native_vault = derive_swap_endpoint_native_vault_account(swap_endpoint_program).unwrap(); assert_eq!( swap_endpoint_native_vault, PdaAndBump { - address: SolAddress::from_str("EWaGcrFXhf9Zq8yxSdpAa75kZmDXkRxaP17sYiL6UpZN") - .unwrap(), + address: Address::from_str("EWaGcrFXhf9Zq8yxSdpAa75kZmDXkRxaP17sYiL6UpZN").unwrap(), bump: 254u8 } ); diff --git a/foreign-chains/solana/sol-prim/src/alt.rs b/foreign-chains/solana/sol-prim/src/alt.rs new file mode 100644 index 00000000000..61fbe9ce32a --- /dev/null +++ b/foreign-chains/solana/sol-prim/src/alt.rs @@ -0,0 +1,190 @@ +pub use crate::{ + short_vec, Address, CompileError, CompiledInstruction, Digest, Instruction, Pubkey, Signature, +}; +use serde::{Deserialize, Serialize}; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +/// Address table lookups describe an on-chain address lookup table to use +/// for loading more readonly and writable accounts in a single tx. +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct MessageAddressTableLookup { + /// Address lookup table account key + pub account_key: Pubkey, + /// List of indexes used to load writable account addresses + #[serde(with = "short_vec")] + pub writable_indexes: Vec, + /// List of indexes used to load readonly account addresses + #[serde(with = "short_vec")] + pub readonly_indexes: Vec, +} + +/// The definition of address lookup table accounts. +/// +/// As used by the `crate::message::v0` message format. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct AddressLookupTableAccount { + pub key: Pubkey, + pub addresses: Vec, +} + +/// Collection of static and dynamically loaded keys used to load accounts +/// during transaction processing. +#[derive(Clone, Default, Debug, Eq)] +pub struct AccountKeys<'a> { + static_keys: &'a [Pubkey], + dynamic_keys: Option<&'a LoadedAddresses>, +} + +impl<'a> AccountKeys<'a> { + pub fn new(static_keys: &'a [Pubkey], dynamic_keys: Option<&'a LoadedAddresses>) -> Self { + Self { static_keys, dynamic_keys } + } + + /// Returns an iterator of account key segments. The ordering of segments + /// affects how account indexes from compiled instructions are resolved and + /// so should not be changed. + #[inline] + fn key_segment_iter(&self) -> impl Iterator + Clone { + if let Some(dynamic_keys) = self.dynamic_keys { + [self.static_keys, &dynamic_keys.writable, &dynamic_keys.readonly].into_iter() + } else { + // empty segments added for branch type compatibility + [self.static_keys, &[], &[]].into_iter() + } + } + + /// Returns the address of the account at the specified index of the list of + /// message account keys constructed from static keys, followed by dynamically + /// loaded writable addresses, and lastly the list of dynamically loaded + /// readonly addresses. + #[inline] + pub fn get(&self, mut index: usize) -> Option<&'a Pubkey> { + for key_segment in self.key_segment_iter() { + if index < key_segment.len() { + return Some(&key_segment[index]); + } + index = index.saturating_sub(key_segment.len()); + } + + None + } + + /// Returns the total length of loaded accounts for a message + #[inline] + pub fn len(&self) -> usize { + let mut len = 0usize; + for key_segment in self.key_segment_iter() { + len = len.saturating_add(key_segment.len()); + } + len + } + + /// Returns true if this collection of account keys is empty + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Iterator for the addresses of the loaded accounts for a message + #[inline] + pub fn iter(&self) -> impl Iterator + Clone { + self.key_segment_iter().flatten() + } + + /// Compile instructions using the order of account keys to determine + /// compiled instruction account indexes. + /// + /// # Panics + /// + /// Panics when compiling fails. See [`AccountKeys::try_compile_instructions`] + /// for a full description of failure scenarios. + pub fn compile_instructions(&self, instructions: &[Instruction]) -> Vec { + self.try_compile_instructions(instructions).expect("compilation failure") + } + + /// Compile instructions using the order of account keys to determine + /// compiled instruction account indexes. + /// + /// # Errors + /// + /// Compilation will fail if any `instructions` use account keys which are not + /// present in this account key collection. + /// + /// Compilation will fail if any `instructions` use account keys which are located + /// at an index which cannot be cast to a `u8` without overflow. + pub fn try_compile_instructions( + &self, + instructions: &[Instruction], + ) -> Result, CompileError> { + let mut account_index_map = BTreeMap::<&Pubkey, u8>::new(); + for (index, key) in self.iter().enumerate() { + let index = u8::try_from(index).map_err(|_| CompileError::AccountIndexOverflow)?; + account_index_map.insert(key, index); + } + + let get_account_index = |key: &Pubkey| -> Result { + account_index_map + .get(key) + .cloned() + .ok_or(CompileError::UnknownInstructionKey(*key)) + }; + + instructions + .iter() + .map(|ix| { + let accounts: Vec = ix + .accounts + .iter() + .map(|account_meta| get_account_index(&account_meta.pubkey)) + .collect::, CompileError>>()?; + + Ok(CompiledInstruction { + program_id_index: get_account_index(&ix.program_id)?, + data: ix.data.clone(), + accounts, + }) + }) + .collect() + } +} + +impl PartialEq for AccountKeys<'_> { + fn eq(&self, other: &Self) -> bool { + self.iter().zip(other.iter()).all(|(a, b)| a == b) + } +} + +/// Collection of addresses loaded from on-chain lookup tables, split +/// by readonly and writable. +#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct LoadedAddresses { + /// List of addresses for writable loaded accounts + pub writable: Vec, + /// List of addresses for read-only loaded accounts + pub readonly: Vec, +} + +impl FromIterator for LoadedAddresses { + fn from_iter>(iter: T) -> Self { + let (writable, readonly): (Vec>, Vec>) = iter + .into_iter() + .map(|addresses| (addresses.writable, addresses.readonly)) + .unzip(); + LoadedAddresses { + writable: writable.into_iter().flatten().collect(), + readonly: readonly.into_iter().flatten().collect(), + } + } +} + +impl LoadedAddresses { + /// Checks if there are no writable or readonly addresses + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Combined length of loaded writable and readonly addresses + pub fn len(&self) -> usize { + self.writable.len().saturating_add(self.readonly.len()) + } +} diff --git a/foreign-chains/solana/sol-prim/src/consts.rs b/foreign-chains/solana/sol-prim/src/consts.rs index b0c47edb139..8ab5d87624d 100644 --- a/foreign-chains/solana/sol-prim/src/consts.rs +++ b/foreign-chains/solana/sol-prim/src/consts.rs @@ -10,6 +10,14 @@ pub const SOLANA_PDA_MAX_SEEDS: u8 = 16; pub const SOLANA_PDA_MAX_SEED_LEN: usize = 32; pub const SOLANA_PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress"; +pub const HASH_BYTES: usize = 32; + +/// Maximum string length of a base58 encoded pubkey +pub const MAX_BASE58_LEN: usize = 44; + +/// Bit mask that indicates whether a serialized message is versioned. +pub const MESSAGE_VERSION_PREFIX: u8 = 0x80; + pub const fn const_address(s: &'static str) -> Address { Address(bs58_array(s)) } @@ -27,6 +35,9 @@ pub const SYS_VAR_RECENT_BLOCKHASHES: Address = const_address("SysvarRecentB1ockHashes11111111111111111111"); pub const SYS_VAR_INSTRUCTIONS: Address = const_address("Sysvar1nstructions1111111111111111111111111"); +pub const ADDRESS_LOOKUP_TABLE_PROGRAM_ID: Address = + const_address("AddressLookupTab1e1111111111111111111111111"); + pub const SYS_VAR_RENT: Address = const_address("SysvarRent111111111111111111111111111111111"); pub const SYS_VAR_CLOCK: Address = const_address("SysvarC1ock11111111111111111111111111111111"); pub const BPF_LOADER_UPGRADEABLE_ID: Address = diff --git a/foreign-chains/solana/sol-prim/src/instructions.rs b/foreign-chains/solana/sol-prim/src/instructions.rs new file mode 100644 index 00000000000..f3a88a7d929 --- /dev/null +++ b/foreign-chains/solana/sol-prim/src/instructions.rs @@ -0,0 +1,4 @@ +pub mod bpf_loader_instructions; +pub mod compute_budget; +pub mod program_instructions; +pub mod token_instructions; diff --git a/state-chain/chains/src/sol/sol_tx_core/bpf_loader_instructions.rs b/foreign-chains/solana/sol-prim/src/instructions/bpf_loader_instructions.rs similarity index 98% rename from state-chain/chains/src/sol/sol_tx_core/bpf_loader_instructions.rs rename to foreign-chains/solana/sol-prim/src/instructions/bpf_loader_instructions.rs index 43ef7142c57..fe0c6545183 100644 --- a/state-chain/chains/src/sol/sol_tx_core/bpf_loader_instructions.rs +++ b/foreign-chains/solana/sol-prim/src/instructions/bpf_loader_instructions.rs @@ -1,8 +1,7 @@ -use super::{AccountMeta, Instruction, Pubkey}; -use crate::vec; +use crate::{consts::BPF_LOADER_UPGRADEABLE_ID, AccountMeta, Instruction, Pubkey}; +use sp_std::vec; use serde::{Deserialize, Serialize}; -use sol_prim::consts::BPF_LOADER_UPGRADEABLE_ID; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum UpgradeableLoaderInstruction { diff --git a/state-chain/chains/src/sol/sol_tx_core/compute_budget.rs b/foreign-chains/solana/sol-prim/src/instructions/compute_budget.rs similarity index 96% rename from state-chain/chains/src/sol/sol_tx_core/compute_budget.rs rename to foreign-chains/solana/sol-prim/src/instructions/compute_budget.rs index 542cee451c6..f6bde567561 100644 --- a/state-chain/chains/src/sol/sol_tx_core/compute_budget.rs +++ b/foreign-chains/solana/sol-prim/src/instructions/compute_budget.rs @@ -1,10 +1,9 @@ -use super::Instruction; +use crate::{consts::COMPUTE_BUDGET_PROGRAM, Instruction}; +use sp_std::vec; + use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use crate::vec; -use sol_prim::consts::COMPUTE_BUDGET_PROGRAM; - /// Compute Budget Instructions #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] pub enum ComputeBudgetInstruction { diff --git a/state-chain/chains/src/sol/sol_tx_core/program_instructions.rs b/foreign-chains/solana/sol-prim/src/instructions/program_instructions.rs similarity index 98% rename from state-chain/chains/src/sol/sol_tx_core/program_instructions.rs rename to foreign-chains/solana/sol-prim/src/instructions/program_instructions.rs index 27c5ef4ac81..f4663d77d61 100644 --- a/state-chain/chains/src/sol/sol_tx_core/program_instructions.rs +++ b/foreign-chains/solana/sol-prim/src/instructions/program_instructions.rs @@ -1,12 +1,10 @@ -use super::{AccountMeta, Instruction, Pubkey}; +use crate::{consts::SYSTEM_PROGRAM_ID, AccountMeta, Instruction, Pubkey}; use borsh::{BorshDeserialize, BorshSerialize}; use cf_utilities::SliceToArray; -use core::str::FromStr; use scale_info::prelude::string::String; use serde::{Deserialize, Serialize}; -use sol_prim::consts::SYSTEM_PROGRAM_ID; -use sp_std::{vec, vec::Vec}; +use sp_std::{str::FromStr, vec, vec::Vec}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub enum SystemProgramInstruction { @@ -313,6 +311,7 @@ macro_rules! solana_program { program_id: Pubkey, } + #[allow(clippy::too_many_arguments)] impl $program { pub fn with_id(program_id: impl Into) -> Self { Self { program_id: program_id.into() } @@ -402,7 +401,7 @@ macro_rules! solana_program { mod test { use super::*; use std::collections::BTreeSet; - use $crate::sol::sol_tx_core::program_instructions::idl::Idl; + use $crate::instructions::program_instructions::idl::Idl; const IDL_RAW: &str = include_str!($idl_path); @@ -892,12 +891,6 @@ pub mod swap_endpoints { ] ); - impl From for types::CcmParams { - fn from(ccm: crate::CcmChannelMetadata) -> Self { - types::CcmParams { message: ccm.message.to_vec(), gas_amount: ccm.gas_budget as u64 } - } - } - 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] = diff --git a/state-chain/chains/src/sol/sol_tx_core/token_instructions.rs b/foreign-chains/solana/sol-prim/src/instructions/token_instructions.rs similarity index 91% rename from state-chain/chains/src/sol/sol_tx_core/token_instructions.rs rename to foreign-chains/solana/sol-prim/src/instructions/token_instructions.rs index acbd9e56be0..11b5e172931 100644 --- a/state-chain/chains/src/sol/sol_tx_core/token_instructions.rs +++ b/foreign-chains/solana/sol-prim/src/instructions/token_instructions.rs @@ -1,7 +1,10 @@ //! Program instructions -use super::{AccountMeta, Instruction, Pubkey}; -use crate::vec; +use crate::{ + consts::{ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_PROGRAM_ID}, + AccountMeta, Instruction, Pubkey, +}; use borsh::{BorshDeserialize, BorshSerialize}; +use sp_std::vec; // https://docs.rs/spl-associated-token-account/2.3.0/src/spl_associated_token_account/instruction.rs.html#1-161 @@ -65,12 +68,12 @@ impl AssociatedTokenAccountInstruction { AccountMeta::new(*associated_account_address, false), AccountMeta::new_readonly(*wallet_address, false), AccountMeta::new_readonly(*token_mint_address, false), - AccountMeta::new_readonly(sol_prim::consts::SYSTEM_PROGRAM_ID.into(), false), - AccountMeta::new_readonly(sol_prim::consts::TOKEN_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(SYSTEM_PROGRAM_ID.into(), false), + AccountMeta::new_readonly(TOKEN_PROGRAM_ID.into(), false), ]; Instruction::new_with_borsh( // program id of the system program - sol_prim::consts::ASSOCIATED_TOKEN_PROGRAM_ID.into(), + ASSOCIATED_TOKEN_PROGRAM_ID.into(), &Self::CreateIdempotent, account_metas, ) diff --git a/foreign-chains/solana/sol-prim/src/lib.rs b/foreign-chains/solana/sol-prim/src/lib.rs index e5a5c20568d..b01db1bc012 100644 --- a/foreign-chains/solana/sol-prim/src/lib.rs +++ b/foreign-chains/solana/sol-prim/src/lib.rs @@ -19,9 +19,16 @@ pub mod pda; #[cfg(test)] mod tests; +pub mod address_derivation; +pub mod alt; pub mod consts; pub mod errors; +pub mod instructions; pub mod short_vec; +pub mod transaction; + +pub use alt::*; +pub use instructions::*; #[cfg(feature = "std")] pub mod signer; @@ -33,6 +40,8 @@ pub type SlotNumber = u64; pub type ComputeLimit = u32; pub type AccountBump = u8; +use crate::consts::{HASH_BYTES, MAX_BASE58_LEN, SOLANA_SIGNATURE_LEN}; + define_binary!(address, Address, crate::consts::SOLANA_ADDRESS_LEN, "A"); define_binary!(digest, Digest, crate::consts::SOLANA_DIGEST_LEN, "D"); define_binary!(signature, Signature, crate::consts::SOLANA_SIGNATURE_LEN, "S"); @@ -44,11 +53,6 @@ pub struct PdaAndBump { pub bump: AccountBump, } -pub const HASH_BYTES: usize = 32; - -/// Maximum string length of a base58 encoded pubkey -pub const MAX_BASE58_LEN: usize = 44; - /// A directive for a single invocation of a Solana program. /// /// An instruction specifies which program it is calling, which accounts it may @@ -359,6 +363,66 @@ impl CompiledKeys { Ok((header, static_account_keys)) } + + pub fn try_extract_table_lookup( + &mut self, + lookup_table_account: &AddressLookupTableAccount, + ) -> Result, CompileError> { + let (writable_indexes, drained_writable_keys) = self + .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| { + !meta.is_signer && !meta.is_invoked && meta.is_writable + })?; + let (readonly_indexes, drained_readonly_keys) = self + .try_drain_keys_found_in_lookup_table(&lookup_table_account.addresses, |meta| { + !meta.is_signer && !meta.is_invoked && !meta.is_writable + })?; + + // Don't extract lookup if no keys were found + if writable_indexes.is_empty() && readonly_indexes.is_empty() { + return Ok(None); + } + + Ok(Some(( + MessageAddressTableLookup { + account_key: lookup_table_account.key, + writable_indexes, + readonly_indexes, + }, + LoadedAddresses { writable: drained_writable_keys, readonly: drained_readonly_keys }, + ))) + } + + fn try_drain_keys_found_in_lookup_table( + &mut self, + lookup_table_addresses: &[Pubkey], + key_meta_filter: impl Fn(&CompiledKeyMeta) -> bool, + ) -> Result<(Vec, Vec), CompileError> { + let mut lookup_table_indexes = Vec::new(); + let mut drained_keys = Vec::new(); + + for search_key in self + .key_meta_map + .iter() + .filter_map(|(key, meta)| key_meta_filter(meta).then_some(key)) + { + for (key_index, key) in lookup_table_addresses.iter().enumerate() { + if key == search_key { + let lookup_table_index = u8::try_from(key_index) + .map_err(|_| CompileError::AddressTableLookupIndexOverflow)?; + + lookup_table_indexes.push(lookup_table_index); + drained_keys.push(*search_key); + break; + } + } + } + + for key in &drained_keys { + self.key_meta_map.remove_entry(key); + } + + Ok((lookup_table_indexes, drained_keys)) + } } #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -481,7 +545,7 @@ impl TryFrom> for Pubkey { #[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, Copy)] pub struct RawSignature(GenericArray); -const SIGNATURE_BYTES: usize = 64; + impl RawSignature { #[cfg(test)] pub(self) fn verify_verbose( @@ -500,8 +564,8 @@ impl RawSignature { } } -impl From<[u8; SIGNATURE_BYTES]> for RawSignature { - fn from(signature: [u8; SIGNATURE_BYTES]) -> Self { +impl From<[u8; SOLANA_SIGNATURE_LEN]> for RawSignature { + fn from(signature: [u8; SOLANA_SIGNATURE_LEN]) -> Self { Self(GenericArray::from(signature)) } } diff --git a/foreign-chains/solana/sol-prim/src/signer.rs b/foreign-chains/solana/sol-prim/src/signer.rs index 557f089c03f..bd2c4666f33 100644 --- a/foreign-chains/solana/sol-prim/src/signer.rs +++ b/foreign-chains/solana/sol-prim/src/signer.rs @@ -62,6 +62,15 @@ pub trait Signer { fn is_interactive(&self) -> bool; } +/// Convenience trait for working with mixed collections of `Signer`s +pub trait Signers { + fn pubkeys(&self) -> Vec; + fn try_pubkeys(&self) -> Result, SignerError>; + fn sign_message(&self, message: &[u8]) -> Vec; + fn try_sign_message(&self, message: &[u8]) -> Result, SignerError>; + fn is_interactive(&self) -> bool; +} + pub mod presigner { use thiserror::Error; diff --git a/foreign-chains/solana/sol-prim/src/tests/address.rs b/foreign-chains/solana/sol-prim/src/tests/address.rs index 88fcf89b29b..e85dbc544e5 100644 --- a/foreign-chains/solana/sol-prim/src/tests/address.rs +++ b/foreign-chains/solana/sol-prim/src/tests/address.rs @@ -1,4 +1,7 @@ +#![cfg(test)] + #[cfg(feature = "str")] +#[cfg(test)] mod from_and_to_str { use core::fmt::Write; @@ -39,6 +42,7 @@ mod from_and_to_str { } #[cfg(feature = "serde")] +#[cfg(test)] mod feature_serde { use crate::{address::Address, consts}; diff --git a/foreign-chains/solana/sol-prim/src/tests/digest.rs b/foreign-chains/solana/sol-prim/src/tests/digest.rs index ecc6005d8a2..179cb777184 100644 --- a/foreign-chains/solana/sol-prim/src/tests/digest.rs +++ b/foreign-chains/solana/sol-prim/src/tests/digest.rs @@ -1,4 +1,5 @@ #[cfg(feature = "str")] +#[cfg(test)] mod from_and_to_str { use core::fmt::Write; diff --git a/foreign-chains/solana/sol-prim/src/tests/pda.rs b/foreign-chains/solana/sol-prim/src/tests/pda.rs index 3ac5b8591b7..28d4cfc92d2 100644 --- a/foreign-chains/solana/sol-prim/src/tests/pda.rs +++ b/foreign-chains/solana/sol-prim/src/tests/pda.rs @@ -1,14 +1,12 @@ #![cfg(feature = "pda")] -use crate::{ - address::Address, - pda::{Pda, PdaError}, -}; - +#[cfg(test)] mod failures { - use crate::{consts, PdaAndBump}; - - use super::*; + use crate::{ + consts, + pda::{Pda, PdaError}, + Address, PdaAndBump, + }; #[test] fn seed_too_long() { @@ -55,10 +53,10 @@ mod failures { } } +#[cfg(test)] mod happy { - use crate::PdaAndBump; + use crate::{pda::Pda, Address, PdaAndBump}; - use super::*; fn run_single(public_key: &str, seeds: &[&str], expected_pda: &str) { let public_key: Address = public_key.parse().expect("public-key"); let expected_pda: Address = expected_pda.parse().expect("expected-pda"); diff --git a/foreign-chains/solana/sol-prim/src/tests/signature.rs b/foreign-chains/solana/sol-prim/src/tests/signature.rs index e0bb1019db9..977bf41df8d 100644 --- a/foreign-chains/solana/sol-prim/src/tests/signature.rs +++ b/foreign-chains/solana/sol-prim/src/tests/signature.rs @@ -1,8 +1,8 @@ #[cfg(feature = "str")] +#[cfg(test)] mod from_and_to_str { - use core::fmt::Write; - use crate::{signature::Signature, utils::WriteBuffer}; + use core::fmt::Write; #[test] fn round_trip() { diff --git a/foreign-chains/solana/sol-prim/src/transaction.rs b/foreign-chains/solana/sol-prim/src/transaction.rs new file mode 100644 index 00000000000..6ef94f1c4cd --- /dev/null +++ b/foreign-chains/solana/sol-prim/src/transaction.rs @@ -0,0 +1,876 @@ +use crate::{ + compile_instructions, consts::MESSAGE_VERSION_PREFIX, short_vec, AddressLookupTableAccount, + CompiledInstruction, CompiledKeys, Hash, Instruction, MessageAddressTableLookup, MessageHeader, + Pubkey, RawSignature, Signature, +}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_std::{vec, vec::Vec}; + +use serde::{ + de::{self, SeqAccess, Unexpected, Visitor}, + ser::SerializeTuple, + Deserializer, Serializer, +}; +use sp_std::fmt; + +#[cfg(any(test, feature = "runtime-integration-tests"))] +use crate::{ + errors::TransactionError, + signer::{Signer, SignerError, TestSigners}, +}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub enum TransactionVersion { + Legacy(Legacy), + Number(u8), +} +impl TransactionVersion { + pub const LEGACY: Self = Self::Legacy(Legacy::Legacy); +} + +/// Type that serializes to the string "legacy" +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Legacy { + Legacy, +} + +/// Either a legacy message or a v0 message. +/// +/// # Serialization +/// +/// If the first bit is set, the remaining 7 bits will be used to determine +/// which message version is serialized starting from version `0`. If the first +/// is bit is not set, all bytes are used to encode the legacy `Message` +/// format. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum VersionedMessage { + Legacy(legacy::LegacyMessage), + V0(v0::VersionedMessageV0), +} + +impl VersionedMessage { + pub fn header(&self) -> &MessageHeader { + match self { + Self::Legacy(message) => &message.header, + Self::V0(message) => &message.header, + } + } + + pub fn static_account_keys(&self) -> &[Pubkey] { + match self { + Self::Legacy(message) => &message.account_keys, + Self::V0(message) => &message.account_keys, + } + } + + pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> { + match self { + Self::Legacy(_) => None, + Self::V0(message) => Some(&message.address_table_lookups), + } + } + + /// Returns true if the account at the specified index signed this + /// message. + pub fn is_signer(&self, index: usize) -> bool { + index < usize::from(self.header().num_required_signatures) + } + + pub fn recent_blockhash(&self) -> &Hash { + match self { + Self::Legacy(message) => &message.recent_blockhash, + Self::V0(message) => &message.recent_blockhash, + } + } + + pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) { + match self { + Self::Legacy(message) => message.recent_blockhash = recent_blockhash, + Self::V0(message) => message.recent_blockhash = recent_blockhash, + } + } + + /// Program instructions that will be executed in sequence and committed in + /// one atomic transaction if all succeed. + pub fn instructions(&self) -> &[CompiledInstruction] { + match self { + Self::Legacy(message) => &message.instructions, + Self::V0(message) => &message.instructions, + } + } + + pub fn serialize(&self) -> Vec { + bincode::serde::encode_to_vec(self, bincode::config::legacy()).unwrap() + } +} + +impl Default for VersionedMessage { + fn default() -> Self { + Self::Legacy(legacy::LegacyMessage::default()) + } +} + +impl serde::Serialize for VersionedMessage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Self::Legacy(message) => { + let mut seq = serializer.serialize_tuple(1)?; + seq.serialize_element(message)?; + seq.end() + }, + Self::V0(message) => { + let mut seq = serializer.serialize_tuple(2)?; + seq.serialize_element(&MESSAGE_VERSION_PREFIX)?; + seq.serialize_element(message)?; + seq.end() + }, + } + } +} +enum MessagePrefix { + Legacy(u8), + Versioned(u8), +} + +#[allow(clippy::needless_lifetimes)] +impl<'de> serde::Deserialize<'de> for MessagePrefix { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct PrefixVisitor; + + impl<'de> Visitor<'de> for PrefixVisitor { + type Value = MessagePrefix; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("message prefix byte") + } + + // Serde's integer visitors bubble up to u64 so check the prefix + // with this function instead of visit_u8. This approach is + // necessary because serde_json directly calls visit_u64 for + // unsigned integers. + fn visit_u64(self, value: u64) -> Result { + if value > u8::MAX as u64 { + Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?; + } + + let byte = value as u8; + if byte & MESSAGE_VERSION_PREFIX != 0 { + Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX)) + } else { + Ok(MessagePrefix::Legacy(byte)) + } + } + } + + deserializer.deserialize_u8(PrefixVisitor) + } +} + +impl<'de> serde::Deserialize<'de> for VersionedMessage { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct MessageVisitor; + + impl<'de> Visitor<'de> for MessageVisitor { + type Value = VersionedMessage; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("message bytes") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let prefix: MessagePrefix = + seq.next_element()?.ok_or_else(|| de::Error::invalid_length(0, &self))?; + + match prefix { + MessagePrefix::Legacy(num_required_signatures) => { + // The remaining fields of the legacy Message struct after the first + // byte. + #[derive(Serialize, Deserialize)] + struct RemainingLegacyMessage { + pub num_readonly_signed_accounts: u8, + pub num_readonly_unsigned_accounts: u8, + #[serde(with = "short_vec")] + pub account_keys: Vec, + pub recent_blockhash: Hash, + #[serde(with = "short_vec")] + pub instructions: Vec, + } + + let message: RemainingLegacyMessage = + seq.next_element()?.ok_or_else(|| { + // will never happen since tuple length is always 2 + de::Error::invalid_length(1, &self) + })?; + + Ok(VersionedMessage::Legacy(legacy::LegacyMessage { + header: MessageHeader { + num_required_signatures, + num_readonly_signed_accounts: message.num_readonly_signed_accounts, + num_readonly_unsigned_accounts: message + .num_readonly_unsigned_accounts, + }, + account_keys: message.account_keys, + recent_blockhash: message.recent_blockhash, + instructions: message.instructions, + })) + }, + MessagePrefix::Versioned(version) => { + match version { + 0 => { + Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else(|| { + // will never happen since tuple length is always 2 + de::Error::invalid_length(1, &self) + })?)) + }, + 127 => { + // 0xff is used as the first byte of the off-chain messages + // which corresponds to version 127 of the versioned messages. + // This explicit check is added to prevent the usage of version + // 127 in the runtime as a valid transaction. + Err(de::Error::custom("off-chain messages are not accepted")) + }, + _ => Err(de::Error::invalid_value( + de::Unexpected::Unsigned(version as u64), + &"a valid transaction message version", + )), + } + }, + } + } + } + + deserializer.deserialize_tuple(2, MessageVisitor) + } +} + +// NOTE: Serialization-related changes must be paired with the direct read at sigverify. +/// An atomic transaction +#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +pub struct VersionedTransaction { + /// List of signatures + #[serde(with = "short_vec")] + pub signatures: Vec, + /// Message to sign. + pub message: VersionedMessage, +} + +impl From for VersionedTransaction { + fn from(transaction: legacy::LegacyTransaction) -> Self { + Self { + signatures: transaction.signatures, + message: VersionedMessage::Legacy(transaction.message), + } + } +} + +impl VersionedTransaction { + pub fn new_unsigned(message: VersionedMessage) -> Self { + Self { + signatures: vec![ + Signature::default(); + message.header().num_required_signatures as usize + ], + message, + } + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn new_with_payer( + instructions: &[Instruction], + payer: Option, + lookup_tables: &[AddressLookupTableAccount], + ) -> Self { + let message = + VersionedMessage::V0(v0::VersionedMessageV0::new(instructions, payer, lookup_tables)); + Self::new_unsigned(message) + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn sign(&mut self, signers: TestSigners, recent_blockhash: Hash) { + let positions = self.get_signing_keypair_positions(signers.pubkeys()); + + // if you change the blockhash, you're re-signing... + if recent_blockhash != *self.message.recent_blockhash() { + self.message.set_recent_blockhash(recent_blockhash); + self.signatures + .iter_mut() + .for_each(|signature| *signature = Signature::default()); + } + + let signatures = signers + .try_sign_message(&self.message_data()) + .expect("Transaction signing should never fail."); + for i in 0..positions.len() { + self.signatures[positions[i]] = signatures[i]; + } + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn get_signing_keypair_positions(&self, pubkeys: Vec) -> Vec { + let account_keys = self.message.static_account_keys(); + let required_sigs = self.message.header().num_required_signatures as usize; + if account_keys.len() < required_sigs { + panic!("Too many signing keys provided."); + } + + let signed_keys = account_keys[0..required_sigs].to_vec(); + + pubkeys + .iter() + .map(|pubkey| signed_keys.iter().position(|x| x == pubkey)) + .map(|index| index.expect("Signing key must be part of account_keys.")) + .collect() + } + + pub fn is_signed(&self) -> bool { + self.signatures.iter().all(|signature| *signature != Signature::default()) + } + + /// Return the message containing all data that should be signed. + pub fn message(&self) -> &VersionedMessage { + &self.message + } + + /// Return the serialized message data to sign. + pub fn message_data(&self) -> Vec { + self.message().serialize() + } + + /// Due to different Serialization between Signature and Solana native Signature type, + /// the Signatures needs to be converted into the RawSignature type before the + /// transaction is serialized as whole. + pub fn finalize_and_serialize(self) -> Result, bincode::error::EncodeError> { + bincode::serde::encode_to_vec(RawTransaction::from(self), bincode::config::legacy()) + } + + /// Returns the version of the transaction + pub fn version(&self) -> TransactionVersion { + match self.message { + VersionedMessage::Legacy(_) => TransactionVersion::LEGACY, + VersionedMessage::V0(_) => TransactionVersion::Number(0), + } + } + + /// Returns a legacy transaction if the transaction message is legacy. + pub fn into_legacy_transaction(self) -> Option { + match self.message { + VersionedMessage::Legacy(message) => + Some(legacy::LegacyTransaction { signatures: self.signatures, message }), + _ => None, + } + } +} + +pub mod v0 { + use super::*; + use crate::{AccountKeys, CompileError, MessageAddressTableLookup}; + + #[cfg(test)] + use crate::instructions::program_instructions; + + /// A Solana transaction message (v0). + /// + /// This message format supports succinct account loading with + /// on-chain address lookup tables. + /// + /// See the [`message`] module documentation for further description. + /// + /// [`message`]: crate::message + #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)] + #[serde(rename_all = "camelCase")] + pub struct VersionedMessageV0 { + /// The message header, identifying signed and read-only `account_keys`. + /// Header values only describe static `account_keys`, they do not describe + /// any additional account keys loaded via address table lookups. + pub header: MessageHeader, + + /// List of accounts loaded by this transaction. + #[serde(with = "short_vec")] + pub account_keys: Vec, + + /// The blockhash of a recent block. + pub recent_blockhash: Hash, + + /// Instructions that invoke a designated program, are executed in sequence, + /// and committed in one atomic transaction if all succeed. + /// + /// # Notes + /// + /// Program indexes must index into the list of message `account_keys` because + /// program id's cannot be dynamically loaded from a lookup table. + /// + /// Account indexes must index into the list of addresses + /// constructed from the concatenation of three key lists: + /// 1) message `account_keys` + /// 2) ordered list of keys loaded from `writable` lookup table indexes + /// 3) ordered list of keys loaded from `readable` lookup table indexes + #[serde(with = "short_vec")] + pub instructions: Vec, + + /// List of address table lookups used to load additional accounts + /// for this transaction. + #[serde(with = "short_vec")] + pub address_table_lookups: Vec, + } + + impl VersionedMessageV0 { + pub fn new_with_blockhash( + instructions: &[Instruction], + payer: Option, + blockhash: Hash, + lookup_tables: &[AddressLookupTableAccount], + ) -> Self { + Self::try_compile(payer, instructions, lookup_tables, blockhash) + .expect("Message construction should never fail.") + } + + pub fn new( + instructions: &[Instruction], + payer: Option, + lookup_tables: &[AddressLookupTableAccount], + ) -> Self { + Self::new_with_blockhash(instructions, payer, Hash::default(), lookup_tables) + } + + #[cfg(test)] + pub fn new_with_nonce( + mut instructions: Vec, + payer: Option, + nonce_account_pubkey: &Pubkey, + nonce_authority_pubkey: &Pubkey, + lookup_tables: &[AddressLookupTableAccount], + ) -> Self { + let nonce_ix = program_instructions::SystemProgramInstruction::advance_nonce_account( + nonce_account_pubkey, + nonce_authority_pubkey, + ); + instructions.insert(0, nonce_ix); + Self::new(&instructions, payer, lookup_tables) + } + + /// Create a signable transaction message from a `payer` public key, + /// `recent_blockhash`, list of `instructions`, and a list of + /// `address_lookup_table_accounts`. + /// + /// # Examples + /// + /// This example uses the [`solana_rpc_client`], [`solana_sdk`], and [`anyhow`] crates. + /// + /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client + /// [`solana_sdk`]: https://docs.rs/solana-sdk + /// [`anyhow`]: https://docs.rs/anyhow + /// + /// ```ignore + /// # use solana_program::example_mocks::{ + /// # solana_rpc_client, + /// # solana_sdk, + /// # }; + /// # use std::borrow::Cow; + /// # use solana_sdk::account::Account; + /// use anyhow::Result; + /// use solana_rpc_client::rpc_client::RpcClient; + /// use solana_program::address_lookup_table::{self, state::{AddressLookupTable, LookupTableMeta}}; + /// use solana_sdk::{ + /// address_lookup_table::AddressLookupTableAccount, + /// instruction::{AccountMeta, Instruction}, + /// message::{VersionedMessage, v0}, + /// pubkey::Pubkey, + /// signature::{Keypair, Signer}, + /// transaction::VersionedTransaction, + /// }; + /// + /// fn create_tx_with_address_table_lookup( + /// client: &RpcClient, + /// instruction: Instruction, + /// address_lookup_table_key: Pubkey, + /// payer: &Keypair, + /// ) -> Result { + /// # client.set_get_account_response(address_lookup_table_key, Account { + /// # lamports: 1, + /// # data: AddressLookupTable { + /// # meta: LookupTableMeta::default(), + /// # addresses: Cow::Owned(instruction.accounts.iter().map(|meta| meta.pubkey).collect()), + /// # }.serialize_for_tests().unwrap(), + /// # owner: address_lookup_table::program::id(), + /// # executable: false, + /// # rent_epoch: 1, + /// # }); + /// let raw_account = client.get_account(&address_lookup_table_key)?; + /// let address_lookup_table = AddressLookupTable::deserialize(&raw_account.data)?; + /// let address_lookup_table_account = AddressLookupTableAccount { + /// key: address_lookup_table_key, + /// addresses: address_lookup_table.addresses.to_vec(), + /// }; + /// + /// let blockhash = client.get_latest_blockhash()?; + /// let tx = VersionedTransaction::try_new( + /// VersionedMessage::V0(v0::Message::try_compile( + /// &payer.pubkey(), + /// &[instruction], + /// &[address_lookup_table_account], + /// blockhash, + /// )?), + /// &[payer], + /// )?; + /// + /// # assert!(tx.message.address_table_lookups().unwrap().len() > 0); + /// Ok(tx) + /// } + /// # + /// # let client = RpcClient::new(String::new()); + /// # let payer = Keypair::new(); + /// # let address_lookup_table_key = Pubkey::new_unique(); + /// # let instruction = Instruction::new_with_bincode(Pubkey::new_unique(), &(), vec![ + /// # AccountMeta::new(Pubkey::new_unique(), false), + /// # ]); + /// # create_tx_with_address_table_lookup(&client, instruction, address_lookup_table_key, &payer)?; + /// # Ok::<(), anyhow::Error>(()) + /// ``` + pub fn try_compile( + payer: Option, + instructions: &[Instruction], + address_lookup_table_accounts: &[AddressLookupTableAccount], + recent_blockhash: Hash, + ) -> Result { + let mut compiled_keys = CompiledKeys::compile(instructions, payer); + + let mut address_table_lookups = Vec::with_capacity(address_lookup_table_accounts.len()); + let mut loaded_addresses_list = Vec::with_capacity(address_lookup_table_accounts.len()); + for lookup_table_account in address_lookup_table_accounts { + if let Some((lookup, loaded_addresses)) = + compiled_keys.try_extract_table_lookup(lookup_table_account)? + { + address_table_lookups.push(lookup); + loaded_addresses_list.push(loaded_addresses); + } + } + + let (header, static_keys) = compiled_keys.try_into_message_components()?; + let dynamic_keys = loaded_addresses_list.into_iter().collect(); + let account_keys = AccountKeys::new(&static_keys, Some(&dynamic_keys)); + let instructions = account_keys.try_compile_instructions(instructions)?; + + Ok(Self { + header, + account_keys: static_keys, + recent_blockhash, + instructions, + address_table_lookups, + }) + } + + /// Serialize this message with a version #0 prefix using bincode encoding. + /// MODIFIED: use `encode_to_vec` instead - since special version don't have `serialize()`. + pub fn serialize(&self) -> Vec { + bincode::serde::encode_to_vec(self, bincode::config::legacy()).unwrap() + } + } +} + +pub mod legacy { + use super::*; + + #[cfg(test)] + use crate::instructions::program_instructions; + + /// A Solana transaction message (legacy). + /// + /// See the [`message`] module documentation for further description. + /// + /// [`message`]: crate::message + /// + /// Some constructors accept an optional `payer`, the account responsible for + /// paying the cost of executing a transaction. In most cases, callers should + /// specify the payer explicitly in these constructors. In some cases though, + /// the caller is not _required_ to specify the payer, but is still allowed to: + /// in the `Message` structure, the first account is always the fee-payer, so if + /// the caller has knowledge that the first account of the constructed + /// transaction's `Message` is both a signer and the expected fee-payer, then + /// redundantly specifying the fee-payer is not strictly required. + // NOTE: Serialization-related changes must be paired with the custom serialization + // for versioned messages in the `RemainingMessage` struct. + #[derive( + Encode, Decode, TypeInfo, Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, + )] + #[serde(rename_all = "camelCase")] + pub struct LegacyMessage { + /// The message header, identifying signed and read-only `account_keys`. + // NOTE: Serialization-related changes must be paired with the direct read at sigverify. + pub header: MessageHeader, + + /// All the account keys used by this transaction. + #[serde(with = "short_vec")] + pub account_keys: Vec, + + /// The id of a recent ledger entry. + pub recent_blockhash: Hash, + + /// Programs that will be executed in sequence and committed in one atomic transaction if + /// all succeed. + #[serde(with = "short_vec")] + pub instructions: Vec, + } + + impl LegacyMessage { + pub fn new_with_blockhash( + instructions: &[Instruction], + payer: Option<&Pubkey>, + blockhash: &Hash, + ) -> Self { + let compiled_keys = CompiledKeys::compile(instructions, payer.cloned()); + let (header, account_keys) = compiled_keys + .try_into_message_components() + .expect("overflow when compiling message keys"); + let instructions = compile_instructions(instructions, &account_keys); + Self::new_with_compiled_instructions( + header.num_required_signatures, + header.num_readonly_signed_accounts, + header.num_readonly_unsigned_accounts, + account_keys, + *blockhash, + instructions, + ) + } + + pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self { + Self::new_with_blockhash(instructions, payer, &Hash::default()) + } + + #[cfg(test)] + pub fn new_with_nonce( + mut instructions: Vec, + payer: Option<&Pubkey>, + nonce_account_pubkey: &Pubkey, + nonce_authority_pubkey: &Pubkey, + ) -> Self { + let nonce_ix = program_instructions::SystemProgramInstruction::advance_nonce_account( + nonce_account_pubkey, + nonce_authority_pubkey, + ); + instructions.insert(0, nonce_ix); + Self::new(&instructions, payer) + } + + fn new_with_compiled_instructions( + num_required_signatures: u8, + num_readonly_signed_accounts: u8, + num_readonly_unsigned_accounts: u8, + account_keys: Vec, + recent_blockhash: Hash, + instructions: Vec, + ) -> Self { + Self { + header: MessageHeader { + num_required_signatures, + num_readonly_signed_accounts, + num_readonly_unsigned_accounts, + }, + account_keys, + recent_blockhash, + instructions, + } + } + + pub fn serialize(&self) -> Vec { + bincode::serde::encode_to_vec(self, bincode::config::legacy()).unwrap() + } + } + + /// An atomically-committed sequence of instructions. + /// + /// While [`Instruction`]s are the basic unit of computation in Solana, + /// they are submitted by clients in [`Transaction`]s containing one or + /// more instructions, and signed by one or more [`Signer`]s. + /// + /// [`Signer`]: crate::signer::Signer + /// + /// See the [module documentation] for more details about transactions. + /// + /// [module documentation]: self + /// + /// Some constructors accept an optional `payer`, the account responsible for + /// paying the cost of executing a transaction. In most cases, callers should + /// specify the payer explicitly in these constructors. In some cases though, + /// the caller is not _required_ to specify the payer, but is still allowed to: + /// in the [`Message`] structure, the first account is always the fee-payer, so + /// if the caller has knowledge that the first account of the constructed + /// transaction's `Message` is both a signer and the expected fee-payer, then + /// redundantly specifying the fee-payer is not strictly required. + #[derive( + Encode, Decode, TypeInfo, Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, + )] + pub struct LegacyTransaction { + /// A set of signatures of a serialized [`Message`], signed by the first + /// keys of the `Message`'s [`account_keys`], where the number of signatures + /// is equal to [`num_required_signatures`] of the `Message`'s + /// [`MessageHeader`]. + /// + /// [`account_keys`]: Message::account_keys + /// [`MessageHeader`]: crate::message::MessageHeader + /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures + // NOTE: Serialization-related changes must be paired with the direct read at sigverify. + #[serde(with = "short_vec")] + pub signatures: Vec, + + /// The message to sign. + pub message: LegacyMessage, + } + + impl LegacyTransaction { + pub fn new_unsigned(message: LegacyMessage) -> Self { + Self { + signatures: vec![ + Signature::default(); + message.header.num_required_signatures as usize + ], + message, + } + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self { + let message = LegacyMessage::new(instructions, payer); + Self::new_unsigned(message) + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn sign(&mut self, signers: TestSigners, recent_blockhash: Hash) { + if let Err(e) = self.try_sign(signers, recent_blockhash) { + panic!("Transaction::sign failed with error {e:?}"); + } + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn try_sign( + &mut self, + signers: TestSigners, + recent_blockhash: Hash, + ) -> Result<(), SignerError> { + self.try_partial_sign(signers, recent_blockhash)?; + + if !self.is_signed() { + Err(SignerError::NotEnoughSigners) + } else { + Ok(()) + } + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn try_partial_sign( + &mut self, + signers: TestSigners, + recent_blockhash: Hash, + ) -> Result<(), SignerError> { + let positions = self.get_signing_keypair_positions(signers.pubkeys())?; + if positions.iter().any(|pos| pos.is_none()) { + return Err(SignerError::KeypairPubkeyMismatch) + } + let positions: Vec = positions.iter().map(|pos| pos.unwrap()).collect(); + self.try_partial_sign_unchecked(signers, positions, recent_blockhash) + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn try_partial_sign_unchecked( + &mut self, + signers: TestSigners, + positions: Vec, + recent_blockhash: Hash, + ) -> Result<(), SignerError> { + // if you change the blockhash, you're re-signing... + if recent_blockhash != self.message.recent_blockhash { + self.message.recent_blockhash = recent_blockhash; + self.signatures + .iter_mut() + .for_each(|signature| *signature = Signature::default()); + } + + let signatures = signers.try_sign_message(&self.message_data())?; + for i in 0..positions.len() { + self.signatures[positions[i]] = signatures[i]; + } + Ok(()) + } + + #[cfg(any(test, feature = "runtime-integration-tests"))] + pub fn get_signing_keypair_positions( + &self, + pubkeys: Vec, + ) -> Result>, TransactionError> { + if self.message.account_keys.len() < + self.message.header.num_required_signatures as usize + { + return Err(TransactionError::InvalidAccountIndex) + } + let signed_keys = + &self.message.account_keys[0..self.message.header.num_required_signatures as usize]; + + Ok(pubkeys + .iter() + .map(|pubkey| signed_keys.iter().position(|x| x == pubkey)) + .collect()) + } + + pub fn is_signed(&self) -> bool { + self.signatures.iter().all(|signature| *signature != Signature::default()) + } + + /// Return the message containing all data that should be signed. + pub fn message(&self) -> &LegacyMessage { + &self.message + } + + /// Return the serialized message data to sign. + pub fn message_data(&self) -> Vec { + self.message().serialize() + } + + /// Due to different Serialization between Signature and Solana native Signature type, + /// the Signatures needs to be converted into the RawSignature type before the + /// transaction is serialized as whole. + pub fn finalize_and_serialize(self) -> Result, bincode::error::EncodeError> { + bincode::serde::encode_to_vec(RawTransaction::from(self), bincode::config::legacy()) + } + } +} + +/// Internal raw transaction type used for correct Serialization and Encoding +#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +struct RawTransaction { + #[serde(with = "short_vec")] + pub signatures: Vec, + pub message: Message, +} + +impl From for RawTransaction { + fn from(from: legacy::LegacyTransaction) -> Self { + Self { + signatures: from.signatures.into_iter().map(RawSignature::from).collect(), + message: from.message, + } + } +} + +impl From for RawTransaction { + fn from(from: VersionedTransaction) -> Self { + Self { + signatures: from.signatures.into_iter().map(RawSignature::from).collect(), + message: from.message, + } + } +} diff --git a/state-chain/chains/Cargo.toml b/state-chain/chains/Cargo.toml index c0cfdc9fc56..76134692708 100644 --- a/state-chain/chains/Cargo.toml +++ b/state-chain/chains/Cargo.toml @@ -47,7 +47,6 @@ bincode = { version = "2.0.0-rc.3", default-features = false, features = [ "serde", ] } curve25519-dalek = { workspace = true, optional = true } -sha2-const = { workspace = true } ed25519-dalek = { workspace = true, optional = true } # Other @@ -72,11 +71,11 @@ sp-std = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +sha2-const = { workspace = true } [dev-dependencies] cf-test-utilities = { workspace = true } serde_json = { workspace = true } -heck = { workspace = true } rand = { workspace = true, features = ["std"] } ed25519-dalek = { workspace = true, features = ["rand_core"] } @@ -132,4 +131,4 @@ try-runtime = [ "cf-runtime-utilities/try-runtime", ] -runtime-integration-tests = ["std", "dep:rand", "ed25519-dalek/rand_core"] +runtime-integration-tests = ["std", "dep:rand", "ed25519-dalek/rand_core", "sol-prim/runtime-integration-tests"] diff --git a/state-chain/chains/src/ccm_checker.rs b/state-chain/chains/src/ccm_checker.rs index 81a08dab1f4..fdf16f67702 100644 --- a/state-chain/chains/src/ccm_checker.rs +++ b/state-chain/chains/src/ccm_checker.rs @@ -1,15 +1,17 @@ use crate::{ address::EncodedAddress, - sol::{SolAsset, SolCcmAccounts, SolPubkey, MAX_CCM_BYTES_SOL, MAX_CCM_BYTES_USDC}, + sol::{ + sol_tx_core::consts::{ + ACCOUNT_KEY_LENGTH_IN_TRANSACTION, ACCOUNT_REFERENCE_LENGTH_IN_TRANSACTION, + SYSTEM_PROGRAM_ID, SYS_VAR_INSTRUCTIONS, TOKEN_PROGRAM_ID, + }, + SolAsset, SolCcmAccounts, SolPubkey, MAX_CCM_BYTES_SOL, MAX_CCM_BYTES_USDC, + }, CcmChannelMetadata, }; use cf_primitives::{Asset, ForeignChain}; use codec::{Decode, Encode}; use scale_info::TypeInfo; -use sol_prim::consts::{ - ACCOUNT_KEY_LENGTH_IN_TRANSACTION, ACCOUNT_REFERENCE_LENGTH_IN_TRANSACTION, SYSTEM_PROGRAM_ID, - SYS_VAR_INSTRUCTIONS, TOKEN_PROGRAM_ID, -}; use sp_runtime::DispatchError; use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 9146e4d1a12..386caec568a 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -3,17 +3,21 @@ #![feature(extract_if)] #![feature(split_array)] use crate::{ - btc::BitcoinCrypto, dot::PolkadotCrypto, evm::EvmCrypto, none::NoneChainCrypto, - sol::SolanaCrypto, + benchmarking_value::{BenchmarkValue, BenchmarkValueExtended}, + btc::BitcoinCrypto, + dot::PolkadotCrypto, + evm::EvmCrypto, + none::NoneChainCrypto, + sol::{ + api::SolanaTransactionBuildingError, + sol_tx_core::instructions::program_instructions::swap_endpoints::types::CcmParams, + SolanaCrypto, SolanaTransactionInId, + }, }; use core::{fmt::Display, iter::Step}; use sol::api::VaultSwapAccountAndSender; use sp_std::marker::PhantomData; -use crate::{ - benchmarking_value::{BenchmarkValue, BenchmarkValueExtended}, - sol::{api::SolanaTransactionBuildingError, SolanaTransactionInId}, -}; pub use address::ForeignChainAddress; use address::{ AddressConverter, AddressDerivationApi, AddressDerivationError, EncodedAddress, @@ -774,6 +778,12 @@ impl BenchmarkValue for CcmChannelMetadata { } } +impl From for CcmParams { + fn from(ccm: crate::CcmChannelMetadata) -> Self { + CcmParams { message: ccm.message.to_vec(), gas_amount: ccm.gas_budget as u64 } + } +} + #[derive( Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, Serialize, Deserialize, PartialOrd, Ord, )] diff --git a/state-chain/chains/src/sol.rs b/state-chain/chains/src/sol.rs index ad34169846a..cb847c9cf6d 100644 --- a/state-chain/chains/src/sol.rs +++ b/state-chain/chains/src/sol.rs @@ -4,11 +4,11 @@ use cf_primitives::ChannelId; use sp_core::ConstBool; use sp_std::{vec, vec::Vec}; -use sol_prim::{AccountBump, SlotNumber}; - use crate::{ - address, assets, DepositChannel, DepositDetailsToTransactionInId, FeeEstimationApi, - FeeRefundCalculator, TypeInfo, + address, assets, + sol::sol_tx_core::{AccountBump, SlotNumber}, + DepositChannel, DepositDetailsToTransactionInId, FeeEstimationApi, FeeRefundCalculator, + TypeInfo, }; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{sp_runtime::RuntimeDebug, Parameter}; @@ -21,6 +21,7 @@ pub mod api; pub mod benchmarking; pub mod instruction_builder; pub mod sol_tx_core; +mod tests; pub mod transaction_builder; pub use crate::assets::sol::Asset as SolAsset; @@ -31,14 +32,16 @@ pub use sol_prim::{ TOKEN_ACCOUNT_RENT, }, pda::{Pda as DerivedAddressBuilder, PdaError as AddressDerivationError}, + transaction::legacy::{ + LegacyMessage as SolLegacyMessage, LegacyTransaction as SolLegacyTransaction, + }, Address as SolAddress, Amount as SolAmount, ComputeLimit as SolComputeLimit, Digest as SolHash, - Signature as SolSignature, SlotNumber as SolBlockNumber, + Hash as RawSolHash, Instruction as SolInstruction, InstructionRpc as SolInstructionRpc, + Pubkey as SolPubkey, Signature as SolSignature, SlotNumber as SolBlockNumber, }; pub use sol_tx_core::{ rpc_types, AccountMeta as SolAccountMeta, CcmAccounts as SolCcmAccounts, - CcmAddress as SolCcmAddress, Hash as RawSolHash, Instruction as SolInstruction, - InstructionRpc as SolInstructionRpc, LegacyMessage as SolLegacyMessage, - LegacyTransaction as SolLegacyTransaction, Pubkey as SolPubkey, + CcmAddress as SolCcmAddress, }; // Due to transaction size limit in Solana, we have a limit on number of fetches in a solana fetch diff --git a/state-chain/chains/src/sol/api.rs b/state-chain/chains/src/sol/api.rs index f2a59d40881..491f735fd50 100644 --- a/state-chain/chains/src/sol/api.rs +++ b/state-chain/chains/src/sol/api.rs @@ -5,7 +5,6 @@ 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}; @@ -15,8 +14,12 @@ use crate::{ DecodedCcmAdditionalData, VersionedSolanaCcmAdditionalData, }, sol::{ - transaction_builder::SolanaTransactionBuilder, SolAddress, SolAmount, SolApiEnvironment, - SolAsset, SolHash, SolLegacyTransaction, SolTrackedData, SolanaCrypto, + sol_tx_core::{ + address_derivation::derive_associated_token_account, consts::SOL_USDC_DECIMAL, + }, + transaction_builder::SolanaTransactionBuilder, + SolAddress, SolAmount, SolApiEnvironment, SolAsset, SolHash, SolLegacyTransaction, + SolTrackedData, SolanaCrypto, }, AllBatch, AllBatchError, ApiCall, CcmChannelMetadata, ChainCrypto, ChainEnvironment, ConsolidateCall, ConsolidationError, ExecutexSwapAndCall, ExecutexSwapAndCallError, @@ -264,8 +267,7 @@ impl SolanaApi { compute_price, ), SolAsset::SolUsdc => { - let ata = - crate::sol::sol_tx_core::address_derivation::derive_associated_token_account( + let ata = derive_associated_token_account( transfer_param.to, sol_api_environment.usdc_token_mint_pubkey, ) @@ -421,12 +423,11 @@ impl SolanaApi { compute_limit, ), SolAsset::SolUsdc => { - let ata = - crate::sol::sol_tx_core::address_derivation::derive_associated_token_account( - transfer_param.to, - sol_api_environment.usdc_token_mint_pubkey, - ) - .map_err(SolanaTransactionBuildingError::FailedToDeriveAddress)?; + let ata = derive_associated_token_account( + transfer_param.to, + sol_api_environment.usdc_token_mint_pubkey, + ) + .map_err(SolanaTransactionBuildingError::FailedToDeriveAddress)?; SolanaTransactionBuilder::ccm_transfer_token( ata.address, diff --git a/state-chain/chains/src/sol/instruction_builder.rs b/state-chain/chains/src/sol/instruction_builder.rs index abd3344194c..7e9589beb97 100644 --- a/state-chain/chains/src/sol/instruction_builder.rs +++ b/state-chain/chains/src/sol/instruction_builder.rs @@ -5,13 +5,14 @@ //! Such Instruction can be signed and sent to the Program on Solana directly to invoke //! certain functions. -use sol_prim::consts::{SOL_USDC_DECIMAL, SYSTEM_PROGRAM_ID, TOKEN_PROGRAM_ID}; - use crate::{ address::EncodedAddress, sol::{ - sol_tx_core::program_instructions::swap_endpoints::{ - SwapEndpointProgram, SwapNativeParams, SwapTokenParams, + sol_tx_core::{ + consts::{SOL_USDC_DECIMAL, SYSTEM_PROGRAM_ID, TOKEN_PROGRAM_ID}, + program_instructions::swap_endpoints::{ + SwapEndpointProgram, SwapNativeParams, SwapTokenParams, + }, }, SolAddress, SolAmount, SolApiEnvironment, SolInstruction, SolPubkey, }, @@ -102,8 +103,12 @@ mod test { use crate::{ cf_parameters::build_cf_parameters, sol::{ - signing_key::SolSigningKey, sol_tx_core::sol_test_values::*, SolAddress, SolHash, - SolLegacyMessage, SolLegacyTransaction, + signing_key::SolSigningKey, + sol_tx_core::{ + consts::{const_address, MAX_TRANSACTION_LENGTH}, + sol_test_values::*, + }, + SolAddress, SolHash, SolLegacyMessage, SolLegacyTransaction, }, ChannelRefundParameters, }; @@ -111,7 +116,6 @@ mod test { chains::Solana, AccountId, AffiliateAndFee, AffiliateShortId, BasisPoints, DcaParameters, MAX_AFFILIATES, }; - use sol_prim::consts::{const_address, MAX_TRANSACTION_LENGTH}; use sp_core::ConstU32; use sp_runtime::BoundedVec; diff --git a/state-chain/chains/src/sol/sol_tx_core.rs b/state-chain/chains/src/sol/sol_tx_core.rs index e70088e646a..ff51fe72581 100644 --- a/state-chain/chains/src/sol/sol_tx_core.rs +++ b/state-chain/chains/src/sol/sol_tx_core.rs @@ -6,18 +6,9 @@ use sp_std::vec::Vec; use crate::sol::SolAddress; -pub mod address_derivation; -pub mod bpf_loader_instructions; -pub mod compute_budget; -pub mod program_instructions; -pub mod token_instructions; -pub mod transaction; - pub use sol_prim::*; pub use transaction::legacy::{LegacyMessage, LegacyTransaction}; -pub mod tests; - /// Provides alternative version of internal types that uses `Address` instead of Pubkey: /// /// |----------------------| @@ -139,7 +130,7 @@ pub mod sol_test_values { }, CcmChannelMetadata, CcmDepositMetadata, ForeignChain, ForeignChainAddress, }; - use sol_prim::consts::{const_address, const_hash}; + use sol_prim::consts::{const_address, const_hash, MAX_TRANSACTION_LENGTH}; use sp_std::vec; pub const VAULT_PROGRAM: SolAddress = @@ -317,7 +308,7 @@ pub mod sol_test_values { .finalize_and_serialize() .expect("Transaction serialization must succeed"); - assert!(serialized_tx.len() <= sol_prim::consts::MAX_TRANSACTION_LENGTH); + assert!(serialized_tx.len() <= MAX_TRANSACTION_LENGTH); if serialized_tx != expected_serialized_tx { panic!( diff --git a/state-chain/chains/src/sol/sol_tx_core/transaction.rs b/state-chain/chains/src/sol/sol_tx_core/transaction.rs deleted file mode 100644 index 3a20ce416fd..00000000000 --- a/state-chain/chains/src/sol/sol_tx_core/transaction.rs +++ /dev/null @@ -1,304 +0,0 @@ -use crate::sol::{ - sol_tx_core::{ - compile_instructions, short_vec, CompiledInstruction, CompiledKeys, Hash, Instruction, - MessageHeader, Pubkey, RawSignature, - }, - SolSignature, -}; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; -#[cfg(any(test, feature = "runtime-integration-tests"))] -use sol_prim::errors::TransactionError; -use sp_std::{vec, vec::Vec}; - -#[cfg(any(test, feature = "runtime-integration-tests"))] -use crate::sol::sol_tx_core::signer::{Signer, SignerError, TestSigners}; - -#[cfg(test)] -use crate::sol::sol_tx_core::program_instructions; - -pub mod versioned_v0 {} - -pub mod legacy { - use super::*; - - /// A Solana transaction message (legacy). - /// - /// See the [`message`] module documentation for further description. - /// - /// [`message`]: crate::message - /// - /// Some constructors accept an optional `payer`, the account responsible for - /// paying the cost of executing a transaction. In most cases, callers should - /// specify the payer explicitly in these constructors. In some cases though, - /// the caller is not _required_ to specify the payer, but is still allowed to: - /// in the `Message` structure, the first account is always the fee-payer, so if - /// the caller has knowledge that the first account of the constructed - /// transaction's `Message` is both a signer and the expected fee-payer, then - /// redundantly specifying the fee-payer is not strictly required. - // NOTE: Serialization-related changes must be paired with the custom serialization - // for versioned messages in the `RemainingMessage` struct. - #[derive( - Encode, Decode, TypeInfo, Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, - )] - #[serde(rename_all = "camelCase")] - pub struct LegacyMessage { - /// The message header, identifying signed and read-only `account_keys`. - // NOTE: Serialization-related changes must be paired with the direct read at sigverify. - pub header: MessageHeader, - - /// All the account keys used by this transaction. - #[serde(with = "short_vec")] - pub account_keys: Vec, - - /// The id of a recent ledger entry. - pub recent_blockhash: Hash, - - /// Programs that will be executed in sequence and committed in one atomic transaction if - /// all succeed. - #[serde(with = "short_vec")] - pub instructions: Vec, - } - - impl LegacyMessage { - pub fn new_with_blockhash( - instructions: &[Instruction], - payer: Option<&Pubkey>, - blockhash: &Hash, - ) -> Self { - let compiled_keys = CompiledKeys::compile(instructions, payer.cloned()); - let (header, account_keys) = compiled_keys - .try_into_message_components() - .expect("overflow when compiling message keys"); - let instructions = compile_instructions(instructions, &account_keys); - Self::new_with_compiled_instructions( - header.num_required_signatures, - header.num_readonly_signed_accounts, - header.num_readonly_unsigned_accounts, - account_keys, - *blockhash, - instructions, - ) - } - - pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self { - Self::new_with_blockhash(instructions, payer, &Hash::default()) - } - - #[cfg(test)] - pub fn new_with_nonce( - mut instructions: Vec, - payer: Option<&Pubkey>, - nonce_account_pubkey: &Pubkey, - nonce_authority_pubkey: &Pubkey, - ) -> Self { - let nonce_ix = program_instructions::SystemProgramInstruction::advance_nonce_account( - nonce_account_pubkey, - nonce_authority_pubkey, - ); - instructions.insert(0, nonce_ix); - Self::new(&instructions, payer) - } - - fn new_with_compiled_instructions( - num_required_signatures: u8, - num_readonly_signed_accounts: u8, - num_readonly_unsigned_accounts: u8, - account_keys: Vec, - recent_blockhash: Hash, - instructions: Vec, - ) -> Self { - Self { - header: MessageHeader { - num_required_signatures, - num_readonly_signed_accounts, - num_readonly_unsigned_accounts, - }, - account_keys, - recent_blockhash, - instructions, - } - } - - pub fn serialize(&self) -> Vec { - bincode::serde::encode_to_vec(self, bincode::config::legacy()).unwrap() - } - } - - /// An atomically-committed sequence of instructions. - /// - /// While [`Instruction`]s are the basic unit of computation in Solana, - /// they are submitted by clients in [`Transaction`]s containing one or - /// more instructions, and signed by one or more [`Signer`]s. - /// - /// [`Signer`]: crate::signer::Signer - /// - /// See the [module documentation] for more details about transactions. - /// - /// [module documentation]: self - /// - /// Some constructors accept an optional `payer`, the account responsible for - /// paying the cost of executing a transaction. In most cases, callers should - /// specify the payer explicitly in these constructors. In some cases though, - /// the caller is not _required_ to specify the payer, but is still allowed to: - /// in the [`Message`] structure, the first account is always the fee-payer, so - /// if the caller has knowledge that the first account of the constructed - /// transaction's `Message` is both a signer and the expected fee-payer, then - /// redundantly specifying the fee-payer is not strictly required. - #[derive( - Encode, Decode, TypeInfo, Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, - )] - pub struct LegacyTransaction { - /// A set of signatures of a serialized [`Message`], signed by the first - /// keys of the `Message`'s [`account_keys`], where the number of signatures - /// is equal to [`num_required_signatures`] of the `Message`'s - /// [`MessageHeader`]. - /// - /// [`account_keys`]: Message::account_keys - /// [`MessageHeader`]: crate::message::MessageHeader - /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures - // NOTE: Serialization-related changes must be paired with the direct read at sigverify. - #[serde(with = "short_vec")] - pub signatures: Vec, - - /// The message to sign. - pub message: LegacyMessage, - } - - impl LegacyTransaction { - pub fn new_unsigned(message: LegacyMessage) -> Self { - Self { - signatures: vec![ - SolSignature::default(); - message.header.num_required_signatures as usize - ], - message, - } - } - - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self { - let message = LegacyMessage::new(instructions, payer); - Self::new_unsigned(message) - } - - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn sign(&mut self, signers: TestSigners, recent_blockhash: Hash) { - if let Err(e) = self.try_sign(signers, recent_blockhash) { - panic!("Transaction::sign failed with error {e:?}"); - } - } - - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn try_sign( - &mut self, - signers: TestSigners, - recent_blockhash: Hash, - ) -> Result<(), SignerError> { - self.try_partial_sign(signers, recent_blockhash)?; - - if !self.is_signed() { - Err(SignerError::NotEnoughSigners) - } else { - Ok(()) - } - } - - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn try_partial_sign( - &mut self, - signers: TestSigners, - recent_blockhash: Hash, - ) -> Result<(), SignerError> { - let positions = self.get_signing_keypair_positions(signers.pubkeys())?; - if positions.iter().any(|pos| pos.is_none()) { - return Err(SignerError::KeypairPubkeyMismatch) - } - let positions: Vec = positions.iter().map(|pos| pos.unwrap()).collect(); - self.try_partial_sign_unchecked(signers, positions, recent_blockhash) - } - - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn try_partial_sign_unchecked( - &mut self, - signers: TestSigners, - positions: Vec, - recent_blockhash: Hash, - ) -> Result<(), SignerError> { - // if you change the blockhash, you're re-signing... - if recent_blockhash != self.message.recent_blockhash { - self.message.recent_blockhash = recent_blockhash; - self.signatures - .iter_mut() - .for_each(|signature| *signature = SolSignature::default()); - } - - let signatures = signers.try_sign_message(&self.message_data())?; - for i in 0..positions.len() { - self.signatures[positions[i]] = signatures[i]; - } - Ok(()) - } - - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn get_signing_keypair_positions( - &self, - pubkeys: Vec, - ) -> Result>, TransactionError> { - if self.message.account_keys.len() < - self.message.header.num_required_signatures as usize - { - return Err(TransactionError::InvalidAccountIndex) - } - let signed_keys = - &self.message.account_keys[0..self.message.header.num_required_signatures as usize]; - - Ok(pubkeys - .iter() - .map(|pubkey| signed_keys.iter().position(|x| x == pubkey)) - .collect()) - } - - pub fn is_signed(&self) -> bool { - self.signatures.iter().all(|signature| *signature != SolSignature::default()) - } - - /// Return the message containing all data that should be signed. - pub fn message(&self) -> &LegacyMessage { - &self.message - } - - /// Return the serialized message data to sign. - pub fn message_data(&self) -> Vec { - self.message().serialize() - } - - /// Due to different Serialization between SolSignature and Solana native Signature type, - /// the SolSignatures needs to be converted into the RawSignature type before the - /// transaction is serialized as whole. - pub fn finalize_and_serialize(self) -> Result, bincode::error::EncodeError> { - bincode::serde::encode_to_vec( - LegacyRawTransaction::from(self), - bincode::config::legacy(), - ) - } - } - - /// Internal raw transaction type used for correct Serialization and Encoding - #[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] - struct LegacyRawTransaction { - #[serde(with = "short_vec")] - pub signatures: Vec, - pub message: LegacyMessage, - } - - impl From for LegacyRawTransaction { - fn from(from: LegacyTransaction) -> Self { - Self { - signatures: from.signatures.into_iter().map(RawSignature::from).collect(), - message: from.message, - } - } - } -} diff --git a/state-chain/chains/src/sol/sol_tx_core/tests.rs b/state-chain/chains/src/sol/tests.rs similarity index 92% rename from state-chain/chains/src/sol/sol_tx_core/tests.rs rename to state-chain/chains/src/sol/tests.rs index ab37fc5bd19..bf50db4d7df 100644 --- a/state-chain/chains/src/sol/sol_tx_core/tests.rs +++ b/state-chain/chains/src/sol/tests.rs @@ -11,12 +11,16 @@ use crate::{ derive_token_supported_account, }, compute_budget::ComputeBudgetInstruction, + consts::{ + MAX_TRANSACTION_LENGTH, SOL_USDC_DECIMAL, SYSTEM_PROGRAM_ID, SYS_VAR_INSTRUCTIONS, + TOKEN_PROGRAM_ID, + }, program_instructions::{InstructionExt, SystemProgramInstruction, VaultProgram}, signer::Signer, sol_test_values::*, token_instructions::AssociatedTokenAccountInstruction, AccountMeta, CompiledInstruction, Hash, Instruction, LegacyMessage, LegacyTransaction, - MessageHeader, Pubkey, + MessageHeader, PdaAndBump, Pubkey, }, SolAddress, SolHash, SolSignature, }, @@ -25,14 +29,6 @@ use crate::{ use core::str::FromStr; -use sol_prim::{ - consts::{ - MAX_TRANSACTION_LENGTH, SOL_USDC_DECIMAL, SYSTEM_PROGRAM_ID, SYS_VAR_INSTRUCTIONS, - TOKEN_PROGRAM_ID, - }, - PdaAndBump, -}; - #[derive(BorshSerialize, BorshDeserialize)] enum BankInstruction { Initialize, @@ -40,6 +36,86 @@ enum BankInstruction { Withdraw { lamports: u64 }, } +#[cfg(test)] +mod versioned_transaction { + use crate::sol::sol_tx_core::{ + consts::{const_address, const_hash}, + transaction::{v0::VersionedMessageV0, VersionedMessage, VersionedTransaction}, + AddressLookupTableAccount, + }; + + use super::*; + + #[test] + fn create_transfer_native_no_address_lookup_table() { + let durable_nonce = TEST_DURABLE_NONCE.into(); + let agg_key_keypair = SolSigningKey::from_bytes(&RAW_KEYPAIR).unwrap(); + let agg_key_pubkey = agg_key_keypair.pubkey(); + + let to_pubkey = TRANSFER_TO_ACCOUNT.into(); + let instructions = [ + SystemProgramInstruction::advance_nonce_account( + &NONCE_ACCOUNTS[0].into(), + &agg_key_pubkey, + ), + ComputeBudgetInstruction::set_compute_unit_price(COMPUTE_UNIT_PRICE), + ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_UNIT_LIMIT), + SystemProgramInstruction::transfer(&agg_key_pubkey, &to_pubkey, TRANSFER_AMOUNT), + ]; + let message = VersionedMessage::V0(VersionedMessageV0::new_with_blockhash( + &instructions, + Some(agg_key_pubkey), + durable_nonce, + &[], + )); + let mut tx = VersionedTransaction::new_unsigned(message); + tx.sign(vec![agg_key_keypair].into(), durable_nonce); + + let serialized_tx = tx.finalize_and_serialize().unwrap(); + let expected_serialized_tx = "012e1beb02a24f6e59148fc4eb64aeaeaad291e5f241b8b2d01775a6d3956392ac7186fbee0963d6ca0720bddb5d8b555ada6beb2cd3e9bd0415c343a5ca0cde0b8001000306f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd400000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004030301050004040000000400090340420f000000000004000502e0930400030200020c0200000000ca9a3b0000000000"; + + assert_eq!(hex::encode(serialized_tx.clone()), expected_serialized_tx); + assert!(serialized_tx.len() <= MAX_TRANSACTION_LENGTH) + } + + #[test] + fn create_transfer_native_with_address_lookup_table() { + let durable_nonce = ( + const_address("2cNMwUCF51djw2xAiiU54wz1WrU8uG4Q8Kp8nfEuwghw").into(), + const_hash("2qVz58R5aPmF5Q61VaKXnpWQtngdh4Jgbeko32fEcECu").into(), + ); + let alt = AddressLookupTableAccount { + key: const_address("4EQ4ZTskvNwkBaQjBJW5grcmV5Js82sUooNLHNTpdHdi").into(), + addresses: vec![const_address("CFnQk1nVmkPThKvLU8EUPFtTuJro45JLSoqux4v23ZGy").into()], + }; + + let agg_key_keypair = SolSigningKey::from_bytes(&RAW_KEYPAIR).unwrap(); + let agg_key_pubkey = agg_key_keypair.pubkey(); + + let to_pubkey = const_address("CFnQk1nVmkPThKvLU8EUPFtTuJro45JLSoqux4v23ZGy").into(); + let instructions = [ + SystemProgramInstruction::advance_nonce_account(&durable_nonce.0, &agg_key_pubkey), + ComputeBudgetInstruction::set_compute_unit_price(COMPUTE_UNIT_PRICE), + ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_UNIT_LIMIT), + SystemProgramInstruction::transfer(&agg_key_pubkey, &to_pubkey, TRANSFER_AMOUNT), + ]; + let message = VersionedMessage::V0(VersionedMessageV0::new_with_blockhash( + &instructions, + Some(agg_key_pubkey), + durable_nonce.1, + &[alt], + )); + let mut tx = VersionedTransaction::new_unsigned(message); + tx.sign(vec![agg_key_keypair].into(), durable_nonce.1); + + let serialized_tx = tx.finalize_and_serialize().unwrap(); + let expected_serialized_tx = "01ed1357672e0e660e9afd6dd948bee446639a232171900b89a1d403e78e58ad30d8da3986888c9e07ec066b19198b59f99428c00fcf858040e669185473ded5008001000305f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19200000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000001b48568b09b08111ebdcf9a5073d86a4506a3c3fe2a6d47a8a5ce0c459a65bce04020301040004040000000300090340420f000000000003000502e0930400020200050c0200000000ca9a3b00000000013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a1010000"; + + assert_eq!(hex::encode(serialized_tx.clone()), expected_serialized_tx); + assert!(serialized_tx.len() <= MAX_TRANSACTION_LENGTH) + } +} + #[test] fn create_simple_tx() { let program_id = Pubkey([0u8; 32]); diff --git a/state-chain/chains/src/sol/transaction_builder.rs b/state-chain/chains/src/sol/transaction_builder.rs index 10a6079889f..456d11d887d 100644 --- a/state-chain/chains/src/sol/transaction_builder.rs +++ b/state-chain/chains/src/sol/transaction_builder.rs @@ -4,11 +4,6 @@ //! Transactions with some level of abstraction. //! This avoids the need to deal with low level Solana core types. -use sol_prim::consts::{ - MAX_TRANSACTION_LENGTH, SOL_USDC_DECIMAL, SYSTEM_PROGRAM_ID, SYS_VAR_INSTRUCTIONS, - TOKEN_PROGRAM_ID, -}; - use crate::{ sol::{ api::{DurableNonceAndAccount, SolanaTransactionBuildingError, VaultSwapAccountAndSender}, @@ -27,6 +22,10 @@ use crate::{ derive_swap_endpoint_native_vault_account, derive_token_supported_account, }, compute_budget::ComputeBudgetInstruction, + consts::{ + MAX_TRANSACTION_LENGTH, SOL_USDC_DECIMAL, SYSTEM_PROGRAM_ID, SYS_VAR_INSTRUCTIONS, + TOKEN_PROGRAM_ID, + }, program_instructions::{ swap_endpoints::SwapEndpointProgram, InstructionExt, SystemProgramInstruction, VaultProgram, @@ -534,14 +533,15 @@ pub mod test { use super::*; use crate::{ sol::{ - sol_tx_core::{address_derivation::derive_deposit_address, sol_test_values::*}, + sol_tx_core::{ + address_derivation::derive_deposit_address, consts::SOL_USDC_DECIMAL, + sol_test_values::*, PdaAndBump, + }, SolanaDepositFetchId, MAX_BATCH_SIZE_OF_VAULT_SWAP_ACCOUNT_CLOSURES, }, TransferAssetParams, }; - use sol_prim::{consts::SOL_USDC_DECIMAL, PdaAndBump}; - fn get_fetch_params( channel_id: Option, asset: SolAsset, diff --git a/state-chain/runtime/Cargo.toml b/state-chain/runtime/Cargo.toml index 5c5c9268dfb..6b0db4c231d 100644 --- a/state-chain/runtime/Cargo.toml +++ b/state-chain/runtime/Cargo.toml @@ -35,8 +35,6 @@ cf-runtime-utilities = { workspace = true } cf-traits = { workspace = true } cf-utilities = { workspace = true } -sol-prim = { workspace = true } - pallet-cf-account-roles = { workspace = true } pallet-cf-asset-balances = { workspace = true } pallet-cf-broadcast = { workspace = true } @@ -190,7 +188,6 @@ std = [ "pallet-cf-cfe-interface/std", "scale-info/std", "serde/std", - "sol-prim/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", diff --git a/state-chain/runtime/src/chainflip/address_derivation/sol.rs b/state-chain/runtime/src/chainflip/address_derivation/sol.rs index 054561fef22..cc6a5698e81 100644 --- a/state-chain/runtime/src/chainflip/address_derivation/sol.rs +++ b/state-chain/runtime/src/chainflip/address_derivation/sol.rs @@ -1,9 +1,11 @@ use cf_chains::{ address::{AddressDerivationApi, AddressDerivationError}, - sol::{api::SolanaEnvironment, sol_tx_core::address_derivation::derive_deposit_address}, + sol::{ + api::SolanaEnvironment, + sol_tx_core::{address_derivation::derive_deposit_address, PdaAndBump}, + }, Solana, }; -use sol_prim::PdaAndBump; use super::AddressDerivation; use crate::SolEnvironment; diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 25f26c52335..24d31a24fb2 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -12,6 +12,7 @@ use cf_chains::{ VaultSwapAccountAndSender, }, compute_units_costs::MIN_COMPUTE_PRICE, + sol_tx_core::SlotNumber, SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, SolanaCrypto, }, CcmDepositMetadata, Chain, ChannelRefundParameters, FeeEstimationApi, @@ -46,7 +47,6 @@ use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; #[cfg(feature = "runtime-benchmarks")] use cf_chains::benchmarking_value::BenchmarkValue; -use sol_prim::SlotNumber; use super::SolEnvironment; From 6b6bbb784bb684a71505bbbd19d0cd09e2560251 Mon Sep 17 00:00:00 2001 From: kylezs Date: Tue, 11 Feb 2025 08:46:27 +0100 Subject: [PATCH 18/20] chore: update session pallet benchmarks to v2 (#5623) chore: update session pallet migrations to v2 --- .../cf-session-benchmarking/src/lib.rs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/state-chain/cf-session-benchmarking/src/lib.rs b/state-chain/cf-session-benchmarking/src/lib.rs index c4f4eb3c84d..879e58c530a 100644 --- a/state-chain/cf-session-benchmarking/src/lib.rs +++ b/state-chain/cf-session-benchmarking/src/lib.rs @@ -2,14 +2,13 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Decode; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::v2::*; use frame_support::{assert_ok, sp_runtime::traits::Convert}; use frame_system::RawOrigin; use pallet_session::*; use rand::{RngCore, SeedableRng}; use sp_std::{prelude::*, vec}; -#[allow(dead_code)] pub struct Pallet(pallet_session::Pallet); pub trait Config: pallet_session::Config + pallet_session::historical::Config {} @@ -20,24 +19,34 @@ fn generate_key(seed: u64) -> T::Keys { Decode::decode(&mut &key[..]).unwrap() } -benchmarks! { - set_keys { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_keys() { let caller: T::AccountId = whitelisted_caller(); let validator_id = T::ValidatorIdOf::convert(caller.clone()).unwrap(); >::insert(validator_id.clone(), generate_key::(1)); frame_system::Pallet::::inc_providers(&caller); assert_ok!(frame_system::Pallet::::inc_consumers(&caller)); let new_key = generate_key::(0); - }: _(RawOrigin::Signed(caller), new_key.clone(), vec![]) - verify { + + #[extrinsic_call] + set_keys(RawOrigin::Signed(caller), new_key.clone(), vec![]); + assert_eq!(>::get(validator_id).expect("No key for id"), new_key); } - purge_keys { + + #[benchmark] + fn purge_keys() { let caller: T::AccountId = whitelisted_caller(); let validator_id = T::ValidatorIdOf::convert(caller.clone()).unwrap(); >::insert(validator_id.clone(), generate_key::(0)); - }: _(RawOrigin::Signed(caller)) - verify { + + #[extrinsic_call] + purge_keys(RawOrigin::Signed(caller)); + assert_eq!(>::get(validator_id), None); } } From a4e7ac6250b8faf2f5f939b782a51fa45fffc516 Mon Sep 17 00:00:00 2001 From: Albert Llimos <53186777+albert-llimos@users.noreply.github.com> Date: Tue, 11 Feb 2025 12:28:48 +0100 Subject: [PATCH 19/20] chore: remove incorrect logging (#5627) --- state-chain/runtime/src/chainflip.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state-chain/runtime/src/chainflip.rs b/state-chain/runtime/src/chainflip.rs index 7ddb31e61f4..b22900ee305 100644 --- a/state-chain/runtime/src/chainflip.rs +++ b/state-chain/runtime/src/chainflip.rs @@ -247,7 +247,8 @@ macro_rules! impl_transaction_builder_for_evm_chain { ) } - /// Calculate the gas limit for this evm chain's call. This is only for CCM calls. + /// Calculate the gas limit for this evm chain's call. For CCM calls the gas limit is calculated from the gas budget + /// while for regular calls the gas limit is set to None and the engines will estimate the gas required on broadcast. fn calculate_gas_limit(call: &$chain_api<$env>) -> Option { if let (Some((gas_budget, message_length, transfer_asset)), Some(native_asset)) = (call.ccm_transfer_data(), <$env as EvmEnvironmentProvider<$chain>>::token_address($chain::GAS_ASSET)) { @@ -261,7 +262,6 @@ macro_rules! impl_transaction_builder_for_evm_chain { Some(gas_limit.into()) } else { - log::warn!("CCM calls should have all the data. This should never happen. Please check {}", $chain::NAME); None } } From f5aff96644d49520a31a335abf583a1265c4ac7f Mon Sep 17 00:00:00 2001 From: Maxim Urschumzew Date: Tue, 11 Feb 2025 14:30:37 +0100 Subject: [PATCH 20/20] feat: refresh solana elections instead of recreate (#5629) feat: refresh solana ingress elections instead of recreate. --- .../blockchain/delta_based_ingress.rs | 14 +++++++---- .../src/electoral_systems/mocks.rs | 7 ++++++ .../tests/delta_based_ingress.rs | 25 ++++++++++++++++--- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs b/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs index db8756ffd70..5fee91f195d 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/blockchain/delta_based_ingress.rs @@ -331,7 +331,7 @@ where } } - let election_access = ElectoralAccess::election_mut(election_identifier); + let mut election_access = ElectoralAccess::election_mut(election_identifier); if new_properties.is_empty() { // Note: it's possible that there are still some remaining pending totals, but if @@ -340,11 +340,15 @@ where election_access.delete(); } else if new_properties != properties { log::debug!("recreate delta based ingress election: recreate since properties changed from: {properties:?}, to: {new_properties:?}"); - election_access.delete(); - ElectoralAccess::new_election( - Default::default(), + + election_access.clear_votes(); + election_access.set_state(new_pending_ingress_totals)?; + election_access.refresh( + election_identifier + .extra() + .checked_add(1) + .ok_or_else(CorruptStorageError::new)?, new_properties, - new_pending_ingress_totals, )?; } else { log::debug!("recreate delta based ingress election: keeping old because properties didn't change: {properties:?}"); 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 a41a4858eeb..bd9c9828a60 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/mocks.rs @@ -413,6 +413,13 @@ register_checks! { "Expected the election id to be incremented.", ); }, + election_id_updated_by(pre_finalize, post_finalize, update: impl Fn(ElectionIdentifierOf) -> ElectionIdentifierOf + Clone + 'static) { + assert_eq!( + update(pre_finalize.election_identifiers[0]), + post_finalize.election_identifiers[0], + "Expected the election id to be updated by given `update` function.", + ); + }, all_elections_deleted(pre_finalize, post_finalize) { assert!( !pre_finalize.election_identifiers.is_empty(), diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs b/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs index 9f67366aaea..942e5ab7d66 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/tests/delta_based_ingress.rs @@ -542,7 +542,15 @@ mod channel_closure { vec![ Check::channel_not_closed(DEFAULT_CHANNEL_ACCOUNT), Check::ingressed(vec![(DEFAULT_CHANNEL_ACCOUNT, Asset::Sol, DEPOSIT_AMOUNT)]), - Check::election_id_incremented(), + Check::election_id_updated_by(|id| { + ElectionIdentifier::new(*id.unique_monotonic(), id.extra() + 1) + }), + // Channel state not cleaned up yet, since the channel is not yet closed. + Check::ended_at_state_map_state([DepositChannel { + total_ingressed: DEPOSIT_AMOUNT, + block_number: DEPOSIT_BLOCK, + ..DEFAULT_CHANNEL + }]), ], ) // Chain tracking reaches close block, channel is closed. @@ -958,7 +966,9 @@ fn pending_ingresses_update_with_consensus() { deposit_channel_pending.asset, deposit_channel_pending.total_ingressed, )]), - Check::election_id_incremented(), + Check::election_id_updated_by(|id| { + ElectionIdentifier::new(*id.unique_monotonic(), id.extra() + 1) + }), Check::ended_at_state(to_state(vec![deposit_channel_with_next_deposit])), ], ) @@ -1035,7 +1045,12 @@ mod multiple_deposits { .test_on_finalize( &{ TOTAL_1.block_number - 1 }, |_| {}, - [Check::ingressed(vec![]), Check::election_id_incremented()], + [ + Check::ingressed(vec![]), + Check::election_id_updated_by(|id| { + ElectionIdentifier::new(*id.unique_monotonic(), id.extra() + 1) + }), + ], ) // Simulate a second deposit at a later block. .force_consensus_update(ConsensusStatus::Gained { @@ -1049,7 +1064,9 @@ mod multiple_deposits { |_| {}, [ Check::ingressed(vec![(DEPOSIT_ADDRESS, Asset::Sol, TOTAL_1.amount)]), - Check::election_id_incremented(), + Check::election_id_updated_by(|id| { + ElectionIdentifier::new(*id.unique_monotonic(), id.extra() + 1) + }), ], ) // Finalize with chain tracking at the block of the second deposit. Both should be