use miden_protocol::account::AccountId;
use miden_protocol::asset::FungibleAsset;
use miden_protocol::crypto::SequentialCommit;
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::PublicKey;
use miden_protocol::crypto::rand::FeltRng;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAttachment,
NoteAttachmentScheme,
NoteScript,
NoteScriptRoot,
NoteTag,
};
use miden_protocol::{Felt, WORD_SIZE, Word};
use miden_standards::note::{
MintNote,
MintNoteStorage,
NetworkAccountTarget,
NoteExecutionHint,
P2idNoteStorage,
StandardNoteAttachment,
};
use crate::xreserve::encoding::{
BYTES_PER_PACKED_FELT,
CircleDomain,
DepositIntent,
DepositIntentHeader,
DepositNonce,
MintIntent,
Signature,
};
pub const XUSDC_MINT_TRANSPORT_ATTACHMENT_SCHEME: u16 =
StandardNoteAttachment::UsdcxMint.attachment_scheme().as_u16();
pub const XUSDC_MINT_ATTESTATION_NUM_WORDS: usize = 9;
pub const XUSDC_MINT_TRANSPORT_PAYLOAD_WORD_OFF: usize = XUSDC_MINT_ATTESTATION_NUM_WORDS;
pub const XUSDC_MINT_TRANSPORT_HOOK_DATA_MAX_LEN: usize = (NoteAttachment::MAX_NUM_WORDS as usize
* WORD_SIZE
- XUSDC_MINT_ATTESTATION_NUM_WORDS * WORD_SIZE
- MintIntent::NUM_FELTS)
* BYTES_PER_PACKED_FELT;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DepositAttestation {
signature: Signature,
pubkey: PublicKey,
}
impl DepositAttestation {
pub fn new(signature: Signature, pubkey: PublicKey) -> Self {
Self { signature, pubkey }
}
pub fn signature(&self) -> Signature {
self.signature
}
pub fn pubkey(&self) -> &PublicKey {
&self.pubkey
}
}
pub struct XUsdcMintNoteStorage {
storage: MintNoteStorage,
}
impl XUsdcMintNoteStorage {
pub fn new(intent_header: &DepositIntentHeader, target: AccountId) -> Self {
let recipient_id = intent_header.remote_recipient();
let asset = FungibleAsset::new(target, intent_header.amount().as_u64())
.expect("asset amount should be valid");
let serial_num = intent_header.nonce().to_word();
let recipient = P2idNoteStorage::new(recipient_id).into_recipient(serial_num);
let tag = NoteTag::with_account_target(recipient_id);
let storage = MintNoteStorage::new_public(recipient, asset, tag)
.expect("p2id note storage should not exceed max number of storage items");
Self { storage }
}
pub fn as_mint_storage(&self) -> &MintNoteStorage {
&self.storage
}
pub fn into_mint_storage(self) -> MintNoteStorage {
self.storage
}
}
pub struct XUsdcDeposit {
intent: MintIntent,
attestation: DepositAttestation,
}
impl XUsdcDeposit {
pub fn new(intent: MintIntent, attestation: DepositAttestation) -> Self {
Self { intent, attestation }
}
pub fn intent(&self) -> &MintIntent {
&self.intent
}
pub fn attestation(&self) -> &DepositAttestation {
&self.attestation
}
}
impl From<&XUsdcDeposit> for NoteAttachment {
fn from(deposit: &XUsdcDeposit) -> Self {
let mut elements: Vec<Felt> = Vec::new();
elements.extend(deposit.attestation.pubkey().to_elements());
elements.extend(deposit.attestation.signature().to_elements());
elements.extend([Felt::ZERO; 3]);
debug_assert_eq!(elements.len(), XUSDC_MINT_TRANSPORT_PAYLOAD_WORD_OFF * 4);
elements.extend(deposit.intent.to_elements());
while !elements.len().is_multiple_of(Word::NUM_ELEMENTS) {
elements.push(Felt::ZERO);
}
let words: Vec<Word> = elements
.as_chunks::<{ Word::NUM_ELEMENTS }>()
.0
.iter()
.map(|chunk| Word::new(*chunk))
.collect();
NoteAttachment::with_words(
NoteAttachmentScheme::new(XUSDC_MINT_TRANSPORT_ATTACHMENT_SCHEME)
.expect("the transport scheme is neither reserved nor past the protocol maximum"),
words,
)
.expect("the hookData bound keeps the transport within the per-attachment word cap")
}
}
pub struct XUsdcMintNote {
sender: AccountId,
storage: XUsdcMintNoteStorage,
serial_number: Word,
deposit: XUsdcDeposit,
network_account_target: NetworkAccountTarget,
}
impl XUsdcMintNote {
pub fn script() -> NoteScript {
MintNote::script()
}
pub fn script_root() -> NoteScriptRoot {
MintNote::script_root()
}
pub fn deposit(&self) -> &XUsdcDeposit {
&self.deposit
}
pub fn nonce(&self) -> DepositNonce {
self.deposit.intent().nonce()
}
}
#[bon::bon]
impl XUsdcMintNote {
#[builder]
pub fn new(
sender: AccountId,
target: AccountId,
remote_domain: CircleDomain,
deposit_intent: DepositIntent,
attestation: DepositAttestation,
serial_number: Word,
) -> Result<Self, NoteError> {
let mint_intent = MintIntent::from_deposit_intent(&deposit_intent, target, remote_domain)
.map_err(|source| {
NoteError::other_with_source(
"deposit intent cannot be carried by the mint transport",
source,
)
})?;
let network_account_target = NetworkAccountTarget::new(target, NoteExecutionHint::Always)
.map_err(|err| {
NoteError::other_with_source("faucet id is not a public network account", err)
})?;
let storage = XUsdcMintNoteStorage::new(deposit_intent.header(), target);
Ok(Self {
sender,
storage,
serial_number,
deposit: XUsdcDeposit::new(mint_intent, attestation),
network_account_target,
})
}
}
impl<S: x_usdc_mint_note_builder::State> XUsdcMintNoteBuilder<S>
where
S::SerialNumber: x_usdc_mint_note_builder::IsUnset,
{
pub fn generate_serial_number(
self,
rng: &mut impl FeltRng,
) -> XUsdcMintNoteBuilder<x_usdc_mint_note_builder::SetSerialNumber<S>> {
self.serial_number(rng.draw_word())
}
}
impl From<XUsdcMintNote> for Note {
fn from(note: XUsdcMintNote) -> Self {
MintNote::builder()
.sender(note.sender)
.mint_storage(note.storage.into_mint_storage())
.serial_number(note.serial_number)
.attachment(¬e.deposit)
.attachment(note.network_account_target)
.build()
.expect("two attachments are within the protocol's per-note limit")
.into()
}
}