diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index 1ac1b1f1..2fddfb53 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -159,14 +159,14 @@ impl MintNft for SpendableLauncher { #[cfg(test)] mod tests { use chia_bls::{sign, Signature}; - use chia_protocol::SpendBundle; + use chia_protocol::{Coin, SpendBundle}; use chia_puzzles::DeriveSynthetic; use clvm_utils::CurriedProgram; use clvmr::Allocator; use crate::{ - testing::SECRET_KEY, Chainable, CreateDid, IntermediateLauncher, Launcher, - RequiredSignature, StandardDidSpend, StandardSpend, WalletSimulator, + testing::SECRET_KEY, Chainable, CreateCoinWithoutMemos, CreateDid, IntermediateLauncher, + Launcher, RequiredSignature, StandardDidSpend, StandardSpend, WalletSimulator, }; use super::*; @@ -247,4 +247,170 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn test_nonstandard_intermediate_mint() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(); + let pk = sk.public_key(); + + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); + + let parent = sim.generate_coin(puzzle_hash, 3).await.coin; + + let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk)?; + + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk)?; + + let intermediate_coin = Coin::new(did_info.coin.coin_id(), puzzle_hash, 0); + + let (create_launcher, launcher) = + Launcher::new(intermediate_coin.coin_id(), 1).create_from_intermediate(&mut ctx)?; + + let mint = StandardMint { + metadata: (), + royalty_puzzle_hash: puzzle_hash, + royalty_percentage: 100, + owner_puzzle_hash: puzzle_hash, + synthetic_key: pk, + did_id: did_info.launcher_id, + did_inner_puzzle_hash: did_info.did_inner_puzzle_hash, + }; + + let (mint_nft, _nft_info) = launcher.mint_standard_nft(&mut ctx, mint)?; + + StandardDidSpend::new() + .chain(mint_nft) + .condition(ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash, + amount: 0, + })?) + .recreate() + .finish(&mut ctx, pk, did_info)?; + + StandardSpend::new() + .chain(create_launcher) + .finish(&mut ctx, intermediate_coin, pk)?; + + let coin_spends = ctx.take_spends(); + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?; + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); + let ack = peer.send_transaction(spend_bundle).await?; + + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + Ok(()) + } + + #[tokio::test] + async fn test_nonstandard_intermediate_mint_recreated_did() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(); + let pk = sk.public_key(); + + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); + + let parent = sim.generate_coin(puzzle_hash, 3).await.coin; + + let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk)?; + + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk)?; + + let intermediate_coin = Coin::new(did_info.coin.coin_id(), puzzle_hash, 0); + + let (create_launcher, launcher) = + Launcher::new(intermediate_coin.coin_id(), 1).create_from_intermediate(&mut ctx)?; + + let mint = StandardMint { + metadata: (), + royalty_puzzle_hash: puzzle_hash, + royalty_percentage: 100, + owner_puzzle_hash: puzzle_hash, + synthetic_key: pk, + did_id: did_info.launcher_id, + did_inner_puzzle_hash: did_info.did_inner_puzzle_hash, + }; + + let (mint_nft, _nft_info) = launcher.mint_standard_nft(&mut ctx, mint)?; + + let did_info = StandardDidSpend::new() + .condition(ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash, + amount: 0, + })?) + .recreate() + .finish(&mut ctx, pk, did_info)?; + + StandardDidSpend::new() + .chain(mint_nft) + .recreate() + .finish(&mut ctx, pk, did_info)?; + + StandardSpend::new() + .chain(create_launcher) + .finish(&mut ctx, intermediate_coin, pk)?; + + let coin_spends = ctx.take_spends(); + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?; + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); + let ack = peer.send_transaction(spend_bundle).await?; + + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + Ok(()) + } }