From c428eb142e8b6042ba27a27f6b81f5c22421ee05 Mon Sep 17 00:00:00 2001 From: albert Date: Thu, 13 Feb 2025 11:13:50 +0100 Subject: [PATCH 1/7] chore: add ci when PR against base branch --- .github/workflows/ci-development.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-development.yml b/.github/workflows/ci-development.yml index 5cf46a275c4..eb20a5c53d7 100644 --- a/.github/workflows/ci-development.yml +++ b/.github/workflows/ci-development.yml @@ -5,6 +5,7 @@ on: branches: - main - "release/*" + - "feat/solana-versioned-transaction-base-branch" concurrency: group: ${{ github.ref }}-release-development cancel-in-progress: true From 96dc08ccd3b5a39686e12eb9642170e914957e9f Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Mon, 3 Feb 2025 18:21:26 +1300 Subject: [PATCH 2/7] Added versioned transaction and versioned message support --- foreign-chains/solana/sol-prim/src/consts.rs | 2 + foreign-chains/solana/sol-prim/src/lib.rs | 187 +++- .../chains/src/sol/sol_tx_core/transaction.rs | 880 ++++++++++++++++++ 3 files changed, 1068 insertions(+), 1 deletion(-) create mode 100644 state-chain/chains/src/sol/sol_tx_core/transaction.rs diff --git a/foreign-chains/solana/sol-prim/src/consts.rs b/foreign-chains/solana/sol-prim/src/consts.rs index 8ab5d87624d..b2ac0ea9ab7 100644 --- a/foreign-chains/solana/sol-prim/src/consts.rs +++ b/foreign-chains/solana/sol-prim/src/consts.rs @@ -18,6 +18,8 @@ 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 SIGNATURE_BYTES: usize = 64; + pub const fn const_address(s: &'static str) -> Address { Address(bs58_array(s)) } diff --git a/foreign-chains/solana/sol-prim/src/lib.rs b/foreign-chains/solana/sol-prim/src/lib.rs index b01db1bc012..da661cf4fd9 100644 --- a/foreign-chains/solana/sol-prim/src/lib.rs +++ b/foreign-chains/solana/sol-prim/src/lib.rs @@ -40,7 +40,7 @@ pub type SlotNumber = u64; pub type ComputeLimit = u32; pub type AccountBump = u8; -use crate::consts::{HASH_BYTES, MAX_BASE58_LEN, SOLANA_SIGNATURE_LEN}; +use crate::consts::{HASH_BYTES, MAX_BASE58_LEN, SIGNATURE_BYTES}; define_binary!(address, Address, crate::consts::SOLANA_ADDRESS_LEN, "A"); define_binary!(digest, Digest, crate::consts::SOLANA_DIGEST_LEN, "D"); @@ -632,3 +632,188 @@ impl FromStr for Hash { } } } + +/// 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/state-chain/chains/src/sol/sol_tx_core/transaction.rs b/state-chain/chains/src/sol/sol_tx_core/transaction.rs new file mode 100644 index 00000000000..23878193168 --- /dev/null +++ b/state-chain/chains/src/sol/sol_tx_core/transaction.rs @@ -0,0 +1,880 @@ +use crate::sol::{ + sol_tx_core::{ + compile_instructions, short_vec, AddressLookupTableAccount, 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}; + +use crate::sol::sol_tx_core::MessageAddressTableLookup; +use sol_prim::consts::MESSAGE_VERSION_PREFIX; + +use serde::{ + de::{self, SeqAccess, Unexpected, Visitor}, + ser::SerializeTuple, + Deserializer, Serializer, +}; +use sp_std::fmt; + +#[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; + +#[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![ + 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, + 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 = SolSignature::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 != SolSignature::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 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(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::sol::sol_tx_core::{AccountKeys, CompileError, MessageAddressTableLookup}; + + /// 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) + .map_err(|e| { + log::error!("Failed to compile Solana Message: {:?}", e); + }) + .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::*; + + /// 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(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, + } + } +} From 72e44ce99c54d8a5407ead2c5d5ef874d91137c3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 6 Feb 2025 10:58:17 +0100 Subject: [PATCH 3/7] Added unit test for Versioned transactions with Address lookup table --- state-chain/chains/src/sol/sol_tx_core/transaction.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state-chain/chains/src/sol/sol_tx_core/transaction.rs b/state-chain/chains/src/sol/sol_tx_core/transaction.rs index 23878193168..8e624500d4e 100644 --- a/state-chain/chains/src/sol/sol_tx_core/transaction.rs +++ b/state-chain/chains/src/sol/sol_tx_core/transaction.rs @@ -1,7 +1,8 @@ use crate::sol::{ sol_tx_core::{ compile_instructions, short_vec, AddressLookupTableAccount, CompiledInstruction, - CompiledKeys, Hash, Instruction, MessageHeader, Pubkey, RawSignature, + CompiledKeys, Hash, Instruction, MessageAddressTableLookup, MessageHeader, Pubkey, + RawSignature, }, SolSignature, }; @@ -12,7 +13,6 @@ use serde::{Deserialize, Serialize}; use sol_prim::errors::TransactionError; use sp_std::{vec, vec::Vec}; -use crate::sol::sol_tx_core::MessageAddressTableLookup; use sol_prim::consts::MESSAGE_VERSION_PREFIX; use serde::{ From 543f7c747d0f04e4c01a4639d84fa594f1690516 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Mon, 10 Feb 2025 18:33:26 +1300 Subject: [PATCH 4/7] Moved (almost) everything in sol_tx_core into sol-prim Moved ALT related stuff to its own file. --- foreign-chains/solana/sol-prim/src/consts.rs | 2 - foreign-chains/solana/sol-prim/src/lib.rs | 187 +--- .../chains/src/sol/sol_tx_core/transaction.rs | 880 ------------------ 3 files changed, 1 insertion(+), 1068 deletions(-) delete mode 100644 state-chain/chains/src/sol/sol_tx_core/transaction.rs diff --git a/foreign-chains/solana/sol-prim/src/consts.rs b/foreign-chains/solana/sol-prim/src/consts.rs index b2ac0ea9ab7..8ab5d87624d 100644 --- a/foreign-chains/solana/sol-prim/src/consts.rs +++ b/foreign-chains/solana/sol-prim/src/consts.rs @@ -18,8 +18,6 @@ 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 SIGNATURE_BYTES: usize = 64; - pub const fn const_address(s: &'static str) -> Address { Address(bs58_array(s)) } diff --git a/foreign-chains/solana/sol-prim/src/lib.rs b/foreign-chains/solana/sol-prim/src/lib.rs index da661cf4fd9..b01db1bc012 100644 --- a/foreign-chains/solana/sol-prim/src/lib.rs +++ b/foreign-chains/solana/sol-prim/src/lib.rs @@ -40,7 +40,7 @@ pub type SlotNumber = u64; pub type ComputeLimit = u32; pub type AccountBump = u8; -use crate::consts::{HASH_BYTES, MAX_BASE58_LEN, SIGNATURE_BYTES}; +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"); @@ -632,188 +632,3 @@ impl FromStr for Hash { } } } - -/// 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/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 8e624500d4e..00000000000 --- a/state-chain/chains/src/sol/sol_tx_core/transaction.rs +++ /dev/null @@ -1,880 +0,0 @@ -use crate::sol::{ - sol_tx_core::{ - compile_instructions, short_vec, AddressLookupTableAccount, CompiledInstruction, - CompiledKeys, Hash, Instruction, MessageAddressTableLookup, 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}; - -use sol_prim::consts::MESSAGE_VERSION_PREFIX; - -use serde::{ - de::{self, SeqAccess, Unexpected, Visitor}, - ser::SerializeTuple, - Deserializer, Serializer, -}; -use sp_std::fmt; - -#[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; - -#[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![ - 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, - 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 = SolSignature::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 != SolSignature::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 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(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::sol::sol_tx_core::{AccountKeys, CompileError, MessageAddressTableLookup}; - - /// 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) - .map_err(|e| { - log::error!("Failed to compile Solana Message: {:?}", e); - }) - .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::*; - - /// 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(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, - } - } -} From 56a6108e49e46e9e199d6b4d908bfc049d585dc0 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Thu, 13 Feb 2025 22:45:40 +1300 Subject: [PATCH 5/7] Initial commit for changing Solana Transaction to VersionedTransaction --- Cargo.lock | 1 + foreign-chains/solana/sol-prim/Cargo.toml | 3 +- foreign-chains/solana/sol-prim/src/alt.rs | 19 +- foreign-chains/solana/sol-prim/src/consts.rs | 2 + .../solana/sol-prim/src/transaction.rs | 95 +++++--- state-chain/cf-integration-tests/Cargo.toml | 1 + .../cf-integration-tests/src/solana.rs | 9 +- .../cf-integration-tests/src/swapping.rs | 1 + .../src/threshold_signing.rs | 6 +- state-chain/cfe-events/src/tests.rs | 19 +- state-chain/chains/Cargo.toml | 2 +- state-chain/chains/src/arb/api.rs | 1 + state-chain/chains/src/btc/api.rs | 1 + state-chain/chains/src/ccm_checker.rs | 229 +++++++++++++----- state-chain/chains/src/dot/api.rs | 1 + state-chain/chains/src/eth/api.rs | 1 + state-chain/chains/src/lib.rs | 4 +- state-chain/chains/src/sol.rs | 16 +- state-chain/chains/src/sol/api.rs | 49 +++- state-chain/chains/src/sol/benchmarking.rs | 14 +- .../chains/src/sol/instruction_builder.rs | 27 ++- state-chain/chains/src/sol/sol_tx_core.rs | 67 ++++- state-chain/chains/src/sol/tests.rs | 66 +++-- .../chains/src/sol/transaction_builder.rs | 106 +++++--- .../pallets/cf-environment/src/mock.rs | 24 +- .../pallets/cf-ingress-egress/src/lib.rs | 4 + .../pallets/cf-ingress-egress/src/tests.rs | 3 + state-chain/runtime/src/chainflip.rs | 39 ++- state-chain/runtime/src/lib.rs | 6 +- state-chain/traits/src/mocks/api_call.rs | 4 +- 30 files changed, 579 insertions(+), 241 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de4b1d6a3b9..a05b1092ebc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1364,6 +1364,7 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", + "hex", "hex-literal", "libsecp256k1", "log", diff --git a/foreign-chains/solana/sol-prim/Cargo.toml b/foreign-chains/solana/sol-prim/Cargo.toml index 94b88570a9b..086aa4fa559 100644 --- a/foreign-chains/solana/sol-prim/Cargo.toml +++ b/foreign-chains/solana/sol-prim/Cargo.toml @@ -63,5 +63,4 @@ std = [ "sp-std/std", "sp-core/std", "cf-primitives/std", -] -runtime-integration-tests = ["std", "ed25519-dalek/rand_core"] \ No newline at end of file +] \ No newline at end of file diff --git a/foreign-chains/solana/sol-prim/src/alt.rs b/foreign-chains/solana/sol-prim/src/alt.rs index 61fbe9ce32a..a834961f376 100644 --- a/foreign-chains/solana/sol-prim/src/alt.rs +++ b/foreign-chains/solana/sol-prim/src/alt.rs @@ -1,12 +1,16 @@ pub use crate::{ short_vec, Address, CompileError, CompiledInstruction, Digest, Instruction, Pubkey, Signature, }; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; 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)] +#[derive( + Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, +)] #[serde(rename_all = "camelCase")] pub struct MessageAddressTableLookup { /// Address lookup table account key @@ -28,6 +32,19 @@ pub struct AddressLookupTableAccount { pub addresses: Vec, } +impl AddressLookupTableAccount { + pub fn new(key: Address, addresses: Vec
) -> AddressLookupTableAccount { + AddressLookupTableAccount { + key: key.into(), + addresses: addresses.into_iter().map(|a| a.into()).collect::>(), + } + } + + pub fn is_empty(&self) -> bool { + self.addresses.is_empty() + } +} + /// Collection of static and dynamically loaded keys used to load accounts /// during transaction processing. #[derive(Clone, Default, Debug, Eq)] diff --git a/foreign-chains/solana/sol-prim/src/consts.rs b/foreign-chains/solana/sol-prim/src/consts.rs index 8ab5d87624d..b52e02624ba 100644 --- a/foreign-chains/solana/sol-prim/src/consts.rs +++ b/foreign-chains/solana/sol-prim/src/consts.rs @@ -56,3 +56,5 @@ pub const NONCE_ACCOUNT_LENGTH: u64 = 80u64; pub const SOL_USDC_DECIMAL: u8 = 6u8; pub const ACCOUNT_KEY_LENGTH_IN_TRANSACTION: usize = 32usize; pub const ACCOUNT_REFERENCE_LENGTH_IN_TRANSACTION: usize = 1usize; + +pub const MAX_CCM_USER_ALTS: u8 = 5u8; // TODO: Albert come up with a good number for this diff --git a/foreign-chains/solana/sol-prim/src/transaction.rs b/foreign-chains/solana/sol-prim/src/transaction.rs index 6ef94f1c4cd..11df17ffbe2 100644 --- a/foreign-chains/solana/sol-prim/src/transaction.rs +++ b/foreign-chains/solana/sol-prim/src/transaction.rs @@ -15,7 +15,7 @@ use serde::{ }; use sp_std::fmt; -#[cfg(any(test, feature = "runtime-integration-tests"))] +#[cfg(feature = "std")] use crate::{ errors::TransactionError, signer::{Signer, SignerError, TestSigners}, @@ -46,13 +46,27 @@ pub enum Legacy { /// 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)] +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] pub enum VersionedMessage { Legacy(legacy::LegacyMessage), V0(v0::VersionedMessageV0), } impl VersionedMessage { + pub fn new( + instructions: &[Instruction], + payer: Option, + blockhash: Option, + lookup_tables: &[AddressLookupTableAccount], + ) -> VersionedMessage { + VersionedMessage::V0(v0::VersionedMessageV0::new_with_blockhash( + instructions, + payer, + blockhash.unwrap_or_default(), + lookup_tables, + )) + } + pub fn header(&self) -> &MessageHeader { match self { Self::Legacy(message) => &message.header, @@ -67,6 +81,26 @@ impl VersionedMessage { } } + pub fn map_static_account_keys(&mut self, f: impl Fn(Pubkey) -> Pubkey) { + match self { + Self::Legacy(message) => + for k in message.account_keys.iter_mut() { + *k = f(*k); + }, + Self::V0(message) => + for k in message.account_keys.iter_mut() { + *k = f(*k); + }, + } + } + + pub fn set_static_account_keys(&mut self, new_keys: Vec) { + match self { + Self::Legacy(message) => message.account_keys = new_keys, + Self::V0(message) => message.account_keys = new_keys, + } + } + pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> { match self { Self::Legacy(_) => None, @@ -261,7 +295,9 @@ impl<'de> serde::Deserialize<'de> for VersionedMessage { // NOTE: Serialization-related changes must be paired with the direct read at sigverify. /// An atomic transaction -#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +#[derive( + Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, Encode, Decode, TypeInfo, +)] pub struct VersionedTransaction { /// List of signatures #[serde(with = "short_vec")] @@ -290,19 +326,8 @@ impl VersionedTransaction { } } - #[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) { + #[cfg(feature = "std")] + pub fn test_only_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... @@ -321,8 +346,8 @@ impl VersionedTransaction { } } - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn get_signing_keypair_positions(&self, pubkeys: Vec) -> Vec { + #[cfg(feature = "std")] + 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 { @@ -392,7 +417,9 @@ pub mod v0 { /// See the [`message`] module documentation for further description. /// /// [`message`]: crate::message - #[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)] + #[derive( + Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, + )] #[serde(rename_all = "camelCase")] pub struct VersionedMessageV0 { /// The message header, identifying signed and read-only `account_keys`. @@ -743,21 +770,19 @@ pub mod legacy { } } - #[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) { + #[cfg(feature = "std")] + pub fn test_only_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( + #[cfg(feature = "std")] + fn try_sign( &mut self, signers: TestSigners, recent_blockhash: Hash, @@ -771,8 +796,8 @@ pub mod legacy { } } - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn try_partial_sign( + #[cfg(feature = "std")] + fn try_partial_sign( &mut self, signers: TestSigners, recent_blockhash: Hash, @@ -785,8 +810,8 @@ pub mod legacy { self.try_partial_sign_unchecked(signers, positions, recent_blockhash) } - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn try_partial_sign_unchecked( + #[cfg(feature = "std")] + fn try_partial_sign_unchecked( &mut self, signers: TestSigners, positions: Vec, @@ -807,8 +832,8 @@ pub mod legacy { Ok(()) } - #[cfg(any(test, feature = "runtime-integration-tests"))] - pub fn get_signing_keypair_positions( + #[cfg(feature = "std")] + fn get_signing_keypair_positions( &self, pubkeys: Vec, ) -> Result>, TransactionError> { diff --git a/state-chain/cf-integration-tests/Cargo.toml b/state-chain/cf-integration-tests/Cargo.toml index 89c978a2603..7dab01bf2c2 100644 --- a/state-chain/cf-integration-tests/Cargo.toml +++ b/state-chain/cf-integration-tests/Cargo.toml @@ -21,6 +21,7 @@ log = { workspace = true } [dev-dependencies] libsecp256k1 = { workspace = true, default-features = true, features = ["static-context"] } rand = { workspace = true, default-features = true } +hex = { workspace = true } hex-literal = { workspace = true, default-features = true } secp256k1 = { workspace = true, features = ["rand-std"] } arrayref = { workspace = true } diff --git a/state-chain/cf-integration-tests/src/solana.rs b/state-chain/cf-integration-tests/src/solana.rs index 9f4a05d3e73..a0f0749603a 100644 --- a/state-chain/cf-integration-tests/src/solana.rs +++ b/state-chain/cf-integration-tests/src/solana.rs @@ -634,11 +634,11 @@ fn solana_resigning() { ).unwrap(); transaction.signatures = vec![[1u8; 64].into()]; - let original_account_keys = transaction.message.account_keys.clone(); + let original_account_keys = transaction.message.static_account_keys(); let apicall = SolanaApi { call_type: cf_chains::sol::api::SolanaTransactionType::Transfer, - transaction, + transaction: transaction.clone(), signer: Some(CURRENT_SIGNER.into()), _phantom: PhantomData::, }; @@ -651,7 +651,7 @@ fn solana_resigning() { if let RequiresSignatureRefresh::True(call) = modified_call { let agg_key = ::current_agg_key().unwrap(); let transaction = call.clone().unwrap().transaction; - for (modified_key, original_key) in transaction.message.account_keys.iter().zip(original_account_keys.iter()) { + for (modified_key, original_key) in transaction.message.static_account_keys().iter().zip(original_account_keys.iter()) { if *original_key != SolPubkey::from(CURRENT_SIGNER) { assert_eq!(modified_key, original_key); assert_ne!(*modified_key, SolPubkey::from(agg_key)); @@ -665,7 +665,8 @@ fn solana_resigning() { // Compare against a manually crafted transaction that works with the current test values and // agg_key. Not the signature itself - let expected_serialized_tx = hex_literal::hex!("010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000306f68d61e8d834034cf583f486f2a08ef53ce4134ed41c4d88f4720c39518745b617eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d192cf1dd130e0341d60a0771ac40ea7900106a423354d2ecd6e609bd5e2ed833dec00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004030301050004040000000400090380969800000000000400050284030000030200020c020000008096980000000000").to_vec(); + let expected_serialized_tx = hex_literal::hex!("01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001000306f68d61e8d834034cf583f486f2a08ef53ce4134ed41c4d88f4720c39518745b617eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d192cf1dd130e0341d60a0771ac40ea7900106a423354d2ecd6e609bd5e2ed833dec00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004030301050004040000000400090380969800000000000400050284030000030200020c02000000809698000000000000").to_vec(); + assert_eq!(&serialized_tx[1+64..], &expected_serialized_tx[1+64..]); assert_eq!(&serialized_tx[0], &expected_serialized_tx[0]); } else { diff --git a/state-chain/cf-integration-tests/src/swapping.rs b/state-chain/cf-integration-tests/src/swapping.rs index 3156835a7c1..e8aed28c594 100644 --- a/state-chain/cf-integration-tests/src/swapping.rs +++ b/state-chain/cf-integration-tests/src/swapping.rs @@ -685,6 +685,7 @@ fn ethereum_ccm_can_calculate_gas_limits() { gas_budget, vec![], vec![], + Default::default(), ) .unwrap() }; diff --git a/state-chain/cf-integration-tests/src/threshold_signing.rs b/state-chain/cf-integration-tests/src/threshold_signing.rs index e76c9c39b46..67eacd3d574 100644 --- a/state-chain/cf-integration-tests/src/threshold_signing.rs +++ b/state-chain/cf-integration-tests/src/threshold_signing.rs @@ -4,8 +4,8 @@ use cf_chains::{ dot::{EncodedPolkadotPayload, PolkadotPair, PolkadotPublicKey, PolkadotSignature}, evm::{to_evm_address, AggKey, SchnorrVerificationComponents}, sol::{ - signing_key::SolSigningKey, sol_tx_core::signer::Signer, SolAddress, SolLegacyMessage, - SolSignature, + signing_key::SolSigningKey, sol_tx_core::signer::Signer, SolAddress, SolSignature, + SolVersionedMessage, }, }; use cf_primitives::{EpochIndex, GENESIS_EPOCH}; @@ -258,7 +258,7 @@ impl Default for SolThresholdSigner { impl KeyUtils for SolKeyComponents { type SigVerification = SolSignature; type AggKey = SolAddress; - type Message = SolLegacyMessage; + type Message = SolVersionedMessage; fn sign(&self, message: &Self::Message) -> Self::SigVerification { self.secret.sign_message(message.serialize().as_slice()) diff --git a/state-chain/cfe-events/src/tests.rs b/state-chain/cfe-events/src/tests.rs index 85a0ed8b69c..6bacacc6287 100644 --- a/state-chain/cfe-events/src/tests.rs +++ b/state-chain/cfe-events/src/tests.rs @@ -8,7 +8,8 @@ use cf_chains::{ evm::{self, Address, ParityBit, H256}, sol::{ sol_tx_core::{CompiledInstruction, MessageHeader}, - RawSolHash, SolLegacyMessage, SolLegacyTransaction, SolPubkey, SolanaTransactionData, + RawSolHash, SolPubkey, SolVersionedMessage, SolVersionedMessageV0, SolVersionedTransaction, + SolanaTransactionData, }, }; use cf_primitives::AccountId; @@ -85,7 +86,7 @@ fn event_decoding() { epoch_index: 2, key: [7u8;32].into(), signatories: participants.clone(), - payload: SolLegacyMessage { + payload: SolVersionedMessage::V0(SolVersionedMessageV0{ header: MessageHeader { num_readonly_signed_accounts: 1, num_readonly_unsigned_accounts: 1, @@ -98,8 +99,9 @@ fn event_decoding() { accounts: vec![0], data: vec![3,4,5,6] }], - } - }), "0d010000000000000002000000070707070707070707070707070707070707070707070707070707070707070708010101010101010101010101010101010101010101010101010101010101010102020202020202020202020202020202020202020202020202020202020202020101010407070707070707070707070707070707070707070707070707070707070707070808080808080808080808080808080808080808080808080808080808080808040104001003040506"); + address_table_lookups: vec![] // TODO roy: add test case with ALT + }) + }), "0d0100000000000000020000000707070707070707070707070707070707070707070707070707070707070707080101010101010101010101010101010101010101010101010101010101010101020202020202020202020202020202020202020202020202020202020202020201010101040707070707070707070707070707070707070707070707070707070707070707080808080808080808080808080808080808080808080808080808080808080804010400100304050600"); } // Keygen requests @@ -179,9 +181,9 @@ fn event_decoding() { check_encoding(CfeEvent::SolTxBroadcastRequest(TxBroadcastRequest { broadcast_id: 1, nominee: AccountId::from([1; 32]), - payload: SolanaTransactionData{ serialized_transaction: (SolLegacyTransaction { + payload: SolanaTransactionData{ serialized_transaction: (SolVersionedTransaction { signatures: vec![[9u8; 64].into()], - message: SolLegacyMessage { + message: SolVersionedMessage::V0( SolVersionedMessageV0 { header: MessageHeader { num_readonly_signed_accounts: 2, num_readonly_unsigned_accounts: 2, @@ -194,11 +196,12 @@ fn event_decoding() { accounts: vec![1], data: vec![31,41,51,61] }], - }, + address_table_lookups: vec![] // TODO roy: add test case with ALT + }), }).finalize_and_serialize().unwrap(), skip_preflight: false, } - }), "0f01000000010101010101010101010101010101010101010101010101010101010101010139020109090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909020202010a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b01020101041f29333d00"); + }), "0f0100000001010101010101010101010101010101010101010101010101010101010101014102010909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090980020202010a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b01020101041f29333d0000"); } // P2P registration/deregistration diff --git a/state-chain/chains/Cargo.toml b/state-chain/chains/Cargo.toml index 76134692708..9799eb0c5f2 100644 --- a/state-chain/chains/Cargo.toml +++ b/state-chain/chains/Cargo.toml @@ -131,4 +131,4 @@ try-runtime = [ "cf-runtime-utilities/try-runtime", ] -runtime-integration-tests = ["std", "dep:rand", "ed25519-dalek/rand_core", "sol-prim/runtime-integration-tests"] +runtime-integration-tests = ["std", "dep:rand", "ed25519-dalek/rand_core",] diff --git a/state-chain/chains/src/arb/api.rs b/state-chain/chains/src/arb/api.rs index f7ea4ec0247..bdfdf3a5dae 100644 --- a/state-chain/chains/src/arb/api.rs +++ b/state-chain/chains/src/arb/api.rs @@ -71,6 +71,7 @@ where gas_budget: GasAmount, message: Vec, _ccm_additional_data: Vec, + _swap_request_id: SwapRequestId, ) -> Result { let transfer_param = EncodableTransferAssetParams { asset: E::token_address(transfer_param.asset) diff --git a/state-chain/chains/src/btc/api.rs b/state-chain/chains/src/btc/api.rs index b17e8032eda..9642e445047 100644 --- a/state-chain/chains/src/btc/api.rs +++ b/state-chain/chains/src/btc/api.rs @@ -142,6 +142,7 @@ impl> ExecutexSwapAndCall for Bitc _gas_budget: GasAmount, _message: Vec, _ccm_additional_data: Vec, + _swap_request_id: SwapRequestId, ) -> Result { Err(ExecutexSwapAndCallError::Unsupported) } diff --git a/state-chain/chains/src/ccm_checker.rs b/state-chain/chains/src/ccm_checker.rs index fdf16f67702..86cdcd7e03b 100644 --- a/state-chain/chains/src/ccm_checker.rs +++ b/state-chain/chains/src/ccm_checker.rs @@ -3,9 +3,9 @@ use crate::{ sol::{ sol_tx_core::consts::{ ACCOUNT_KEY_LENGTH_IN_TRANSACTION, ACCOUNT_REFERENCE_LENGTH_IN_TRANSACTION, - SYSTEM_PROGRAM_ID, SYS_VAR_INSTRUCTIONS, TOKEN_PROGRAM_ID, + MAX_CCM_USER_ALTS, SYSTEM_PROGRAM_ID, SYS_VAR_INSTRUCTIONS, TOKEN_PROGRAM_ID, }, - SolAsset, SolCcmAccounts, SolPubkey, MAX_CCM_BYTES_SOL, MAX_CCM_BYTES_USDC, + SolAddress, SolAsset, SolCcmAccounts, SolPubkey, MAX_CCM_BYTES_SOL, MAX_CCM_BYTES_USDC, }, CcmChannelMetadata, }; @@ -13,7 +13,7 @@ use cf_primitives::{Asset, ForeignChain}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::DispatchError; -use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; +use sp_std::{collections::btree_set::BTreeSet, vec, vec::Vec}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo)] pub enum CcmValidityError { @@ -22,6 +22,7 @@ pub enum CcmValidityError { CcmAdditionalDataContainsInvalidAccounts, RedundantDataSupplied, InvalidDestinationAddress, + TooManyAddressLookupTables, } impl From for DispatchError { fn from(value: CcmValidityError) -> Self { @@ -35,6 +36,8 @@ impl From for DispatchError { "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(), + CcmValidityError::TooManyAddressLookupTables => + "Invalid Ccm: Too many Address Lookup tables supplied".into(), } } } @@ -47,6 +50,13 @@ pub trait CcmValidityCheck { ) -> Result { Ok(DecodedCcmAdditionalData::NotRequired) } + + fn decode_unchecked( + _ccm: &CcmChannelMetadata, + _chain: ForeignChain, + ) -> Result { + Ok(DecodedCcmAdditionalData::NotRequired) + } } #[derive(Clone, Debug, Decode, PartialEq, Eq)] @@ -58,6 +68,23 @@ pub enum DecodedCcmAdditionalData { #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] pub enum VersionedSolanaCcmAdditionalData { V0(SolCcmAccounts), + V1 { ccm_accounts: SolCcmAccounts, alts: Vec }, +} + +impl VersionedSolanaCcmAdditionalData { + pub fn ccm_accounts(&self) -> SolCcmAccounts { + match self { + VersionedSolanaCcmAdditionalData::V0(ccm_accounts) => ccm_accounts.clone(), + VersionedSolanaCcmAdditionalData::V1 { ccm_accounts, .. } => ccm_accounts.clone(), + } + } + + pub fn address_lookup_tables(&self) -> Vec { + match self { + VersionedSolanaCcmAdditionalData::V0(..) => vec![], + VersionedSolanaCcmAdditionalData::V1 { alts, .. } => alts.clone(), + } + } } pub struct CcmValidityChecker; @@ -80,67 +107,80 @@ impl CcmValidityCheck for CcmValidityChecker { .expect("Only Solana chain's asset will be checked. This conversion must succeed."); // Check if the cf_parameter can be decoded - match VersionedSolanaCcmAdditionalData::decode( - &mut &ccm.ccm_additional_data.clone()[..], - ) - .map_err(|_| CcmValidityError::CannotDecodeCcmAdditionalData)? - { - VersionedSolanaCcmAdditionalData::V0(ccm_accounts) => { - // It's hard at this stage to compute exactly the length of the finally build - // transaction from the message and the additional accounts. Duplicated - // accounts only take one reference byte while new accounts take 32 bytes. - // Technically it shouldn't be necessary to pass duplicated accounts as - // it will all be executed in the same instruction. However when integrating - // with other protocols, many of the account's values are part of a returned - // payload from an API and it makes it cumbersome to then dedpulicate on the - // fly and then make it match with the receiver contract. It can be done - // but it then requires extra configuration bytes in the payload, which - // then defeats the purpose. - // 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. - // 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); - } - let mut accounts_length = ccm_accounts.additional_accounts.len() * - ACCOUNT_REFERENCE_LENGTH_IN_TRANSACTION; - - for ccm_address in &ccm_accounts.additional_accounts { - if seen_addresses.insert(ccm_address.pubkey.into()) { - accounts_length += ACCOUNT_KEY_LENGTH_IN_TRANSACTION; - } - } - - let ccm_length = ccm.message.len() + accounts_length; - - if ccm_length > - match asset { - SolAsset::Sol => MAX_CCM_BYTES_SOL, - SolAsset::SolUsdc => MAX_CCM_BYTES_USDC, - } { - return Err(CcmValidityError::CcmIsTooLong) - } - - Ok(DecodedCcmAdditionalData::Solana(VersionedSolanaCcmAdditionalData::V0( - ccm_accounts, - ))) - }, + let decoded_data = + VersionedSolanaCcmAdditionalData::decode(&mut &ccm.ccm_additional_data.clone()[..]) + .map_err(|_| CcmValidityError::CannotDecodeCcmAdditionalData)?; + + let ccm_accounts = decoded_data.ccm_accounts(); + + // It's hard at this stage to compute exactly the length of the finally build + // transaction from the message and the additional accounts. Duplicated + // accounts only take one reference byte while new accounts take 32 bytes. + // Technically it shouldn't be necessary to pass duplicated accounts as + // it will all be executed in the same instruction. However when integrating + // with other protocols, many of the account's values are part of a returned + // payload from an API and it makes it cumbersome to then deduplicate on the + // fly and then make it match with the receiver contract. It can be done + // but it then requires extra configuration bytes in the payload, which + // then defeats the purpose. + // 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. + // The Chainflip accounts are irrelevant to the user except for a + // few that are accounted for here. + 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); + } + let mut accounts_length = + ccm_accounts.additional_accounts.len() * ACCOUNT_REFERENCE_LENGTH_IN_TRANSACTION; + + for ccm_address in &ccm_accounts.additional_accounts { + if seen_addresses.insert(ccm_address.pubkey.into()) { + accounts_length += ACCOUNT_KEY_LENGTH_IN_TRANSACTION; + } + } + + let ccm_length = ccm.message.len() + accounts_length; + + if ccm_length > + match asset { + SolAsset::Sol => MAX_CCM_BYTES_SOL, + SolAsset::SolUsdc => MAX_CCM_BYTES_USDC, + } { + return Err(CcmValidityError::CcmIsTooLong) + } + + if decoded_data.address_lookup_tables().len() > MAX_CCM_USER_ALTS as usize { + return Err(CcmValidityError::TooManyAddressLookupTables) } + + Ok(DecodedCcmAdditionalData::Solana(decoded_data)) } else if !ccm.ccm_additional_data.is_empty() { Err(CcmValidityError::RedundantDataSupplied) } else { Ok(DecodedCcmAdditionalData::NotRequired) } } + + fn decode_unchecked( + ccm: &CcmChannelMetadata, + chain: ForeignChain, + ) -> Result { + if chain == ForeignChain::Solana { + VersionedSolanaCcmAdditionalData::decode(&mut &ccm.ccm_additional_data.clone()[..]) + .map(DecodedCcmAdditionalData::Solana) + .map_err(|_| CcmValidityError::CannotDecodeCcmAdditionalData) + } else { + Ok(DecodedCcmAdditionalData::NotRequired) + } + } } /// Checks if the given CCM accounts contains any blacklisted accounts. @@ -166,7 +206,10 @@ mod test { use Asset; use super::*; - use crate::sol::{sol_tx_core::sol_test_values, SolCcmAddress, SolPubkey, MAX_CCM_BYTES_SOL}; + use crate::sol::{ + sol_tx_core::sol_test_values::{self, ccm_accounts, ccm_parameter_v1, user_alt}, + SolCcmAddress, SolPubkey, MAX_CCM_BYTES_SOL, + }; pub const DEST_ADDR: EncodedAddress = EncodedAddress::Sol([0x00; 32]); pub const MOCK_ADDR: SolPubkey = SolPubkey([0x01; 32]); @@ -381,7 +424,7 @@ mod test { pubkey: sol_test_values::TOKEN_VAULT_PDA_ACCOUNT.into(), is_writable: false, }, - SolCcmAddress { pubkey: crate::sol::SolPubkey([0x02; 32]), is_writable: false }, + SolCcmAddress { pubkey: SolPubkey([0x02; 32]), is_writable: false }, ], fallback_address: FALLBACK_ADDR, }; @@ -398,7 +441,7 @@ mod test { }, additional_accounts: vec![ SolCcmAddress { pubkey: MOCK_ADDR, is_writable: false }, - SolCcmAddress { pubkey: crate::sol::SolPubkey([0x02; 32]), is_writable: false }, + SolCcmAddress { pubkey: SolPubkey([0x02; 32]), is_writable: false }, ], fallback_address: FALLBACK_ADDR, }; @@ -411,7 +454,7 @@ mod test { 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 }, + SolCcmAddress { pubkey: SolPubkey([0x02; 32]), is_writable: false }, ], fallback_address: FALLBACK_ADDR, }; @@ -549,4 +592,68 @@ mod test { Err(CcmValidityError::InvalidDestinationAddress) ); } + + #[test] + fn can_decode_unchecked() { + let ccm = sol_test_values::ccm_parameter().channel_metadata; + assert_ok!(CcmValidityChecker::decode_unchecked(&ccm, ForeignChain::Solana)); + assert_eq!( + CcmValidityChecker::decode_unchecked(&ccm, ForeignChain::Ethereum), + Ok(DecodedCcmAdditionalData::NotRequired) + ); + } + + #[test] + fn can_decode_unchecked_ccm_v1() { + let ccm = sol_test_values::ccm_parameter_v1().channel_metadata; + assert_ok!(CcmValidityChecker::decode_unchecked(&ccm, ForeignChain::Solana)); + assert_eq!( + CcmValidityChecker::decode_unchecked(&ccm, ForeignChain::Ethereum), + Ok(DecodedCcmAdditionalData::NotRequired) + ); + } + + #[test] + fn additional_data_v1_support_works() { + let mut ccm = sol_test_values::ccm_parameter_v1().channel_metadata; + assert_eq!( + CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, DEST_ADDR), + Ok(DecodedCcmAdditionalData::Solana(VersionedSolanaCcmAdditionalData::V1 { + ccm_accounts: sol_test_values::ccm_accounts(), + alts: vec![user_alt().key.into()], + })) + ); + + assert_eq!( + CcmValidityChecker::check_and_decode(&ccm, Asset::Eth, DEST_ADDR), + Err(CcmValidityError::RedundantDataSupplied) + ); + + ccm.ccm_additional_data.clear(); + assert_eq!( + CcmValidityChecker::check_and_decode(&ccm, Asset::Eth, DEST_ADDR), + Ok(DecodedCcmAdditionalData::NotRequired) + ); + + assert_eq!( + CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, INVALID_DEST_ADDR), + Err(CcmValidityError::InvalidDestinationAddress) + ); + } + + #[test] + fn can_check_for_too_many_alts() { + let mut ccm = ccm_parameter_v1().channel_metadata; + ccm.ccm_additional_data = codec::Encode::encode(&VersionedSolanaCcmAdditionalData::V1 { + ccm_accounts: ccm_accounts(), + alts: (0..=MAX_CCM_USER_ALTS).map(|i| SolAddress([i; 32])).collect(), + }) + .try_into() + .unwrap(); + + assert_eq!( + CcmValidityChecker::check_and_decode(&ccm, Asset::Sol, DEST_ADDR), + Err(CcmValidityError::TooManyAddressLookupTables) + ); + } } diff --git a/state-chain/chains/src/dot/api.rs b/state-chain/chains/src/dot/api.rs index 6193e963c12..cd97f2ebe5e 100644 --- a/state-chain/chains/src/dot/api.rs +++ b/state-chain/chains/src/dot/api.rs @@ -129,6 +129,7 @@ where _gas_budget: GasAmount, _message: Vec, _ccm_additional_data: Vec, + _swap_request_id: SwapRequestId, ) -> Result { Err(ExecutexSwapAndCallError::Unsupported) } diff --git a/state-chain/chains/src/eth/api.rs b/state-chain/chains/src/eth/api.rs index 2acfbaf6225..e4ec3bd767e 100644 --- a/state-chain/chains/src/eth/api.rs +++ b/state-chain/chains/src/eth/api.rs @@ -195,6 +195,7 @@ where gas_budget: GasAmount, message: Vec, _ccm_additional_data: Vec, + _swap_request_id: SwapRequestId, ) -> Result { let transfer_param = EncodableTransferAssetParams { asset: E::token_address(transfer_param.asset) diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 386caec568a..45b89cbd076 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -25,7 +25,8 @@ use address::{ }; use cf_amm_math::Price; use cf_primitives::{ - AssetAmount, BlockNumber, BroadcastId, ChannelId, EgressId, EthAmount, GasAmount, TxId, + AssetAmount, BlockNumber, BroadcastId, ChannelId, EgressId, EthAmount, GasAmount, + SwapRequestId, TxId, }; use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ @@ -590,6 +591,7 @@ pub trait ExecutexSwapAndCall: ApiCall { gas_budget: GasAmount, message: Vec, ccm_additional_data: Vec, + swap_request_id: SwapRequestId, ) -> Result; } diff --git a/state-chain/chains/src/sol.rs b/state-chain/chains/src/sol.rs index cb847c9cf6d..607f41513b7 100644 --- a/state-chain/chains/src/sol.rs +++ b/state-chain/chains/src/sol.rs @@ -32,12 +32,14 @@ pub use sol_prim::{ TOKEN_ACCOUNT_RENT, }, pda::{Pda as DerivedAddressBuilder, PdaError as AddressDerivationError}, - transaction::legacy::{ - LegacyMessage as SolLegacyMessage, LegacyTransaction as SolLegacyTransaction, + transaction::{ + v0::VersionedMessageV0 as SolVersionedMessageV0, VersionedMessage as SolVersionedMessage, + VersionedTransaction as SolVersionedTransaction, }, - Address as SolAddress, Amount as SolAmount, ComputeLimit as SolComputeLimit, Digest as SolHash, - Hash as RawSolHash, Instruction as SolInstruction, InstructionRpc as SolInstructionRpc, - Pubkey as SolPubkey, Signature as SolSignature, SlotNumber as SolBlockNumber, + Address as SolAddress, AddressLookupTableAccount as SolAddressLookupTableAccount, + Amount as SolAmount, ComputeLimit as SolComputeLimit, Digest as SolHash, 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, @@ -107,7 +109,7 @@ impl ChainCrypto for SolanaCrypto { type KeyHandoverIsRequired = ConstBool; type AggKey = SolAddress; - type Payload = SolLegacyMessage; + type Payload = SolVersionedMessage; type ThresholdSignature = SolSignature; type TransactionInId = SolanaTransactionInId; type TransactionOutId = Self::ThresholdSignature; @@ -130,7 +132,7 @@ impl ChainCrypto for SolanaCrypto { } fn agg_key_to_payload(agg_key: Self::AggKey, _for_handover: bool) -> Self::Payload { - SolLegacyMessage::new(&[], Some(&SolPubkey::from(agg_key))) + SolVersionedMessage::new(&[], Some(SolPubkey::from(agg_key)), None, &[]) } fn maybe_broadcast_barriers_on_rotation( diff --git a/state-chain/chains/src/sol/api.rs b/state-chain/chains/src/sol/api.rs index 491f735fd50..44a179838fe 100644 --- a/state-chain/chains/src/sol/api.rs +++ b/state-chain/chains/src/sol/api.rs @@ -11,15 +11,15 @@ use sp_std::{vec, vec::Vec}; use crate::{ ccm_checker::{ check_ccm_for_blacklisted_accounts, CcmValidityCheck, CcmValidityChecker, CcmValidityError, - DecodedCcmAdditionalData, VersionedSolanaCcmAdditionalData, + DecodedCcmAdditionalData, }, sol::{ sol_tx_core::{ address_derivation::derive_associated_token_account, consts::SOL_USDC_DECIMAL, }, transaction_builder::SolanaTransactionBuilder, - SolAddress, SolAmount, SolApiEnvironment, SolAsset, SolHash, SolLegacyTransaction, - SolTrackedData, SolanaCrypto, + SolAddress, SolAddressLookupTableAccount, SolAmount, SolApiEnvironment, SolAsset, SolHash, + SolTrackedData, SolVersionedTransaction, SolanaCrypto, }, AllBatch, AllBatchError, ApiCall, CcmChannelMetadata, ChainCrypto, ChainEnvironment, ConsolidateCall, ConsolidationError, ExecutexSwapAndCall, ExecutexSwapAndCallError, @@ -28,7 +28,7 @@ use crate::{ TransferFallbackError, }; -use cf_primitives::{EgressId, ForeignChain, GasAmount}; +use cf_primitives::{EgressId, ForeignChain, GasAmount, SwapRequestId}; #[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] pub struct ComputePrice; @@ -42,6 +42,10 @@ pub struct ApiEnvironment; pub struct CurrentAggKey; #[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] pub struct CurrentOnChainKey; +#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] +pub struct SolanaAddressLookupTables(pub SwapRequestId); +#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)] +pub struct ChainflipAddressLookupTable; pub type DurableNonceAndAccount = (SolAddress, SolHash); @@ -73,6 +77,8 @@ pub trait SolanaEnvironment: + ChainEnvironment + ChainEnvironment + ChainEnvironment> + + ChainEnvironment> + + ChainEnvironment + RecoverDurableNonce { fn compute_price() -> Result { @@ -103,6 +109,20 @@ pub trait SolanaEnvironment: .map(|nonces| nonces.into_iter().map(|(addr, _hash)| addr).collect::>()) .ok_or(SolanaTransactionBuildingError::NoNonceAccountsSet) } + + /// Get all relevant Address lookup tables from the Environment. + /// Returns Chainflip's ALT proceeded with user's ALTs. + fn get_address_lookup_tables(id: Option) -> Vec { + let mut alts = Self::get_cf_address_lookup_table().map(|alt| vec![alt]).unwrap_or_default(); + if let Some(id) = id { + alts.extend(Self::lookup(SolanaAddressLookupTables(id)).unwrap_or_default()); + } + alts + } + + fn get_cf_address_lookup_table() -> Option { + Self::lookup(ChainflipAddressLookupTable) + } } /// IMPORTANT: This should only be used if the nonce has not been used to sign a transaction. @@ -162,7 +182,7 @@ pub enum SolanaTransactionType { #[scale_info(skip_type_params(Environment))] pub struct SolanaApi { pub call_type: SolanaTransactionType, - pub transaction: SolLegacyTransaction, + pub transaction: SolVersionedTransaction, pub signer: Option, #[doc(hidden)] #[codec(skip)] @@ -309,6 +329,7 @@ impl SolanaApi { let nonce_accounts = Environment::all_nonce_accounts()?; let compute_price = Environment::compute_price()?; let durable_nonce = Environment::nonce_account()?; + let address_lookup_tables = Environment::get_address_lookup_tables(None); // Build the transaction let transaction = SolanaTransactionBuilder::rotate_agg_key( @@ -319,6 +340,7 @@ impl SolanaApi { agg_key, durable_nonce, compute_price, + address_lookup_tables, ) .inspect_err(|e| { // Vault Rotation call building NOT transactional - meaning when this fails, @@ -350,6 +372,7 @@ impl SolanaApi { gas_budget: GasAmount, message: Vec, ccm_additional_data: Vec, + swap_request_id: SwapRequestId, ) -> Result { // For extra safety, re-verify the validity of the CCM message here // and extract the decoded `ccm_accounts` from `ccm_additional_data`. @@ -375,9 +398,7 @@ impl SolanaApi { let ccm_accounts = if let DecodedCcmAdditionalData::Solana(versioned_sol_data) = decoded_ccm_additional_data { - match versioned_sol_data { - VersionedSolanaCcmAdditionalData::V0(ccm_accounts) => Ok(ccm_accounts), - } + Ok(versioned_sol_data.ccm_accounts()) } else { Err(SolanaTransactionBuildingError::InvalidCcm( CcmValidityError::CannotDecodeCcmAdditionalData, @@ -386,6 +407,8 @@ impl SolanaApi { let sol_api_environment = Environment::api_environment()?; let agg_key = Environment::current_agg_key()?; + // TODO roy: Coordinate with Ramiz on the interface for getting ALTS + let address_lookup_tables = Environment::get_address_lookup_tables(Some(swap_request_id)); // Ensure the CCM parameters do not contain blacklisted accounts. check_ccm_for_blacklisted_accounts( @@ -421,6 +444,7 @@ impl SolanaApi { durable_nonce, compute_price, compute_limit, + address_lookup_tables, ), SolAsset::SolUsdc => { let ata = derive_associated_token_account( @@ -447,6 +471,7 @@ impl SolanaApi { compute_price, SOL_USDC_DECIMAL, compute_limit, + address_lookup_tables, ) }, } @@ -481,6 +506,7 @@ impl SolanaApi { let sol_api_environment = Environment::api_environment()?; let compute_price = Environment::compute_price()?; let durable_nonce = Environment::nonce_account()?; + let address_lookup_tables = Environment::get_address_lookup_tables(None); // Build the transaction let transaction = SolanaTransactionBuilder::fetch_and_close_vault_swap_accounts( @@ -491,6 +517,7 @@ impl SolanaApi { agg_key, durable_nonce, compute_price, + address_lookup_tables, )?; Ok(Self { @@ -513,6 +540,7 @@ impl SolanaApi { let sol_api_environment = Environment::api_environment()?; let compute_price = Environment::compute_price()?; let durable_nonce = Environment::nonce_account()?; + let address_lookup_tables = Environment::get_address_lookup_tables(None); // Build the transaction let transaction = SolanaTransactionBuilder::set_program_swaps_parameters( @@ -528,6 +556,7 @@ impl SolanaApi { agg_key, durable_nonce, compute_price, + address_lookup_tables, )?; Ok(Self { @@ -546,6 +575,7 @@ impl SolanaApi { let sol_api_environment = Environment::api_environment()?; let compute_price = Environment::compute_price()?; let durable_nonce = Environment::nonce_account()?; + let address_lookup_tables = Environment::get_address_lookup_tables(None); // Build the transaction let transaction = SolanaTransactionBuilder::enable_token_support( @@ -558,6 +588,7 @@ impl SolanaApi { agg_key, durable_nonce, compute_price, + address_lookup_tables, )?; Ok(Self { @@ -638,6 +669,7 @@ impl ExecutexSwapAndCall for SolanaApi gas_budget: GasAmount, message: Vec, ccm_additional_data: Vec, + swap_request_id: SwapRequestId, ) -> Result { Self::ccm_transfer( transfer_param, @@ -648,6 +680,7 @@ impl ExecutexSwapAndCall for SolanaApi gas_budget, message, ccm_additional_data, + swap_request_id, ) .map_err(|e| { log::error!("Failed to construct Solana CCM transfer transaction! \nError: {:?}", e); diff --git a/state-chain/chains/src/sol/benchmarking.rs b/state-chain/chains/src/sol/benchmarking.rs index 847b937d6fa..a9ba3fef24e 100644 --- a/state-chain/chains/src/sol/benchmarking.rs +++ b/state-chain/chains/src/sol/benchmarking.rs @@ -2,8 +2,8 @@ use super::{ api::{SolanaApi, VaultSwapAccountAndSender}, - SolAddress, SolHash, SolLegacyMessage, SolLegacyTransaction, SolSignature, SolTrackedData, - SolanaTransactionData, + SolAddress, SolHash, SolSignature, SolTrackedData, SolVersionedMessage, + SolVersionedTransaction, SolanaTransactionData, }; use crate::benchmarking_value::{BenchmarkValue, BenchmarkValueExtended}; @@ -26,22 +26,22 @@ impl BenchmarkValue for SolTrackedData { } } -impl BenchmarkValue for SolLegacyMessage { +impl BenchmarkValue for SolVersionedMessage { fn benchmark_value() -> Self { - Self::new_with_blockhash(&[], None, &SolHash::default().into()) + Self::new(&[], None, None, &[]) } } -impl BenchmarkValue for SolLegacyTransaction { +impl BenchmarkValue for SolVersionedTransaction { fn benchmark_value() -> Self { - SolLegacyTransaction::new_unsigned(SolLegacyMessage::benchmark_value()) + SolVersionedTransaction::new_unsigned(SolVersionedMessage::benchmark_value()) } } impl BenchmarkValue for SolanaTransactionData { fn benchmark_value() -> Self { SolanaTransactionData { - serialized_transaction: SolLegacyTransaction::benchmark_value() + serialized_transaction: SolVersionedTransaction::benchmark_value() .finalize_and_serialize() .expect("Failed to serialize payload"), skip_preflight: false, diff --git a/state-chain/chains/src/sol/instruction_builder.rs b/state-chain/chains/src/sol/instruction_builder.rs index 7e9589beb97..a55c3a80a96 100644 --- a/state-chain/chains/src/sol/instruction_builder.rs +++ b/state-chain/chains/src/sol/instruction_builder.rs @@ -108,7 +108,8 @@ mod test { consts::{const_address, MAX_TRANSACTION_LENGTH}, sol_test_values::*, }, - SolAddress, SolHash, SolLegacyMessage, SolLegacyTransaction, + SolAddress, SolAddressLookupTableAccount, SolHash, SolVersionedMessage, + SolVersionedTransaction, }, ChannelRefundParameters, }; @@ -200,11 +201,17 @@ mod test { ) } - fn into_transaction(instructions: SolInstruction, payer: SolPubkey) -> SolLegacyTransaction { + fn into_transaction( + instructions: SolInstruction, + payer: SolPubkey, + alt: &[SolAddressLookupTableAccount], + ) -> SolVersionedTransaction { // Build mock Transaction for testing. - let transaction = SolLegacyTransaction::new_unsigned(SolLegacyMessage::new( + let transaction = SolVersionedTransaction::new_unsigned(SolVersionedMessage::new( &[instructions], - Some(&payer), + Some(payer), + Default::default(), + alt, )); let mock_serialized_tx = transaction @@ -235,9 +242,10 @@ mod test { None, ), FROM.into(), + &[chainflip_alt()], ); - let expected_serialized_tx = hex_literal::hex!("021d485a1e6df1d3b4dcee7f4c1442443d61c33ba047556e5461cfa14fdccf7eb4b16b1bfad0428704b01dc85e46ac29f19a8f4e82620c6c6cc87cc1a1e5fb1908baa3bed94d223389107f6fff96ce9b85d646ab64f95394f6d4be7455f27571030edc6746ed42c9b8a16abdfd6efc4fbc7e1df5c8f9fc24e5b7ff3ccd8ca0ca0c02000307cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b0000000000000000000000000000000000000000000000000000000000000000010506060300010204ab01a3265ce2f3698dc4d2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000076000000000a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214").to_vec(); + let expected_serialized_tx = hex_literal::hex!("02297de611719580c43ee59d349f18af1da21fae4c8ed8535b0a6c18281d2cfc8cfb581371896ef2dc3999807e3107889bb2d2f7f7bdf0c291ce0325810b92d70e13ea28637448af5bf7343305cbd6835e1f979307180405573f05ff4b11265e378b5a814166c66600029a78b0eb631d566cddf525a921a40fd7d70d1b70547b088002000205cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89ff79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229380000000000000000000000000000000000000000000000000000000000000000010406060200010503ab01a3265ce2f3698dc4d2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000076000000000a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a101080102").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = @@ -266,9 +274,10 @@ mod test { Some(ccm_parameter().channel_metadata), ), FROM.into(), + &[chainflip_alt()], ); - let expected_serialized_tx = hex_literal::hex!("026b0befc47440952c815d5c94691b3ffab988b83e02ee74731b420cf1e572d8f06774330f43a7639528aa004435e10ec49accee16bd3cb73733a502d61fb10e07d4b8cbf900562ab1f613d70be1b28142c869c3d47bfacb6742dd50fa61e6820ce4ec8f10b2a2f330fdd0a351935fecd850323123223af2acb8bbed4b7e86970102000307cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b0000000000000000000000000000000000000000000000000000000000000000010506060300010204ad02a3265ce2f3698dc4d20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0a00000001040000007c1d0f070000000000000000dc000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214").to_vec(); + let expected_serialized_tx = hex_literal::hex!("029269e0d838f8c7dc39ab8c07c8cdfbf2fd1e09bd6a2d6a91b4be880d699717300c518011cb85822c288016b688177bc3ab7a72809921992e10d081c3dcb4740ed419659340a7ce633192081233b8df64e4689ea25f4cf9ebe9260a27950d1761b7b88d62376f01fecf8f96ea1ca9e7a323ca38f7b60020d1964d246f4da6c3078002000205cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89ff79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb00000000000000000000000000000000000000000000000000000000000000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229380000000000000000000000000000000000000000000000000000000000000000010406060200010503ad02a3265ce2f3698dc4d20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0a00000001040000007c1d0f070000000000000000dc000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a0214013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a101080102").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = @@ -307,9 +316,10 @@ mod test { None, ), FROM.into(), + &[chainflip_alt()], ); - let expected_serialized_tx = hex_literal::hex!("02d546874a4fc16d7c369ea5eb86a3344c3bdeda58b0721de0c2c5a372b455d565232421d5ceabb4782dd2fb23cac7812ef1836032b65afc823094b56d01500201bcc4d183f10ae173e8f7b1fe9fcd8fc2297b4aab8ed58a5d220877dbbbeecedaab2a1d913e1ad6870245e8ed9b4ee0ea1e2f4cb948bf0143defede95c83aa8040200060bcf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d227fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8ae91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee871ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b000000000000000000000000000000000000000000000000000000000000000001080a0a040003010209060705ac014532fc63e55377ebd2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000076000000000a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406").to_vec(); + let expected_serialized_tx = hex_literal::hex!("02e958c1c2694aae31d25837e9b7a36b24e5c14fc2d4132f47e9b92ba7d9d888c7c8647e708686dfccdf365859c7962e30083f4f6568a7f8386980303ee314f00e48f7405e65de9a542320c386db554ca40850614d3aa764cf9d2c82e68f976b15af8a4960b457b5808280e3fc72ae8e19a197de42e56431d9c3ac5b16801884068002000407cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f27fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8a000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a91ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769c000000000000000000000000000000000000000000000000000000000000000001050a0a080002010706040903ac014532fc63e55377ebd2029649000000000100000014000000756fbde9c71eae05c2f7169f816b0bd11d978020010000000076000000000a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a1020805020302").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = @@ -348,9 +358,10 @@ mod test { Some(ccm_parameter().channel_metadata), ), FROM.into(), + &[chainflip_alt()], ); - let expected_serialized_tx = hex_literal::hex!("0283f23209bc3d33c8e7fc33d4541de8fb6172f28d6d73212d1c7630491c0605a9051648fe23da7870a180649a5fd509af4dd0a5a4b4563ca2a5bf8386ecb9fe053160493fa3dd5274744ec6c42b5c6152ef58aff96c2c1230fefc06a6a89fde864db41185571b1d2740d7639ff64a53b35db561d0ee75a68fa9f8268f5807fd060200060bcf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f1c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d227fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8ae91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee871ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b000000000000000000000000000000000000000000000000000000000000000001080a0a040003010209060705ae024532fc63e55377ebd20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0900000001040000007c1d0f070000000000000000dc000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406").to_vec(); + let expected_serialized_tx = hex_literal::hex!("02e6d173a6df8b9b1dbbd93cf4d94d2089fe6827fd1db8f34963227bbee8ca92f337f4f8e6cf8473ae3b2269e584e41e6aa5b40ed08b3ff1bb82671713724eaf02048c71002512de4926e57d72217bb5786669a083da3ada13b37d3a7b9843c40f06cdab381591ed34dcbd78a7be5c3b68d9ee938b9d061dbab72ea473a62983038002000407cf2a079e1506b29d02c8feac98d589a9059a740891dcd3dab6c64b3160bc28317f799121d6c125f312c5f423a51959ce1d41df06af977e9a17f48b2c82ecf89f27fefc7ec198ef3eacb0f17871e1fad81f07a40cd55f4f364c3915877d89bd8a000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a91ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc16229382e9acf1ff8568fbe655e616a167591aeedc250afbc88d759ec959b1982e8769c000000000000000000000000000000000000000000000000000000000000000001050a0a080002010706040903ae024532fc63e55377ebd20296490000000005000000200000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0900000001040000007c1d0f070000000000000000dc000000009101007417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed480104a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e50090e0b0f5b60147b325842c1fc68f6c90fe26419ea7c4afeb982f71f1f54b5b440a0000009e0d6a70e12d54edf90971cc977fa26a1d3bb4b0b26e72470171c36b0006b01f0000000000000000000000000000000000000000000000000000000000000000010a0000001400000002a0edda1a4beee4fe2df32c0802aa6759da49ae6165fcdb5c40d7f4cd5a30db0e010008010a021406013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a1020805020302").to_vec(); let from_signing_key = SolSigningKey::from_bytes(&FROM_KEY_BYTES).unwrap(); let event_data_account_signing_key = diff --git a/state-chain/chains/src/sol/sol_tx_core.rs b/state-chain/chains/src/sol/sol_tx_core.rs index ff51fe72581..a064429d49f 100644 --- a/state-chain/chains/src/sol/sol_tx_core.rs +++ b/state-chain/chains/src/sol/sol_tx_core.rs @@ -7,7 +7,6 @@ use sp_std::vec::Vec; use crate::sol::SolAddress; pub use sol_prim::*; -pub use transaction::legacy::{LegacyMessage, LegacyTransaction}; /// Provides alternative version of internal types that uses `Address` instead of Pubkey: /// @@ -125,11 +124,12 @@ pub mod sol_test_values { api::{DurableNonceAndAccount, VaultSwapAccountAndSender}, signing_key::SolSigningKey, sol_tx_core::signer::{Signer, TestSigners}, - SolAddress, SolAmount, SolApiEnvironment, SolAsset, SolCcmAccounts, SolCcmAddress, - SolComputeLimit, SolHash, + SolAddress, SolAddressLookupTableAccount, SolAmount, SolApiEnvironment, SolAsset, + SolCcmAccounts, SolCcmAddress, SolComputeLimit, SolHash, SolVersionedTransaction, }, CcmChannelMetadata, CcmDepositMetadata, ForeignChain, ForeignChainAddress, }; + use itertools::Itertools; use sol_prim::consts::{const_address, const_hash, MAX_TRANSACTION_LENGTH}; use sp_std::vec; @@ -289,19 +289,74 @@ pub mod sol_test_values { } } + pub fn ccm_parameter_v1() -> CcmDepositMetadata { + let mut ccm = ccm_parameter(); + ccm.channel_metadata.ccm_additional_data = + codec::Encode::encode(&VersionedSolanaCcmAdditionalData::V1 { + ccm_accounts: ccm_accounts(), + alts: vec![user_alt().key.into()], + }) + .try_into() + .unwrap(); + ccm + } + pub fn agg_key() -> SolAddress { SolSigningKey::from_bytes(&RAW_KEYPAIR).unwrap().pubkey().into() } + pub fn chainflip_alt() -> SolAddressLookupTableAccount { + SolAddressLookupTableAccount { + key: const_address("4EQ4ZTskvNwkBaQjBJW5grcmV5Js82sUooNLHNTpdHdi").into(), + addresses: vec![ + vec![ + VAULT_PROGRAM, + VAULT_PROGRAM_DATA_ADDRESS, + VAULT_PROGRAM_DATA_ACCOUNT, + USDC_TOKEN_MINT_PUB_KEY, + TOKEN_VAULT_PDA_ACCOUNT, + USDC_TOKEN_VAULT_ASSOCIATED_TOKEN_ACCOUNT, + SWAP_ENDPOINT_DATA_ACCOUNT_ADDRESS, + SWAP_ENDPOINT_PROGRAM, + SWAP_ENDPOINT_PROGRAM_DATA_ACCOUNT, + ], + vec![ + const_address("2cNMwUCF51djw2xAiiU54wz1WrU8uG4Q8Kp8nfEuwghw"), + const_address("HVG21SovGzMBJDB9AQNuWb6XYq4dDZ6yUwCbRUuFnYDo"), + const_address("HDYArziNzyuNMrK89igisLrXFe78ti8cvkcxfx4qdU2p"), + const_address("HLPsNyxBqfq2tLE31v6RiViLp2dTXtJRgHgsWgNDRPs2"), + const_address("GKMP63TqzbueWTrFYjRwMNkAyTHpQ54notRbAbMDmePM"), + const_address("EpmHm2aSPsB5ZZcDjqDhQ86h1BV32GFCbGSMuC58Y2tn"), + const_address("9yBZNMrLrtspj4M7bEf2X6tqbqHxD2vNETw8qSdvJHMa"), + const_address("J9dT7asYJFGS68NdgDCYjzU2Wi8uBoBusSHN1Z6JLWna"), + const_address("GUMpVpQFNYJvSbyTtUarZVL7UDUgErKzDTSVJhekUX55"), + const_address("AUiHYbzH7qLZSkb3u7nAqtvqC7e41sEzgWjBEvXrpfGv"), + ], + ] + .into_iter() + .concat() + .into_iter() + .map(|a| a.into()) + .collect::>(), + } + } + + pub fn user_alt() -> SolAddressLookupTableAccount { + SolAddressLookupTableAccount { + key: const_address("3VBLeVu7rZciyk19M9V7VbHBM2uFm9YbnKKPB33mGRW8").into(), + addresses: vec![TRANSFER_TO_ACCOUNT.into()], + } + } + #[track_caller] pub fn test_constructed_transaction_with_signer( - mut transaction: crate::sol::SolLegacyTransaction, + mut transaction: SolVersionedTransaction, expected_serialized_tx: Vec, signers: TestSigners, blockhash: super::Hash, ) { // Sign the transaction with the given singers and blockhash. - transaction.sign(signers, blockhash); + transaction.test_only_sign(signers, blockhash); let serialized_tx = transaction .clone() @@ -321,7 +376,7 @@ pub mod sol_test_values { #[track_caller] pub fn test_constructed_transaction( - transaction: crate::sol::SolLegacyTransaction, + transaction: SolVersionedTransaction, expected_serialized_tx: Vec, ) { let agg_key_keypair = SolSigningKey::from_bytes(&RAW_KEYPAIR).unwrap(); diff --git a/state-chain/chains/src/sol/tests.rs b/state-chain/chains/src/sol/tests.rs index bf50db4d7df..f8638db0b59 100644 --- a/state-chain/chains/src/sol/tests.rs +++ b/state-chain/chains/src/sol/tests.rs @@ -19,8 +19,8 @@ use crate::{ signer::Signer, sol_test_values::*, token_instructions::AssociatedTokenAccountInstruction, - AccountMeta, CompiledInstruction, Hash, Instruction, LegacyMessage, LegacyTransaction, - MessageHeader, PdaAndBump, Pubkey, + transaction::legacy::{LegacyMessage, LegacyTransaction}, + AccountMeta, CompiledInstruction, Hash, Instruction, MessageHeader, PdaAndBump, Pubkey, }, SolAddress, SolHash, SolSignature, }, @@ -38,10 +38,10 @@ enum BankInstruction { #[cfg(test)] mod versioned_transaction { - use crate::sol::sol_tx_core::{ - consts::{const_address, const_hash}, - transaction::{v0::VersionedMessageV0, VersionedMessage, VersionedTransaction}, - AddressLookupTableAccount, + use crate::sol::{ + sol_tx_core::consts::{const_address, const_hash}, + SolAddressLookupTableAccount, SolVersionedMessage, SolVersionedMessageV0, + SolVersionedTransaction, }; use super::*; @@ -62,14 +62,14 @@ mod versioned_transaction { 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( + let message = SolVersionedMessage::V0(SolVersionedMessageV0::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 mut tx = SolVersionedTransaction::new_unsigned(message); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = "012e1beb02a24f6e59148fc4eb64aeaeaad291e5f241b8b2d01775a6d3956392ac7186fbee0963d6ca0720bddb5d8b555ada6beb2cd3e9bd0415c343a5ca0cde0b8001000306f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd400000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004030301050004040000000400090340420f000000000004000502e0930400030200020c0200000000ca9a3b0000000000"; @@ -84,7 +84,7 @@ mod versioned_transaction { const_address("2cNMwUCF51djw2xAiiU54wz1WrU8uG4Q8Kp8nfEuwghw").into(), const_hash("2qVz58R5aPmF5Q61VaKXnpWQtngdh4Jgbeko32fEcECu").into(), ); - let alt = AddressLookupTableAccount { + let alt = SolAddressLookupTableAccount { key: const_address("4EQ4ZTskvNwkBaQjBJW5grcmV5Js82sUooNLHNTpdHdi").into(), addresses: vec![const_address("CFnQk1nVmkPThKvLU8EUPFtTuJro45JLSoqux4v23ZGy").into()], }; @@ -99,14 +99,14 @@ mod versioned_transaction { 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( + let message = SolVersionedMessage::V0(SolVersionedMessageV0::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 mut tx = SolVersionedTransaction::new_unsigned(message); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce.1); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = "01ed1357672e0e660e9afd6dd948bee446639a232171900b89a1d403e78e58ad30d8da3986888c9e07ec066b19198b59f99428c00fcf858040e669185473ded5008001000305f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19200000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000001b48568b09b08111ebdcf9a5073d86a4506a3c3fe2a6d47a8a5ce0c459a65bce04020301040004040000000300090340420f000000000003000502e0930400020200050c0200000000ca9a3b00000000013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a1010000"; @@ -116,18 +116,6 @@ mod versioned_transaction { } } -#[test] -fn create_simple_tx() { - let program_id = Pubkey([0u8; 32]); - let payer = SolSigningKey::new(); - let bank_instruction = BankInstruction::Initialize; - - let instruction = Instruction::new_with_borsh(program_id, &bank_instruction, vec![]); - - let mut tx = LegacyTransaction::new_with_payer(&[instruction], Some(&payer.pubkey())); - tx.sign(vec![payer].into(), Default::default()); -} - #[test] fn create_transfer_native() { let durable_nonce = TEST_DURABLE_NONCE.into(); @@ -143,7 +131,7 @@ fn create_transfer_native() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("01345c86d1be2bcdf2c93c75b6054b6232e5b1e7f2fe7b3ca241d48c8a5f993af3e474bf581b2e9a1543af13104b3f3a53530d849731cc403418da313743a57e0401000306f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd400000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004030301050004040000000400090340420f000000000004000502e0930400030200020c0200000000ca9a3b00000000").to_vec(); @@ -169,7 +157,7 @@ fn create_transfer_cu_priority_fees() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("017036ecc82313548a7f1ef280b9d7c53f9747e23abcb4e76d86c8df6aa87e82d460ad7cea2e8d972a833d3e1802341448a99be200ad4648c454b9d5a5e2d5020d01000306f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd400000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000012c57218f6315b83818802f3522fe7e04c596ae4fe08841e7940bc2f958aaaea04030301050004040000000400090340420f000000000004000502e0930400030200020c0200000040420f0000000000").to_vec(); @@ -207,7 +195,7 @@ fn create_fetch_native() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().expect("Transaction serialization should succeed"); @@ -262,7 +250,7 @@ fn create_fetch_native_in_batch() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().expect("Transaction serialization should succeed"); @@ -334,7 +322,7 @@ fn create_fetch_tokens() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("01399c2b68c7fae634a32d15c3417c2a92e3632707fff366cf9f92a085642344915b7de51181defe33f87ac0a718cd6df5849e229d54d7a06b362b621855c367010100080df79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19242ff6863b52c3f8faf95739e6541bda5d0ac593f00c6c07d9ab37096bf26d910ae85f2fb6289c70bfe37df150dddb17dd84f403fd0b1aa1bfee85795159de21fe91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8746cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a7072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004050301070004040000000600090340420f000000000006000502e09304000b090c000a02040908030516494710642cb0c646080000000000000000000000ff06").to_vec(); @@ -411,7 +399,7 @@ fn create_batch_fetch() { ]; let message = LegacyMessage::new(&instructions, Some(&agg_key_pubkey)); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("018117c8fd4d21069f04599ab5c79c6d093991392ca54dacfcefac64585928ae13ae81a9aa51a003a10a47a0d9372301e36df2ca2a0e7797d179d030b30563b20d01000912f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1921ad0968d57ee79348476716f9b2cd44ec4284b8f52c36648d560949e41589a5540de1c0451cccb6edd1fda9b4a48c282b279350b55a7a9716800cc0132b6f0b042ff6863b52c3f8faf95739e6541bda5d0ac593f00c6c07d9ab37096bf26d910a140fd3d05766f0087d57bf99df05731e894392ffcc8e8d7e960ba73c09824aaae85f2fb6289c70bfe37df150dddb17dd84f403fd0b1aa1bfee85795159de21fb4baefcd4965beb1c71311a2ffe76419d4b8f8d35fbc4cf514b1bd02da2df2e3e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8746cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a7072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8d9871ed5fb2ee05765af23b7cabcc0d6b08ed370bb9f616a0d4dea40a25f870a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900060903010b0004040000000a00090340420f00000000000a000502e09304000f0911000e04080d0c060916494710642cb0c646080000000000000000000000ff060f0911001002080d0c030916494710642cb0c646080000000100000000000000ff060f051100050709158e24658f6c59298c080000000200000000000000ff").to_vec(); @@ -455,7 +443,7 @@ fn create_transfer_tokens() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("012c77634fbfac44e246e991202e24d1b6c2fc438482fa9dbea617b0387aa3d19e2561dd12929db8cc2ae43ccd0f19185882bfac2ef6f7baf1438929cd5b99dd0701000a0ef79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1925ec7baaea7200eb2a66ccd361ee73bc87a7e5222ecedcbc946e97afb59ec4616e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8731e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd472b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bab1d2a644046552e73f4d05b5a6ef53848973a9ee9febba42ddefb034b5f5130c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890005040301060004040000000500090340420f000000000005000502e09304000b0600020908040701010a070c000d030208071136b4eeaf4a557ebc00ca9a3b0000000006").to_vec(); @@ -496,7 +484,7 @@ fn create_full_rotation() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("0118e0ec3502ff3656ffeecb9c56cc0e6676bba9ac1026a535e24ab3e2f9ef78353e1c3698bd3582a1e711b68cc0e39802c04dd48a298a572e55fd2e2a6bc2770101000411f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1926744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900448541f57201f277c5f3ffb631d0212e26e7f47749c26c4808718174a0ab2a09a18cd28baa84f2067bbdf24513c2d44e44bf408f2e6da6e60762e3faa4a62a0adba1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bcd644e45426a41a7cb8369b8a0c1c89bb3f86cf278fdd9cc38b0f69784ad5667e392cd98d3284fd551604be95c14cc8e20123e2940ef9fb784e6b591c7442864e5e1869817a4fd88ddf7ab7a5f7252d7c345b39721769888608592912e8ca9acf0f13460b3fd04b7d53d7421fc874ec00eec769cf36480895e1a407bf1249475f2b2e24122be016983be9369965246cc45e1f621d40fba300c56c7ac50c3874df4f83bd213a59c9785110cf83c718f9486c3484f918593bce20c61dc6a96036afecc89e3b031824af6363174d19bbec12d3a13c4a173e5aeb349b63042bc138f00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293cc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000e0d03010f0004040000000e00090340420f00000000000e000502e093040010040500020d094e518fabdda5d68b000d02010024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d020b0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02090024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d020a0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02070024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02060024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02030024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d020c0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02080024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02040024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be543990044").to_vec(); @@ -538,7 +526,7 @@ fn create_ccm_native_transfer() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("01be5b6acac88600095a934dc7ae8af889c78281664e6b561f3a18bc26887ae95f35fc76d892da32f4a7314a253c0abed2da1c89d5e6daede4d70cacd37942090a0100070bf79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd47417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed4800000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517187bd16635dad40455fdc2c0c124c68f215675a5dbbacb5f0800000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00ba73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e5c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890005040301070004040000000500090340420f000000000005000502e0930400040200020c0200000000ca9a3b0000000008070900020304060a347d050be38042e0b20100000014000000ffffffffffffffffffffffffffffffffffffffff040000007c1d0f0700ca9a3b00000000").to_vec(); @@ -600,7 +588,7 @@ fn create_ccm_token_transfer() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("01bd5f6ad13bbce1d97011814f8b7758b42a392ecc0b993c7b0be88499cbb089b3b364eaed5f3998f1dd97670f5a4b146c3be9681cb2d71fc81066657b7423d40501000c11f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1925ec7baaea7200eb2a66ccd361ee73bc87a7e5222ecedcbc946e97afb59ec46167417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed48e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517187bd16635dad40455fdc2c0c124c68f215675a5dbbacb5f0800000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8731e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd472b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00ba73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e5ab1d2a644046552e73f4d05b5a6ef53848973a9ee9febba42ddefb034b5f5130c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890006050301080004040000000600090340420f000000000006000502e09304000d0600020b0a050901010c070e001004020a091136b4eeaf4a557ebc00ca9a3b00000000060c080e000203090a070f346cb8a27b9fdeaa230100000014000000ffffffffffffffffffffffffffffffffffffffff040000007c1d0f0700ca9a3b00000000").to_vec(); @@ -634,7 +622,7 @@ fn create_idempotent_associated_token_account() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = hex_literal::hex!("01eb287ff9329fbaf83592ec56709d52d3d7f7edcab7ab53fc8371acff871016c51dfadde692630545a91d6534095bb5697b5fb9ee17dc292552eabf9ab6e3390601000609f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d192ca03f3e6d6fd79aaf8ebd4ce053492a34f22d0edafbfa88a380848d9a4735150000000000000000000000000000000000000000000000000000000000000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90c4a8e3702f6e26d9d0c900c1461da4e3debef5743ce253bb9f0308a68c944220f1b83220b1108ea0e171b5391e6c0157370c8353516b74e962f855be6d787038c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f85921b22d7dfc8cdeba6027384563948d038a11eba06289de51a15c3d649d1f7e2c020303010400040400000008060002060703050101").to_vec(); @@ -670,7 +658,7 @@ fn create_set_program_swaps_parameters() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = [ @@ -723,7 +711,7 @@ fn create_enable_token_support() { let message = LegacyMessage::new_with_blockhash(&instructions, Some(&agg_key_pubkey), &durable_nonce); let mut tx = LegacyTransaction::new_unsigned(message); - tx.sign(vec![agg_key_keypair].into(), durable_nonce); + tx.test_only_sign(vec![agg_key_keypair].into(), durable_nonce); let serialized_tx = tx.finalize_and_serialize().unwrap(); let expected_serialized_tx = [ @@ -773,7 +761,7 @@ fn create_sample_transaction() -> LegacyTransaction { let instruction = Instruction::new_with_bincode(program_id, &(1u8, 2u8, 3u8), account_metas); let message = LegacyMessage::new(&[instruction], Some(&keypair.pubkey())); let mut tx: LegacyTransaction = LegacyTransaction::new_unsigned(message); - tx.sign(vec![keypair].into(), Hash::default()); + tx.test_only_sign(vec![keypair].into(), Hash::default()); tx } diff --git a/state-chain/chains/src/sol/transaction_builder.rs b/state-chain/chains/src/sol/transaction_builder.rs index 456d11d887d..b9bb88f646d 100644 --- a/state-chain/chains/src/sol/transaction_builder.rs +++ b/state-chain/chains/src/sol/transaction_builder.rs @@ -33,8 +33,9 @@ use crate::{ token_instructions::AssociatedTokenAccountInstruction, AccountMeta, }, - AccountBump, SolAddress, SolAmount, SolApiEnvironment, SolAsset, SolCcmAccounts, - SolComputeLimit, SolInstruction, SolLegacyMessage, SolLegacyTransaction, SolPubkey, Solana, + AccountBump, SolAddress, SolAddressLookupTableAccount, SolAmount, SolApiEnvironment, + SolAsset, SolCcmAccounts, SolComputeLimit, SolInstruction, SolPubkey, SolVersionedMessage, + SolVersionedTransaction, Solana, }, FetchAssetParams, ForeignChainAddress, }; @@ -65,14 +66,15 @@ impl SolanaTransactionBuilder { /// complete. This will add some extra instruction required for the integrity of the Solana /// Transaction. /// - /// Returns the finished Instruction Set to construct the SolLegacyTransaction. + /// Returns the finished Instruction Set to construct the SolVersionedTransaction. fn build( mut instructions: Vec, durable_nonce: DurableNonceAndAccount, agg_key: SolPubkey, compute_price: SolAmount, compute_limit: SolComputeLimit, - ) -> Result { + address_lookup_tables: Vec, + ) -> Result { let mut final_instructions = vec![SystemProgramInstruction::advance_nonce_account( &durable_nonce.0.into(), &agg_key, @@ -88,10 +90,11 @@ impl SolanaTransactionBuilder { final_instructions.append(&mut instructions); // Test serialize the final transaction to obtain its length. - let transaction = SolLegacyTransaction::new_unsigned(SolLegacyMessage::new_with_blockhash( + let transaction = SolVersionedTransaction::new_unsigned(SolVersionedMessage::new( &final_instructions, - Some(&agg_key), - &durable_nonce.1.into(), + Some(agg_key), + Some(durable_nonce.1.into()), + &address_lookup_tables[..], )); let mock_serialized_tx = transaction @@ -116,7 +119,7 @@ impl SolanaTransactionBuilder { agg_key: SolAddress, durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, - ) -> Result { + ) -> Result { let mut compute_limit: SolComputeLimit = BASE_COMPUTE_UNITS_PER_TX; let instructions = fetch_params .into_iter() @@ -186,6 +189,7 @@ impl SolanaTransactionBuilder { agg_key.into(), compute_price, compute_limit_with_buffer(compute_limit), + vec![], ) } @@ -197,7 +201,7 @@ impl SolanaTransactionBuilder { agg_key: SolAddress, durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, - ) -> Result { + ) -> Result { let instructions = vec![SystemProgramInstruction::transfer(&agg_key.into(), &to.into(), amount)]; @@ -209,6 +213,7 @@ impl SolanaTransactionBuilder { compute_limit_with_buffer( BASE_COMPUTE_UNITS_PER_TX + COMPUTE_UNITS_PER_TRANSFER_NATIVE, ), + vec![], ) } @@ -226,7 +231,7 @@ impl SolanaTransactionBuilder { durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, token_decimals: u8, - ) -> Result { + ) -> Result { let instructions = vec![ AssociatedTokenAccountInstruction::create_associated_token_account_idempotent_instruction( &agg_key.into(), @@ -253,6 +258,7 @@ impl SolanaTransactionBuilder { agg_key.into(), compute_price, compute_limit_with_buffer(BASE_COMPUTE_UNITS_PER_TX + COMPUTE_UNITS_PER_TRANSFER_TOKEN), + vec![], ) } @@ -265,7 +271,8 @@ impl SolanaTransactionBuilder { agg_key: SolAddress, durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, - ) -> Result { + address_lookup_tables: Vec, + ) -> Result { let mut instructions = vec![VaultProgram::with_id(vault_program).rotate_agg_key( false, vault_program_data_account, @@ -287,6 +294,7 @@ impl SolanaTransactionBuilder { agg_key.into(), compute_price, compute_limit_with_buffer(COMPUTE_UNITS_PER_ROTATION), + address_lookup_tables, ) } @@ -304,7 +312,8 @@ impl SolanaTransactionBuilder { durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, compute_limit: SolComputeLimit, - ) -> Result { + address_lookup_tables: Vec, + ) -> Result { let instructions = vec![ SystemProgramInstruction::transfer(&agg_key.into(), &to.into(), amount), VaultProgram::with_id(vault_program) @@ -323,7 +332,14 @@ impl SolanaTransactionBuilder { .with_additional_accounts(ccm_accounts.additional_account_metas()), ]; - Self::build(instructions, durable_nonce, agg_key.into(), compute_price, compute_limit) + Self::build( + instructions, + durable_nonce, + agg_key.into(), + compute_price, + compute_limit, + address_lookup_tables, + ) } pub fn ccm_transfer_token( @@ -344,7 +360,8 @@ impl SolanaTransactionBuilder { compute_price: SolAmount, token_decimals: u8, compute_limit: SolComputeLimit, - ) -> Result { + address_lookup_tables: Vec, + ) -> Result { let instructions = vec![ AssociatedTokenAccountInstruction::create_associated_token_account_idempotent_instruction( &agg_key.into(), @@ -377,7 +394,14 @@ impl SolanaTransactionBuilder { sys_var_instructions(), ).with_additional_accounts(ccm_accounts.additional_account_metas())]; - Self::build(instructions, durable_nonce, agg_key.into(), compute_price, compute_limit) + Self::build( + instructions, + durable_nonce, + agg_key.into(), + compute_price, + compute_limit, + address_lookup_tables, + ) } /// Create an instruction set to set the current GovKey with the agg key. @@ -388,7 +412,7 @@ impl SolanaTransactionBuilder { agg_key: SolAddress, durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, - ) -> Result { + ) -> Result { let instructions = vec![VaultProgram::with_id(vault_program).set_gov_key_with_agg_key( new_gov_key.into(), vault_program_data_account, @@ -401,6 +425,7 @@ impl SolanaTransactionBuilder { agg_key.into(), compute_price, compute_limit_with_buffer(COMPUTE_UNITS_PER_SET_GOV_KEY), + vec![], ) } @@ -414,7 +439,8 @@ impl SolanaTransactionBuilder { agg_key: SolAddress, durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, - ) -> Result { + address_lookup_tables: Vec, + ) -> Result { let number_of_accounts = vault_swap_accounts.len(); let swap_and_sender_vec: Vec = vault_swap_accounts .into_iter() @@ -457,6 +483,7 @@ impl SolanaTransactionBuilder { COMPUTE_UNITS_PER_FETCH_AND_CLOSE_VAULT_SWAP_ACCOUNTS + COMPUTE_UNITS_PER_CLOSE_ACCOUNT * number_of_accounts as u32, ), + address_lookup_tables, ) } @@ -472,7 +499,8 @@ impl SolanaTransactionBuilder { gov_key: SolAddress, durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, - ) -> Result { + address_lookup_tables: Vec, + ) -> Result { let instructions = vec![VaultProgram::with_id(vault_program).set_program_swaps_parameters( min_native_swap_amount, max_dst_address_len, @@ -489,6 +517,7 @@ impl SolanaTransactionBuilder { gov_key.into(), compute_price, compute_limit_with_buffer(COMPUTE_UNITS_PER_SET_PROGRAM_SWAPS_PARAMS), + address_lookup_tables, ) } @@ -501,7 +530,8 @@ impl SolanaTransactionBuilder { gov_key: SolAddress, durable_nonce: DurableNonceAndAccount, compute_price: SolAmount, - ) -> Result { + address_lookup_tables: Vec, + ) -> Result { let token_supported_account = derive_token_supported_account(vault_program, token_mint_pubkey) .map_err(SolanaTransactionBuildingError::FailedToDeriveAddress)?; @@ -521,6 +551,7 @@ impl SolanaTransactionBuilder { gov_key.into(), compute_price, compute_limit_with_buffer(COMPUTE_UNITS_PER_ENABLE_TOKEN_SUPPORT), + address_lookup_tables, ) } } @@ -569,7 +600,7 @@ pub mod test { .unwrap(); // Serialized tx built in `create_fetch_native` test - let expected_serialized_tx = hex_literal::hex!("015209c97accd5ca5e27c5d3c8a3264ec0e754e0ad6f53ad11ca58e043db14d8f8d25ee05b6d5b263234ce0eea4010bf0211fed0c631b393ad8053b799e7d63f0701000509f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1923a4539fbb757256442c16343f639b15db95c39a6d35721439f7f94f5c8776b7bfd35d0bf8686de2e369c3d97a8033b31e6bc33518629f59314bc3d9050956c8d00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000404030106000404000000050009038096980000000000050005021f95000007050800030204158e24658f6c59298c080000000b0c0d3700000000fc").to_vec(); + let expected_serialized_tx = hex_literal::hex!("0183f284c4160d449a41f0a7b30c3710a7e1876d514ef6d87b89a35ae203d50c6928b1dcd2f821496ac4027bfd84e07f921a912537e3d3f3cd4530935b0cae36028001000509f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1923a4539fbb757256442c16343f639b15db95c39a6d35721439f7f94f5c8776b7bfd35d0bf8686de2e369c3d97a8033b31e6bc33518629f59314bc3d9050956c8d00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000404030106000404000000050009038096980000000000050005021f95000007050800030204158e24658f6c59298c080000000b0c0d3700000000fc00").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -591,7 +622,7 @@ pub mod test { .unwrap(); // Serialized tx built in `create_fetch_native_in_batch` test - let expected_serialized_tx = hex_literal::hex!("0196d9b370524f1d7b185958b3eb06b355945a0d50f3b38c034a47e339105ca4ebfd290834dccde71958057e1e82f15ac08dcbf8610838764052f6bf16fa8c08090100050bf79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19238861d2f0bf5cd80031b701a6c25d13b4c812dd92f9d6301fafd9a58fb9e438646cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a708d9871ed5fb2ee05765af23b7cabcc0d6b08ed370bb9f616a0d4dea40a25f870b5b9d633289c8fd72fb05f33349bf4cc44e82add5d865311ae346d7c9a67b7dd00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900050603010800040400000007000903809698000000000007000502c34a010009050a00030206158e24658f6c59298c080000000000000000000000ff09050a00040506158e24658f6c59298c080000000100000000000000ff").to_vec(); + let expected_serialized_tx = hex_literal::hex!("01f6a40d02eb553db89f9be4a37bfccd9c9a18ea6687c6e092cec5935863f8b4416c2a290cc474be118a404dc3035866e714dd70d4c77a6549b59a21a6bdb6bf06800100050bf79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19238861d2f0bf5cd80031b701a6c25d13b4c812dd92f9d6301fafd9a58fb9e438646cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a708d9871ed5fb2ee05765af23b7cabcc0d6b08ed370bb9f616a0d4dea40a25f870b5b9d633289c8fd72fb05f33349bf4cc44e82add5d865311ae346d7c9a67b7dd00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900050603010800040400000007000903809698000000000007000502c34a010009050a00030206158e24658f6c59298c080000000000000000000000ff09050a00040506158e24658f6c59298c080000000100000000000000ff00").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -609,7 +640,7 @@ pub mod test { .unwrap(); // Serialized tx built in `create_fetch_tokens` test - let expected_serialized_tx = hex_literal::hex!("01adb99aa6d5cef660e14d01cd0d78a74dc0046bb6e8e3719fb524c548271e0348d2253f2ffcbd4ecdf12c1d79f97596dbf8d54638b409982debde797c65e0d3000100080df79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19242ff6863b52c3f8faf95739e6541bda5d0ac593f00c6c07d9ab37096bf26d910ae85f2fb6289c70bfe37df150dddb17dd84f403fd0b1aa1bfee85795159de21fe91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8746cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a7072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000405030107000404000000060009038096980000000000060005024f0a01000b090c000a02040908030516494710642cb0c646080000000000000000000000ff06").to_vec(); + let expected_serialized_tx = hex_literal::hex!("013f38d2f1a1669aabbc375de98a7030310f36e4482732ad92adc2fff31c1e63cf5713c92dd41f56b11fe2edf0679f3e095d88a5bb96055c1c84d5bfe0779e3606800100080df79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19242ff6863b52c3f8faf95739e6541bda5d0ac593f00c6c07d9ab37096bf26d910ae85f2fb6289c70bfe37df150dddb17dd84f403fd0b1aa1bfee85795159de21fe91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8746cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a7072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000405030107000404000000060009038096980000000000060005024f0a01000b090c000a02040908030516494710642cb0c646080000000000000000000000ff0600").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -630,7 +661,7 @@ pub mod test { .unwrap(); // Serialized tx built in `create_batch_fetch` test - let expected_serialized_tx = hex_literal::hex!("0172ba9088cd8a6229db1ec41c83295eebb8ea509103efc26969a1b1cc0cc5901799ced09e740d1bd01430dad236180e7b510a52dfed86fb7c6f527bca2054630401000912f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1921ad0968d57ee79348476716f9b2cd44ec4284b8f52c36648d560949e41589a5540de1c0451cccb6edd1fda9b4a48c282b279350b55a7a9716800cc0132b6f0b042ff6863b52c3f8faf95739e6541bda5d0ac593f00c6c07d9ab37096bf26d910a140fd3d05766f0087d57bf99df05731e894392ffcc8e8d7e960ba73c09824aaae85f2fb6289c70bfe37df150dddb17dd84f403fd0b1aa1bfee85795159de21fb4baefcd4965beb1c71311a2ffe76419d4b8f8d35fbc4cf514b1bd02da2df2e3e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8746cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a7072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8d9871ed5fb2ee05765af23b7cabcc0d6b08ed370bb9f616a0d4dea40a25f870a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900060903010b0004040000000a00090380969800000000000a000502e7bb02000f0911000e04080d0c060916494710642cb0c646080000000000000000000000ff060f0911001002080d0c030916494710642cb0c646080000000100000000000000ff060f051100050709158e24658f6c59298c080000000200000000000000ff").to_vec(); + let expected_serialized_tx = hex_literal::hex!("01815a9e25f301f6877feb533c18b7ecaec40b2164ccffada07a0a228a7d67beb339b77a0146c5c44f18030ddadcce4ab6c18e9033d1b89ac7dcd0814d9ba7d6078001000912f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1921ad0968d57ee79348476716f9b2cd44ec4284b8f52c36648d560949e41589a5540de1c0451cccb6edd1fda9b4a48c282b279350b55a7a9716800cc0132b6f0b042ff6863b52c3f8faf95739e6541bda5d0ac593f00c6c07d9ab37096bf26d910a140fd3d05766f0087d57bf99df05731e894392ffcc8e8d7e960ba73c09824aaae85f2fb6289c70bfe37df150dddb17dd84f403fd0b1aa1bfee85795159de21fb4baefcd4965beb1c71311a2ffe76419d4b8f8d35fbc4cf514b1bd02da2df2e3e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8746cd507258c10454d484e64ba59d3e7570658001c5f854b6b3ebb57be90e7a7072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8d9871ed5fb2ee05765af23b7cabcc0d6b08ed370bb9f616a0d4dea40a25f870a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900060903010b0004040000000a00090380969800000000000a000502e7bb02000f0911000e04080d0c060916494710642cb0c646080000000000000000000000ff060f0911001002080d0c030916494710642cb0c646080000000100000000000000ff060f051100050709158e24658f6c59298c080000000200000000000000ff00").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -647,7 +678,7 @@ pub mod test { .unwrap(); // Serialized tx built in `create_transfer_native` test - let expected_serialized_tx = hex_literal::hex!("01c3eec1d2c8ccf7a5eb2bbfe8819a1b46e0fb7409c809f9b01f09b049f455c964ed545aad28cd6b7f8bdc6df41f8b5b2df5e36115893e431053ad6ddc02da0d0701000306f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd400000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004030301050004040000000400090380969800000000000400050284030000030200020c0200000000ca9a3b00000000").to_vec(); + let expected_serialized_tx = hex_literal::hex!("01cf95647e8340f44ff54c971187192f31f12abe07d1a2c12bfa21c8a36b311efdcf4f80323c52025cea58480b3a62ad3b7e39b86fb1f6d2f793fe9d3bd1b3a2018001000306f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd400000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea9400000c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004030301050004040000000400090380969800000000000400050284030000030200020c0200000000ca9a3b0000000000").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -680,7 +711,7 @@ pub mod test { .unwrap(); // Serialized tx built in `create_transfer_token` test - let expected_serialized_tx = hex_literal::hex!("01940a66ffe66a72d345e45eedb1c54e6b051ed71ae13f5c354a682e2a3f9aba0161855cd404f1ab0e65c294f350803f9c0a040cb8c16c40fd8103293ab454a40501000a0ef79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1925ec7baaea7200eb2a66ccd361ee73bc87a7e5222ecedcbc946e97afb59ec4616e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8731e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd472b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bab1d2a644046552e73f4d05b5a6ef53848973a9ee9febba42ddefb034b5f5130c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000504030106000404000000050009038096980000000000050005029b2701000b0600020908040701010a070c000d030208071136b4eeaf4a557ebc00ca9a3b0000000006").to_vec(); + let expected_serialized_tx = hex_literal::hex!("0140fde7de6759fc58d4a6cfc85f8e58f222bc42b03045cf6d63c5cdf29a52c2cb64cbee0f6cf4c256b9e042380bccf7c4673adf592b6f8c4386c09c7eaf502f008001000a0ef79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1925ec7baaea7200eb2a66ccd361ee73bc87a7e5222ecedcbc946e97afb59ec4616e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8731e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd472b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bab1d2a644046552e73f4d05b5a6ef53848973a9ee9febba42ddefb034b5f5130c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000504030106000404000000050009038096980000000000050005029b2701000b0600020908040701010a070c000d030208071136b4eeaf4a557ebc00ca9a3b000000000600").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -697,11 +728,12 @@ pub mod test { agg_key(), durable_nonce(), compute_price(), + vec![chainflip_alt()], ) .unwrap(); // Serialized tx built in `rotate_agg_key` test - let expected_serialized_tx = hex_literal::hex!("012aed8e2d575872e3068dfdaf234fd02349d6e05015a369efb6fa11997b79aaaa531329bc383dd0e8595bf796cd2729b1a9a1504bfa0f473a4bb5cab3cc154b0701000411f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1926744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900448541f57201f277c5f3ffb631d0212e26e7f47749c26c4808718174a0ab2a09a18cd28baa84f2067bbdf24513c2d44e44bf408f2e6da6e60762e3faa4a62a0adba1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bcd644e45426a41a7cb8369b8a0c1c89bb3f86cf278fdd9cc38b0f69784ad5667e392cd98d3284fd551604be95c14cc8e20123e2940ef9fb784e6b591c7442864e5e1869817a4fd88ddf7ab7a5f7252d7c345b39721769888608592912e8ca9acf0f13460b3fd04b7d53d7421fc874ec00eec769cf36480895e1a407bf1249475f2b2e24122be016983be9369965246cc45e1f621d40fba300c56c7ac50c3874df4f83bd213a59c9785110cf83c718f9486c3484f918593bce20c61dc6a96036afecc89e3b031824af6363174d19bbec12d3a13c4a173e5aeb349b63042bc138f00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293cc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000e0d03010f0004040000000e00090380969800000000000e000502e02e000010040500020d094e518fabdda5d68b000d02010024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d020b0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02090024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d020a0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02070024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02060024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02030024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d020c0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02080024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440d02040024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be543990044").to_vec(); + let expected_serialized_tx = hex_literal::hex!("01ab37f60681ba0afe0aeda3c39f5021f49332695e63243af28769b59de314f4b62445271f2155e4d49c83153b63a9c64ddeb2feda66acf2ad6058f411c95cf7038001000406f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb6744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004400000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293cc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000e0203060400040400000003000903809698000000000003000502e02e0000050409000102094e518fabdda5d68b000202060024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004402020f0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004402020d0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004402020e0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004402020b0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004402020a0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440202070024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440202100024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004402020c0024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be5439900440202080024070000006744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be543990044013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a10b090f12020e0d110b0c0a1000").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -718,11 +750,12 @@ pub mod test { agg_key(), durable_nonce(), compute_price(), + vec![chainflip_alt()], ) .unwrap(); // Serialized tx built in `fetch_and_close_vault_swap_accounts` test - let expected_serialized_tx = hex_literal::hex!("01cd1677377a30a65d0e4640d54ddb5bbf83989f66f72201ad63c2681f618612312cfa488ce6cfc37fe5ecc9046aa9b4d8c50ef5aad8e98bb782ecdceb2bd6cf0d0100050bf79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17e5cc1f4d51a40626e11c783b75a45a4922615ecd7f5320b9d4d46481a196a317eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1921c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2665730decf59d4cd6db8437dab77302287431eb7562b5997601851a0eab6946fc8bb64258728f7a98b57a72fade81639eb845674b3d259b51991a97a1821a31900000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000506030208000404000000070009038096980000000000070005025898000009040a050006098579b3e88abc5343fe09050a0003010408a5663d01b94dbd79").to_vec(); + let expected_serialized_tx = hex_literal::hex!("01d61138dfa0a3d4d9b19a9f10db9d7ad45fcac1a27766ce497bc7490ddceebc91046ae06adfcda2d9533c5670f1bf868e2adc607a9c16c2efe8e969531ec009048001000408f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17e5cc1f4d51a40626e11c783b75a45a4922615ecd7f5320b9d4d46481a196a3665730decf59d4cd6db8437dab77302287431eb7562b5997601851a0eab6946fc8bb64258728f7a98b57a72fade81639eb845674b3d259b51991a97a1821a31900000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000504030806000404000000050009038096980000000000050005025898000007040a030004098579b3e88abc5343fe07050a0009010208a5663d01b94dbd79013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a10209080102").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -748,10 +781,11 @@ pub mod test { agg_key(), durable_nonce(), compute_price(), + vec![chainflip_alt()], ) .unwrap(); - let expected_serialized_tx = hex_literal::hex!("01266e852bae2d37833fd43fed3c2b24083af053de8e9fade9e65ef13c6475410c0c4573eb832ce790ec7cbfde3f58aa28b1742f791c15041ccab9b2cb8c5acf0101000513f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb05a557a331e51b8bf444d3bacdf6f48d8fd583aa79f9b956dd68f13a67ad096417e5cc1f4d51a40626e11c783b75a45a4922615ecd7f5320b9d4d46481a196a317eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1921c11d80c98e8c11e79fd97b6c10ad733782bdbe25a710b807bbf14dedaa314861c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d257d7f5b3e6c340824caca3b6c34c03e2fe0e636430b2b729ddfe32146ba4b3795c4b1e73c84b8d3f9e006c22fe8b865b9900e296345d88cdaaa7077ef17d9a31665730decf59d4cd6db8437dab77302287431eb7562b5997601851a0eab6946fa7e867ab720f01897e5ede67fc232e41729d0be2a530391619743822ff6d95bea9dff663e1d13345d96daede8066cd30a1474635f2d64052d1a50ac04aed3f99bd9ce2f9674b65bfaefb62c9b8252fd0080357b1cbff44d0dad8568535dbc230c8bb64258728f7a98b57a72fade81639eb845674b3d259b51991a97a1821a319d33096c9d0fa193639345c07abfe81175fc4d153cf0ab7b5668006538f19538200000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00bc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900050e0303100004040000000f00090380969800000000000f000502f82401001104120c000e098579b3e88abc5343fe110d120005020806010b0a0904070d08a5663d01b94dbd79").to_vec(); + let expected_serialized_tx = hex_literal::hex!("013ce4a7169ecb011374e4cea953491bf340bd8144f7c97b7ceb930310cda703b4e7f8296a987623c00879ddffc8dff71e3b9db2da8ef6895d50a58e92a6bf020f8001000410f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb05a557a331e51b8bf444d3bacdf6f48d8fd583aa79f9b956dd68f13a67ad096417e5cc1f4d51a40626e11c783b75a45a4922615ecd7f5320b9d4d46481a196a31c11d80c98e8c11e79fd97b6c10ad733782bdbe25a710b807bbf14dedaa3148657d7f5b3e6c340824caca3b6c34c03e2fe0e636430b2b729ddfe32146ba4b3795c4b1e73c84b8d3f9e006c22fe8b865b9900e296345d88cdaaa7077ef17d9a31665730decf59d4cd6db8437dab77302287431eb7562b5997601851a0eab6946fa7e867ab720f01897e5ede67fc232e41729d0be2a530391619743822ff6d95bea9dff663e1d13345d96daede8066cd30a1474635f2d64052d1a50ac04aed3f99bd9ce2f9674b65bfaefb62c9b8252fd0080357b1cbff44d0dad8568535dbc230c8bb64258728f7a98b57a72fade81639eb845674b3d259b51991a97a1821a319d33096c9d0fa193639345c07abfe81175fc4d153cf0ab7b5668006538f19538200000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000001ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900050c03100e0004040000000d00090380969800000000000d000502f82401000f04120a000c098579b3e88abc5343fe0f0d1200110206040109080703050b08a5663d01b94dbd79013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a10209080102").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -779,11 +813,12 @@ pub mod test { durable_nonce(), compute_price(), TEST_COMPUTE_LIMIT, + vec![chainflip_alt()], ) .unwrap(); // Serialized tx built in `create_ccm_native_transfer` test - let expected_serialized_tx = hex_literal::hex!("01f38ab7045a32761b6438c5e29a29f53685508de759f51e683b490a2927d9fe88c412fb87f2ababf85d9df74e5e46dd8f974ea44dcf2e1e1fb63955f0ae3077070100070bf79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d19231e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd47417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed4800000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517187bd16635dad40455fdc2c0c124c68f215675a5dbbacb5f0800000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00ba73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e5c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900050403010700040400000005000903809698000000000005000502e0930400040200020c0200000000ca9a3b0000000008070900020304060a347d050be38042e0b20100000014000000ffffffffffffffffffffffffffffffffffffffff040000007c1d0f0700ca9a3b00000000").to_vec(); + let expected_serialized_tx = hex_literal::hex!("0102281baf609788e68ce97ba072a021a6c9788bb08b6f69546b713ffa6faabf5e925c5396ea66e8dd7f2cffe0c66fa7268c63019720b752ee073e3f4bd8fac0058001000609f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb31e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd47417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed4800000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517187bd16635dad40455fdc2c0c124c68f215675a5dbbacb5f0800000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293ca73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e5c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900050303090600040400000004000903809698000000000004000502e0930400030200010c0200000000ca9a3b0000000007070a000102030508347d050be38042e0b20100000014000000ffffffffffffffffffffffffffffffffffffffff040000007c1d0f0700ca9a3b00000000013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a101090102").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -817,11 +852,12 @@ pub mod test { compute_price(), SOL_USDC_DECIMAL, TEST_COMPUTE_LIMIT, + vec![chainflip_alt()], ) .unwrap(); // Serialized tx built in `create_ccm_token_transfer` test - let expected_serialized_tx = hex_literal::hex!("011007091828e8a0357a6e4827daaa2d6e44f79d2daf5ac45e26d409f8d7baadf8b8df231ffde59d9f581754178a695b89d7a42e197781912aa8653dac2a9e390d01000c11f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1925ec7baaea7200eb2a66ccd361ee73bc87a7e5222ecedcbc946e97afb59ec46167417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed48e91372b3d301c202a633da0a92365a736e462131aecfad1fac47322cf8863ada00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517187bd16635dad40455fdc2c0c124c68f215675a5dbbacb5f0800000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a90fb9ba52b1f09445f1e3a7508d59f0797923acf744fbe2da303fb06da859ee8731e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd472b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00ba73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e5ab1d2a644046552e73f4d05b5a6ef53848973a9ee9febba42ddefb034b5f5130c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900060503010800040400000006000903809698000000000006000502e09304000d0600020b0a050901010c070e001004020a091136b4eeaf4a557ebc00ca9a3b00000000060c080e000203090a070f346cb8a27b9fdeaa230100000014000000ffffffffffffffffffffffffffffffffffffffff040000007c1d0f0700ca9a3b00000000").to_vec(); + let expected_serialized_tx = hex_literal::hex!("016f23b283e6b171c042d714ad6d24c6ab09fcbb22fcd977a2fb9b2ab53a3bf36418a2b7f8814672c10c7be41272e64d585e5fc7e913ad455d4e8d9d797ecdbb0e800100090cf79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb5ec7baaea7200eb2a66ccd361ee73bc87a7e5222ecedcbc946e97afb59ec46167417da8b99d7748127a76b03d61fee69c80dfef73ad2d5503737beedc5a9ed4800000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517187bd16635dad40455fdc2c0c124c68f215675a5dbbacb5f0800000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a931e9528aae784fecbbd0bee129d9539c57be0e90061af6b6f4a5e274654e5bd472b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293c8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859a73bdf31e341218a693b8772c43ecfcecd4cf35fada09a87ea0f860d028168e5c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e489000603030c0600040400000004000903809698000000000004000502e09304000a060001080e0307010109070f00100d010e071136b4eeaf4a557ebc00ca9a3b000000000609080f000102070e050b346cb8a27b9fdeaa230100000014000000ffffffffffffffffffffffffffffffffffffffff040000007c1d0f0700ca9a3b00000000013001afd71da9456a977233960b08eba77d2e3690b8c7259637c8fb8f82cf58a102090503030204").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } @@ -841,16 +877,18 @@ pub mod test { .unwrap(); // Serialized tx built in `set_gov_key_with_agg_key` test - let expected_serialized_tx = hex_literal::hex!("01b6b605651cd375e812a7d47eeb2f20e204b16544a1a12f32fd97cdfb18d36ffa2179abb5fcac6fe677a5c21651c26ab41e3a84c1b2c2bc8ab4bcbb3e438c670d01000407f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d192a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293cc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900040303010500040400000004000903809698000000000004000502e4570000060202002842403a280f4bd7a26744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be543990044").to_vec(); + let expected_serialized_tx = hex_literal::hex!("01f8d58bd4d78f2e7bf02a54053a251932fff530a278422b38d38363d2532afcf6f0c975584e27fcc6f14520f3bd8e95b4b06c42a116ca32677619e3ff78278c018001000407f79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d192a1e031c8bc9bec3b610cf7b36eb3bf3aa40237c9e5be2c7893878578439eb00b00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea940000072b5d2051d300b10b74314b7e25ace9998ca66eb2c7fbc10ef130dd67028293cc27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e48900040303010500040400000004000903809698000000000004000502e4570000060202002842403a280f4bd7a26744e9d9790761c45a800a074687b5ff47b449a90c722a3852543be54399004400").to_vec(); test_constructed_transaction(transaction, expected_serialized_tx); } #[test] fn transactions_above_max_lengths_will_fail() { + let limit = 9; + // with 28 Fetches, the length is 1232 <= 1232 assert_ok!(SolanaTransactionBuilder::fetch_from( - [get_fetch_params(None, SOL); 28].to_vec(), + (0..limit).map(|i| get_fetch_params(Some(i), SOL)).collect(), api_env(), agg_key(), durable_nonce(), @@ -859,13 +897,13 @@ pub mod test { assert_err!( SolanaTransactionBuilder::fetch_from( - [get_fetch_params(None, SOL); 29].to_vec(), + (0..=limit).map(|i| get_fetch_params(Some(i), SOL)).collect(), api_env(), agg_key(), durable_nonce(), compute_price(), ), - SolanaTransactionBuildingError::FinalTransactionExceededMaxLength(1261) + SolanaTransactionBuildingError::FinalTransactionExceededMaxLength(1288) ); } } diff --git a/state-chain/pallets/cf-environment/src/mock.rs b/state-chain/pallets/cf-environment/src/mock.rs index 3c88f174650..7856b41a376 100644 --- a/state-chain/pallets/cf-environment/src/mock.rs +++ b/state-chain/pallets/cf-environment/src/mock.rs @@ -7,11 +7,11 @@ use cf_chains::{ eth, sol::{ api::{ - AllNonceAccounts, ApiEnvironment, ComputePrice, CurrentAggKey, CurrentOnChainKey, - DurableNonce, DurableNonceAndAccount, RecoverDurableNonce, SolanaApi, - SolanaEnvironment, + AllNonceAccounts, ApiEnvironment, ChainflipAddressLookupTable, ComputePrice, + CurrentAggKey, CurrentOnChainKey, DurableNonce, DurableNonceAndAccount, + RecoverDurableNonce, SolanaAddressLookupTables, SolanaApi, SolanaEnvironment, }, - SolAddress, SolAmount, SolApiEnvironment, SolHash, + SolAddress, SolAddressLookupTableAccount, SolAmount, SolApiEnvironment, SolHash, }, ApiCall, Arbitrum, Bitcoin, Chain, ChainCrypto, ChainEnvironment, Polkadot, Solana, }; @@ -183,6 +183,22 @@ impl RecoverDurableNonce for MockSolEnvironment { unimplemented!(); } } + +impl ChainEnvironment> + for MockSolEnvironment +{ + fn lookup(_s: SolanaAddressLookupTables) -> Option> { + unimplemented!() + } +} +impl ChainEnvironment + for MockSolEnvironment +{ + fn lookup(_s: ChainflipAddressLookupTable) -> Option { + unimplemented!() + } +} + impl SolanaEnvironment for MockSolEnvironment {} pub struct MockSolanaBroadcaster; diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index 507b93e773e..38a82c299c7 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -258,6 +258,7 @@ pub struct CrossChainMessage { // Where funds might be returned to if the message fails. pub ccm_additional_data: CcmAdditionalData, pub gas_budget: GasAmount, + pub swap_request_id: SwapRequestId, } impl CrossChainMessage { @@ -1724,6 +1725,7 @@ impl, I: 'static> Pallet { ccm.gas_budget, ccm.message.to_vec(), ccm.ccm_additional_data.to_vec(), + ccm.swap_request_id, ) { Ok(api_call) => { let broadcast_id = T::Broadcaster::threshold_sign_and_broadcast_with_callback( @@ -2834,6 +2836,8 @@ impl, I: 'static> EgressApi for Pallet { source_chain, source_address, gas_budget, + swap_request_id: Default::default(), /* TODO Ramiz: Pass this in as a + * parameter */ }); Ok(egress_details) diff --git a/state-chain/pallets/cf-ingress-egress/src/tests.rs b/state-chain/pallets/cf-ingress-egress/src/tests.rs index b1beeb6c818..3bd41fb1406 100644 --- a/state-chain/pallets/cf-ingress-egress/src/tests.rs +++ b/state-chain/pallets/cf-ingress-egress/src/tests.rs @@ -164,6 +164,7 @@ fn blacklisted_asset_will_not_egress_via_ccm() { source_address: ccm.source_address.clone(), ccm_additional_data: ccm.channel_metadata.ccm_additional_data, gas_budget, + swap_request_id: Default::default(), }] ); @@ -523,6 +524,7 @@ fn can_egress_ccm() { source_chain: ForeignChain::Ethereum, source_address: Some(ForeignChainAddress::Eth([0xcf; 20].into())), gas_budget: GAS_BUDGET, + swap_request_id: Default::default(), } ]); @@ -541,6 +543,7 @@ fn can_egress_ccm() { GAS_BUDGET, ccm.channel_metadata.message.to_vec(), vec![], + Default::default(), ).unwrap()]); // Storage should be cleared diff --git a/state-chain/runtime/src/chainflip.rs b/state-chain/runtime/src/chainflip.rs index b22900ee305..1b714cc6b48 100644 --- a/state-chain/runtime/src/chainflip.rs +++ b/state-chain/runtime/src/chainflip.rs @@ -50,12 +50,12 @@ use cf_chains::{ }, sol::{ api::{ - AllNonceAccounts, ApiEnvironment, ComputePrice, CurrentAggKey, CurrentOnChainKey, - DurableNonce, DurableNonceAndAccount, RecoverDurableNonce, SolanaApi, - SolanaEnvironment, + AllNonceAccounts, ApiEnvironment, ChainflipAddressLookupTable, ComputePrice, + CurrentAggKey, CurrentOnChainKey, DurableNonce, DurableNonceAndAccount, + RecoverDurableNonce, SolanaAddressLookupTables, SolanaApi, SolanaEnvironment, }, - SolAddress, SolAmount, SolApiEnvironment, SolanaCrypto, SolanaTransactionData, - NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_TRANSFER, + SolAddress, SolAddressLookupTableAccount, SolAmount, SolApiEnvironment, SolanaCrypto, + SolanaTransactionData, NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_TRANSFER, }, AnyChain, ApiCall, Arbitrum, CcmChannelMetadata, CcmDepositMetadata, Chain, ChainCrypto, ChainEnvironment, ChainState, ChannelRefundParametersDecoded, ForeignChain, @@ -371,11 +371,14 @@ impl TransactionBuilder> for SolanaTransaction RequiresSignatureRefresh::False, |active_epoch_key| { let current_aggkey = active_epoch_key.key; - for key in modified_call.transaction.message.account_keys.iter_mut() { - if *key == signer.into() { - *key = current_aggkey.into() + modified_call.transaction.message.map_static_account_keys(|key| { + if key == signer.into() { + current_aggkey.into() + } else { + key } - } + }); + for sig in modified_call.transaction.signatures.iter_mut() { *sig = Default::default() } @@ -593,6 +596,24 @@ impl RecoverDurableNonce for SolEnvironment { } } +impl ChainEnvironment> + for SolEnvironment +{ + fn lookup(_s: SolanaAddressLookupTables) -> Option> { + // TODO Ramiz: Lookup SolanaElection pallet for the ALTS using the given SwapRequestId + None + } +} + +impl ChainEnvironment + for SolEnvironment +{ + fn lookup(_s: ChainflipAddressLookupTable) -> Option { + // TODO Roy: Read Chainflip's ALT from the Environment pallet. + None + } +} + impl SolanaEnvironment for SolEnvironment {} pub struct TokenholderGovernanceBroadcaster; diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index dcf74bd11fc..42c89e9b62e 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -48,7 +48,7 @@ use cf_chains::{ btc::{api::BitcoinApi, BitcoinCrypto, BitcoinRetryPolicy, ScriptPubkey}, ccm_checker::{ check_ccm_for_blacklisted_accounts, CcmValidityCheck, CcmValidityChecker, - DecodedCcmAdditionalData, VersionedSolanaCcmAdditionalData, + DecodedCcmAdditionalData, }, dot::{self, PolkadotAccountId, PolkadotCrypto}, eth::{self, api::EthereumApi, Address as EthereumAddress, Ethereum}, @@ -2220,7 +2220,9 @@ impl_runtime_apis! { // Ensure CCM message is valid match CcmValidityChecker::check_and_decode(ccm, destination_asset, destination_address.clone()) { - Ok(DecodedCcmAdditionalData::Solana(VersionedSolanaCcmAdditionalData::V0(ccm_accounts))) => { + Ok(DecodedCcmAdditionalData::Solana(decoded)) => { + let ccm_accounts = decoded.ccm_accounts(); + // Ensure the CCM parameters do not contain blacklisted accounts. // Load up environment variables. let api_environment = diff --git a/state-chain/traits/src/mocks/api_call.rs b/state-chain/traits/src/mocks/api_call.rs index 2c20bd96559..43d1ea58b1f 100644 --- a/state-chain/traits/src/mocks/api_call.rs +++ b/state-chain/traits/src/mocks/api_call.rs @@ -6,7 +6,7 @@ use cf_chains::{ ExecutexSwapAndCallError, FetchAssetParams, ForeignChainAddress, RejectCall, RejectError, TransferAssetParams, TransferFallback, TransferFallbackError, }; -use cf_primitives::{chains::assets, EgressId, ForeignChain, GasAmount}; +use cf_primitives::{chains::assets, EgressId, ForeignChain, GasAmount, SwapRequestId}; use codec::{Decode, Encode}; use frame_support::{sp_runtime::DispatchError, CloneNoBound, DebugNoBound, PartialEqNoBound}; use scale_info::TypeInfo; @@ -151,6 +151,7 @@ impl ExecutexSwapAndCall for MockEthereumApiCall { gas_budget: GasAmount, message: Vec, _ccm_additional_data: Vec, + _swap_request_id: SwapRequestId, ) -> Result { if MockEvmEnvironment::lookup(transfer_param.asset).is_none() { Err(ExecutexSwapAndCallError::DispatchError(DispatchError::CannotLookup)) @@ -288,6 +289,7 @@ impl ExecutexSwapAndCall for MockBitcoinApiCall { gas_budget: GasAmount, message: Vec, _ccm_additional_data: Vec, + _swap_request_id: SwapRequestId, ) -> Result { if MockBtcEnvironment::lookup(transfer_param.asset).is_none() { Err(ExecutexSwapAndCallError::DispatchError(DispatchError::CannotLookup)) From 2e5dc0e9e0f10214e3530585d3096a9e47d2d475 Mon Sep 17 00:00:00 2001 From: albert Date: Thu, 13 Feb 2025 12:06:14 +0100 Subject: [PATCH 6/7] chore: update bouncer to get versioned transactions --- bouncer/shared/sol_vault_swap.ts | 5 ++++- bouncer/shared/utils.ts | 4 +++- bouncer/tests/gaslimit_ccm.ts | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bouncer/shared/sol_vault_swap.ts b/bouncer/shared/sol_vault_swap.ts index 941065d6d02..b6e5e602540 100644 --- a/bouncer/shared/sol_vault_swap.ts +++ b/bouncer/shared/sol_vault_swap.ts @@ -165,7 +165,10 @@ export async function executeSolVaultSwap( { commitment: 'confirmed' }, ); - const transactionData = await connection.getTransaction(txHash, { commitment: 'confirmed' }); + const transactionData = await connection.getTransaction(txHash, { + commitment: 'confirmed', + maxSupportedTransactionVersion: 0, + }); if (transactionData === null) { throw new Error('Solana TransactionData is empty'); } diff --git a/bouncer/shared/utils.ts b/bouncer/shared/utils.ts index 7164b1eb16b..88990ba0a5c 100644 --- a/bouncer/shared/utils.ts +++ b/bouncer/shared/utils.ts @@ -799,7 +799,9 @@ export async function observeSolanaCcmEvent( for (let i = 0; i < 300; i++) { const txSignatures = await connection.getSignaturesForAddress(cfTesterAddress); for (const txSignature of txSignatures) { - const tx = await connection.getTransaction(txSignature.signature); + const tx = await connection.getTransaction(txSignature.signature, { + maxSupportedTransactionVersion: 0, + }); if (tx) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const eventParser = new EventParser(cfTesterAddress, new BorshCoder(idl as any)); diff --git a/bouncer/tests/gaslimit_ccm.ts b/bouncer/tests/gaslimit_ccm.ts index 10427f10435..71ed656a660 100644 --- a/bouncer/tests/gaslimit_ccm.ts +++ b/bouncer/tests/gaslimit_ccm.ts @@ -181,6 +181,7 @@ async function testGasLimitSwapToSolana(sourceAsset: Asset, destAsset: Asset) { const transaction = await connection.getTransaction(txSignature, { commitment: 'confirmed', + maxSupportedTransactionVersion: 0, }); // Checking that the compute limit is set correctly (and < MAX_CAP) is cumbersome without manually parsing instructions const totalFee = From 1ccc7a8d59b549c254a925608c195f88cd57333a Mon Sep 17 00:00:00 2001 From: Ramiz Siddiqui Date: Thu, 13 Feb 2025 16:54:58 +0100 Subject: [PATCH 7/7] feat: alt elections --- engine/src/elections/voter_api.rs | 2 +- engine/src/witness/sol.rs | 26 +++++-- foreign-chains/solana/sol-prim/src/alt.rs | 15 +++- .../cf-integration-tests/src/mock_runtime.rs | 4 +- .../cf-integration-tests/src/solana.rs | 2 +- .../src/electoral_systems/composite.rs | 2 +- .../src/vote_storage/composite.rs | 2 +- .../src/vote_storage/individual/composite.rs | 2 +- state-chain/pallets/cf-environment/src/lib.rs | 20 +++++- state-chain/primitives/src/lib.rs | 2 +- .../runtime/src/chainflip/solana_elections.rs | 68 +++++++++++++++++-- .../solana_remove_unused_channels_state.rs | 2 +- 12 files changed, 127 insertions(+), 20 deletions(-) diff --git a/engine/src/elections/voter_api.rs b/engine/src/elections/voter_api.rs index 2281ebe0477..47a67643ffb 100644 --- a/engine/src/elections/voter_api.rs +++ b/engine/src/elections/voter_api.rs @@ -80,4 +80,4 @@ macro_rules! generate_voter_api_tuple_impls { } } -generate_voter_api_tuple_impls!(tuple_6_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0))); +generate_voter_api_tuple_impls!(tuple_7_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0), (GG, G0))); diff --git a/engine/src/witness/sol.rs b/engine/src/witness/sol.rs index fc70f963128..c5d2e38abc3 100644 --- a/engine/src/witness/sol.rs +++ b/engine/src/witness/sol.rs @@ -27,9 +27,9 @@ use pallet_cf_elections::{ }; use state_chain_runtime::{ chainflip::solana_elections::{ - SolanaBlockHeightTracking, SolanaEgressWitnessing, SolanaElectoralSystemRunner, - SolanaIngressTracking, SolanaLiveness, SolanaNonceTracking, SolanaVaultSwapTracking, - TransactionSuccessDetails, + SolanaAltWitnessing, SolanaBlockHeightTracking, SolanaEgressWitnessing, + SolanaElectoralSystemRunner, SolanaIngressTracking, SolanaLiveness, SolanaNonceTracking, + SolanaVaultSwapTracking, TransactionSuccessDetails, }, SolanaInstance, }; @@ -200,6 +200,23 @@ impl VoterApi for SolanaVaultSwapsVoter { } } +#[allow(dead_code)] +#[derive(Clone)] +struct SolanaAltWitnessingVoter { + client: SolRetryRpcClient, +} + +#[async_trait::async_trait] +impl VoterApi for SolanaAltWitnessingVoter { + async fn vote( + &self, + _settings: ::ElectoralSettings, + _properties: ::ElectionProperties, + ) -> Result>, anyhow::Error> { + todo!() + } +} + pub async fn start( scope: &Scope<'_, anyhow::Error>, client: SolRetryRpcClient, @@ -226,7 +243,8 @@ where SolanaNonceTrackingVoter { client: client.clone() }, SolanaEgressWitnessingVoter { client: client.clone() }, SolanaLivenessVoter { client: client.clone() }, - SolanaVaultSwapsVoter { client }, + SolanaVaultSwapsVoter { client: client.clone() }, + SolanaAltWitnessingVoter { client }, )), ) .continuously_vote() diff --git a/foreign-chains/solana/sol-prim/src/alt.rs b/foreign-chains/solana/sol-prim/src/alt.rs index a834961f376..990c8022c99 100644 --- a/foreign-chains/solana/sol-prim/src/alt.rs +++ b/foreign-chains/solana/sol-prim/src/alt.rs @@ -26,7 +26,20 @@ pub struct MessageAddressTableLookup { /// The definition of address lookup table accounts. /// /// As used by the `crate::message::v0` message format. -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive( + Serialize, + Deserialize, + Default, + Debug, + PartialEq, + Eq, + Clone, + Encode, + Decode, + TypeInfo, + Ord, + PartialOrd, +)] pub struct AddressLookupTableAccount { pub key: Pubkey, pub addresses: Vec, diff --git a/state-chain/cf-integration-tests/src/mock_runtime.rs b/state-chain/cf-integration-tests/src/mock_runtime.rs index 7fced7d3945..2d0ef8ba05a 100644 --- a/state-chain/cf-integration-tests/src/mock_runtime.rs +++ b/state-chain/cf-integration-tests/src/mock_runtime.rs @@ -307,8 +307,9 @@ impl ExtBuilder { (), (), Default::default(), + (), ), - unsynchronised_settings: ((), (), (), (), (), ()), + unsynchronised_settings: ((), (), (), (), (), (), ()), settings: ( (), SolanaIngressSettings { @@ -323,6 +324,7 @@ impl ExtBuilder { sol_test_values::SWAP_ENDPOINT_DATA_ACCOUNT_ADDRESS, usdc_token_mint_pubkey: sol_test_values::USDC_TOKEN_MINT_PUB_KEY, }, + (), ), }), }, diff --git a/state-chain/cf-integration-tests/src/solana.rs b/state-chain/cf-integration-tests/src/solana.rs index a0f0749603a..22170069faf 100644 --- a/state-chain/cf-integration-tests/src/solana.rs +++ b/state-chain/cf-integration-tests/src/solana.rs @@ -27,7 +27,7 @@ use frame_support::{ traits::{OnFinalize, UnfilteredDispatchable}, }; use pallet_cf_elections::{ - vote_storage::{composite::tuple_6_impls::CompositeVote, AuthorityVote}, + vote_storage::{composite::tuple_7_impls::CompositeVote, AuthorityVote}, AuthorityVoteOf, ElectionIdentifierOf, MAXIMUM_VOTES_PER_EXTRINSIC, }; use pallet_cf_ingress_egress::{ diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs b/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs index cc25ca5a751..0cc6b9389ce 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs @@ -463,4 +463,4 @@ macro_rules! generate_electoral_system_tuple_impls { }; } -generate_electoral_system_tuple_impls!(tuple_6_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0))); +generate_electoral_system_tuple_impls!(tuple_7_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0), (GG, G0))); diff --git a/state-chain/pallets/cf-elections/src/vote_storage/composite.rs b/state-chain/pallets/cf-elections/src/vote_storage/composite.rs index 88e1099710c..3de8dc9e971 100644 --- a/state-chain/pallets/cf-elections/src/vote_storage/composite.rs +++ b/state-chain/pallets/cf-elections/src/vote_storage/composite.rs @@ -274,4 +274,4 @@ macro_rules! generate_vote_storage_tuple_impls { } } -generate_vote_storage_tuple_impls!(tuple_6_impls: (A, B, C, D, EE, FF)); +generate_vote_storage_tuple_impls!(tuple_7_impls: (A, B, C, D, EE, FF, GG)); diff --git a/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs b/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs index 6258427b696..33accbb882b 100644 --- a/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs +++ b/state-chain/pallets/cf-elections/src/vote_storage/individual/composite.rs @@ -76,4 +76,4 @@ macro_rules! generate_individual_vote_storage_tuple_impls { } #[cfg(test)] generate_individual_vote_storage_tuple_impls!(tuple_2_impls: (A, B)); -generate_individual_vote_storage_tuple_impls!(tuple_6_impls: (A, B, C, D, EE, FF)); +generate_individual_vote_storage_tuple_impls!(tuple_7_impls: (A, B, C, D, EE, FF, GG)); diff --git a/state-chain/pallets/cf-environment/src/lib.rs b/state-chain/pallets/cf-environment/src/lib.rs index 3dd391b9bf8..49908bc1600 100644 --- a/state-chain/pallets/cf-environment/src/lib.rs +++ b/state-chain/pallets/cf-environment/src/lib.rs @@ -14,13 +14,14 @@ use cf_chains::{ eth::Address as EvmAddress, sol::{ api::{DurableNonceAndAccount, SolanaApi, SolanaEnvironment, SolanaGovCall}, - SolAddress, SolApiEnvironment, SolHash, Solana, NONCE_NUMBER_CRITICAL_NONCES, + SolAddress, SolAddressLookupTableAccount, SolApiEnvironment, SolHash, Solana, + NONCE_NUMBER_CRITICAL_NONCES, }, Chain, }; use cf_primitives::{ chains::assets::{arb::Asset as ArbAsset, eth::Asset as EthAsset}, - BroadcastId, NetworkEnvironment, SemVer, + BroadcastId, NetworkEnvironment, SemVer, SwapRequestId, }; use cf_traits::{ Broadcaster, CompatibleCfeVersions, GetBitcoinFeeInfo, KeyProvider, NetworkEnvironmentProvider, @@ -241,6 +242,11 @@ pub mod pallet { #[pallet::getter(fn solana_api_environment)] pub type SolanaApiEnvironment = StorageValue<_, SolApiEnvironment, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn solana_ccm_swap_alts)] + pub type SolanaCcmSwapAlts = + StorageMap<_, Blake2_128Concat, SwapRequestId, SolAddressLookupTableAccount>; + // OTHER ENVIRONMENT ITEMS #[pallet::storage] #[pallet::getter(fn safe_mode)] @@ -801,6 +807,16 @@ impl Pallet { log::error!("Nonce account {nonce_account} not found in unavailable nonce accounts"); } } + + pub fn add_sol_ccm_swap_alt(swap_request_id: SwapRequestId, alt: SolAddressLookupTableAccount) { + SolanaCcmSwapAlts::::insert(swap_request_id, alt); + } + + pub fn take_sol_ccm_swap_alt( + swap_request_id: SwapRequestId, + ) -> Option { + SolanaCcmSwapAlts::::take(swap_request_id) + } } impl CompatibleCfeVersions for Pallet { diff --git a/state-chain/primitives/src/lib.rs b/state-chain/primitives/src/lib.rs index 8a82aeefc25..a5a90ab1c54 100644 --- a/state-chain/primitives/src/lib.rs +++ b/state-chain/primitives/src/lib.rs @@ -97,7 +97,7 @@ pub type BroadcastId = u32; define_wrapper_type!(SwapId, u64, extra_derives: Serialize, Deserialize); -define_wrapper_type!(SwapRequestId, u64, extra_derives: Serialize, Deserialize); +define_wrapper_type!(SwapRequestId, u64, extra_derives: Serialize, Deserialize, Ord, PartialOrd); pub type PrewitnessedDepositId = u64; diff --git a/state-chain/runtime/src/chainflip/solana_elections.rs b/state-chain/runtime/src/chainflip/solana_elections.rs index 24d31a24fb2..5b2786f1122 100644 --- a/state-chain/runtime/src/chainflip/solana_elections.rs +++ b/state-chain/runtime/src/chainflip/solana_elections.rs @@ -13,12 +13,13 @@ use cf_chains::{ }, compute_units_costs::MIN_COMPUTE_PRICE, sol_tx_core::SlotNumber, - SolAddress, SolAmount, SolHash, SolSignature, SolTrackedData, SolanaCrypto, + SolAddress, SolAddressLookupTableAccount, SolAmount, SolHash, SolSignature, SolTrackedData, + SolanaCrypto, }, CcmDepositMetadata, Chain, ChannelRefundParameters, FeeEstimationApi, FetchAndCloseSolanaVaultSwapAccounts, ForeignChain, Solana, }; -use cf_primitives::{AffiliateShortId, Affiliates, Beneficiary, DcaParameters}; +use cf_primitives::{AffiliateShortId, Affiliates, Beneficiary, DcaParameters, SwapRequestId}; use cf_runtime_utilities::log_or_panic; use cf_traits::{ offence_reporting::OffenceReporter, AdjustedFeeEstimationApi, Broadcaster, Chainflip, @@ -30,7 +31,7 @@ use pallet_cf_elections::{ electoral_system::{ElectoralReadAccess, ElectoralSystem, ElectoralSystemTypes}, electoral_systems::{ self, - composite::{tuple_6_impls::Hooks, CompositeRunner}, + composite::{tuple_7_impls::Hooks, CompositeRunner}, egress_success::OnEgressSuccess, liveness::OnCheckComplete, monotonic_change::OnChangeHook, @@ -60,6 +61,7 @@ pub type SolanaElectoralSystemRunner = CompositeRunner< SolanaEgressWitnessing, SolanaLiveness, SolanaVaultSwapTracking, + SolanaAltWitnessing, ), ::ValidatorId, RunnerStorageAccess, @@ -84,8 +86,9 @@ pub fn initial_state( (), (), 0u32, + (), ), - unsynchronised_settings: ((), (), (), (), (), ()), + unsynchronised_settings: ((), (), (), (), (), (), ()), settings: ( (), SolanaIngressSettings { vault_program, usdc_token_mint_pubkey }, @@ -93,6 +96,7 @@ pub fn initial_state( (), LIVENESS_CHECK_DURATION, SolanaVaultSwapsSettings { swap_endpoint_data_account_address, usdc_token_mint_pubkey }, + (), ), } } @@ -154,6 +158,46 @@ pub type SolanaVaultSwapTracking = SolanaTransactionBuildingError, >; +#[derive( + Serialize, + Deserialize, + Default, + Debug, + PartialEq, + Eq, + Clone, + Encode, + Decode, + TypeInfo, + Ord, + PartialOrd, +)] +pub struct SolanaAltWitnessingIdentifier { + pub swap_request_id: SwapRequestId, + pub alt_address: SolAddress, +} + +pub type SolanaAltWitnessing = electoral_systems::egress_success::EgressSuccess< + SolanaAltWitnessingIdentifier, + SolAddressLookupTableAccount, + (), + SolanaAltWitnessingHook, + ::ValidatorId, +>; + +pub struct SolanaAltWitnessingHook; + +impl OnEgressSuccess + for SolanaAltWitnessingHook +{ + fn on_egress_success( + alt_identifier: SolanaAltWitnessingIdentifier, + alt: SolAddressLookupTableAccount, + ) { + Environment::add_sol_ccm_swap_alt(alt_identifier.swap_request_id, alt); + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, TypeInfo)] pub struct TransactionSuccessDetails { pub tx_fee: u64, @@ -235,6 +279,7 @@ impl SolanaEgressWitnessing, SolanaLiveness, SolanaVaultSwapTracking, + SolanaAltWitnessing, > for SolanaElectionHooks { fn on_finalize( @@ -245,6 +290,7 @@ impl egress_witnessing_identifiers, liveness_identifiers, vault_swap_identifiers, + alt_witnessing_identifiers, ): ( Vec< ElectionIdentifier< @@ -276,6 +322,11 @@ impl ::ElectionIdentifierExtra, >, >, + Vec< + ElectionIdentifier< + ::ElectionIdentifierExtra, + >, + >, ), ) -> Result<(), CorruptStorageError> { let current_sc_block_number = crate::System::block_number(); @@ -317,6 +368,13 @@ impl RunnerStorageAccess, >, >(vault_swap_identifiers, ¤t_sc_block_number)?; + SolanaAltWitnessing::on_finalize::< + DerivedElectoralAccess< + _, + SolanaAltWitnessing, + RunnerStorageAccess, + >, + >(alt_witnessing_identifiers, &())?; Ok(()) } } @@ -337,7 +395,7 @@ impl BenchmarkValue for SolanaIngressSettings { } } -use pallet_cf_elections::electoral_systems::composite::tuple_6_impls::DerivedElectoralAccess; +use pallet_cf_elections::electoral_systems::composite::tuple_7_impls::DerivedElectoralAccess; pub struct SolanaChainTrackingProvider; impl GetBlockHeight for SolanaChainTrackingProvider { diff --git a/state-chain/runtime/src/migrations/housekeeping/solana_remove_unused_channels_state.rs b/state-chain/runtime/src/migrations/housekeeping/solana_remove_unused_channels_state.rs index 40aadc9ecb2..2e4f7505e56 100644 --- a/state-chain/runtime/src/migrations/housekeeping/solana_remove_unused_channels_state.rs +++ b/state-chain/runtime/src/migrations/housekeeping/solana_remove_unused_channels_state.rs @@ -2,7 +2,7 @@ use crate::*; use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; use pallet_cf_elections::{ - electoral_systems::composite::tuple_6_impls::CompositeElectoralUnsynchronisedStateMapKey, + electoral_systems::composite::tuple_7_impls::CompositeElectoralUnsynchronisedStateMapKey, ElectoralUnsynchronisedStateMap, }; use sp_core::bounded::alloc::collections::BTreeSet;