Skip to content

Commit

Permalink
feat: add support for notes without asset in transctions (#654)
Browse files Browse the repository at this point in the history
* feat: support txs with notes without assets

* feat: add unit test

* chore: update CHANGELOG
  • Loading branch information
tomyrd authored Jan 9, 2025
1 parent 2149038 commit 3e229c4
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

### Changes

* Add support for notes without assets in transaction requests (#654).
* Refactored RPC functions and structs to improve code quality (#616).
* [BREAKING] Added support for new two `Felt` account ID (#639).
* [BREAKING] Removed unnecessary methods from `Client` (#631).
Expand Down
70 changes: 67 additions & 3 deletions crates/rust-client/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use alloc::vec::Vec;
// ================================================================================================
use miden_lib::{
accounts::{auth::RpoFalcon512, faucets::BasicFungibleFaucet, wallets::BasicWallet},
notes::utils,
transaction::TransactionKernel,
};
use miden_objects::{
Expand All @@ -13,22 +14,28 @@ use miden_objects::{
},
assets::{FungibleAsset, TokenSymbol},
crypto::{dsa::rpo_falcon512::SecretKey, rand::FeltRng},
notes::{NoteFile, NoteTag},
notes::{
Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteFile, NoteMetadata, NoteTag,
NoteType,
},
testing::account_id::{
ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2,
ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN,
ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN,
},
Felt, FieldElement, Word,
transaction::OutputNote,
Felt, FieldElement, Word, ZERO,
};
use miden_tx::utils::{Deserializable, Serializable};

use crate::{
mock::create_test_client,
rpc::NodeRpcClient,
store::{InputNoteRecord, NoteFilter, Store, StoreError},
transactions::TransactionRequestBuilder,
transactions::{
TransactionRequestBuilder, TransactionRequestError, TransactionScriptBuilderError,
},
Client, ClientError,
};

Expand Down Expand Up @@ -631,3 +638,60 @@ async fn test_no_nonce_change_transaction_request() {
Err(ClientError::StoreError(StoreError::AccountHashAlreadyExists(_)))
));
}

#[tokio::test]
async fn test_note_without_asset() {
let (mut client, _rpc_api) = create_test_client().await;

let (faucet, _seed) = insert_new_fungible_faucet(&mut client, AccountStorageMode::Private)
.await
.unwrap();

let (wallet, _seed) =
insert_new_wallet(&mut client, AccountStorageMode::Private).await.unwrap();

client.sync_state().await.unwrap();

// Create note without assets
let serial_num = client.rng().draw_word();
let recipient = utils::build_p2id_recipient(wallet.id(), serial_num).unwrap();
let tag = NoteTag::from_account_id(wallet.id(), NoteExecutionMode::Local).unwrap();
let metadata =
NoteMetadata::new(wallet.id(), NoteType::Private, tag, NoteExecutionHint::always(), ZERO)
.unwrap();
let vault = NoteAssets::new(vec![]).unwrap();

let note = Note::new(vault.clone(), metadata, recipient.clone());

// Create and execute transaction
let transaction_request = TransactionRequestBuilder::new()
.with_own_output_notes(vec![OutputNote::Full(note)])
.unwrap()
.build();

let transaction = client.new_transaction(wallet.id(), transaction_request.clone()).await;

assert!(transaction.is_ok());

// Create the same transaction for the faucet
let metadata =
NoteMetadata::new(faucet.id(), NoteType::Private, tag, NoteExecutionHint::always(), ZERO)
.unwrap();
let note = Note::new(vault, metadata, recipient);

let transaction_request = TransactionRequestBuilder::new()
.with_own_output_notes(vec![OutputNote::Full(note)])
.unwrap()
.build();

let error = client.new_transaction(faucet.id(), transaction_request).await.unwrap_err();

assert!(matches!(
error,
ClientError::TransactionRequestError(
TransactionRequestError::TransactionScriptBuilderError(
TransactionScriptBuilderError::FaucetNoteWithoutAsset
)
)
));
}
24 changes: 15 additions & 9 deletions crates/rust-client/src/transactions/script_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ impl AccountInterface {
/// Errors:
/// - [TransactionScriptBuilderError::InvalidSenderAccount] if the sender of the note isn't the
/// account for which the script is being built.
/// - [TransactionScriptBuilderError::InvalidAssetAmount] if the note doesn't contain exactly
/// one asset.
/// - [TransactionScriptBuilderError::FaucetNoteWithoutAsset] if the note created by the faucet
/// doesn't contain exactly one asset.
/// - [TransactionScriptBuilderError::InvalidAsset] if a faucet tries to distribute an asset
/// with a different faucet ID.
fn send_note_procedure(
Expand All @@ -54,8 +54,6 @@ impl AccountInterface {
));
}

let asset = partial_note.assets().iter().next().expect("There should be an asset");

body.push_str(&format!(
"
push.{recipient}
Expand All @@ -73,6 +71,14 @@ impl AccountInterface {

match self {
AccountInterface::BasicFungibleFaucet => {
if partial_note.assets().num_assets() != 1 {
return Err(TransactionScriptBuilderError::FaucetNoteWithoutAsset);
}

// SAFETY: We checked that the note contains exactly one asset
let asset =
partial_note.assets().iter().next().expect("note should contain an asset");

if asset.faucet_id_prefix() != account_id.prefix() {
return Err(TransactionScriptBuilderError::InvalidAsset(
asset.faucet_id_prefix(),
Expand All @@ -90,15 +96,13 @@ impl AccountInterface {
AccountInterface::BasicWallet => {
body.push_str(
"
call.wallet::create_note",
call.wallet::create_note\n",
);

for asset in partial_note.assets().iter() {
body.push_str(&format!(
"
push.{asset}
call.wallet::move_asset_to_note dropw
",
"push.{asset}
call.wallet::move_asset_to_note dropw\n",
asset = prepare_word(&asset.into())
))
}
Expand Down Expand Up @@ -223,6 +227,8 @@ impl TransactionScriptBuilder {
pub enum TransactionScriptBuilderError {
#[error("invalid asset: {0}")]
InvalidAsset(AccountIdPrefix),
#[error("note created by the faucet doesn't contain exactly one asset")]
FaucetNoteWithoutAsset,
#[error("invalid transaction script")]
InvalidTransactionScript(#[source] TransactionScriptError),
#[error("invalid sender account: {0}")]
Expand Down

0 comments on commit 3e229c4

Please sign in to comment.