Skip to content

PSwap Contract#2636

Open
VAIBHAVJINDAL3012 wants to merge 16 commits into0xMiden:nextfrom
inicio-labs:pswap-note
Open

PSwap Contract#2636
VAIBHAVJINDAL3012 wants to merge 16 commits into0xMiden:nextfrom
inicio-labs:pswap-note

Conversation

@VAIBHAVJINDAL3012
Copy link
Copy Markdown

  • Add PSWAP (partially-fillable swap) note to miden-standards — a new note script that enables partial
    fills, creator reclaim, and inflight cross-swaps for decentralized asset exchange

    • MASM script (pswap.masm) uses 18-item KEY+VALUE storage layout, proportional price calculation with
      1e5 precision factor, and an early-return optimization for full fills to avoid integer truncation
    • Rust library (PswapNote) provides note creation, output note construction (P2ID payback + optional
      remainder), storage parsing, and calculate_offered_for_requested convenience method
    • 12 integration tests covering full fill, private fill, partial fill, inflight cross-swap, creator
      reclaim, invalid input rejection, multiple fill amounts, non-exact ratios, fuzz cases, and chained
      partial fills

    Test plan

    • cargo build -p miden-standards — MASM compiles into standards library
    • cargo test -p miden-standards pswap — 4 unit tests (script loading, tag construction, output
      calculation, storage parsing)
    • cargo test -p miden-testing pswap — 12 integration tests (full/partial/private fills, cross-swaps,
      reclaim, fuzz, chained fills)

/// - Note can be partially or fully filled by consumers
/// - Unfilled portions create remainder notes
/// - Creator receives requested assets via P2ID notes
pub struct PswapNote;
Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter Mar 19, 2026

Choose a reason for hiding this comment

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

Since we're adding this as a completely new note, it would be great if we could immediately set it up with the structure we want to end up with, i.e. the one described in #2283.

So roughly:

pub struct PswapNoteStorage {
    requested_key: Word,
    requested_value: Word,
    swapp_tag: NoteTag,
    p2id_tag: NoteTag,
    swap_count: u64,
    creator_account_id: AccountId,
}

impl PswapNoteStorage {
    pub fn into_recipient(self, serial_num: Word) -> NoteRecipient {
        todo!()
    }
}

impl From<PswapNoteStorage> for NoteStorage {
    fn from(pswap_storage: PswapNoteStorage) -> Self {
        todo!()
    }
}

#[derive(Debug, Clone, bon::Builder)]
#[builder(finish_fn(vis = "", name = build_internal))]
pub struct PswapNote {
    sender: AccountId,

    storage: PswapNoteStorage,

    serial_number: Word,

    #[builder(default = NoteType::Private)]
    note_type: NoteType,

    #[builder(default)]
    assets: NoteAssets,

    #[builder(default)]
    attachment: NoteAttachment,
}

impl PswapNote {
    pub fn execute(
        &self,
        consumer_account_id: AccountId,
        input_amount: u64,
        inflight_amount: u64,
    ) -> Result<(Note, Option<PswapNote>), NoteError> {
        todo!("what create_output_notes does now")
    }
}

This would result in a more natural API, i.e. execute takes &self instead of create_output_notes taking an arbitrary &Note.

What do you think?

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.

Yes, agree to this.
I have made the change.

Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter left a comment

Choose a reason for hiding this comment

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

Looks good, thank you! I primarily looked at the Rust code for now and left a few comments, but on a high level:

  • I think we should use the builder-style way of creating PswapNote and PswapNoteStorage, since both have quite a few fields.
  • Some lints are failing (see CI or run make lint locally).
  • If this PR is ready for review, can we put it ouf of draft mode?
  • Nits: Would be nice to use attachment instead of aux, storage instead of inputs, sender instead of creator, etc.

Comment on lines +526 to +530
/// Builds a P2ID payback note that delivers the filled assets to the swap creator.
///
/// The note inherits its type (public/private) from this PSWAP note and derives a
/// deterministic serial number via `hmerge(swap_count + 1, serial_num)`.
pub fn build_p2id_payback_note(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: I'd change build_ to create_ for consistency with P2idNote::create, PswapNote::create, etc. Applies to build_remainder_pswap_note and build_tag as well. (Not for this PR, but we should eventually rename SwapNote::build_tag as well).

Comment on lines +316 to +323
pub fn create<R: FeltRng>(
creator_account_id: AccountId,
offered_asset: Asset,
requested_asset: Asset,
note_type: NoteType,
note_attachment: NoteAttachment,
rng: &mut R,
) -> Result<Note, NoteError> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As motivated in #2283, I think we should get rid of this method and steer users towards using the builder methods to create notes.

This requires adding a public build method which we could do like this:

// Use a custom build method for custom validation.
impl<S: IsComplete> PswapNoteBuilder<S> {
    pub fn build(self) -> Result<PswapNote, NoteError> {
        let note = self.build_internal();

        // TODO: Validate note if needed.
        // We should probably validate the number of assets here, for instance.

        Ok(note)
    }
}

Vaibhav Jindal and others added 14 commits March 25, 2026 09:14
…MS, remove create_ wrappers, add builder finish_fn

- Rename swapp_tag -> pswap_tag and SWAPp -> PSWAP throughout
- Rename NUM_ITEMS -> NUM_STORAGE_ITEMS for clarity
- Remove create_p2id_payback_note and create_remainder_note wrappers,
  make build_ functions public instead
- Compute p2id_tag inside build_p2id_payback_note from self.storage
- Add #[builder(finish_fn(vis = "", name = build_internal))] to PswapNote
…ctly

Replace all test helper wrappers with direct calls to library functions:
- create_pswap_note -> PswapNote::create()
- create_expected_pswap_p2id_note + create_expected_pswap_remainder_note -> pswap.execute()
- build_pswap_storage -> PswapNoteStorage::from_parts()
- Remove make_pswap_tag, make_note_assets, make_note_args, compute_p2id_tag_*
- Inline calculate_output_amount as PswapNote::calculate_output_amount()
- Replace storage layout list with markdown table
- Remove trivial "Returns the X" docs on simple getters
- Add # Errors sections where relevant
- Rewrite method docs to describe intent, not implementation
- Add one-line docs on From/TryFrom conversion impls
- Tighten PswapNote struct doc
- Rename RpoRandomCoin to RandomCoin (miden-crypto 0.23 rename)
- Store full ASSET_KEY instead of prefix/suffix to preserve callback
  metadata in faucet_id_suffix_and_metadata
- Replace create_fungible_asset calls with direct ASSET_KEY + manual
  ASSET_VALUE construction, avoiding the new enable_callbacks parameter
- Update hardcoded P2ID script root to match current P2idNote::script_root()
- Replace hardcoded P2ID script root with procref.p2id::main for
  compile-time resolution
- Default to full fill when both input and inflight amounts are zero
- Replace magic address 4000 with named P2ID_RECIPIENT_STORAGE constant
- Remove step numbering from comments, fix memory layout docs
- Rename P2ID_RECIPIENT_STORAGE to P2ID_RECIPIENT_SUFFIX/PREFIX
- Add METADATA_HEADER word layout docs with source reference
- Document attachment_scheme parameter in set_word_attachment call
- Add stack views after condition checks
…lper, simplify build_p2id_payback_note

Rename requested_key/requested_value/requested_amount to
requested_asset_key/requested_asset_value/requested_asset_amount for
clarity. Extract offered_asset_amount() helper on PswapNote to
deduplicate offered asset extraction. Simplify build_p2id_payback_note
to take fill_amount: u64 instead of aux_word: Word, constructing the
aux word internally.
- Use NoteTag::default() instead of NoteTag::new(0)
- Change swap_count from u64 to u16 with safe try_into conversion
- Rename p2id_tag to payback_note_tag, p2id_payback_note to payback_note
- Rename build_ prefix to create_ for consistency
- Rename aux_word to attachment_word
- Replace Felt::new with Felt::from where possible
- Rename inputs to note_storage in TryFrom impl
- Make create_payback_note and create_remainder_pswap_note private
- Add offered_faucet_id helper to replace unreachable!()
- Remove PswapNote::create, add public build() with validation on builder
- Add bon::Builder to PswapNoteStorage, remove new() and from_parts()
- Remove payback_note_tag field, compute from creator_account_id on the fly
- Fix clippy warnings (useless conversions, needless borrows)
- Update all unit and integration tests to use builder pattern
- Compare full faucet_id instead of just prefix in build validation
- Rename swap_note to pswap_note in integration tests for consistency
- Extract PswapNoteStorage builder into separate let bindings
- Replace manual current_swap_count counter with enumerate index
- Fix clippy useless_conversion and needless_borrow warnings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants