From b3dca76b677602fa94ee1874adb430fd179d8eb7 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 17 Dec 2025 11:44:52 +0200 Subject: [PATCH] fix: intercommit --- common/src/error.rs | 3 + integration_tests/src/test_suite_map.rs | 196 +++++++++++++- nssa/core/src/program.rs | 1 + nssa/program_methods/guest/src/bin/amm.rs | 1 - nssa/src/public_transaction/message.rs | 14 + wallet/src/cli/account.rs | 6 +- wallet/src/program_facades/amm.rs | 301 ++++++++++++++++++---- 7 files changed, 463 insertions(+), 59 deletions(-) diff --git a/common/src/error.rs b/common/src/error.rs index e1634a6..5c81a10 100644 --- a/common/src/error.rs +++ b/common/src/error.rs @@ -1,3 +1,4 @@ +use nssa::AccountId; use serde::Deserialize; use crate::rpc_primitives::errors::RpcError; @@ -49,4 +50,6 @@ pub enum ExecutionFailureKind { SequencerClientError(#[from] SequencerClientError), #[error("Can not pay for operation")] InsufficientFundsError, + #[error("Account {0} data is invalid")] + AccountDataError(AccountId), } diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 8012e17..6c78d6e 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -23,8 +23,8 @@ use wallet::{ account::{AccountSubcommand, NewSubcommand}, config::ConfigSubcommand, programs::{ - native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, - token::TokenProgramAgnosticSubcommand, + amm::AmmProgramAgnosticSubcommand, native_token_transfer::AuthTransferSubcommand, + pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, }, }, config::PersistentStorage, @@ -2035,6 +2035,198 @@ pub fn prepare_function_map() -> HashMap { info!("Success!"); } + #[nssa_integration_test] + pub async fn test_amm_public() { + info!("########## test_amm_public ##########"); + let wallet_config = fetch_config().await.unwrap(); + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_1, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_2, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: make_public_account_input_from_str( + &definition_account_id.to_string(), + ), + supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(Command::Token(subcommand)) + .await + .unwrap(); + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: make_public_account_input_from_str(&supply_account_id.to_string()), + to: Some(make_public_account_input_from_str( + &recipient_account_id_1.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(Command::Token(subcommand)) + .await + .unwrap(); + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: make_public_account_input_from_str(&supply_account_id.to_string()), + to: Some(make_public_account_input_from_str( + &recipient_account_id_2.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(Command::Token(subcommand)) + .await + .unwrap(); + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("=================== SETUP FINISHED ==============="); + + // Create new AMM + + // Setup accounts + // Create new account for the amm pool + let SubcommandReturnValue::RegisterAccount { + account_id: amm_pool, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the vault a + let SubcommandReturnValue::RegisterAccount { + account_id: vault_holding_a, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the vault b + let SubcommandReturnValue::RegisterAccount { + account_id: vault_holding_b, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the pool lp + let SubcommandReturnValue::RegisterAccount { + account_id: pool_lp, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the user holding lp + let SubcommandReturnValue::RegisterAccount { + account_id: user_holding_lp, + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { cci: None }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + + // Send creation tx + let subcommand = AmmProgramAgnosticSubcommand::New { + amm_pool: make_public_account_input_from_str(&amm_pool.to_string()), + vault_holding_a: make_public_account_input_from_str(&vault_holding_a.to_string()), + vault_holding_b: make_public_account_input_from_str(&vault_holding_b.to_string()), + pool_lp: make_public_account_input_from_str(&pool_lp.to_string()), + user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), + user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), + user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), + balance_a: 3, + balance_b: 3, + }; + + wallet::cli::execute_subcommand(Command::AMM(subcommand)) + .await + .unwrap(); + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let amm_pool_acc = seq_client + .get_account(amm_pool.to_string()) + .await + .unwrap() + .account; + + info!("AMM pool is {amm_pool_acc:#?}"); + + info!("Success!"); + } + println!("{function_map:#?}"); function_map diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 03fe163..4ad0569 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -124,6 +124,7 @@ pub struct ProgramOutput { pub fn read_nssa_inputs() -> (ProgramInput, InstructionData) { let pre_states: Vec = env::read(); let instruction_words: InstructionData = env::read(); + println!("INSTRUCTION WORKDS IS {instruction_words:?}"); let instruction = T::deserialize(&mut Deserializer::new(instruction_words.as_ref())).unwrap(); ( ProgramInput { diff --git a/nssa/program_methods/guest/src/bin/amm.rs b/nssa/program_methods/guest/src/bin/amm.rs index 275defa..0882a0d 100644 --- a/nssa/program_methods/guest/src/bin/amm.rs +++ b/nssa/program_methods/guest/src/bin/amm.rs @@ -55,7 +55,6 @@ use nssa_core::{ // * compute_pool_pda_seed: token definitions for the pool pair // * compute_vault_pda_seed: pool definition id, definition token id, // * compute_liquidity_token_pda_seed: pool definition id -// const POOL_DEFINITION_DATA_SIZE: usize = 225; diff --git a/nssa/src/public_transaction/message.rs b/nssa/src/public_transaction/message.rs index 63ed03f..a585c2c 100644 --- a/nssa/src/public_transaction/message.rs +++ b/nssa/src/public_transaction/message.rs @@ -30,4 +30,18 @@ impl Message { instruction_data, }) } + + pub fn new_preserialized( + program_id: ProgramId, + account_ids: Vec, + nonces: Vec, + instruction_data: Vec, + ) -> Self { + Self { + program_id, + account_ids, + nonces, + instruction_data, + } + } } diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 480480d..67cf8f8 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -25,10 +25,10 @@ struct TokenDefinition { total_supply: u128, } -struct TokenHolding { +pub struct TokenHolding { #[allow(unused)] account_type: u8, - definition_id: AccountId, + pub definition_id: AccountId, balance: u128, } @@ -51,7 +51,7 @@ impl TokenDefinition { } impl TokenHolding { - fn parse(data: &[u8]) -> Option { + pub fn parse(data: &[u8]) -> Option { if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { None } else { diff --git a/wallet/src/program_facades/amm.rs b/wallet/src/program_facades/amm.rs index 13e4940..682c8d3 100644 --- a/wallet/src/program_facades/amm.rs +++ b/wallet/src/program_facades/amm.rs @@ -1,30 +1,43 @@ use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse}; use nssa::{AccountId, program::Program}; use nssa_core::{SharedSecretKey, program::InstructionData}; -use serde::Serialize; -use crate::{PrivacyPreservingAccount, WalletCore}; +use crate::{PrivacyPreservingAccount, WalletCore, cli::account::TokenHolding}; -struct OrphanHack65BytesInput([u8; 65]); +struct OrphanHack65BytesInput([u32; 65]); -impl Serialize for OrphanHack65BytesInput { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bytes(&self.0) +impl OrphanHack65BytesInput { + fn expand(orig: [u8; 65]) -> Self { + let mut res = [0u32; 65]; + + for (idx, val) in orig.into_iter().enumerate() { + res[idx] = val as u32; + } + + Self(res) } + + fn words(&self) -> Vec { + self.0.to_vec() + } } -struct OrphanHack49BytesInput([u8; 49]); +struct OrphanHack49BytesInput([u32; 49]); -impl Serialize for OrphanHack49BytesInput { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bytes(&self.0) +impl OrphanHack49BytesInput { + fn expand(orig: [u8; 49]) -> Self { + let mut res = [0u32; 49]; + + for (idx, val) in orig.into_iter().enumerate() { + res[idx] = val as u32; + } + + Self(res) } + + fn words(&self) -> Vec { + self.0.to_vec() + } } pub struct AMM<'w>(pub &'w WalletCore); @@ -33,17 +46,91 @@ impl AMM<'_> { #[allow(clippy::too_many_arguments)] pub async fn send_new_amm_definition( &self, - _amm_pool: PrivacyPreservingAccount, - _vault_holding_a: PrivacyPreservingAccount, - _vault_holding_b: PrivacyPreservingAccount, - _pool_lp: PrivacyPreservingAccount, - _user_holding_a: PrivacyPreservingAccount, - _user_holding_b: PrivacyPreservingAccount, - _user_holding_lp: PrivacyPreservingAccount, - _balance_a: u128, - _balance_b: u128, + amm_pool: PrivacyPreservingAccount, + vault_holding_a: PrivacyPreservingAccount, + vault_holding_b: PrivacyPreservingAccount, + pool_lp: PrivacyPreservingAccount, + user_holding_a: PrivacyPreservingAccount, + user_holding_b: PrivacyPreservingAccount, + user_holding_lp: PrivacyPreservingAccount, + balance_a: u128, + balance_b: u128, ) -> Result { - todo!() + let (instruction, program) = amm_program_preparation_definition(balance_a, balance_b); + + match ( + amm_pool, + vault_holding_a, + vault_holding_b, + pool_lp, + user_holding_a, + user_holding_b, + user_holding_lp, + ) { + ( + PrivacyPreservingAccount::Public(amm_pool), + PrivacyPreservingAccount::Public(vault_holding_a), + PrivacyPreservingAccount::Public(vault_holding_b), + PrivacyPreservingAccount::Public(pool_lp), + PrivacyPreservingAccount::Public(user_holding_a), + PrivacyPreservingAccount::Public(user_holding_b), + PrivacyPreservingAccount::Public(user_holding_lp), + ) => { + let account_ids = vec![ + amm_pool, + vault_holding_a, + vault_holding_b, + pool_lp, + user_holding_a, + user_holding_b, + user_holding_lp, + ]; + + let Ok(nonces) = self + .0 + .get_accounts_nonces(vec![user_holding_a, user_holding_b]) + .await + else { + return Err(ExecutionFailureKind::SequencerError); + }; + + let Some(signing_key_a) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_a) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some(signing_key_b) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_b) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let message = nssa::public_transaction::Message::try_new( + program.id(), + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message( + &message, + &[signing_key_a, signing_key_b], + ); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + _ => unreachable!(), + } } #[allow(clippy::too_many_arguments)] @@ -99,18 +186,55 @@ impl AMM<'_> { user_holding_b, ]; - // ToDo: Correct authorization - // ToDo: Also correct instruction serialization + let account_id_auth; - let message = nssa::public_transaction::Message::try_new( + // Checking, which account are associated with TokenDefinition + let token_holder_acc_a = self + .0 + .get_account_public(user_holding_a) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + let token_holder_acc_b = self + .0 + .get_account_public(user_holding_b) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let token_holder_a = TokenHolding::parse(&token_holder_acc_a.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?; + let token_holder_b = TokenHolding::parse(&token_holder_acc_b.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_b))?; + + if token_holder_a.definition_id == token_definition_id { + account_id_auth = user_holding_a; + } else if token_holder_b.definition_id == token_definition_id { + account_id_auth = user_holding_b; + } else { + return Err(ExecutionFailureKind::AccountDataError(token_definition_id)); + } + + let Ok(nonces) = self.0.get_accounts_nonces(vec![account_id_auth]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; + + let Some(signing_key) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&account_id_auth) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let message = nssa::public_transaction::Message::new_preserialized( program.id(), account_ids, - vec![], + nonces, instruction, - ) - .unwrap(); + ); - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); let tx = nssa::PublicTransaction::new(message, witness_set); @@ -218,18 +342,44 @@ impl AMM<'_> { user_holding_lp, ]; - // ToDo: Correct authorization - // ToDo: Also correct instruction serialization + let Ok(nonces) = self + .0 + .get_accounts_nonces(vec![user_holding_a, user_holding_b]) + .await + else { + return Err(ExecutionFailureKind::SequencerError); + }; + + let Some(signing_key_a) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_a) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some(signing_key_b) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_b) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; let message = nssa::public_transaction::Message::try_new( program.id(), account_ids, - vec![], + nonces, instruction, ) .unwrap(); - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let witness_set = nssa::public_transaction::WitnessSet::for_message( + &message, + &[signing_key_a, signing_key_b], + ); let tx = nssa::PublicTransaction::new(message, witness_set); @@ -343,18 +493,44 @@ impl AMM<'_> { user_holding_lp, ]; - // ToDo: Correct authorization - // ToDo: Also correct instruction serialization + let Ok(nonces) = self + .0 + .get_accounts_nonces(vec![user_holding_a, user_holding_b]) + .await + else { + return Err(ExecutionFailureKind::SequencerError); + }; + + let Some(signing_key_a) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_a) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let Some(signing_key_b) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_b) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; let message = nssa::public_transaction::Message::try_new( program.id(), account_ids, - vec![], + nonces, instruction, ) .unwrap(); - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let witness_set = nssa::public_transaction::WitnessSet::for_message( + &message, + &[signing_key_a, signing_key_b], + ); let tx = nssa::PublicTransaction::new(message, witness_set); @@ -431,7 +607,7 @@ fn amm_program_preparation_definition( ) -> (InstructionData, Program) { // An instruction data of 65-bytes, indicating the initial amm reserves' balances and // token_program_id with the following layout: - // [0x00 || array of balances (little-endian 16 bytes) || AMM_PROGRAM_ID)] + // [0x00 || array of balances (little-endian 16 bytes) || TOKEN_PROGRAM_ID)] let amm_program_id = Program::token().id(); let mut instruction = [0; 65]; @@ -448,9 +624,8 @@ fn amm_program_preparation_definition( instruction[57..61].copy_from_slice(&amm_program_id[6].to_le_bytes()); instruction[61..].copy_from_slice(&amm_program_id[7].to_le_bytes()); - let instruction_data = - Program::serialize_instruction(OrphanHack65BytesInput(instruction)).unwrap(); - let program = Program::token(); + let instruction_data = OrphanHack65BytesInput::expand(instruction).words(); + let program = Program::amm(); (instruction_data, program) } @@ -471,8 +646,8 @@ fn amm_program_preparation_swap( instruction[33..].copy_from_slice(&token_definition_id.to_bytes()); let instruction_data = - Program::serialize_instruction(OrphanHack65BytesInput(instruction)).unwrap(); - let program = Program::token(); + OrphanHack65BytesInput::expand(instruction).words(); + let program = Program::amm(); (instruction_data, program) } @@ -492,9 +667,8 @@ fn amm_program_preparation_add_liq( instruction[17..33].copy_from_slice(&max_amount_a.to_le_bytes()); instruction[33..49].copy_from_slice(&max_amount_b.to_le_bytes()); - let instruction_data = - Program::serialize_instruction(OrphanHack49BytesInput(instruction)).unwrap(); - let program = Program::token(); + let instruction_data = OrphanHack49BytesInput::expand(instruction).words(); + let program = Program::amm(); (instruction_data, program) } @@ -514,9 +688,30 @@ fn amm_program_preparation_remove_liq( instruction[17..33].copy_from_slice(&max_amount_a.to_le_bytes()); instruction[33..49].copy_from_slice(&max_amount_b.to_le_bytes()); - let instruction_data = - Program::serialize_instruction(OrphanHack49BytesInput(instruction)).unwrap(); - let program = Program::token(); + let instruction_data = OrphanHack49BytesInput::expand(instruction).words(); + let program = Program::amm(); (instruction_data, program) } + +#[cfg(test)] +mod tests { + use crate::program_facades::amm::OrphanHack65BytesInput; + + + #[test] + fn test_correct_ser() { + let mut arr = [0u8; 65]; + + for i in 0..64 { + arr[i] = i as u8; + } + + let hack = OrphanHack65BytesInput::expand(arr); + let instruction_data = hack.words(); + + println!("{instruction_data:?}"); + + //assert_eq!(serialization_res_1, serialization_res_2); + } +}