Skip to content

Commit c1de90f

Browse files
PoulavBhowmick03JackTheGit
authored andcommitted
feat: introduce SwapNoteStorage (0xMiden#2592)
1 parent b8367c3 commit c1de90f

3 files changed

Lines changed: 213 additions & 25 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
- Implemented the `on_before_asset_added_to_account` asset callback ([#2571](https://github.com/0xMiden/protocol/pull/2571)).
3737
- Implemented the `on_before_asset_added_to_note` asset callback ([#2595](https://github.com/0xMiden/protocol/pull/2595)).
3838
- 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)).
39+
- Added `SwapNoteStorage` for typed serialization/deserialization of SWAP note storage ([#2585](https://github.com/0xMiden/protocol/pull/2585)).
3940
- 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)).
4041
- Added `component_metadata()` to all account components to expose their metadata ([#2596](https://github.com/0xMiden/protocol/pull/2596)).
4142
- Added `Package` support in `MockChainBuilder` & `NoteScript` ([#2502](https://github.com/0xMiden/protocol/pull/2502)).

crates/miden-standards/src/note/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mod p2ide;
2727
pub use p2ide::{P2ideNote, P2ideNoteStorage};
2828

2929
mod swap;
30-
pub use swap::SwapNote;
30+
pub use swap::{SwapNote, SwapNoteStorage};
3131

3232
mod network_account_target;
3333
pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError};

crates/miden-standards/src/note/swap.rs

Lines changed: 211 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl SwapNote {
4848
// --------------------------------------------------------------------------------------------
4949

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

5353
// PUBLIC ACCESSORS
5454
// --------------------------------------------------------------------------------------------
@@ -89,42 +89,31 @@ impl SwapNote {
8989
return Err(NoteError::other("requested asset same as offered asset"));
9090
}
9191

92-
let note_script = Self::script();
93-
9492
let payback_serial_num = rng.draw_word();
95-
let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_num);
9693

97-
let payback_tag = NoteTag::with_account_target(sender);
98-
99-
let attachment_scheme = Felt::from(payback_note_attachment.attachment_scheme().as_u32());
100-
let attachment_kind = Felt::from(payback_note_attachment.attachment_kind().as_u8());
101-
let attachment = payback_note_attachment.content().to_word();
94+
let swap_storage = SwapNoteStorage::new(
95+
sender,
96+
requested_asset,
97+
payback_note_type,
98+
payback_note_attachment,
99+
payback_serial_num,
100+
);
102101

103-
let mut storage = Vec::with_capacity(SwapNote::NUM_STORAGE_ITEMS);
104-
storage.extend_from_slice(&[
105-
payback_note_type.into(),
106-
payback_tag.into(),
107-
attachment_scheme,
108-
attachment_kind,
109-
]);
110-
storage.extend_from_slice(attachment.as_elements());
111-
storage.extend_from_slice(&requested_asset.as_elements());
112-
storage.extend_from_slice(payback_recipient.digest().as_elements());
113-
let inputs = NoteStorage::new(storage)?;
102+
let serial_num = rng.draw_word();
103+
let recipient = swap_storage.into_recipient(serial_num);
114104

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

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

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

@@ -171,17 +160,215 @@ impl SwapNote {
171160
}
172161
}
173162

163+
// SWAP NOTE STORAGE
164+
// ================================================================================================
165+
166+
/// Canonical storage representation for a SWAP note.
167+
///
168+
/// Contains the payback note configuration and the requested asset that the
169+
/// swap creator wants to receive in exchange for the offered asset contained
170+
/// in the note's vault.
171+
#[derive(Debug, Clone, PartialEq, Eq)]
172+
pub struct SwapNoteStorage {
173+
payback_note_type: NoteType,
174+
payback_tag: NoteTag,
175+
payback_attachment: NoteAttachment,
176+
requested_asset: Asset,
177+
payback_recipient_digest: Word,
178+
}
179+
180+
impl SwapNoteStorage {
181+
// CONSTANTS
182+
// --------------------------------------------------------------------------------------------
183+
184+
/// Expected number of storage items of the SWAP note.
185+
pub const NUM_ITEMS: usize = 20;
186+
187+
// CONSTRUCTORS
188+
// --------------------------------------------------------------------------------------------
189+
190+
/// Creates new SWAP note storage with the specified parameters.
191+
pub fn new(
192+
sender: AccountId,
193+
requested_asset: Asset,
194+
payback_note_type: NoteType,
195+
payback_attachment: NoteAttachment,
196+
payback_serial_number: Word,
197+
) -> Self {
198+
let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_number);
199+
let payback_tag = NoteTag::with_account_target(sender);
200+
201+
Self::from_parts(
202+
payback_note_type,
203+
payback_tag,
204+
payback_attachment,
205+
requested_asset,
206+
payback_recipient.digest(),
207+
)
208+
}
209+
210+
/// Creates a [`SwapNoteStorage`] from raw parts.
211+
pub fn from_parts(
212+
payback_note_type: NoteType,
213+
payback_tag: NoteTag,
214+
payback_attachment: NoteAttachment,
215+
requested_asset: Asset,
216+
payback_recipient_digest: Word,
217+
) -> Self {
218+
Self {
219+
payback_note_type,
220+
payback_tag,
221+
payback_attachment,
222+
requested_asset,
223+
payback_recipient_digest,
224+
}
225+
}
226+
227+
/// Returns the payback note type.
228+
pub fn payback_note_type(&self) -> NoteType {
229+
self.payback_note_type
230+
}
231+
232+
/// Returns the payback note tag.
233+
pub fn payback_tag(&self) -> NoteTag {
234+
self.payback_tag
235+
}
236+
237+
/// Returns the payback note attachment.
238+
pub fn payback_attachment(&self) -> &NoteAttachment {
239+
&self.payback_attachment
240+
}
241+
242+
/// Returns the requested asset.
243+
pub fn requested_asset(&self) -> Asset {
244+
self.requested_asset
245+
}
246+
247+
/// Returns the payback recipient digest.
248+
pub fn payback_recipient_digest(&self) -> Word {
249+
self.payback_recipient_digest
250+
}
251+
252+
/// Consumes the storage and returns a SWAP [`NoteRecipient`] with the provided serial number.
253+
///
254+
/// Notes created with this recipient will be SWAP notes whose storage encodes the payback
255+
/// configuration and the requested asset stored in this [`SwapNoteStorage`].
256+
pub fn into_recipient(self, serial_num: Word) -> NoteRecipient {
257+
NoteRecipient::new(serial_num, SwapNote::script(), NoteStorage::from(self))
258+
}
259+
}
260+
261+
impl From<SwapNoteStorage> for NoteStorage {
262+
fn from(storage: SwapNoteStorage) -> Self {
263+
let attachment_scheme = Felt::from(storage.payback_attachment.attachment_scheme().as_u32());
264+
let attachment_kind = Felt::from(storage.payback_attachment.attachment_kind().as_u8());
265+
let attachment = storage.payback_attachment.content().to_word();
266+
267+
let mut storage_values = Vec::with_capacity(SwapNoteStorage::NUM_ITEMS);
268+
storage_values.extend_from_slice(&[
269+
storage.payback_note_type.into(),
270+
storage.payback_tag.into(),
271+
attachment_scheme,
272+
attachment_kind,
273+
]);
274+
storage_values.extend_from_slice(attachment.as_elements());
275+
storage_values.extend_from_slice(&storage.requested_asset.as_elements());
276+
storage_values.extend_from_slice(storage.payback_recipient_digest.as_elements());
277+
278+
NoteStorage::new(storage_values)
279+
.expect("number of storage items should not exceed max storage items")
280+
}
281+
}
282+
283+
// NOTE: TryFrom<&[Felt]> for SwapNoteStorage is not implemented because
284+
// array attachment content cannot be reconstructed from storage alone. See https://github.com/0xMiden/protocol/issues/2555
285+
174286
// TESTS
175287
// ================================================================================================
176288

177289
#[cfg(test)]
178290
mod tests {
179-
use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType};
291+
use miden_protocol::Felt;
292+
use miden_protocol::account::{AccountIdVersion, AccountStorageMode, AccountType};
180293
use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
181-
use miden_protocol::{self};
294+
use miden_protocol::note::{NoteAttachment, NoteStorage, NoteTag, NoteType};
295+
use miden_protocol::testing::account_id::{
296+
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
297+
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
298+
};
182299

183300
use super::*;
184301

302+
fn fungible_faucet() -> AccountId {
303+
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap()
304+
}
305+
306+
fn non_fungible_faucet() -> AccountId {
307+
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap()
308+
}
309+
310+
fn fungible_asset() -> Asset {
311+
Asset::Fungible(FungibleAsset::new(fungible_faucet(), 1000).unwrap())
312+
}
313+
314+
fn non_fungible_asset() -> Asset {
315+
let details =
316+
NonFungibleAssetDetails::new(non_fungible_faucet(), vec![0xaa, 0xbb]).unwrap();
317+
Asset::NonFungible(NonFungibleAsset::new(&details).unwrap())
318+
}
319+
320+
#[test]
321+
fn swap_note_storage() {
322+
let payback_note_type = NoteType::Private;
323+
let payback_tag = NoteTag::new(0x12345678);
324+
let payback_attachment = NoteAttachment::default();
325+
let requested_asset = fungible_asset();
326+
let payback_recipient_digest =
327+
Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
328+
329+
let storage = SwapNoteStorage::from_parts(
330+
payback_note_type,
331+
payback_tag,
332+
payback_attachment.clone(),
333+
requested_asset,
334+
payback_recipient_digest,
335+
);
336+
337+
assert_eq!(storage.payback_note_type(), payback_note_type);
338+
assert_eq!(storage.payback_tag(), payback_tag);
339+
assert_eq!(storage.payback_attachment(), &payback_attachment);
340+
assert_eq!(storage.requested_asset(), requested_asset);
341+
assert_eq!(storage.payback_recipient_digest(), payback_recipient_digest);
342+
343+
// Convert to NoteStorage
344+
let note_storage = NoteStorage::from(storage);
345+
assert_eq!(note_storage.num_items() as usize, SwapNoteStorage::NUM_ITEMS);
346+
}
347+
348+
#[test]
349+
fn swap_note_storage_with_non_fungible_asset() {
350+
let payback_note_type = NoteType::Public;
351+
let payback_tag = NoteTag::new(0xaabbccdd);
352+
let payback_attachment = NoteAttachment::default();
353+
let requested_asset = non_fungible_asset();
354+
let payback_recipient_digest =
355+
Word::new([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]);
356+
357+
let storage = SwapNoteStorage::from_parts(
358+
payback_note_type,
359+
payback_tag,
360+
payback_attachment,
361+
requested_asset,
362+
payback_recipient_digest,
363+
);
364+
365+
assert_eq!(storage.payback_note_type(), payback_note_type);
366+
assert_eq!(storage.requested_asset(), requested_asset);
367+
368+
let note_storage = NoteStorage::from(storage);
369+
assert_eq!(note_storage.num_items() as usize, SwapNoteStorage::NUM_ITEMS);
370+
}
371+
185372
#[test]
186373
fn swap_tag() {
187374
// Construct an ID that starts with 0xcdb1.

0 commit comments

Comments
 (0)