Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- Implemented the `on_before_asset_added_to_account` asset callback ([#2571](https://github.com/0xMiden/protocol/pull/2571)).
- Implemented the `on_before_asset_added_to_note` asset callback ([#2595](https://github.com/0xMiden/protocol/pull/2595)).
- Added `InputNoteCommitment::from_parts()` for construction of input note commitments from a nullifier and optional note header ([#2588](https://github.com/0xMiden/protocol/pull/2588)).
- Added `SwapNoteStorage` for typed serialization/deserialization of SWAP note storage ([#2585](https://github.com/0xMiden/protocol/pull/2585)).
- Added `bool` schema type to the type registry and updated ACL auth component to use it for boolean config fields ([#2591](https://github.com/0xMiden/protocol/pull/2591)).
- Added `component_metadata()` to all account components to expose their metadata ([#2596](https://github.com/0xMiden/protocol/pull/2596)).
- Added `Package` support in `MockChainBuilder` & `NoteScript` ([#2502](https://github.com/0xMiden/protocol/pull/2502)).
Expand Down
2 changes: 1 addition & 1 deletion crates/miden-standards/src/note/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod p2ide;
pub use p2ide::{P2ideNote, P2ideNoteStorage};

mod swap;
pub use swap::SwapNote;
pub use swap::{SwapNote, SwapNoteStorage};

mod network_account_target;
pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};
Expand Down
235 changes: 211 additions & 24 deletions crates/miden-standards/src/note/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl SwapNote {
// --------------------------------------------------------------------------------------------

/// Expected number of storage items of the SWAP note.
pub const NUM_STORAGE_ITEMS: usize = 20;
pub const NUM_STORAGE_ITEMS: usize = SwapNoteStorage::NUM_ITEMS;

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -89,42 +89,31 @@ impl SwapNote {
return Err(NoteError::other("requested asset same as offered asset"));
}

let note_script = Self::script();

let payback_serial_num = rng.draw_word();
let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_num);

let payback_tag = NoteTag::with_account_target(sender);

let attachment_scheme = Felt::from(payback_note_attachment.attachment_scheme().as_u32());
let attachment_kind = Felt::from(payback_note_attachment.attachment_kind().as_u8());
let attachment = payback_note_attachment.content().to_word();
let swap_storage = SwapNoteStorage::new(
sender,
requested_asset,
payback_note_type,
payback_note_attachment,
payback_serial_num,
);

let mut storage = Vec::with_capacity(SwapNote::NUM_STORAGE_ITEMS);
storage.extend_from_slice(&[
payback_note_type.into(),
payback_tag.into(),
attachment_scheme,
attachment_kind,
]);
storage.extend_from_slice(attachment.as_elements());
storage.extend_from_slice(&requested_asset.as_elements());
storage.extend_from_slice(payback_recipient.digest().as_elements());
let inputs = NoteStorage::new(storage)?;
let serial_num = rng.draw_word();
let recipient = swap_storage.into_recipient(serial_num);

// build the tag for the SWAP use case
let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset);
let serial_num = rng.draw_word();

// build the outgoing note
let metadata = NoteMetadata::new(sender, swap_note_type)
.with_tag(tag)
.with_attachment(swap_note_attachment);
let assets = NoteAssets::new(vec![offered_asset])?;
let recipient = NoteRecipient::new(serial_num, note_script, inputs);
let note = Note::new(assets, metadata, recipient);

// build the payback note details
let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_num);
let payback_assets = NoteAssets::new(vec![requested_asset])?;
let payback_note = NoteDetails::new(payback_assets, payback_recipient);

Expand Down Expand Up @@ -171,17 +160,215 @@ impl SwapNote {
}
}

// SWAP NOTE STORAGE
// ================================================================================================

/// Canonical storage representation for a SWAP note.
///
/// Contains the payback note configuration and the requested asset that the
/// swap creator wants to receive in exchange for the offered asset contained
/// in the note's vault.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwapNoteStorage {
payback_note_type: NoteType,
payback_tag: NoteTag,
payback_attachment: NoteAttachment,
requested_asset: Asset,
payback_recipient_digest: Word,
}

impl SwapNoteStorage {
// CONSTANTS
// --------------------------------------------------------------------------------------------

/// Expected number of storage items of the SWAP note.
pub const NUM_ITEMS: usize = 20;

// CONSTRUCTORS
// --------------------------------------------------------------------------------------------

/// Creates new SWAP note storage with the specified parameters.
pub fn new(
sender: AccountId,
requested_asset: Asset,
payback_note_type: NoteType,
payback_attachment: NoteAttachment,
payback_serial_number: Word,
) -> Self {
let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_number);
let payback_tag = NoteTag::with_account_target(sender);

Self::from_parts(
payback_note_type,
payback_tag,
payback_attachment,
requested_asset,
payback_recipient.digest(),
)
}

/// Creates a [`SwapNoteStorage`] from raw parts.
pub fn from_parts(
payback_note_type: NoteType,
payback_tag: NoteTag,
payback_attachment: NoteAttachment,
requested_asset: Asset,
payback_recipient_digest: Word,
) -> Self {
Self {
payback_note_type,
payback_tag,
payback_attachment,
requested_asset,
payback_recipient_digest,
}
}

/// Returns the payback note type.
pub fn payback_note_type(&self) -> NoteType {
self.payback_note_type
}

/// Returns the payback note tag.
pub fn payback_tag(&self) -> NoteTag {
self.payback_tag
}

/// Returns the payback note attachment.
pub fn payback_attachment(&self) -> &NoteAttachment {
&self.payback_attachment
}

/// Returns the requested asset.
pub fn requested_asset(&self) -> Asset {
self.requested_asset
}

/// Returns the payback recipient digest.
pub fn payback_recipient_digest(&self) -> Word {
self.payback_recipient_digest
}

/// Consumes the storage and returns a SWAP [`NoteRecipient`] with the provided serial number.
///
/// Notes created with this recipient will be SWAP notes whose storage encodes the payback
/// configuration and the requested asset stored in this [`SwapNoteStorage`].
pub fn into_recipient(self, serial_num: Word) -> NoteRecipient {
NoteRecipient::new(serial_num, SwapNote::script(), NoteStorage::from(self))
}
}

impl From<SwapNoteStorage> for NoteStorage {
fn from(storage: SwapNoteStorage) -> Self {
let attachment_scheme = Felt::from(storage.payback_attachment.attachment_scheme().as_u32());
let attachment_kind = Felt::from(storage.payback_attachment.attachment_kind().as_u8());
let attachment = storage.payback_attachment.content().to_word();

let mut storage_values = Vec::with_capacity(SwapNoteStorage::NUM_ITEMS);
storage_values.extend_from_slice(&[
storage.payback_note_type.into(),
storage.payback_tag.into(),
attachment_scheme,
attachment_kind,
]);
storage_values.extend_from_slice(attachment.as_elements());
storage_values.extend_from_slice(&storage.requested_asset.as_elements());
storage_values.extend_from_slice(storage.payback_recipient_digest.as_elements());

NoteStorage::new(storage_values)
.expect("number of storage items should not exceed max storage items")
}
}

// NOTE: TryFrom<&[Felt]> for SwapNoteStorage is not implemented because
// array attachment content cannot be reconstructed from storage alone. See https://github.com/0xMiden/protocol/issues/2555

// TESTS
// ================================================================================================

#[cfg(test)]
mod tests {
use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType};
use miden_protocol::Felt;
use miden_protocol::account::{AccountIdVersion, AccountStorageMode, AccountType};
use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
use miden_protocol::{self};
use miden_protocol::note::{NoteAttachment, NoteStorage, NoteTag, NoteType};
use miden_protocol::testing::account_id::{
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
};

use super::*;

fn fungible_faucet() -> AccountId {
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap()
}

fn non_fungible_faucet() -> AccountId {
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap()
}

fn fungible_asset() -> Asset {
Asset::Fungible(FungibleAsset::new(fungible_faucet(), 1000).unwrap())
}

fn non_fungible_asset() -> Asset {
let details =
NonFungibleAssetDetails::new(non_fungible_faucet(), vec![0xaa, 0xbb]).unwrap();
Asset::NonFungible(NonFungibleAsset::new(&details).unwrap())
}

#[test]
fn swap_note_storage() {
let payback_note_type = NoteType::Private;
let payback_tag = NoteTag::new(0x12345678);
let payback_attachment = NoteAttachment::default();
let requested_asset = fungible_asset();
let payback_recipient_digest =
Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);

let storage = SwapNoteStorage::from_parts(
payback_note_type,
payback_tag,
payback_attachment.clone(),
requested_asset,
payback_recipient_digest,
);

assert_eq!(storage.payback_note_type(), payback_note_type);
assert_eq!(storage.payback_tag(), payback_tag);
assert_eq!(storage.payback_attachment(), &payback_attachment);
assert_eq!(storage.requested_asset(), requested_asset);
assert_eq!(storage.payback_recipient_digest(), payback_recipient_digest);

// Convert to NoteStorage
let note_storage = NoteStorage::from(storage);
assert_eq!(note_storage.num_items() as usize, SwapNoteStorage::NUM_ITEMS);
}

#[test]
fn swap_note_storage_with_non_fungible_asset() {
let payback_note_type = NoteType::Public;
let payback_tag = NoteTag::new(0xaabbccdd);
let payback_attachment = NoteAttachment::default();
let requested_asset = non_fungible_asset();
let payback_recipient_digest =
Word::new([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);

let storage = SwapNoteStorage::from_parts(
payback_note_type,
payback_tag,
payback_attachment,
requested_asset,
payback_recipient_digest,
);

assert_eq!(storage.payback_note_type(), payback_note_type);
assert_eq!(storage.requested_asset(), requested_asset);

let note_storage = NoteStorage::from(storage);
assert_eq!(note_storage.num_items() as usize, SwapNoteStorage::NUM_ITEMS);
}

#[test]
fn swap_tag() {
// Construct an ID that starts with 0xcdb1.
Expand Down
Loading