diff --git a/miden-lib/src/accounts/faucets/mod.rs b/miden-lib/src/accounts/faucets/mod.rs index 8dbe483eb..4f3a5ada5 100644 --- a/miden-lib/src/accounts/faucets/mod.rs +++ b/miden-lib/src/accounts/faucets/mod.rs @@ -39,7 +39,7 @@ pub fn create_basic_fungible_faucet( auth_scheme: AuthScheme, ) -> Result<(Account, Word), AccountError> { // Atm we only have RpoFalcon512 as authentication scheme and this is also the default in the - // faucet contract, so we can just use the public key as storage slot 0. + // faucet contract. let (auth_scheme_procedure, auth_data): (&str, Word) = match auth_scheme { AuthScheme::RpoFalcon512 { pub_key } => ("auth_tx_rpo_falcon512", pub_key.into()), diff --git a/miden-tx/src/testing/executor.rs b/miden-tx/src/testing/executor.rs index 8662c57d6..47ee41f67 100644 --- a/miden-tx/src/testing/executor.rs +++ b/miden-tx/src/testing/executor.rs @@ -16,7 +16,7 @@ pub struct CodeExecutor { impl CodeExecutor { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - pub fn new(host: H) -> Self { + pub(crate) fn new(host: H) -> Self { Self { host, stack_inputs: None, diff --git a/miden-tx/src/testing/mock_chain.rs b/miden-tx/src/testing/mock_chain/mod.rs similarity index 73% rename from miden-tx/src/testing/mock_chain.rs rename to miden-tx/src/testing/mock_chain/mod.rs index 40206b659..14a32b354 100644 --- a/miden-tx/src/testing/mock_chain.rs +++ b/miden-tx/src/testing/mock_chain/mod.rs @@ -4,17 +4,17 @@ use core::fmt; use miden_lib::{notes::create_p2id_note, transaction::TransactionKernel}; use miden_objects::{ accounts::{ - delta::AccountUpdateDetails, Account, AccountDelta, AccountId, AccountType, AuthSecretKey, - StorageSlot, + delta::AccountUpdateDetails, Account, AccountDelta, AccountId, AccountStorage, AccountType, + AuthSecretKey, StorageSlot, }, assets::{Asset, FungibleAsset, TokenSymbol}, block::{compute_tx_hash, Block, BlockAccountUpdate, BlockNoteIndex, BlockNoteTree, NoteBatch}, crypto::merkle::{Mmr, MmrError, PartialMmr, Smt}, notes::{Note, NoteId, NoteInclusionProof, NoteType, Nullifier}, - testing::account::AccountBuilder, + testing::{account::AccountBuilder, account_code::DEFAULT_AUTH_SCRIPT}, transaction::{ ChainMmr, ExecutedTransaction, InputNote, InputNotes, OutputNote, ToInputNoteCommitments, - TransactionId, TransactionInputs, + TransactionId, TransactionInputs, TransactionScript, }, AccountError, BlockHeader, FieldElement, NoteError, ACCOUNT_TREE_DEPTH, }; @@ -33,10 +33,21 @@ use crate::auth::BasicAuthenticator; /// Initial timestamp value const TIMESTAMP_START: u32 = 1693348223; -/// Timestamp of timestamp on each new block +/// Timestamp increment on each new block const TIMESTAMP_STEP: u32 = 10; -pub type MockAuthenticator = BasicAuthenticator; +// AUTH +// ================================================================================================ + +/// Specifies which authentication mechanism is desired for accounts +pub enum Auth { + /// Creates a [SecretKey](miden_objects::crypto::dsa::rpo_falcon512::SecretKey) for the + /// account and creates a [BasicAuthenticator] that gets used for authenticating the account + BasicAuth, + + /// Does not create any authentication mechanism for the account. + NoAuth, +} // MOCK FUNGIBLE FAUCET // ================================================================================================ @@ -150,19 +161,6 @@ pub enum MockError { DuplicatedNote, } -// AUTH -// ================================================================================================ - -/// Specifies which authentication mechanism is desired for accounts -pub enum Auth { - /// Creates a [SecretKey](miden_objects::crypto::dsa::rpo_falcon512::SecretKey) for the - /// account and creates a [BasicAuthenticator] that gets used for authenticating the account - BasicAuth, - - /// Does not create any authentication mechanism for the account. - NoAuth, -} - impl fmt::Display for MockError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) @@ -175,7 +173,43 @@ impl std::error::Error for MockError {} // MOCK CHAIN // ================================================================================================ -/// Structure chain data, used to build necessary openings and to construct [BlockHeader]. +/// [MockChain] simulates a simplified blockchain environment for testing purposes. +/// It allows to create and manage accounts, mint assets, execute transactions, and apply state +/// updates. +/// +/// This struct is designed to mock transaction workflows, asset transfers, and +/// note creation in a test setting. Once entities are set up, [TransactionContextBuilder] objects +/// can be obtained in order to execute transactions accordingly. +/// +/// # Examples +/// +/// ## Create mock objects +/// ``` +/// let mut mock_chain = MockChain::default(); +/// let faucet = mock_chain.add_new_faucet(Auth::BasicAuth, "USDT", 100_000); // Create a USDT faucet +/// let asset = faucet.mint(1000); +/// let note = mock_chain.add_p2id_note(asset, sender...); +/// let sender = mock_chain.add_new_wallet(Auth::BasicAuth, vec![]); +/// +/// mock_chain.build_tx_context(sender.id(), &[note.id()], &[]).build().execute() +/// ``` +/// +/// ## Executing a Simple Transaction +/// +/// NOTE: Transaction script is defaulted to either [DEFAULT_AUTH_SCRIPT] if the account includes +/// an authenticator. +/// +/// ``` +/// let mut mock_chain = MockChain::default(); +/// let sender = mock_chain.add_new_wallet(Auth::BasicAuth, vec![asset]); // Add a wallet with assets +/// let receiver = mock_chain.add_new_wallet(Auth::BasicAuth, vec![]); // Add a recipient wallet +/// +/// let tx_context = mock_chain.build_tx_context(sender.id(), &[], &[]); +/// let tx_script = TransactionScript::compile("...", vec![], TransactionKernel::testing_assembler()).unwrap(); +/// +/// let transaction = tx_context.tx_script(tx_script).build().execute().unwrap(); +/// mock_chain.apply_executed_transaction(&transaction); // Apply transaction +/// ``` #[derive(Debug, Clone)] pub struct MockChain { /// An append-only structure used to represent the history of blocks produced for this chain. @@ -205,20 +239,13 @@ pub struct MockChain { available_accounts: BTreeMap, removed_notes: Vec, + + rng: ChaCha20Rng, // RNG field } impl Default for MockChain { fn default() -> Self { - Self::new() - } -} - -impl MockChain { - // CONSTRUCTORS - // ---------------------------------------------------------------------------------------- - - pub fn new() -> Self { - Self { + MockChain { chain: Mmr::default(), blocks: vec![], nullifiers: Smt::default(), @@ -227,10 +254,33 @@ impl MockChain { available_notes: BTreeMap::new(), available_accounts: BTreeMap::new(), removed_notes: vec![], + rng: ChaCha20Rng::from_seed(Default::default()), // Initialize RNG with default seed } } +} + +impl MockChain { + // CONSTRUCTORS + // ---------------------------------------------------------------------------------------- + + /// Creates a new `MockChain` with default settings. + pub fn new() -> Self { + let mut chain = Self { + accounts: SimpleSmt::::new().expect("depth too big for SimpleSmt"), + ..Default::default() + }; + chain.seal_block(None); + chain + } - pub fn add_executed_transaction(&mut self, transaction: ExecutedTransaction) { + /// Sets the seed for the internal RNG. + pub fn set_rng_seed(&mut self, seed: [u8; 32]) { + self.rng = ChaCha20Rng::from_seed(seed); + } + + /// Applies the transaction, adding the entities to the mockchain. + /// Returns the resulting state of the executing account after executing the transaction. + pub fn apply_executed_transaction(&mut self, transaction: &ExecutedTransaction) -> Account { let mut account = transaction.initial_account().clone(); account.apply_delta(transaction.account_delta()).unwrap(); @@ -257,15 +307,17 @@ impl MockChain { self.pending_objects .included_transactions .push((transaction.id(), transaction.account_id())); + + account } - /// Add a public [Note] to the pending objects. + /// Adds a public [Note] to the pending objects. /// A block has to be created to finalize the new entity. pub fn add_note(&mut self, note: Note) { self.pending_objects.output_note_batches.push(vec![OutputNote::Full(note)]); } - /// Add a P2ID [Note] to the pending objects and returns it. + /// Adds a P2ID [Note] to the pending objects and returns it. /// A block has to be created to finalize the new entity. pub fn add_p2id_note( &mut self, @@ -290,31 +342,44 @@ impl MockChain { Ok(note) } - /// Mark a [Note] as consumed by inserting its nullifier into the block. + /// Marks a [Note] as consumed by inserting its nullifier into the block. /// A block has to be created to finalize the new entity. pub fn add_nullifier(&mut self, nullifier: Nullifier) { self.pending_objects.created_nullifiers.push(nullifier); } // OTHER IMPLEMENTATIONS - // ================================================================================================ + // ---------------------------------------------------------------------------------------- + /// Adds a new wallet with the specified authentication method and assets. pub fn add_new_wallet(&mut self, auth_method: Auth, assets: Vec) -> Account { - let account_builder = AccountBuilder::new(ChaCha20Rng::from_seed(Default::default())) + let account_builder = AccountBuilder::new(self.rng.clone()) .default_code(TransactionKernel::testing_assembler()) + // TODO: These are added because the PUBLIC_KEY_SLOT is hardcoded to 3 on basic.masm. + // Once miden-base issue 864 is addressed, this should be changed back. + // See https://github.com/0xPolygonMiden/miden-base/pull/846/files#r1749401077 for more info + .add_storage_slots([AccountStorage::mock_item_0().slot, AccountStorage::mock_item_1().slot, AccountStorage::mock_item_2().slot]) .nonce(Felt::ZERO) .add_assets(assets); self.add_from_account_builder(auth_method, account_builder) } + /// Adds an existing wallet with the specified authentication method and assets. pub fn add_existing_wallet(&mut self, auth_method: Auth, assets: Vec) -> Account { - let account_builder = AccountBuilder::new(ChaCha20Rng::from_seed(Default::default())) + let account_builder = AccountBuilder::new(self.rng.clone()) .default_code(TransactionKernel::testing_assembler()) + // TODO: These are added because the PUBLIC_KEY_SLOT is hardcoded to 3 on basic.masm. + // Once miden-base issue 864 is addressed, this should be changed back. + // See https://github.com/0xPolygonMiden/miden-base/pull/846/files#r1749401077 for more info + .add_storage_slots([AccountStorage::mock_item_0().slot, AccountStorage::mock_item_1().slot, AccountStorage::mock_item_2().slot]) .nonce(Felt::ONE) .add_assets(assets); + self.add_from_account_builder(auth_method, account_builder) } + /// Adds a new fungible faucet with the specified authentication method, token symbol, and max + /// supply. pub fn add_new_faucet( &mut self, auth_method: Auth, @@ -330,7 +395,7 @@ impl MockChain { let faucet_metadata = StorageSlot::Value(metadata); - let account_builder = AccountBuilder::new(ChaCha20Rng::from_seed(Default::default())) + let account_builder = AccountBuilder::new(self.rng.clone()) .default_code(TransactionKernel::testing_assembler()) .nonce(Felt::ZERO) .account_type(AccountType::FungibleFaucet) @@ -341,6 +406,8 @@ impl MockChain { MockFungibleFaucet(account) } + /// Adds an existing fungible faucet with the specified authentication method, token symbol, and + /// max supply. pub fn add_existing_faucet( &mut self, auth_method: Auth, @@ -356,7 +423,7 @@ impl MockChain { let faucet_metadata = StorageSlot::Value(metadata); - let account_builder = AccountBuilder::new(ChaCha20Rng::from_seed(Default::default())) + let account_builder = AccountBuilder::new(self.rng.clone()) .default_code(TransactionKernel::testing_assembler()) .nonce(Felt::ONE) .account_type(AccountType::FungibleFaucet) @@ -364,7 +431,7 @@ impl MockChain { MockFungibleFaucet(self.add_from_account_builder(auth_method, account_builder)) } - /// Add a new [Account] from an [AccountBuilder] to the list of pending objects. + /// Adds a new [Account] from an [AccountBuilder] to the list of pending objects. /// A block has to be created to finalize the new entity. pub fn add_from_account_builder( &mut self, @@ -373,13 +440,11 @@ impl MockChain { ) -> Account { let (account, seed, authenticator) = match auth_method { Auth::BasicAuth => { - let mut rng = ChaCha20Rng::from_seed(Default::default()); - - let (acc, seed, auth) = account_builder.build_with_auth(&mut rng).unwrap(); + let (acc, seed, auth) = account_builder.build_with_auth(&mut self.rng).unwrap(); - let authenticator = BasicAuthenticator::::new_with_rng( + let authenticator = BasicAuthenticator::new_with_rng( &[(auth.public_key().into(), AuthSecretKey::RpoFalcon512(auth))], - rng, + self.rng.clone(), ); (acc, seed, Some(authenticator)) @@ -398,7 +463,7 @@ impl MockChain { account } - /// Add a new [Account] to the list of pending objects. + /// Adds a new [Account] to the list of pending objects. /// A block has to be created to finalize the new entity. pub fn add_account(&mut self, account: Account) { self.pending_objects.updated_accounts.push(BlockAccountUpdate::new( @@ -409,35 +474,72 @@ impl MockChain { )); } - pub fn build_tx_context(&self, account_id: AccountId) -> TransactionContextBuilder { - let mock_account = self.available_accounts.get(&account_id).unwrap(); + /// Initializes a [TransactionContextBuilder]. + /// + /// This initializes the builder with the correct [TransactionInputs] based on what is + /// requested. The account's seed and authenticator are also introduced. Additionally, if + /// the account is set to authenticate with [Auth::BasicAuth], the executed transaction + /// script is initialized to [DEFAULT_AUTH_SCRIPT] (though this can be overridden in the + /// returned builder later). + pub fn build_tx_context( + &mut self, + account_id: AccountId, + note_ids: &[NoteId], + unauthenticated_notes: &[Note], + ) -> TransactionContextBuilder { + let mock_account = self.available_accounts.get(&account_id).unwrap().clone(); + + let tx_inputs = self.get_transaction_inputs( + mock_account.account.clone(), + mock_account.seed().cloned(), + note_ids, + unauthenticated_notes, + ); - TransactionContextBuilder::new(mock_account.account().clone()) + let mut tx_context_builder = TransactionContextBuilder::new(mock_account.account().clone()) .authenticator(mock_account.authenticator().clone()) .account_seed(mock_account.seed().cloned()) - .mock_chain(self.clone()) + .tx_inputs(tx_inputs); + + if mock_account.authenticator.is_some() { + let tx_script = TransactionScript::compile( + DEFAULT_AUTH_SCRIPT, + vec![], + TransactionKernel::testing_assembler(), + ) + .unwrap(); + tx_context_builder = tx_context_builder.tx_script(tx_script); + } + + tx_context_builder } + /// Returns a valid [TransactionInputs] for the specified entities. pub fn get_transaction_inputs( - &self, + &mut self, account: Account, account_seed: Option, notes: &[NoteId], + unauthenticated_notes: &[Note], ) -> TransactionInputs { - let block_header = self.blocks.last().unwrap().header(); + let block = self.blocks.last().unwrap(); let mut input_notes = vec![]; let mut block_headers_map: BTreeMap = BTreeMap::new(); for note in notes { - let input_note = self.available_notes.get(note).unwrap().clone(); - block_headers_map.insert( - input_note.location().unwrap().block_num(), - self.blocks - .get(input_note.location().unwrap().block_num() as usize) - .unwrap() - .header(), - ); - input_notes.push(input_note); + let input_note = self.available_notes.get(note).expect("Note not found").clone(); + let note_block_num = input_note.location().unwrap().block_num(); + if note_block_num != block.header().block_num() { + block_headers_map.insert( + note_block_num, + self.blocks.get(note_block_num as usize).unwrap().header(), + ); + input_notes.push(input_note); + } + } + + for note in unauthenticated_notes { + input_notes.push(InputNote::Unauthenticated { note: note.clone() }) } let block_headers: Vec = block_headers_map.values().cloned().collect(); @@ -446,7 +548,7 @@ impl MockChain { TransactionInputs::new( account, account_seed, - block_header, + block.header(), mmr, InputNotes::new(input_notes).unwrap(), ) @@ -459,7 +561,7 @@ impl MockChain { /// Creates the next block. /// /// This will also make all the objects currently pending available for use. - /// If `block_num` is `Some(number)`, `number` will be used as the new block's number + /// If `block_num` is `Some(number)`, `number` will be used as the new block's number. pub fn seal_block(&mut self, block_num: Option) -> Block { let next_block_num = self.blocks.last().map_or(0, |b| b.header().block_num() + 1); let block_num: u32 = if let Some(input_block_num) = block_num { @@ -584,69 +686,28 @@ impl MockChain { // ACCESSORS // ========================================================================================= - /// Get the latest [ChainMmr]. + /// Gets the latest [ChainMmr]. pub fn chain(&self) -> ChainMmr { let block_headers: Vec = self.blocks.iter().map(|b| b.header()).collect(); mmr_to_chain_mmr(&self.chain, &block_headers).unwrap() } - /// Get a reference to [BlockHeader] with `block_number`. + /// Gets a reference to [BlockHeader] with `block_number`. pub fn block_header(&self, block_number: usize) -> BlockHeader { self.blocks[block_number].header() } - /// Get a reference to the nullifier tree. + /// Gets a reference to the nullifier tree. pub fn nullifiers(&self) -> &Smt { &self.nullifiers } + /// Returns currently available notes. pub fn available_notes(&self) -> Vec { self.available_notes.values().cloned().collect() } } -// MOCK CHAIN BUILDER -// ================================================================================================ - -#[derive(Default)] -pub struct MockChainBuilder { - accounts: Vec, - notes: Vec, - starting_block_num: u32, -} - -impl MockChainBuilder { - pub fn accounts(mut self, accounts: Vec) -> Self { - self.accounts = accounts; - self - } - - pub fn notes(mut self, notes: Vec) -> Self { - self.notes = notes; - self - } - - pub fn starting_block_num(mut self, block_num: u32) -> Self { - self.starting_block_num = block_num; - self - } - - /// Returns a [MockChain] with a single block - pub fn build(self) -> MockChain { - let mut chain = MockChain::new(); - for account in self.accounts { - chain.add_account(account); - } - - for note in self.notes { - chain.add_note(note); - } - - chain.seal_block(Some(self.starting_block_num)); - chain - } -} - // HELPER FUNCTIONS // ================================================================================================ diff --git a/miden-tx/src/testing/tx_context/builder.rs b/miden-tx/src/testing/tx_context/builder.rs index 4ed5cf1f9..9ad5ebd33 100644 --- a/miden-tx/src/testing/tx_context/builder.rs +++ b/miden-tx/src/testing/tx_context/builder.rs @@ -25,15 +25,50 @@ use miden_objects::{ prepare_word, storage::prepare_assets, }, - transaction::{OutputNote, TransactionArgs, TransactionScript}, + transaction::{OutputNote, TransactionArgs, TransactionInputs, TransactionScript}, + FieldElement, }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use vm_processor::{AdviceInputs, AdviceMap, Felt, Word}; use super::TransactionContext; -use crate::testing::mock_chain::{MockAuthenticator, MockChain, MockChainBuilder}; +use crate::{auth::BasicAuthenticator, testing::mock_chain::MockChain}; +pub type MockAuthenticator = BasicAuthenticator; + +// TRANSACTION CONTEXT BUILDER +// ================================================================================================ + +/// [TransactionContextBuilder] is a utility to construct [TransactionContext] for testing +/// purposes. It allows users to build accounts, create notes, provide advice inputs, and +/// execute code. +/// +/// # Examples +/// +/// ## Create a new account and +/// ``` +/// let tx_context = TransactionContextBuilder::with_fungible_faucet( +/// acct_id.into(), +/// Felt::ZERO, +/// Felt::new(1000), +/// ) +/// .build(); +/// +/// let code = " +/// use.kernel::prologue +/// use.test::account +/// +/// begin +/// exec.prologue::prepare_transaction +/// push.0 +/// exec.account::get_item +/// end +/// "; +/// +/// let process = tx_context.execute_code(code); +/// assert!(process.is_ok()); +/// ``` pub struct TransactionContextBuilder { assembler: Assembler, account: Account, @@ -45,8 +80,8 @@ pub struct TransactionContextBuilder { expected_output_notes: Vec, tx_script: Option, note_args: BTreeMap, + transaction_inputs: Option, rng: ChaCha20Rng, - mock_chain: Option, } impl TransactionContextBuilder { @@ -62,11 +97,12 @@ impl TransactionContextBuilder { tx_script: None, authenticator: None, advice_inputs: Default::default(), + transaction_inputs: None, note_args: BTreeMap::new(), - mock_chain: None, } } + /// Initializes a [TransactionContextBuilder] with a mocked standard wallet. pub fn with_standard_account(nonce: Felt) -> Self { // Build standard account with normal assembler because the testing one already contains it let account = Account::mock( @@ -88,11 +124,12 @@ impl TransactionContextBuilder { advice_inputs: Default::default(), rng: ChaCha20Rng::from_seed([0_u8; 32]), tx_script: None, + transaction_inputs: None, note_args: BTreeMap::new(), - mock_chain: None, } } + /// Initializes a [TransactionContextBuilder] with a mocked fungible faucet. pub fn with_fungible_faucet(acct_id: u64, nonce: Felt, initial_balance: Felt) -> Self { let account = Account::mock_fungible_faucet( acct_id, @@ -100,24 +137,11 @@ impl TransactionContextBuilder { initial_balance, TransactionKernel::testing_assembler(), ); - let assembler = TransactionKernel::testing_assembler_with_mock_account(); - Self { - assembler, - account, - account_seed: None, - authenticator: None, - input_notes: Vec::new(), - expected_output_notes: Vec::new(), - advice_inputs: Default::default(), - advice_map: None, - rng: ChaCha20Rng::from_seed([0_u8; 32]), - tx_script: None, - note_args: BTreeMap::new(), - mock_chain: None, - } + Self { account, ..Self::default() } } + /// Initializes a [TransactionContextBuilder] with a mocked non-fungible faucet. pub fn with_non_fungible_faucet(acct_id: u64, nonce: Felt, empty_reserved_slot: bool) -> Self { let account = Account::mock_non_fungible_faucet( acct_id, @@ -125,49 +149,47 @@ impl TransactionContextBuilder { empty_reserved_slot, TransactionKernel::testing_assembler(), ); - let assembler = TransactionKernel::testing_assembler_with_mock_account(); - Self { - assembler, - account, - account_seed: None, - authenticator: None, - input_notes: Vec::new(), - expected_output_notes: Vec::new(), - advice_map: None, - advice_inputs: Default::default(), - rng: ChaCha20Rng::from_seed([0_u8; 32]), - tx_script: None, - note_args: BTreeMap::new(), - mock_chain: None, - } + Self { account, ..Self::default() } } + /// Override and set the account seed manually pub fn account_seed(mut self, account_seed: Option) -> Self { self.account_seed = account_seed; self } + /// Override and set the [AdviceInputs] pub fn advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self { self.advice_inputs = advice_inputs; self } + /// Set the authenticator for the transaction (if needed) pub fn authenticator(mut self, authenticator: Option) -> Self { self.authenticator = authenticator; self } + /// Extend the set of used input notes pub fn input_notes(mut self, input_notes: Vec) -> Self { self.input_notes.extend(input_notes); self } + /// Set the desired transaction script pub fn tx_script(mut self, tx_script: TransactionScript) -> Self { self.tx_script = Some(tx_script); self } + /// Set the desired transaction inputs + pub fn tx_inputs(mut self, tx_inputs: TransactionInputs) -> Self { + self.transaction_inputs = Some(tx_inputs); + self + } + + /// Defines the expected output notes pub fn expected_notes(mut self, output_notes: Vec) -> Self { let output_notes = output_notes.into_iter().filter_map(|n| match n { OutputNote::Full(note) => Some(note), @@ -196,6 +218,7 @@ impl TransactionContextBuilder { note } + /// Add a note from a [NoteBuilder] fn input_note_simple( &mut self, sender: AccountId, @@ -210,6 +233,7 @@ impl TransactionContextBuilder { .unwrap() } + /// Adds one input note with a note script that creates another ouput note. fn input_note_with_one_output_note( &mut self, sender: AccountId, @@ -255,6 +279,7 @@ impl TransactionContextBuilder { .unwrap() } + /// Adds one input note with a note script that creates 2 ouput notes. fn input_note_with_two_output_notes( &mut self, sender: AccountId, @@ -359,6 +384,8 @@ impl TransactionContextBuilder { .unwrap() } + /// Adds a set of input notes that output notes where inputs are smaller than needed and + /// do not add up to match the output. pub fn with_mock_notes_too_few_input(mut self) -> Self { // ACCOUNT IDS // -------------------------------------------------------------------------------------------- @@ -393,6 +420,7 @@ impl TransactionContextBuilder { self.input_notes(vec![input_note1]) } + /// Adds a set of input notes that output notes in an asset-preserving manner. pub fn with_mock_notes_preserved(mut self) -> Self { // ACCOUNT IDS // -------------------------------------------------------------------------------------------- @@ -559,15 +587,35 @@ impl TransactionContextBuilder { self.input_notes(vec![input_note1, input_note2, input_note4]) } + /// Builds the [TransactionContext]. + /// + /// If no transaction inputs were provided manually, an ad-hoc MockChain is created in order + /// to generate valid block data for the required notes. pub fn build(self) -> TransactionContext { - let mut mock_chain = if let Some(mock_chain) = self.mock_chain { - mock_chain - } else { - MockChainBuilder::default().notes(self.input_notes.clone()).build() + let tx_inputs = match self.transaction_inputs { + Some(tx_inputs) => tx_inputs, + None => { + // If no specific transaction inputs was provided, initialize an ad-hoc mockchain + // to generate valid block header/MMR data + + let mut mock_chain = MockChain::default(); + for i in self.input_notes { + mock_chain.add_note(i); + } + + mock_chain.seal_block(None); + + let input_note_ids: Vec = + mock_chain.available_notes().iter().map(|n| n.id()).collect(); + + mock_chain.get_transaction_inputs( + self.account.clone(), + self.account_seed, + &input_note_ids, + &[], + ) + }, }; - for _ in 0..4 { - mock_chain.seal_block(None); - } let mut tx_args = TransactionArgs::new( self.tx_script, @@ -575,19 +623,9 @@ impl TransactionContextBuilder { self.advice_map.unwrap_or_default(), ); - let input_note_ids: Vec = - mock_chain.available_notes().iter().map(|n| n.id()).collect(); - - let tx_inputs = mock_chain.get_transaction_inputs( - self.account.clone(), - self.account_seed, - &input_note_ids, - ); - tx_args.extend_expected_output_notes(self.expected_output_notes.clone()); TransactionContext { - mock_chain, expected_output_notes: self.expected_output_notes, tx_args, tx_inputs, @@ -596,9 +634,10 @@ impl TransactionContextBuilder { assembler: self.assembler, } } +} - pub fn mock_chain(mut self, mock_chain: MockChain) -> TransactionContextBuilder { - self.mock_chain = Some(mock_chain); - self +impl Default for TransactionContextBuilder { + fn default() -> Self { + Self::with_standard_account(Felt::ZERO) } } diff --git a/miden-tx/src/testing/tx_context/mod.rs b/miden-tx/src/testing/tx_context/mod.rs index f044dac61..02cd0fb60 100644 --- a/miden-tx/src/testing/tx_context/mod.rs +++ b/miden-tx/src/testing/tx_context/mod.rs @@ -1,5 +1,6 @@ use alloc::{rc::Rc, sync::Arc, vec::Vec}; +use builder::MockAuthenticator; use miden_lib::transaction::TransactionKernel; use miden_objects::{ accounts::{Account, AccountId}, @@ -10,11 +11,7 @@ use miden_objects::{ use vm_processor::{AdviceInputs, ExecutionError, Process}; use winter_maybe_async::{maybe_async, maybe_await}; -use super::{ - executor::CodeExecutor, - mock_chain::{MockAuthenticator, MockChain}, - MockHost, -}; +use super::{executor::CodeExecutor, MockHost}; use crate::{ DataStore, DataStoreError, TransactionExecutor, TransactionExecutorError, TransactionMastStore, }; @@ -31,7 +28,6 @@ pub use builder::TransactionContextBuilder; /// It implements [DataStore], so transactions may be executed with /// [TransactionExecutor](crate::TransactionExecutor) pub struct TransactionContext { - mock_chain: MockChain, expected_output_notes: Vec, tx_args: TransactionArgs, tx_inputs: TransactionInputs, @@ -94,18 +90,14 @@ impl TransactionContext { &self.expected_output_notes } - pub fn mock_chain(&self) -> &MockChain { - &self.mock_chain - } - - pub fn input_notes(&self) -> InputNotes { - InputNotes::new(self.mock_chain.available_notes().clone()).unwrap() - } - pub fn tx_args(&self) -> &TransactionArgs { &self.tx_args } + pub fn input_notes(&self) -> &InputNotes { + self.tx_inputs.input_notes() + } + pub fn set_tx_args(&mut self, tx_args: TransactionArgs) { self.tx_args = tx_args; } diff --git a/miden-tx/src/tests/kernel_tests/test_account.rs b/miden-tx/src/tests/kernel_tests/test_account.rs index 93c67ee27..2952a1faf 100644 --- a/miden-tx/src/tests/kernel_tests/test_account.rs +++ b/miden-tx/src/tests/kernel_tests/test_account.rs @@ -486,6 +486,11 @@ fn test_storage_offset() { let (mut account, _) = AccountBuilder::new(ChaCha20Rng::from_entropy()) .code(code) + .add_storage_slots([ + AccountStorage::mock_item_2().slot, + AccountStorage::mock_item_0().slot, + AccountStorage::mock_item_1().slot, + ]) .nonce(ONE) .build() .unwrap(); diff --git a/miden-tx/src/tests/kernel_tests/test_note.rs b/miden-tx/src/tests/kernel_tests/test_note.rs index 15986c6f7..d996a4265 100644 --- a/miden-tx/src/tests/kernel_tests/test_note.rs +++ b/miden-tx/src/tests/kernel_tests/test_note.rs @@ -229,8 +229,6 @@ fn test_get_inputs() { .with_mock_notes_preserved() .build(); - let notes = tx_context.mock_chain().available_notes(); - fn construct_input_assertions(note: &Note) -> String { let mut code = String::new(); for input_chunk in note.inputs().values().chunks(WORD_SIZE) { @@ -248,7 +246,7 @@ fn test_get_inputs() { code } - let note0 = notes[0].note(); + let note0 = tx_context.input_notes().get_note(0).note(); let code = format!( " diff --git a/miden-tx/tests/integration/main.rs b/miden-tx/tests/integration/main.rs index 5f73cde63..8aba6c44c 100644 --- a/miden-tx/tests/integration/main.rs +++ b/miden-tx/tests/integration/main.rs @@ -7,17 +7,15 @@ use miden_objects::{ account_id::testing::ACCOUNT_ID_SENDER, Account, AccountCode, AccountId, AccountStorage, }, assets::{Asset, AssetVault, FungibleAsset}, - crypto::{dsa::rpo_falcon512::SecretKey, utils::Serializable}, + crypto::utils::Serializable, notes::{Note, NoteAssets, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteType}, - testing::account_code::DEFAULT_AUTH_SCRIPT, - transaction::{ExecutedTransaction, ProvenTransaction, TransactionArgs, TransactionScript}, + transaction::{ExecutedTransaction, ProvenTransaction}, Felt, Word, ZERO, }; use miden_prover::ProvingOptions; use miden_tx::{ LocalTransactionProver, TransactionProver, TransactionVerifier, TransactionVerifierError, }; -use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use vm_processor::utils::Deserializable; // HELPER FUNCTIONS @@ -46,27 +44,6 @@ pub fn prove_and_verify_transaction( verifier.verify(proven_transaction) } -#[cfg(test)] -pub fn get_new_pk_and_authenticator( -) -> (Word, std::rc::Rc>) { - use std::rc::Rc; - - use miden_objects::accounts::AuthSecretKey; - use miden_tx::auth::BasicAuthenticator; - use rand::rngs::StdRng; - - let seed = [0_u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - - let sec_key = SecretKey::with_rng(&mut rng); - let pub_key: Word = sec_key.public_key().into(); - - let authenticator = - BasicAuthenticator::::new(&[(pub_key, AuthSecretKey::RpoFalcon512(sec_key))]); - - (pub_key, Rc::new(authenticator)) -} - #[cfg(test)] pub fn get_account_with_default_account_code( account_id: AccountId, @@ -119,15 +96,3 @@ pub fn get_note_with_fungible_asset_and_script( Note::new(vault, metadata, recipient) } - -#[cfg(test)] -pub fn build_default_auth_script() -> TransactionScript { - TransactionScript::compile(DEFAULT_AUTH_SCRIPT, [], TransactionKernel::assembler()).unwrap() -} - -#[cfg(test)] -pub fn build_tx_args_from_script(script_source: &str) -> TransactionArgs { - let tx_script = - TransactionScript::compile(script_source, [], TransactionKernel::assembler()).unwrap(); - TransactionArgs::with_tx_script(tx_script) -} diff --git a/miden-tx/tests/integration/scripts/faucet.rs b/miden-tx/tests/integration/scripts/faucet.rs index 9f59143a2..12766f209 100644 --- a/miden-tx/tests/integration/scripts/faucet.rs +++ b/miden-tx/tests/integration/scripts/faucet.rs @@ -2,21 +2,18 @@ extern crate alloc; use miden_lib::transaction::{memory::FAUCET_STORAGE_DATA_SLOT, TransactionKernel}; use miden_objects::{ - accounts::{ - account_id::testing::ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, Account, AccountCode, AccountId, - AccountStorage, StorageSlot, - }, - assets::{Asset, AssetVault, FungibleAsset}, + accounts::{AccountCode, AccountType, StorageSlot}, + assets::{Asset, FungibleAsset}, notes::{NoteAssets, NoteExecutionHint, NoteId, NoteMetadata, NoteTag, NoteType}, - testing::prepare_word, + testing::{account::AccountBuilder, prepare_word}, + transaction::TransactionScript, Felt, Word, ZERO, }; -use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; +use miden_tx::testing::mock_chain::{Auth, MockChain}; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; -use crate::{ - build_tx_args_from_script, get_new_pk_and_authenticator, - get_note_with_fungible_asset_and_script, prove_and_verify_transaction, -}; +use crate::{get_note_with_fungible_asset_and_script, prove_and_verify_transaction}; const FUNGIBLE_FAUCET_SOURCE: &str = " export.::miden::contracts::faucets::basic_fungible::distribute @@ -29,23 +26,11 @@ export.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 #[test] fn prove_faucet_contract_mint_fungible_asset_succeeds() { - let (faucet_pub_key, falcon_auth) = get_new_pk_and_authenticator(); - let faucet_account = - get_faucet_account_with_max_supply_and_total_issuance(faucet_pub_key, 200, None); - // CONSTRUCT AND EXECUTE TX (Success) // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(faucet_account.clone()).build(); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); + let faucet_builder = get_faucet_account_with_max_supply_and_total_issuance(200, None); + let mut mock_chain = MockChain::new(); + let faucet = mock_chain.add_from_account_builder(Auth::BasicAuth, faucet_builder); let recipient = [Felt::new(0), Felt::new(1), Felt::new(2), Felt::new(3)]; let tag = NoteTag::for_local_use_case(0, 0).unwrap(); @@ -70,7 +55,6 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 dropw dropw drop - end ", note_type = note_type as u8, @@ -80,16 +64,17 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { note_execution_hint = Felt::from(note_execution_hint) ); - let tx_args = build_tx_args_from_script(&tx_script_code); + let tx_script = + TransactionScript::compile(tx_script_code, vec![], TransactionKernel::testing_assembler()) + .unwrap(); + let tx_context = + mock_chain.build_tx_context(faucet.id(), &[], &[]).tx_script(tx_script).build(); - let executed_transaction = executor - .execute_transaction(faucet_account.id(), block_ref, ¬e_ids, tx_args) - .unwrap(); + let executed_transaction = tx_context.execute().unwrap(); assert!(prove_and_verify_transaction(executed_transaction.clone()).is_ok()); - let fungible_asset: Asset = - FungibleAsset::new(faucet_account.id(), amount.into()).unwrap().into(); + let fungible_asset: Asset = FungibleAsset::new(faucet.id(), amount.into()).unwrap().into(); let output_note = executed_transaction.output_notes().get_note(0).clone(); @@ -99,30 +84,17 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { assert_eq!(output_note.id(), id); assert_eq!( output_note.metadata(), - &NoteMetadata::new(faucet_account.id(), NoteType::Private, tag, note_execution_hint, aux) - .unwrap() + &NoteMetadata::new(faucet.id(), NoteType::Private, tag, note_execution_hint, aux).unwrap() ); } #[test] fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() { - let (faucet_pub_key, falcon_auth) = get_new_pk_and_authenticator(); - let faucet_account = - get_faucet_account_with_max_supply_and_total_issuance(faucet_pub_key, 200, None); - // CONSTRUCT AND EXECUTE TX (Failure) // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(faucet_account.clone()).build(); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); + let mut mock_chain = MockChain::new(); + let faucet: miden_tx::testing::mock_chain::MockFungibleFaucet = + mock_chain.add_new_faucet(Auth::BasicAuth, "TST", 200u64); let recipient = [Felt::new(0), Felt::new(1), Felt::new(2), Felt::new(3)]; let aux = Felt::new(27); @@ -149,13 +121,17 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() { recipient = prepare_word(&recipient), ); - let tx_args = build_tx_args_from_script(&tx_script_code); + let tx_script = + TransactionScript::compile(tx_script_code, vec![], TransactionKernel::testing_assembler()) + .unwrap(); + let tx = mock_chain + .build_tx_context(faucet.account().id(), &[], &[]) + .tx_script(tx_script) + .build() + .execute(); // Execute the transaction and get the witness - let executed_transaction = - executor.execute_transaction(faucet_account.id(), block_ref, ¬e_ids, tx_args); - - assert!(executed_transaction.is_err()); + assert!(tx.is_err()); } // TESTS BURN FUNGIBLE ASSET @@ -163,9 +139,9 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() { #[test] fn prove_faucet_contract_burn_fungible_asset_succeeds() { - let (faucet_pub_key, falcon_auth) = get_new_pk_and_authenticator(); - let faucet_account = - get_faucet_account_with_max_supply_and_total_issuance(faucet_pub_key, 200, Some(100)); + let faucet_builder = get_faucet_account_with_max_supply_and_total_issuance(200, Some(100)); + let mut mock_chain = MockChain::new(); + let faucet_account = mock_chain.add_from_account_builder(Auth::BasicAuth, faucet_builder); let fungible_asset = FungibleAsset::new(faucet_account.id(), 100).unwrap(); @@ -192,37 +168,23 @@ fn prove_faucet_contract_burn_fungible_asset_succeeds() { let note = get_note_with_fungible_asset_and_script(fungible_asset, note_script); + mock_chain.add_note(note.clone()); + mock_chain.seal_block(None); + // CONSTRUCT AND EXECUTE TX (Success) // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(faucet_account.clone()) - .input_notes(vec![note.clone()]) - .build(); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - // Execute the transaction and get the witness - let executed_transaction = executor - .execute_transaction( - faucet_account.id(), - block_ref, - ¬e_ids, - tx_context.tx_args().clone(), - ) + let executed_transaction = mock_chain + .build_tx_context(faucet_account.id(), &[note.id()], &[]) + .build() + .execute() .unwrap(); // Prove, serialize/deserialize and verify the transaction assert!(prove_and_verify_transaction(executed_transaction.clone()).is_ok()); // check that the account burned the asset - assert_eq!(executed_transaction.account_delta().nonce(), Some(Felt::new(2))); + assert_eq!(executed_transaction.account_delta().nonce(), Some(Felt::new(3))); assert_eq!(executed_transaction.input_notes().get_note(0).id(), note.id()); } @@ -230,12 +192,9 @@ fn prove_faucet_contract_burn_fungible_asset_succeeds() { // ================================================================================================ fn get_faucet_account_with_max_supply_and_total_issuance( - public_key: Word, max_supply: u64, total_issuance: Option, -) -> Account { - let faucet_account_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); - +) -> AccountBuilder { let assembler = TransactionKernel::assembler(); let faucet_account_code = AccountCode::compile(FUNGIBLE_FAUCET_SOURCE, assembler).unwrap(); @@ -247,20 +206,10 @@ fn get_faucet_account_with_max_supply_and_total_issuance( StorageSlot::Value([Felt::new(max_supply), Felt::new(0), Felt::new(0), Felt::new(0)]); let faucet_storage_slot_2 = StorageSlot::Value([Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0)]); - let faucet_storage_slot_3 = StorageSlot::Value(public_key); - let faucet_account_storage = AccountStorage::new(vec![ - faucet_storage_slot_0, - faucet_storage_slot_1, - faucet_storage_slot_2, - faucet_storage_slot_3, - ]) - .unwrap(); - - Account::from_parts( - faucet_account_id, - AssetVault::new(&[]).unwrap(), - faucet_account_storage.clone(), - faucet_account_code.clone(), - Felt::new(1), - ) + + AccountBuilder::new(ChaCha20Rng::from_seed(Default::default())) + .nonce(Felt::new(1)) + .code(faucet_account_code) + .account_type(AccountType::FungibleFaucet) + .add_storage_slots([faucet_storage_slot_0, faucet_storage_slot_1, faucet_storage_slot_2]) } diff --git a/miden-tx/tests/integration/scripts/p2id.rs b/miden-tx/tests/integration/scripts/p2id.rs index ffd4b17f4..6fcadff2f 100644 --- a/miden-tx/tests/integration/scripts/p2id.rs +++ b/miden-tx/tests/integration/scripts/p2id.rs @@ -1,200 +1,62 @@ -use std::rc::Rc; use miden_lib::{notes::create_p2id_note, transaction::TransactionKernel}; use miden_objects::{ accounts::{ account_id::testing::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, + ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, }, - Account, AccountId, AccountType, StorageSlot, + Account, }, assets::{Asset, AssetVault, FungibleAsset}, crypto::rand::RpoRandomCoin, notes::NoteType, - testing::{account::AccountBuilder, account_code::DEFAULT_AUTH_SCRIPT}, - transaction::{TransactionArgs, TransactionScript}, - Felt, FieldElement, + testing::prepare_word, + transaction::{OutputNote, TransactionScript}, + Felt, }; -use miden_tx::{ - auth::BasicAuthenticator, - testing::{ - mock_chain::{Auth, MockChain}, - TransactionContextBuilder, - }, - TransactionExecutor, -}; -use rand::{rngs::StdRng, SeedableRng}; -use rand_chacha::ChaCha20Rng; -use vm_processor::Word; - -use crate::{ - build_default_auth_script, get_account_with_default_account_code, get_new_pk_and_authenticator, - prove_and_verify_transaction, -}; - -// P2ID TESTS -// =============================================================================================== -// We test the Pay to ID script. So we create a note that can only be consumed by the target -// account. -#[test] -fn prove_p2id_script() { - // Create assets - let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let fungible_asset: Asset = FungibleAsset::new(faucet_id, 100).unwrap().into(); - - // Create sender and target account - let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - - let target_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(); - let (target_pub_key, falcon_auth) = get_new_pk_and_authenticator(); +use miden_tx::testing::mock_chain::{Auth, MockChain}; - let target_account = - get_account_with_default_account_code(target_account_id, target_pub_key, None); - - // Create the note - let note = create_p2id_note( - sender_account_id, - target_account_id, - vec![fungible_asset], - NoteType::Public, - Felt::new(0), - &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), - ) - .unwrap(); - - // CONSTRUCT AND EXECUTE TX (Success) - // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(target_account.clone()) - .input_notes(vec![note.clone()]) - .build(); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let tx_script_target = build_default_auth_script(); - let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); - - // Execute the transaction and get the witness - let executed_transaction = executor - .execute_transaction(target_account_id, block_ref, ¬e_ids, tx_args_target) - .unwrap(); - - // Prove, serialize/deserialize and verify the transaction - assert!(prove_and_verify_transaction(executed_transaction.clone()).is_ok()); - - // vault delta - let target_account_after: Account = Account::from_parts( - target_account.id(), - AssetVault::new(&[fungible_asset]).unwrap(), - target_account.storage().clone(), - target_account.code().clone(), - Felt::new(2), - ); - assert_eq!(executed_transaction.final_account().hash(), target_account_after.hash()); - - // CONSTRUCT AND EXECUTE TX (Failure) - // -------------------------------------------------------------------------------------------- - // A "malicious" account tries to consume the note, we expect an error - - let malicious_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2).unwrap(); - let (malicious_pub_key, malicious_falcon_auth) = get_new_pk_and_authenticator(); - let malicious_account = - get_account_with_default_account_code(malicious_account_id, malicious_pub_key, None); - - let tx_context_malicious_account = TransactionContextBuilder::new(malicious_account) - .input_notes(vec![note]) - .build(); - let executor_2 = - TransactionExecutor::new(tx_context_malicious_account.clone(), Some(malicious_falcon_auth)); - - let tx_script_malicious = build_default_auth_script(); - let tx_args_malicious = TransactionArgs::with_tx_script(tx_script_malicious); - - let block_ref = tx_context_malicious_account.tx_inputs().block_header().block_num(); - let note_ids = tx_context_malicious_account - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - // Execute the transaction and get the witness - let executed_transaction_2 = executor_2.execute_transaction( - malicious_account_id, - block_ref, - ¬e_ids, - tx_args_malicious, - ); - - // Check that we got the expected result - TransactionExecutorError - assert!(executed_transaction_2.is_err()); -} +use crate::prove_and_verify_transaction; /// We test the Pay to script with 2 assets to test the loop inside the script. /// So we create a note containing two assets that can only be consumed by the target account. #[test] fn p2id_script_multiple_assets() { - // Create assets - let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let fungible_asset_1: Asset = FungibleAsset::new(faucet_id, 123).unwrap().into(); + let mut mock_chain = MockChain::new(); - let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2).unwrap(); - let fungible_asset_2: Asset = FungibleAsset::new(faucet_id_2, 456).unwrap().into(); + // Create assets + let fungible_asset_1: Asset = FungibleAsset::mock(123); + let fungible_asset_2: Asset = + FungibleAsset::new(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2.try_into().unwrap(), 456) + .unwrap() + .into(); // Create sender and target account - let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - - let target_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN).unwrap(); - - let (target_pub_key, falcon_auth) = get_new_pk_and_authenticator(); - let target_account = - get_account_with_default_account_code(target_account_id, target_pub_key, None); + let sender_account = mock_chain.add_new_wallet(Auth::BasicAuth, vec![]); + let target_account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![]); // Create the note - let note = create_p2id_note( - sender_account_id, - target_account_id, - vec![fungible_asset_1, fungible_asset_2], - NoteType::Public, - Felt::new(0), - &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), - ) - .unwrap(); + let note = mock_chain + .add_p2id_note( + sender_account.id(), + target_account.id(), + &[fungible_asset_1, fungible_asset_2], + NoteType::Public, + ) + .unwrap(); + + mock_chain.seal_block(None); // CONSTRUCT AND EXECUTE TX (Success) // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(target_account.clone()) - .input_notes(vec![note.clone()]) - .build(); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth)); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let tx_script_target = build_default_auth_script(); - let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); - // Execute the transaction and get the witness - let executed_transaction = executor - .execute_transaction(target_account_id, block_ref, ¬e_ids, tx_args_target) + let executed_transaction = mock_chain + .build_tx_context(target_account.id(), &[note.id()], &[]) + .build() + .execute() .unwrap(); // vault delta @@ -209,90 +71,70 @@ fn p2id_script_multiple_assets() { // CONSTRUCT AND EXECUTE TX (Failure) // -------------------------------------------------------------------------------------------- - // A "malicious" account tries to consume the note, we expect an error + // A "malicious" account tries to consume the note, we expect an error (not the correct target) - let malicious_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2).unwrap(); - let (malicious_pub_key, malicious_falcon_auth) = get_new_pk_and_authenticator(); - let malicious_account = - get_account_with_default_account_code(malicious_account_id, malicious_pub_key, None); - - let tx_context_malicious_account = TransactionContextBuilder::new(malicious_account) - .input_notes(vec![note]) - .build(); - let executor_2 = - TransactionExecutor::new(tx_context_malicious_account.clone(), Some(malicious_falcon_auth)); - let tx_script_malicious = build_default_auth_script(); - let tx_args_malicious = TransactionArgs::with_tx_script(tx_script_malicious); - - let block_ref = tx_context_malicious_account.tx_inputs().block_header().block_num(); - let note_origins = tx_context_malicious_account - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); + let malicious_account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![]); + mock_chain.seal_block(None); // Execute the transaction and get the witness - let executed_transaction_2 = executor_2.execute_transaction( - malicious_account_id, - block_ref, - ¬e_origins, - tx_args_malicious, - ); + let executed_transaction_2 = mock_chain + .build_tx_context(malicious_account.id(), &[], &[note]) + .build() + .execute(); // Check that we got the expected result - TransactionExecutorError assert!(executed_transaction_2.is_err()); } -/// Consumes an existing note with a new account -#[test] -fn prove_consume_note_with_new_account() { - let (mut target_account, seed, falcon_auth) = create_new_account(); - let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let fungible_asset_1: Asset = FungibleAsset::new(faucet_id, 123).unwrap().into(); - - // Create the note - let note = create_p2id_note( - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(), - target_account.id(), - vec![fungible_asset_1], - NoteType::Public, - Felt::new(0), - &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), - ) - .unwrap(); - - let tx_context = TransactionContextBuilder::new(target_account.clone()) - .account_seed(Some(seed)) - .input_notes(vec![note.clone()]) - .build(); - - assert!(target_account.is_new()); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth)); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let tx_script_target = build_default_auth_script(); - let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); - - // Execute the transaction and get the witness - let executed_transaction = executor - .execute_transaction(target_account.id(), block_ref, ¬e_ids, tx_args_target) - .unwrap(); - - // Account delta - target_account.apply_delta(executed_transaction.account_delta()).unwrap(); - assert!(!target_account.is_new()); - - assert!(prove_and_verify_transaction(executed_transaction).is_ok()); -} +// /// Consumes an existing note with a new account +// #[test] +// fn prove_consume_note_with_new_account() { +// let (mut target_account, seed, falcon_auth) = create_new_account(); +// let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); +// let fungible_asset_1: Asset = FungibleAsset::new(faucet_id, 123).unwrap().into(); + +// // Create the note +// let note = create_p2id_note( +// ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(), +// target_account.id(), +// vec![fungible_asset_1], +// NoteType::Public, +// Felt::new(0), +// &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), +// ) +// .unwrap(); + +// let tx_context = TransactionContextBuilder::new(target_account.clone()) +// .account_seed(Some(seed)) +// .input_notes(vec![note.clone()]) +// .build(); + +// assert!(target_account.is_new()); + +// let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth)); + +// let block_ref = tx_context.tx_inputs().block_header().block_num(); +// let note_ids = tx_context +// .tx_inputs() +// .input_notes() +// .iter() +// .map(|note| note.id()) +// .collect::>(); + +// let tx_script_target = build_default_auth_script(); +// let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); + +// // Execute the transaction and get the witness +// let executed_transaction = executor +// .execute_transaction(target_account.id(), block_ref, ¬e_ids, tx_args_target) +// .unwrap(); + +// // Account delta +// target_account.apply_delta(executed_transaction.account_delta()).unwrap(); +// assert!(!target_account.is_new()); + +// assert!(prove_and_verify_transaction(executed_transaction).is_ok()); +// } /// Consumes two existing notes (with an asset from a faucet for a combined total of 123 tokens) /// with a basic account @@ -327,13 +169,10 @@ fn prove_consume_multiple_notes() { ) .unwrap(); - let tx_script = - TransactionScript::compile(DEFAULT_AUTH_SCRIPT, vec![], TransactionKernel::assembler()) - .unwrap(); + mock_chain.seal_block(None); + let tx_context = mock_chain - .build_tx_context(account.id()) - .input_notes(vec![note_1, note_2]) - .tx_script(tx_script) + .build_tx_context(account.id(), &[note_1.id(), note_2.id()], &[]) .build(); let executed_transaction = tx_context.execute().unwrap(); @@ -349,21 +188,116 @@ fn prove_consume_multiple_notes() { assert!(prove_and_verify_transaction(executed_transaction).is_ok()); } -// HELPER FUNCTIONS -// =============================================================================================== +/// Consumes two existing notes and creates two other notes in the same transaction +#[test] +fn test_create_consume_multiple_notes() { + let mut mock_chain = MockChain::new(); + let mut account = + mock_chain.add_existing_wallet(Auth::BasicAuth, vec![FungibleAsset::mock(20)]); -fn create_new_account() -> (Account, Word, Rc>) { - let (pub_key, falcon_auth) = get_new_pk_and_authenticator(); + let input_note_faucet_id = ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN; + let input_note_asset_1: Asset = + FungibleAsset::new(input_note_faucet_id.try_into().unwrap(), 11).unwrap().into(); - let storage_slot = StorageSlot::Value(pub_key); + let input_note_asset_2: Asset = + FungibleAsset::new(input_note_faucet_id.try_into().unwrap(), 100) + .unwrap() + .into(); - let (account, seed) = AccountBuilder::new(ChaCha20Rng::from_entropy()) - .add_storage_slot(storage_slot) - .default_code(TransactionKernel::testing_assembler()) - .account_type(AccountType::RegularAccountUpdatableCode) - .nonce(Felt::ZERO) - .build() + let input_note_1 = mock_chain + .add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[input_note_asset_1], + NoteType::Private, + ) + .unwrap(); + + let input_note_2 = mock_chain + .add_p2id_note( + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2.try_into().unwrap(), + account.id(), + &[input_note_asset_2], + NoteType::Private, + ) .unwrap(); - (account, seed, falcon_auth) + mock_chain.seal_block(None); + + let output_note_1 = create_p2id_note( + account.id(), + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2.try_into().unwrap(), + vec![FungibleAsset::mock(10)], + NoteType::Public, + Felt::new(0), + &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), + ) + .unwrap(); + + let output_note_2 = create_p2id_note( + account.id(), + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN.try_into().unwrap(), + vec![FungibleAsset::mock(5)], + NoteType::Public, + Felt::new(0), + &mut RpoRandomCoin::new([Felt::new(4), Felt::new(3), Felt::new(2), Felt::new(1)]), + ) + .unwrap(); + + let tx_script_src = &format!( + " + begin + push.{recipient_1} + push.{note_execution_hint_1} + push.{note_type_1} + push.0 # aux + push.{tag_1} + push.{asset_1} + call.::miden::contracts::wallets::basic::send_asset + dropw dropw dropw dropw + + push.{recipient_2} + push.{note_execution_hint_2} + push.{note_type_2} + push.0 # aux + push.{tag_2} + push.{asset_2} + call.::miden::contracts::wallets::basic::send_asset + dropw dropw dropw dropw + call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 + end + ", + recipient_1 = prepare_word(&output_note_1.recipient().digest()), + note_type_1 = NoteType::Public as u8, + tag_1 = Felt::new(output_note_1.metadata().tag().into()), + asset_1 = prepare_word(&FungibleAsset::mock(10).into()), + note_execution_hint_1 = Felt::from(output_note_1.metadata().execution_hint()), + recipient_2 = prepare_word(&output_note_2.recipient().digest()), + note_type_2 = NoteType::Public as u8, + tag_2 = Felt::new(output_note_2.metadata().tag().into()), + asset_2 = prepare_word(&FungibleAsset::mock(5).into()), + note_execution_hint_2 = Felt::from(output_note_2.metadata().execution_hint()) + ); + + let tx_script = + TransactionScript::compile(tx_script_src, vec![], TransactionKernel::assembler()).unwrap(); + + let tx_context = mock_chain + .build_tx_context(account.id(), &[input_note_1.id(), input_note_2.id()], &[]) + .expected_notes(vec![OutputNote::Full(output_note_1), OutputNote::Full(output_note_2)]) + .tx_script(tx_script) + .build(); + + let executed_transaction = tx_context.execute().unwrap(); + + assert_eq!(executed_transaction.output_notes().num_notes(), 2); + + account.apply_delta(executed_transaction.account_delta()).unwrap(); + for asset in account.vault().assets() { + if u64::from(asset.faucet_id()) == input_note_faucet_id { + assert!(asset.unwrap_fungible().amount() == 111); + } else if u64::from(asset.faucet_id()) == ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN { + assert!(asset.unwrap_fungible().amount() == 5); + } + } } diff --git a/miden-tx/tests/integration/scripts/p2idr.rs b/miden-tx/tests/integration/scripts/p2idr.rs index 83724d5dc..b55420850 100644 --- a/miden-tx/tests/integration/scripts/p2idr.rs +++ b/miden-tx/tests/integration/scripts/p2idr.rs @@ -1,256 +1,257 @@ -use miden_lib::notes::create_p2idr_note; -use miden_objects::{ - accounts::{ - account_id::testing::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, - }, - Account, AccountId, - }, - assets::{Asset, AssetVault, FungibleAsset}, - crypto::rand::RpoRandomCoin, - notes::NoteType, - transaction::TransactionArgs, - Felt, -}; -use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; - -use crate::{ - build_default_auth_script, get_account_with_default_account_code, get_new_pk_and_authenticator, -}; - -// P2IDR TESTS -// =============================================================================================== -// We want to test the Pay to ID Reclaim script, which is a script that allows the user -// to provide a block height to the P2ID script. Before the block height is reached, -// the note can only be consumed by the target account. After the block height is reached, -// the note can also be consumed (reclaimed) by the sender account. -#[test] -fn p2idr_script() { - // Create assets - let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let fungible_asset: Asset = FungibleAsset::new(faucet_id, 100).unwrap().into(); - - // Create sender and target and malicious account - let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - let (sender_pub_key, sender_falcon_auth) = get_new_pk_and_authenticator(); - let sender_account = - get_account_with_default_account_code(sender_account_id, sender_pub_key, None); - - // Now create the target account - let target_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN).unwrap(); - let (target_pub_key, target_falcon_auth) = get_new_pk_and_authenticator(); - let target_account = - get_account_with_default_account_code(target_account_id, target_pub_key, None); - - // Now create the malicious account - let malicious_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2).unwrap(); - let (malicious_pub_key, malicious_falcon_auth) = get_new_pk_and_authenticator(); - let malicious_account = - get_account_with_default_account_code(malicious_account_id, malicious_pub_key, None); - - // -------------------------------------------------------------------------------------------- - // Create notes - // Create the reclaim block height (Note: Current block height is 4) - let reclaim_block_height_in_time = 5_u32; - let reclaim_block_height_reclaimable = 3_u32; - - // Create the notes with the P2IDR script - // Create the note_in_time - let note_in_time = create_p2idr_note( - sender_account_id, - target_account_id, - vec![fungible_asset], - NoteType::Public, - Felt::new(0), - reclaim_block_height_in_time, - &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), - ) - .unwrap(); - - // Create the reclaimable_note - let note_reclaimable = create_p2idr_note( - sender_account_id, - target_account_id, - vec![fungible_asset], - NoteType::Public, - Felt::new(0), - reclaim_block_height_reclaimable, - &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), - ) - .unwrap(); - - // -------------------------------------------------------------------------------------------- - // We have two cases. - // - // Case "in time": block height is 4, reclaim block height is 5. Only the target account can - // consume the note. - // - // Case "reclaimable": block height is 4, reclaim block height is 3. Target and sender account - // can consume the note. The malicious account should never be able to consume the note. - // -------------------------------------------------------------------------------------------- - // CONSTRUCT AND EXECUTE TX (Case "in time" - Target Account Execution Success) - // -------------------------------------------------------------------------------------------- - let tx_context_1 = TransactionContextBuilder::new(target_account.clone()) - .input_notes(vec![note_in_time.clone()]) - .build(); - let executor_1 = - TransactionExecutor::new(tx_context_1.clone(), Some(target_falcon_auth.clone())); - - let block_ref_1 = tx_context_1.tx_inputs().block_header().block_num(); - let note_ids = tx_context_1.input_notes().iter().map(|note| note.id()).collect::>(); - - let tx_script_target = build_default_auth_script(); - let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); - - // Execute the transaction and get the witness - let executed_transaction_1 = executor_1 - .execute_transaction(target_account_id, block_ref_1, ¬e_ids, tx_args_target.clone()) - .unwrap(); - - // Assert that the target_account received the funds and the nonce increased by 1 - let target_account_after: Account = Account::from_parts( - target_account_id, - AssetVault::new(&[fungible_asset]).unwrap(), - target_account.storage().clone(), - target_account.code().clone(), - Felt::new(2), - ); - assert_eq!(executed_transaction_1.final_account().hash(), target_account_after.hash()); - - // CONSTRUCT AND EXECUTE TX (Case "in time" - Sender Account Execution Failure) - // -------------------------------------------------------------------------------------------- - let tx_context_2 = TransactionContextBuilder::new(sender_account.clone()) - .input_notes(vec![note_in_time.clone()]) - .build(); - let executor_2 = - TransactionExecutor::new(tx_context_2.clone(), Some(sender_falcon_auth.clone())); - let tx_script_sender = build_default_auth_script(); - let tx_args_sender = TransactionArgs::with_tx_script(tx_script_sender); - - let block_ref_2 = tx_context_2.tx_inputs().block_header().block_num(); - let note_ids_2 = tx_context_2.input_notes().iter().map(|note| note.id()).collect::>(); - - // Execute the transaction and get the witness - let executed_transaction_2 = executor_2.execute_transaction( - sender_account_id, - block_ref_2, - ¬e_ids_2, - tx_args_sender.clone(), - ); - - // Check that we got the expected result - TransactionExecutorError and not ExecutedTransaction - // Second transaction should not work (sender consumes too early), we expect an error - assert!(executed_transaction_2.is_err()); - - // CONSTRUCT AND EXECUTE TX (Case "in time" - Malicious Target Account Failure) - // -------------------------------------------------------------------------------------------- - let tx_context_3 = TransactionContextBuilder::new(malicious_account.clone()) - .input_notes(vec![note_in_time.clone()]) - .build(); - let executor_3 = - TransactionExecutor::new(tx_context_3.clone(), Some(malicious_falcon_auth.clone())); - - let tx_script_malicious = build_default_auth_script(); - let tx_args_malicious = TransactionArgs::with_tx_script(tx_script_malicious); - - let block_ref_3 = tx_context_3.tx_inputs().block_header().block_num(); - let note_ids_3 = tx_context_3.input_notes().iter().map(|note| note.id()).collect::>(); - - // Execute the transaction and get the witness - let executed_transaction_3 = executor_3.execute_transaction( - malicious_account_id, - block_ref_3, - ¬e_ids_3, - tx_args_malicious.clone(), - ); - - // Check that we got the expected result - TransactionExecutorError and not ExecutedTransaction - // Third transaction should not work (malicious account can never consume), we expect an error - assert!(executed_transaction_3.is_err()); - - // CONSTRUCT AND EXECUTE TX (Case "reclaimable" - Execution Target Account Success) - // -------------------------------------------------------------------------------------------- - let tx_context_4 = TransactionContextBuilder::new(target_account.clone()) - .input_notes(vec![note_reclaimable.clone()]) - .build(); - let executor_4 = TransactionExecutor::new(tx_context_4.clone(), Some(target_falcon_auth)); - - let block_ref_4 = tx_context_4.tx_inputs().block_header().block_num(); - let note_ids_4 = tx_context_4.input_notes().iter().map(|note| note.id()).collect::>(); - - // Execute the transaction and get the witness - let executed_transaction_4 = executor_4 - .execute_transaction(target_account_id, block_ref_4, ¬e_ids_4, tx_args_target) - .unwrap(); - - // Check that we got the expected result - ExecutedTransaction - // Assert that the target_account received the funds and the nonce increased by 1 - // Nonce delta - assert_eq!(executed_transaction_4.account_delta().nonce(), Some(Felt::new(2))); - - // Vault delta - let target_account_after: Account = Account::from_parts( - target_account_id, - AssetVault::new(&[fungible_asset]).unwrap(), - target_account.storage().clone(), - target_account.code().clone(), - Felt::new(2), - ); - assert_eq!(executed_transaction_4.final_account().hash(), target_account_after.hash()); - - // CONSTRUCT AND EXECUTE TX (Case "too late" - Execution Sender Account Success) - // -------------------------------------------------------------------------------------------- - let tx_context_5 = TransactionContextBuilder::new(sender_account.clone()) - .input_notes(vec![note_reclaimable.clone()]) - .build(); - let executor_5 = TransactionExecutor::new(tx_context_5.clone(), Some(sender_falcon_auth)); - - let block_ref_5 = tx_context_5.tx_inputs().block_header().block_num(); - let note_ids_5 = tx_context_5.input_notes().iter().map(|note| note.id()).collect::>(); - - // Execute the transaction and get the witness - let executed_transaction_5 = executor_5 - .execute_transaction(sender_account_id, block_ref_5, ¬e_ids_5, tx_args_sender) - .unwrap(); - - // Assert that the sender_account received the funds and the nonce increased by 1 - // Nonce delta - assert_eq!(executed_transaction_5.account_delta().nonce(), Some(Felt::new(2))); - - // Vault delta (Note: vault was empty before) - let sender_account_after: Account = Account::from_parts( - sender_account_id, - AssetVault::new(&[fungible_asset]).unwrap(), - sender_account.storage().clone(), - sender_account.code().clone(), - Felt::new(2), - ); - assert_eq!(executed_transaction_5.final_account().hash(), sender_account_after.hash()); - - // CONSTRUCT AND EXECUTE TX (Case "too late" - Malicious Account Failure) - // -------------------------------------------------------------------------------------------- - let tx_context_6 = TransactionContextBuilder::new(malicious_account.clone()) - .input_notes(vec![note_reclaimable.clone()]) - .build(); - - let executor_6 = TransactionExecutor::new(tx_context_6.clone(), Some(malicious_falcon_auth)); - - let block_ref_6 = tx_context_6.tx_inputs().block_header().block_num(); - let note_ids_6 = tx_context_6.input_notes().iter().map(|note| note.id()).collect::>(); - - // Execute the transaction and get the witness - let executed_transaction_6 = executor_6.execute_transaction( - malicious_account_id, - block_ref_6, - ¬e_ids_6, - tx_args_malicious, - ); - - // Check that we got the expected result - TransactionExecutorError and not ExecutedTransaction - // Sixth transaction should not work (malicious account can never consume), we expect an error - assert!(executed_transaction_6.is_err()) -} +// use miden_lib::notes::create_p2idr_note; +// use miden_objects::{ +// accounts::{ +// account_id::testing::{ +// ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, +// ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, +// ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, +// }, +// Account, AccountId, +// }, +// assets::{Asset, AssetVault, FungibleAsset}, +// crypto::rand::RpoRandomCoin, +// notes::NoteType, +// transaction::TransactionArgs, +// Felt, +// }; +// use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; + +// use crate::{ +// get_account_with_default_account_code, get_new_pk_and_authenticator, +// }; + +// // P2IDR TESTS +// // =============================================================================================== +// // We want to test the Pay to ID Reclaim script, which is a script that allows the user +// // to provide a block height to the P2ID script. Before the block height is reached, +// // the note can only be consumed by the target account. After the block height is reached, +// // the note can also be consumed (reclaimed) by the sender account. +// #[test] +// fn p2idr_script() { +// // Create assets +// let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); +// let fungible_asset: Asset = FungibleAsset::new(faucet_id, 100).unwrap().into(); + +// // Create sender and target and malicious account +// let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); +// let (sender_pub_key, sender_falcon_auth) = get_new_pk_and_authenticator(); +// let sender_account = +// get_account_with_default_account_code(sender_account_id, sender_pub_key, None); + +// // Now create the target account +// let target_account_id = +// AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN).unwrap(); +// let (target_pub_key, target_falcon_auth) = get_new_pk_and_authenticator(); +// let target_account = +// get_account_with_default_account_code(target_account_id, target_pub_key, None); + +// // Now create the malicious account +// let malicious_account_id = +// AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2).unwrap(); +// let (malicious_pub_key, malicious_falcon_auth) = get_new_pk_and_authenticator(); +// let malicious_account = +// get_account_with_default_account_code(malicious_account_id, malicious_pub_key, None); + +// // -------------------------------------------------------------------------------------------- +// // Create notes +// // Create the reclaim block height (Note: Current block height is 4) +// let reclaim_block_height_in_time = 5_u32; +// let reclaim_block_height_reclaimable = 3_u32; + +// // Create the notes with the P2IDR script +// // Create the note_in_time +// let note_in_time = create_p2idr_note( +// sender_account_id, +// target_account_id, +// vec![fungible_asset], +// NoteType::Public, +// Felt::new(0), +// reclaim_block_height_in_time, +// &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), +// ) +// .unwrap(); + +// // Create the reclaimable_note +// let note_reclaimable = create_p2idr_note( +// sender_account_id, +// target_account_id, +// vec![fungible_asset], +// NoteType::Public, +// Felt::new(0), +// reclaim_block_height_reclaimable, +// &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), +// ) +// .unwrap(); + +// // -------------------------------------------------------------------------------------------- +// // We have two cases. +// // +// // Case "in time": block height is 4, reclaim block height is 5. Only the target account can +// // consume the note. +// // +// // Case "reclaimable": block height is 4, reclaim block height is 3. Target and sender +// account // can consume the note. The malicious account should never be able to consume the +// note. // +// -------------------------------------------------------------------------------------------- +// // CONSTRUCT AND EXECUTE TX (Case "in time" - Target Account Execution Success) +// // -------------------------------------------------------------------------------------------- +// let tx_context_1 = TransactionContextBuilder::new(target_account.clone()) +// .input_notes(vec![note_in_time.clone()]) +// .build(); +// let executor_1 = +// TransactionExecutor::new(tx_context_1.clone(), Some(target_falcon_auth.clone())); + +// let block_ref_1 = tx_context_1.tx_inputs().block_header().block_num(); +// let note_ids = tx_context_1.input_notes().iter().map(|note| note.id()).collect::>(); + +// let tx_script_target = build_default_auth_script(); +// let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); + +// // Execute the transaction and get the witness +// let executed_transaction_1 = executor_1 +// .execute_transaction(target_account_id, block_ref_1, ¬e_ids, tx_args_target.clone()) +// .unwrap(); + +// // Assert that the target_account received the funds and the nonce increased by 1 +// let target_account_after: Account = Account::from_parts( +// target_account_id, +// AssetVault::new(&[fungible_asset]).unwrap(), +// target_account.storage().clone(), +// target_account.code().clone(), +// Felt::new(2), +// ); +// assert_eq!(executed_transaction_1.final_account().hash(), target_account_after.hash()); + +// // CONSTRUCT AND EXECUTE TX (Case "in time" - Sender Account Execution Failure) +// // -------------------------------------------------------------------------------------------- +// let tx_context_2 = TransactionContextBuilder::new(sender_account.clone()) +// .input_notes(vec![note_in_time.clone()]) +// .build(); +// let executor_2 = +// TransactionExecutor::new(tx_context_2.clone(), Some(sender_falcon_auth.clone())); +// let tx_script_sender = build_default_auth_script(); +// let tx_args_sender = TransactionArgs::with_tx_script(tx_script_sender); + +// let block_ref_2 = tx_context_2.tx_inputs().block_header().block_num(); +// let note_ids_2 = tx_context_2.input_notes().iter().map(|note| note.id()).collect::>(); + +// // Execute the transaction and get the witness +// let executed_transaction_2 = executor_2.execute_transaction( +// sender_account_id, +// block_ref_2, +// ¬e_ids_2, +// tx_args_sender.clone(), +// ); + +// // Check that we got the expected result - TransactionExecutorError and not +// ExecutedTransaction // Second transaction should not work (sender consumes too early), we +// expect an error assert!(executed_transaction_2.is_err()); + +// // CONSTRUCT AND EXECUTE TX (Case "in time" - Malicious Target Account Failure) +// // -------------------------------------------------------------------------------------------- +// let tx_context_3 = TransactionContextBuilder::new(malicious_account.clone()) +// .input_notes(vec![note_in_time.clone()]) +// .build(); +// let executor_3 = +// TransactionExecutor::new(tx_context_3.clone(), Some(malicious_falcon_auth.clone())); + +// let tx_script_malicious = build_default_auth_script(); +// let tx_args_malicious = TransactionArgs::with_tx_script(tx_script_malicious); + +// let block_ref_3 = tx_context_3.tx_inputs().block_header().block_num(); +// let note_ids_3 = tx_context_3.input_notes().iter().map(|note| note.id()).collect::>(); + +// // Execute the transaction and get the witness +// let executed_transaction_3 = executor_3.execute_transaction( +// malicious_account_id, +// block_ref_3, +// ¬e_ids_3, +// tx_args_malicious.clone(), +// ); + +// // Check that we got the expected result - TransactionExecutorError and not +// ExecutedTransaction // Third transaction should not work (malicious account can never +// consume), we expect an error assert!(executed_transaction_3.is_err()); + +// // CONSTRUCT AND EXECUTE TX (Case "reclaimable" - Execution Target Account Success) +// // -------------------------------------------------------------------------------------------- +// let tx_context_4 = TransactionContextBuilder::new(target_account.clone()) +// .input_notes(vec![note_reclaimable.clone()]) +// .build(); +// let executor_4 = TransactionExecutor::new(tx_context_4.clone(), Some(target_falcon_auth)); + +// let block_ref_4 = tx_context_4.tx_inputs().block_header().block_num(); +// let note_ids_4 = tx_context_4.input_notes().iter().map(|note| note.id()).collect::>(); + +// // Execute the transaction and get the witness +// let executed_transaction_4 = executor_4 +// .execute_transaction(target_account_id, block_ref_4, ¬e_ids_4, tx_args_target) +// .unwrap(); + +// // Check that we got the expected result - ExecutedTransaction +// // Assert that the target_account received the funds and the nonce increased by 1 +// // Nonce delta +// assert_eq!(executed_transaction_4.account_delta().nonce(), Some(Felt::new(2))); + +// // Vault delta +// let target_account_after: Account = Account::from_parts( +// target_account_id, +// AssetVault::new(&[fungible_asset]).unwrap(), +// target_account.storage().clone(), +// target_account.code().clone(), +// Felt::new(2), +// ); +// assert_eq!(executed_transaction_4.final_account().hash(), target_account_after.hash()); + +// // CONSTRUCT AND EXECUTE TX (Case "too late" - Execution Sender Account Success) +// // -------------------------------------------------------------------------------------------- +// let tx_context_5 = TransactionContextBuilder::new(sender_account.clone()) +// .input_notes(vec![note_reclaimable.clone()]) +// .build(); +// let executor_5 = TransactionExecutor::new(tx_context_5.clone(), Some(sender_falcon_auth)); + +// let block_ref_5 = tx_context_5.tx_inputs().block_header().block_num(); +// let note_ids_5 = tx_context_5.input_notes().iter().map(|note| note.id()).collect::>(); + +// // Execute the transaction and get the witness +// let executed_transaction_5 = executor_5 +// .execute_transaction(sender_account_id, block_ref_5, ¬e_ids_5, tx_args_sender) +// .unwrap(); + +// // Assert that the sender_account received the funds and the nonce increased by 1 +// // Nonce delta +// assert_eq!(executed_transaction_5.account_delta().nonce(), Some(Felt::new(2))); + +// // Vault delta (Note: vault was empty before) +// let sender_account_after: Account = Account::from_parts( +// sender_account_id, +// AssetVault::new(&[fungible_asset]).unwrap(), +// sender_account.storage().clone(), +// sender_account.code().clone(), +// Felt::new(2), +// ); +// assert_eq!(executed_transaction_5.final_account().hash(), sender_account_after.hash()); + +// // CONSTRUCT AND EXECUTE TX (Case "too late" - Malicious Account Failure) +// // -------------------------------------------------------------------------------------------- +// let tx_context_6 = TransactionContextBuilder::new(malicious_account.clone()) +// .input_notes(vec![note_reclaimable.clone()]) +// .build(); + +// let executor_6 = TransactionExecutor::new(tx_context_6.clone(), Some(malicious_falcon_auth)); + +// let block_ref_6 = tx_context_6.tx_inputs().block_header().block_num(); +// let note_ids_6 = tx_context_6.input_notes().iter().map(|note| note.id()).collect::>(); + +// // Execute the transaction and get the witness +// let executed_transaction_6 = executor_6.execute_transaction( +// malicious_account_id, +// block_ref_6, +// ¬e_ids_6, +// tx_args_malicious, +// ); + +// // Check that we got the expected result - TransactionExecutorError and not +// ExecutedTransaction // Sixth transaction should not work (malicious account can never +// consume), we expect an error assert!(executed_transaction_6.is_err()) +// } diff --git a/miden-tx/tests/integration/scripts/swap.rs b/miden-tx/tests/integration/scripts/swap.rs index d8c8319f5..35bcfd100 100644 --- a/miden-tx/tests/integration/scripts/swap.rs +++ b/miden-tx/tests/integration/scripts/swap.rs @@ -1,97 +1,141 @@ use miden_lib::{notes::create_swap_note, transaction::TransactionKernel}; use miden_objects::{ - accounts::{account_id::testing::ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, Account, AccountId}, - assets::{Asset, AssetVault, NonFungibleAsset, NonFungibleAssetDetails}, + accounts::{account_id::testing::ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, AccountId}, + assets::{Asset, NonFungibleAsset}, crypto::rand::RpoRandomCoin, - notes::{ - NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteHeader, NoteId, NoteMetadata, - NoteTag, NoteType, - }, - testing::account_code::DEFAULT_AUTH_SCRIPT, - transaction::TransactionScript, - Felt, ZERO, + notes::{Note, NoteDetails, NoteType}, + testing::prepare_word, + transaction::{OutputNote, TransactionScript}, + Felt, }; use miden_tx::testing::mock_chain::{Auth, MockChain}; use crate::prove_and_verify_transaction; +// Creates a swap note and sends it with send_asset #[test] -fn prove_swap_script() { - // Create assets - let mut chain = MockChain::new(); - let faucet = chain.add_existing_faucet(Auth::NoAuth, "POL", 100000u64); - let offered_asset = faucet.mint(100); - - let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let requested_asset: Asset = NonFungibleAsset::new( - &NonFungibleAssetDetails::new(faucet_id_2, vec![1, 2, 3, 4]).unwrap(), - ) - .unwrap() - .into(); +pub fn prove_send_swap_note() { + let mut mock_chain = MockChain::new(); + let offered_asset = mock_chain.add_new_faucet(Auth::BasicAuth, "USDT", 100000u64).mint(2000); + let requested_asset = + NonFungibleAsset::mock(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, &[1, 2, 3, 4]); + let sender_account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![offered_asset]); - // Create sender and target account - let sender_account = chain.add_new_wallet(Auth::BasicAuth, vec![offered_asset]); - let target_account = chain.add_existing_wallet(Auth::BasicAuth, vec![requested_asset]); + let (note, _payback) = get_swap_notes(sender_account.id(), offered_asset, requested_asset); - // Create the note containing the SWAP script - let (note, payback_note) = create_swap_note( - sender_account.id(), - offered_asset, - requested_asset, - NoteType::Public, - Felt::new(27), - &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), - ) - .unwrap(); + // CREATE SWAP NOTE TX + // -------------------------------------------------------------------------------------------- - chain.add_note(note.clone()); - chain.seal_block(None); + let tx_script_src = &format!( + " + begin + push.{recipient} + push.{note_execution_hint} + push.{note_type} + push.0 # aux + push.{tag} + push.{asset} + call.::miden::contracts::wallets::basic::send_asset + dropw dropw dropw dropw + call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 + end + ", + recipient = prepare_word(¬e.recipient().digest()), + note_type = NoteType::Public as u8, + tag = Felt::new(note.metadata().tag().into()), + asset = prepare_word(&offered_asset.into()), + note_execution_hint = Felt::from(note.metadata().execution_hint()) + ); - // CONSTRUCT AND EXECUTE TX (Success) - // -------------------------------------------------------------------------------------------- - let transaction_script = - TransactionScript::compile(DEFAULT_AUTH_SCRIPT, vec![], TransactionKernel::assembler()) + let tx_script = + TransactionScript::compile(tx_script_src, vec![], TransactionKernel::testing_assembler()) .unwrap(); - let executed_transaction = chain - .build_tx_context(target_account.id()) - .tx_script(transaction_script) + let create_swap_note_tx = mock_chain + .build_tx_context(sender_account.id(), &[], &[]) + .tx_script(tx_script) + .expected_notes(vec![OutputNote::Full(note.clone())]) .build() .execute() .unwrap(); - // target account vault delta - let target_account_after: Account = Account::from_parts( - target_account.id(), - AssetVault::new(&[offered_asset]).unwrap(), - target_account.storage().clone(), - target_account.code().clone(), - Felt::new(2), + let sender_account = mock_chain.apply_executed_transaction(&create_swap_note_tx); + + assert!(create_swap_note_tx.output_notes().iter().any(|n| n.hash() == note.hash())); + assert_eq!(sender_account.vault().assets().count(), 0); // Offered asset should be gone + let swap_output_note = create_swap_note_tx.output_notes().iter().next().unwrap(); + assert_eq!(swap_output_note.assets().unwrap().iter().next().unwrap(), &offered_asset); + assert!(prove_and_verify_transaction(create_swap_note_tx).is_ok()); +} + +// Consumes the swap note (same as the one used in the above test) and proves the transaction +// The sender account also consumes the payback note +#[test] +fn prove_consume_swap_note() { + let mut mock_chain = MockChain::new(); + let offered_asset = mock_chain.add_new_faucet(Auth::BasicAuth, "USDT", 100000u64).mint(2000); + let requested_asset = + NonFungibleAsset::mock(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, &[1, 2, 3, 4]); + let sender_account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![offered_asset]); + + let (note, payback_note) = get_swap_notes(sender_account.id(), offered_asset, requested_asset); + + // CONSUME CREATED NOTE + // -------------------------------------------------------------------------------------------- + + let target_account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![requested_asset]); + mock_chain.add_note(note.clone()); + mock_chain.seal_block(None); + + let consume_swap_note_tx = mock_chain + .build_tx_context(target_account.id(), &[note.id()], &[]) + .build() + .execute() + .unwrap(); + + let target_account = mock_chain.apply_executed_transaction(&consume_swap_note_tx); + + let output_payback_note = consume_swap_note_tx.output_notes().iter().next().unwrap().clone(); + assert!(output_payback_note.id() == payback_note.id()); + assert_eq!(output_payback_note.assets().unwrap().iter().next().unwrap(), &requested_asset); + + assert!(prove_and_verify_transaction(consume_swap_note_tx).is_ok()); + assert!(target_account.vault().assets().count() == 1); + assert!(target_account.vault().assets().any(|asset| asset == offered_asset)); + + // CONSUME PAYBACK P2ID NOTE + // -------------------------------------------------------------------------------------------- + + let full_payback_note = Note::new( + payback_note.assets().clone(), + *output_payback_note.metadata(), + payback_note.recipient().clone(), ); - // Check that the target account has received the asset from the note - assert_eq!(executed_transaction.final_account().hash(), target_account_after.hash()); - - // Check if only one `Note` has been created - assert_eq!(executed_transaction.output_notes().num_notes(), 1); - - // Check if the output `Note` is what we expect - let recipient = payback_note.recipient().clone(); - let tag = NoteTag::from_account_id(sender_account.id(), NoteExecutionMode::Local).unwrap(); - let note_metadata = NoteMetadata::new( - target_account.id(), - NoteType::Private, - tag, - NoteExecutionHint::Always, - ZERO, - ) - .unwrap(); - let assets = NoteAssets::new(vec![requested_asset]).unwrap(); - let note_id = NoteId::new(recipient.digest(), assets.commitment()); + let consume_payback_tx = mock_chain + .build_tx_context(sender_account.id(), &[], &[full_payback_note]) + .build() + .execute() + .unwrap(); - let output_note = executed_transaction.output_notes().get_note(0); - assert_eq!(NoteHeader::from(output_note), NoteHeader::new(note_id, note_metadata)); + let sender_account = mock_chain.apply_executed_transaction(&consume_payback_tx); + assert!(sender_account.vault().assets().any(|asset| asset == requested_asset)); + assert!(prove_and_verify_transaction(consume_payback_tx).is_ok()); +} - // Prove, serialize/deserialize and verify the transaction - assert!(prove_and_verify_transaction(executed_transaction.clone()).is_ok()); +fn get_swap_notes( + sender_account_id: AccountId, + offered_asset: Asset, + requested_asset: Asset, +) -> (Note, NoteDetails) { + // Create the note containing the SWAP script + create_swap_note( + sender_account_id, + offered_asset, + requested_asset, + NoteType::Public, + Felt::new(0), + &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), + ) + .unwrap() } diff --git a/miden-tx/tests/integration/wallet/mod.rs b/miden-tx/tests/integration/wallet/mod.rs index f36583234..c06bd3bc8 100644 --- a/miden-tx/tests/integration/wallet/mod.rs +++ b/miden-tx/tests/integration/wallet/mod.rs @@ -1,190 +1,11 @@ use miden_lib::{accounts::wallets::create_basic_wallet, AuthScheme}; use miden_objects::{ - accounts::{ - account_id::testing::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_OFF_CHAIN_SENDER, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - }, - Account, AccountId, AccountStorage, StorageMap, StorageSlot, - }, - assets::{Asset, AssetVault, FungibleAsset}, - crypto::dsa::rpo_falcon512::SecretKey, - notes::{NoteExecutionHint, NoteTag, NoteType}, - testing::prepare_word, - transaction::TransactionArgs, - Felt, Word, ONE, ZERO, + accounts::AccountId, + crypto::dsa::rpo_falcon512::SecretKey, Word, }; -use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; -use crate::{ - build_default_auth_script, build_tx_args_from_script, get_account_with_default_account_code, - get_new_pk_and_authenticator, get_note_with_fungible_asset_and_script, - prove_and_verify_transaction, -}; - -#[test] -// Testing the basic Miden wallet - receiving an asset -fn prove_receive_asset_via_wallet() { - // Create assets - let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let fungible_asset_1 = FungibleAsset::new(faucet_id_1, 100).unwrap(); - - let target_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(); - let (target_pub_key, target_falcon_auth) = get_new_pk_and_authenticator(); - let target_account = - get_account_with_default_account_code(target_account_id, target_pub_key, None); - - // Create the note - let note_script_src = " - # add the asset - begin - dropw - exec.::miden::note::get_assets drop - mem_loadw - call.::miden::contracts::wallets::basic::receive_asset - dropw - end - "; - - let note = get_note_with_fungible_asset_and_script(fungible_asset_1, note_script_src); - - // CONSTRUCT AND EXECUTE TX (Success) - // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(target_account.clone()) - .input_notes(vec![note]) - .build(); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(target_falcon_auth.clone())); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let tx_script = build_default_auth_script(); - let tx_args = TransactionArgs::with_tx_script(tx_script); - - // Execute the transaction and get the witness - let executed_transaction = executor - .execute_transaction(target_account.id(), block_ref, ¬e_ids, tx_args) - .unwrap(); - - // Prove, serialize/deserialize and verify the transaction - assert!(prove_and_verify_transaction(executed_transaction.clone()).is_ok()); - - // nonce delta - assert_eq!(executed_transaction.account_delta().nonce(), Some(Felt::new(2))); - - // clone account info - let account_storage = AccountStorage::new(vec![ - StorageSlot::Value(Word::default()), - StorageSlot::Value(Word::default()), - StorageSlot::Value(Word::default()), - StorageSlot::Value(target_pub_key), - StorageSlot::Map(StorageMap::default()), - ]) - .unwrap(); - let account_code = target_account.code().clone(); - // vault delta - let target_account_after: Account = Account::from_parts( - target_account.id(), - AssetVault::new(&[fungible_asset_1.into()]).unwrap(), - account_storage, - account_code, - Felt::new(2), - ); - assert_eq!(executed_transaction.final_account().hash(), target_account_after.hash()); -} - -#[test] -/// Testing the basic Miden wallet - sending an asset -fn prove_send_asset_via_wallet() { - let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let fungible_asset_1: Asset = FungibleAsset::new(faucet_id_1, 100).unwrap().into(); - - let sender_account_id = AccountId::try_from(ACCOUNT_ID_OFF_CHAIN_SENDER).unwrap(); - let (sender_pub_key, sender_falcon_auth) = get_new_pk_and_authenticator(); - let sender_account = get_account_with_default_account_code( - sender_account_id, - sender_pub_key, - fungible_asset_1.into(), - ); - - // CONSTRUCT AND EXECUTE TX (Success) - // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(sender_account.clone()).build(); - - let executor = TransactionExecutor::new(tx_context.clone(), Some(sender_falcon_auth.clone())); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; - let aux = Felt::new(27); - let tag = NoteTag::for_local_use_case(0, 0).unwrap(); - let note_type = NoteType::Private; - - assert_eq!(tag.validate(note_type), Ok(tag)); - - let tx_script_src = &format!( - " - begin - push.{recipient} - push.{note_execution_hint} - push.{note_type} - push.{aux} - push.{tag} - push.{asset} - call.::miden::contracts::wallets::basic::send_asset - dropw dropw dropw dropw - call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 - end - ", - recipient = prepare_word(&recipient), - note_type = note_type as u8, - tag = tag, - asset = prepare_word(&fungible_asset_1.into()), - note_execution_hint = Felt::from(NoteExecutionHint::always()) - ); - let tx_args = build_tx_args_from_script(tx_script_src); - - let executed_transaction = executor - .execute_transaction(sender_account.id(), block_ref, ¬e_ids, tx_args) - .unwrap(); - - assert!(prove_and_verify_transaction(executed_transaction.clone()).is_ok()); - - // clones account info - let sender_account_storage = AccountStorage::new(vec![ - StorageSlot::Value(Word::default()), - StorageSlot::Value(Word::default()), - StorageSlot::Value(Word::default()), - StorageSlot::Value(sender_pub_key), - StorageSlot::Map(StorageMap::default()), - ]) - .unwrap(); - let sender_account_code = sender_account.code().clone(); - - // vault delta - let sender_account_after: Account = Account::from_parts( - tx_context.account().id(), - AssetVault::new(&[]).unwrap(), - sender_account_storage, - sender_account_code, - Felt::new(2), - ); - assert_eq!(executed_transaction.final_account().hash(), sender_account_after.hash()); -} +use crate::get_account_with_default_account_code; #[cfg(not(target_arch = "wasm32"))] #[test] diff --git a/objects/src/accounts/delta/mod.rs b/objects/src/accounts/delta/mod.rs index 8702fca9b..4744529a3 100644 --- a/objects/src/accounts/delta/mod.rs +++ b/objects/src/accounts/delta/mod.rs @@ -84,7 +84,7 @@ impl AccountDelta { &self.vault } - /// Returns the new nonce, if the nonce was changes. + /// Returns the new nonce, if the nonce was changed. pub fn nonce(&self) -> Option { self.nonce } diff --git a/objects/src/testing/account_code.rs b/objects/src/testing/account_code.rs index 1d8fc14ac..3758e8d52 100644 --- a/objects/src/testing/account_code.rs +++ b/objects/src/testing/account_code.rs @@ -47,6 +47,8 @@ impl AccountCode { export.::miden::contracts::wallets::basic::send_asset export.::miden::contracts::wallets::basic::create_note export.::miden::contracts::wallets::basic::move_asset_to_note + export.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 + export.::miden::contracts::faucets::basic_fungible::distribute export.incr_nonce push.0 swap diff --git a/objects/src/testing/storage.rs b/objects/src/testing/storage.rs index b5886bd89..f7adc6b03 100644 --- a/objects/src/testing/storage.rs +++ b/objects/src/testing/storage.rs @@ -31,13 +31,7 @@ pub struct AccountStorageBuilder { /// Builder for an `AccountStorage`, the builder can be configured and used multiple times. impl AccountStorageBuilder { pub fn new() -> Self { - Self { - slots: vec![ - AccountStorage::mock_item_2().slot, - AccountStorage::mock_item_0().slot, - AccountStorage::mock_item_1().slot, - ], - } + Self { slots: vec![] } } pub fn add_slot(&mut self, slot: StorageSlot) -> &mut Self {