Skip to content
Draft
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 @@ -4,6 +4,7 @@

### Changes
- [BREAKING] Replaced the `P2idNote` marker type and its `P2idNote::create` factory with a `P2idNote` struct built via a `bon` typestate builder (`P2idNote::builder()`). P2ID notes must now carry at least one asset; a `P2idNote` converts into a `Note` via `Note::from`, and the builder offers `.asset()`/`.assets()`, `.attachment()`/`.attachments()`, and `.generate_serial_number()` ([#2283](https://github.com/0xMiden/protocol/issues/2283)).
- [BREAKING] Replaced the `SwapNote` marker type and its `SwapNote::create` factory with a struct built via a `bon` typestate builder (`SwapNote::builder()`). `SwapNote::into_notes` returns the outgoing SWAP note together with its payback `NoteDetails`, and the builder offers `.attachment()`/`.attachments()` and `.generate_serial_numbers()` ([#2283](https://github.com/0xMiden/protocol/issues/2283)).
- Added a skeleton batch kernel ([#1122](https://github.com/0xMiden/protocol/issues/1122)) wired through `LocalBatchProver::prove` and attached to `ProvenBatch` as an `ExecutionProof`. It does not yet perform any verification.
- [BREAKING] Renamed `AccountStorageDelta` to `AccountStoragePatch` ([#3002](https://github.com/0xMiden/protocol/pull/3002)).
- [BREAKING] Replaced the per-tree account and nullifier backend traits with shared `SmtBackend` and `SmtBackendReader` traits, split into read-only and read-write capabilities, enabling read-only `LargeSmt`-backed tree views via `reader()` ([#2755](https://github.com/0xMiden/protocol/pull/2755), [#3009](https://github.com/0xMiden/protocol/pull/3009)).
Expand Down
19 changes: 9 additions & 10 deletions crates/miden-standards/src/account/interface/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use miden_protocol::account::auth::{self, PublicKeyCommitment};
use miden_protocol::asset::NonFungibleAsset;
use miden_protocol::crypto::rand::RandomCoin;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{NoteAttachments, NoteType};
use miden_protocol::note::NoteType;
use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE;

use crate::account::auth::{AuthMultisig, AuthMultisigConfig, AuthSingleSig, NoAuth};
Expand All @@ -21,15 +21,14 @@ fn test_required_asset_same_as_offered() {
let offered_asset = NonFungibleAsset::mock(&[1, 2, 3, 4]);
let requested_asset = NonFungibleAsset::mock(&[1, 2, 3, 4]);

let result = SwapNote::create(
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(),
offered_asset,
requested_asset,
NoteType::Public,
NoteAttachments::default(),
NoteType::Public,
&mut RandomCoin::new(Word::from([1, 2, 3, 4u32])),
);
let result = SwapNote::builder()
.sender(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap())
.offered_asset(offered_asset)
.requested_asset(requested_asset)
.swap_note_type(NoteType::Public)
.payback_note_type(NoteType::Public)
.generate_serial_numbers(&mut RandomCoin::new(Word::from([1, 2, 3, 4u32])))
.build();

assert_matches!(result, Err(NoteError::Other { error_msg, .. }) if error_msg == "requested asset same as offered asset".into());
}
Expand Down
223 changes: 184 additions & 39 deletions crates/miden-standards/src/note/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteDetails,
NoteRecipient,
Expand Down Expand Up @@ -41,8 +42,62 @@ static SWAP_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
// SWAP NOTE
// ================================================================================================

/// TODO: add docs
pub struct SwapNote;
/// A SWAP note: offers `offered_asset` in exchange for `requested_asset`.
///
/// Any account may consume the note: the consumer receives the `offered_asset` and, in turn,
/// creates a P2ID payback note that returns the `requested_asset` to the swap's `sender`.
///
/// Construct one with the [builder](SwapNote::builder), then call [`SwapNote::into_notes`] to
/// obtain the outgoing SWAP [`Note`] together with the [`NoteDetails`] of the payback note.
#[derive(Debug, Clone)]
pub struct SwapNote {
sender: AccountId,
offered_asset: Asset,
requested_asset: Asset,
swap_note_type: NoteType,
payback_note_type: NoteType,
serial_number: Word,
payback_serial_number: Word,
attachments: NoteAttachments,
}

#[bon::bon]
impl SwapNote {
/// Builds a new [`SwapNote`] offering `offered_asset` in exchange for `requested_asset`.
///
/// # Errors
///
/// Returns an error if the requested asset is the same as the offered asset, or if the
/// attachments exceed their protocol limit (see [`NoteAttachments::new`]).
#[builder]
pub fn new(
#[builder(field)] attachments: Vec<NoteAttachment>,
sender: AccountId,
#[builder(into)] offered_asset: Asset,
#[builder(into)] requested_asset: Asset,
#[builder(default)] swap_note_type: NoteType,
#[builder(default)] payback_note_type: NoteType,
serial_number: Word,
payback_serial_number: Word,
) -> Result<Self, NoteError> {
if requested_asset == offered_asset {
return Err(NoteError::other("requested asset same as offered asset"));
}

let attachments = NoteAttachments::new(attachments)?;

Ok(Self {
sender,
offered_asset,
requested_asset,
swap_note_type,
payback_note_type,
serial_number,
payback_serial_number,
attachments,
})
}
}

impl SwapNote {
// CONSTANTS
Expand All @@ -64,50 +119,72 @@ impl SwapNote {
SWAP_SCRIPT.root()
}

// BUILDERS
// --------------------------------------------------------------------------------------------
/// Returns the account ID of the note's sender.
pub fn sender(&self) -> AccountId {
self.sender
}

/// Generates a SWAP note - swap of assets between two accounts - and returns the note as well
/// as [`NoteDetails`] for the payback note.
///
/// This script enables a swap of 2 assets between the `sender` account and any other account
/// that is willing to consume the note. The consumer will receive the `offered_asset` and
/// will create a new P2ID note with `sender` as target, containing the `requested_asset`.
///
/// # Errors
/// Returns an error if deserialization or compilation of the `SWAP` script fails.
pub fn create<R: FeltRng>(
sender: AccountId,
offered_asset: Asset,
requested_asset: Asset,
swap_note_type: NoteType,
swap_note_attachments: NoteAttachments,
payback_note_type: NoteType,
rng: &mut R,
) -> Result<(Note, NoteDetails), NoteError> {
if requested_asset == offered_asset {
return Err(NoteError::other("requested asset same as offered asset"));
}
/// Returns the asset offered by the note.
pub fn offered_asset(&self) -> Asset {
self.offered_asset
}

let payback_serial_num = rng.draw_word();
/// Returns the asset requested in exchange for the offered asset.
pub fn requested_asset(&self) -> Asset {
self.requested_asset
}

let swap_storage =
SwapNoteStorage::new(sender, requested_asset, payback_note_type, payback_serial_num);
/// Returns the type of the outgoing SWAP note.
pub fn swap_note_type(&self) -> NoteType {
self.swap_note_type
}

let serial_num = rng.draw_word();
let recipient = swap_storage.into_recipient(serial_num);
/// Returns the type of the payback note created on consumption.
pub fn payback_note_type(&self) -> NoteType {
self.payback_note_type
}

/// Returns the serial number of the outgoing SWAP note.
pub fn serial_number(&self) -> Word {
self.serial_number
}

// build the tag for the SWAP use case
let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset);
/// Returns the serial number of the payback note.
pub fn payback_serial_number(&self) -> Word {
self.payback_serial_number
}

/// Returns the attachments carried by the SWAP note.
pub fn attachments(&self) -> &NoteAttachments {
&self.attachments
}

// INSTANCE METHODS
// --------------------------------------------------------------------------------------------

/// Consumes the note and returns the outgoing SWAP [`Note`] together with the [`NoteDetails`]
/// of the payback P2ID note that the consumer will create for the sender.
///
/// # Errors
///
/// Returns an error if constructing the note's assets fails (see [`NoteAssets::new`]).
pub fn into_notes(self) -> Result<(Note, NoteDetails), NoteError> {
let swap_storage = SwapNoteStorage::new(
self.sender,
self.requested_asset,
self.payback_note_type,
self.payback_serial_number,
);
let recipient = swap_storage.into_recipient(self.serial_number);

// build the outgoing note
let metadata = PartialNoteMetadata::new(sender, swap_note_type).with_tag(tag);
let assets = NoteAssets::new(vec![offered_asset])?;
let note = Note::with_attachments(assets, metadata, recipient, swap_note_attachments);
let tag = Self::build_tag(self.swap_note_type, &self.offered_asset, &self.requested_asset);
let metadata = PartialNoteMetadata::new(self.sender, self.swap_note_type).with_tag(tag);
let assets = NoteAssets::new(vec![self.offered_asset])?;
let note = Note::with_attachments(assets, metadata, recipient, self.attachments);

// 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_recipient =
P2idNoteStorage::new(self.sender).into_recipient(self.payback_serial_number);
let payback_assets = NoteAssets::new(vec![self.requested_asset])?;
let payback_note = NoteDetails::new(payback_assets, payback_recipient);

Ok((note, payback_note))
Expand Down Expand Up @@ -153,6 +230,44 @@ impl SwapNote {
}
}

// BUILDER EXTENSIONS
// ================================================================================================

impl<S: swap_note_builder::State> SwapNoteBuilder<S> {
/// Adds a single attachment to the SWAP note.
pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
self.attachments.push(attachment.into());
self
}

/// Adds multiple attachments to the SWAP note.
pub fn attachments(
mut self,
attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
) -> Self {
self.attachments.extend(attachments.into_iter().map(Into::into));
self
}
}

impl<S: swap_note_builder::State> SwapNoteBuilder<S>
where
S::SerialNumber: swap_note_builder::IsUnset,
S::PaybackSerialNumber: swap_note_builder::IsUnset,
{
/// Draws the SWAP and payback serial numbers from `rng` (payback first) and sets them.
pub fn generate_serial_numbers(
self,
rng: &mut impl FeltRng,
) -> SwapNoteBuilder<
swap_note_builder::SetSerialNumber<swap_note_builder::SetPaybackSerialNumber<S>>,
> {
let payback_serial_number = rng.draw_word();
let serial_number = rng.draw_word();
Comment on lines +265 to +266

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How both serial numbers are same???

Check it corereclty

self.payback_serial_number(payback_serial_number).serial_number(serial_number)
}
}

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

Expand Down Expand Up @@ -262,6 +377,7 @@ mod tests {

use miden_protocol::account::{AccountIdVersion, AccountType};
use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
use miden_protocol::crypto::rand::RandomCoin;
use miden_protocol::note::{NoteStorage, NoteTag, NoteType};
use miden_protocol::testing::account_id::{
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
Expand Down Expand Up @@ -389,4 +505,33 @@ mod tests {
"swap script root byte 1 should match with the highest bit set to zero"
);
}

#[test]
fn builder_produces_swap_and_payback_notes() {
// The builder produces a SWAP note whose accessors echo the inputs, and `into_notes`
// splits it into the public SWAP note and its payback note, each carrying one asset.
let sender = AccountId::dummy([7u8; 15], AccountIdVersion::Version1, AccountType::Private);
let offered_asset = fungible_asset();
let requested_asset = non_fungible_asset();
let mut rng = RandomCoin::new(Word::empty());

let swap = SwapNote::builder()
.sender(sender)
.offered_asset(offered_asset)
.requested_asset(requested_asset)
.swap_note_type(NoteType::Public)
.payback_note_type(NoteType::Private)
.generate_serial_numbers(&mut rng)
.build()
.unwrap();

assert_eq!(swap.sender(), sender);
assert_eq!(swap.offered_asset(), offered_asset);
assert_eq!(swap.requested_asset(), requested_asset);

let (note, payback_note) = swap.into_notes().unwrap();
assert_eq!(note.metadata().note_type(), NoteType::Public);
assert_eq!(note.assets().num_assets(), 1);
assert_eq!(payback_note.assets().num_assets(), 1);
}
}
18 changes: 9 additions & 9 deletions crates/miden-testing/src/mock_chain/chain_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -720,15 +720,15 @@ impl MockChainBuilder {
requested_asset: Asset,
payback_note_type: NoteType,
) -> anyhow::Result<(Note, NoteDetails)> {
let (swap_note, payback_note) = SwapNote::create(
sender,
offered_asset,
requested_asset,
NoteType::Public,
NoteAttachments::default(),
payback_note_type,
&mut self.rng,
)?;
let (swap_note, payback_note) = SwapNote::builder()
.sender(sender)
.offered_asset(offered_asset)
.requested_asset(requested_asset)
.swap_note_type(NoteType::Public)
.payback_note_type(payback_note_type)
.generate_serial_numbers(&mut self.rng)
.build()?
.into_notes()?;

self.add_output_note(RawOutputNote::Full(swap_note.clone()));

Expand Down
Loading