@@ -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) ]
178290mod 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