Skip to content
Closed
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
## v0.16.0 (TBD)

### 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`, `BurnNote`, and `MintNote` marker types and their `::create` factories with structs built via `bon` typestate builders (`SwapNote::builder()`, `BurnNote::builder()`, `MintNote::builder()`). `BurnNote` and `MintNote` convert into a `Note` via `Note::from`; a `SwapNote` yields the swap note and its payback `NoteDetails` via `SwapNote::into_notes`. The builders offer `.attachment()`/`.attachments()` and serial-number generation ([#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
204 changes: 163 additions & 41 deletions crates/miden-standards/src/note/burn.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use alloc::vec::Vec;

use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::assembly::Path;
use miden_protocol::asset::Asset;
Expand All @@ -6,6 +9,7 @@ use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteRecipient,
NoteScript,
Expand Down Expand Up @@ -36,8 +40,51 @@ static BURN_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
// BURN NOTE
// ================================================================================================

/// TODO: add docs
pub struct BurnNote;
/// A BURN note: instructs a fungible faucet to burn the asset carried by the note.
///
/// When consumed by the `faucet_id` faucet, the note's asset is destroyed via the faucet's
/// `fungible::receive_and_burn` procedure. BURN notes are always public so they can be executed by
/// the network.
///
/// Construct one with the [builder](BurnNote::builder); convert it into a protocol [`Note`]
/// infallibly via `Note::from`.
#[derive(Debug, Clone)]
pub struct BurnNote {
sender: AccountId,
faucet_id: AccountId,
serial_number: Word,
assets: NoteAssets,
attachments: NoteAttachments,
}

#[bon::bon]
impl BurnNote {
/// Builds a new [`BurnNote`] instructing `faucet_id` to burn `fungible_asset`.
///
/// # Errors
///
/// Returns an error if the asset or attachments exceed their protocol limits (see
/// [`NoteAssets::new`] and [`NoteAttachments::new`]).
#[builder]
pub fn new(
#[builder(field)] attachments: Vec<NoteAttachment>,
sender: AccountId,
faucet_id: AccountId,
#[builder(into)] fungible_asset: Asset,
serial_number: Word,
) -> Result<Self, NoteError> {
let assets = NoteAssets::new(vec![fungible_asset])?;
let attachments = NoteAttachments::new(attachments)?;

Ok(Self {
sender,
faucet_id,
serial_number,
assets,
attachments,
})
}
}

impl BurnNote {
// CONSTANTS
Expand All @@ -59,49 +106,124 @@ impl BurnNote {
BURN_SCRIPT.root()
}

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

/// Generates a BURN note - a note that instructs a faucet to burn a fungible asset.
///
/// This script enables the creation of a PUBLIC note that, when consumed by a fungible
/// faucet, will burn the fungible assets contained in the note. The compiled call targets
/// `fungible::receive_and_burn`.
///
/// BURN notes are always PUBLIC for network execution.
///
/// The passed-in `rng` is used to generate a serial number for the note. The note's tag
/// is automatically set to the faucet's account ID for proper routing.
///
/// # Parameters
/// - `sender`: The account ID of the note creator
/// - `faucet_id`: The account ID of the faucet that will burn the assets
/// - `fungible_asset`: The fungible asset to be burned
/// - `attachment`: The [`NoteAttachments`] of the BURN note
/// - `rng`: Random number generator for creating the serial number
///
/// # Errors
/// Returns an error if note creation fails.
pub fn create<R: FeltRng>(
sender: AccountId,
faucet_id: AccountId,
fungible_asset: Asset,
attachments: NoteAttachments,
rng: &mut R,
) -> Result<Note, NoteError> {
let note_script = Self::script();
let serial_num = rng.draw_word();
/// Returns the account ID of the faucet that will burn the assets.
pub fn faucet_id(&self) -> AccountId {
self.faucet_id
}

// BURN notes are always public
let note_type = NoteType::Public;
/// Returns the note's serial number.
pub fn serial_number(&self) -> Word {
self.serial_number
}

let inputs = NoteStorage::new(vec![])?;
let tag = NoteTag::with_account_target(faucet_id);
/// Returns the assets carried by the note (the assets to be burned).
pub fn assets(&self) -> &NoteAssets {
&self.assets
}

let metadata = PartialNoteMetadata::new(sender, note_type).with_tag(tag);
let assets = NoteAssets::new(vec![fungible_asset])?; // BURN notes contain the asset to burn
let recipient = NoteRecipient::new(serial_num, note_script, inputs);
/// Returns the attachments carried by the note.
pub fn attachments(&self) -> &NoteAttachments {
&self.attachments
}
}

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

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

/// Adds multiple attachments to the 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: burn_note_builder::State> BurnNoteBuilder<S>
where
S::SerialNumber: burn_note_builder::IsUnset,
{
/// Draws a serial number from `rng` and sets it on the builder.
pub fn generate_serial_number(
self,
rng: &mut impl FeltRng,
) -> BurnNoteBuilder<burn_note_builder::SetSerialNumber<S>> {
self.serial_number(rng.draw_word())
}
}

// CONVERSIONS
// ================================================================================================

impl From<BurnNote> for Note {
fn from(note: BurnNote) -> Self {
// BURN notes are always public for network execution and carry no storage.
let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
.with_tag(NoteTag::with_account_target(note.faucet_id));
let recipient = NoteRecipient::new(
note.serial_number,
BurnNote::script(),
NoteStorage::new(vec![]).expect("a BURN note has no storage items"),
);

Note::with_attachments(note.assets, metadata, recipient, note.attachments)
}
}

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

#[cfg(test)]
mod tests {
use miden_protocol::account::{AccountIdVersion, AccountType};
use miden_protocol::asset::FungibleAsset;
use miden_protocol::crypto::rand::RandomCoin;

use super::*;

fn sender() -> AccountId {
AccountId::dummy([1u8; 15], AccountIdVersion::Version1, AccountType::Private)
}

fn faucet() -> AccountId {
AccountId::dummy([2u8; 15], AccountIdVersion::Version1, AccountType::Public)
}

Ok(Note::with_attachments(assets, metadata, recipient, attachments))
/// The builder produces a public note, tagged for the faucet, carrying the asset to burn.
#[test]
fn builder_builds_public_burn_note() {
let mut rng = RandomCoin::new(Word::empty());
let asset = FungibleAsset::new(faucet(), 100).unwrap();

let burn_note = BurnNote::builder()
.sender(sender())
.faucet_id(faucet())
.fungible_asset(asset)
.generate_serial_number(&mut rng)
.build()
.unwrap();

assert_eq!(burn_note.sender(), sender());
assert_eq!(burn_note.faucet_id(), faucet());
assert_eq!(burn_note.assets().num_assets(), 1);
assert_ne!(burn_note.serial_number(), Word::empty());

let note = Note::from(burn_note);
assert_eq!(note.metadata().note_type(), NoteType::Public);
assert_eq!(note.metadata().tag(), NoteTag::with_account_target(faucet()));
assert_eq!(note.assets().num_assets(), 1);
}
}
Loading
Loading