From a9847810e4306caf0510e243769ef55acd6afbdd Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 9 Jun 2024 19:58:45 -0400 Subject: [PATCH 1/4] Refactor part 1 --- Cargo.lock | 3 +- crates/chia-sdk-driver/Cargo.toml | 2 +- crates/chia-sdk-driver/src/conditions.rs | 170 ++++++ crates/chia-sdk-driver/src/lib.rs | 6 +- crates/chia-sdk-driver/src/puzzles.rs | 4 +- .../src/puzzles/cat/cat_spend.rs | 563 +++++++++--------- .../src/puzzles/cat/issue_cat.rs | 436 +++++++------- crates/chia-sdk-driver/src/puzzles/did.rs | 11 +- .../src/puzzles/did/create_did.rs | 20 +- .../src/puzzles/did/did_spend.rs | 140 +---- .../src/puzzles/nft/mint_nft.rs | 195 ++---- .../src/puzzles/nft/nft_spend.rs | 296 ++++----- .../singleton/intermediate_launcher.rs | 22 +- .../src/puzzles/singleton/launcher.rs | 66 +- .../src/puzzles/singleton/singleton_spend.rs | 18 +- .../puzzles/singleton/spendable_launcher.rs | 30 +- .../chia-sdk-driver/src/puzzles/standard.rs | 78 +-- crates/chia-sdk-driver/src/spend.rs | 22 + crates/chia-sdk-driver/src/spend_builder.rs | 259 -------- crates/chia-sdk-driver/src/spend_context.rs | 75 ++- crates/chia-sdk-offers/src/offer_builder.rs | 24 +- crates/chia-sdk-offers/src/settlement.rs | 8 +- crates/chia-sdk-parser/src/conditions.rs | 121 ---- crates/chia-sdk-parser/src/error.rs | 5 + crates/chia-sdk-parser/src/lib.rs | 2 - crates/chia-sdk-parser/src/puzzles/cat.rs | 307 +++++----- crates/chia-sdk-parser/src/puzzles/did.rs | 12 +- crates/chia-sdk-parser/src/puzzles/nft.rs | 17 +- crates/chia-sdk-signer/Cargo.toml | 1 - crates/chia-sdk-signer/src/error.rs | 6 +- .../chia-sdk-signer/src/required_signature.rs | 5 +- crates/chia-sdk-types/Cargo.toml | 2 + crates/chia-sdk-types/src/conditions.rs | 135 ++++- 33 files changed, 1361 insertions(+), 1700 deletions(-) create mode 100644 crates/chia-sdk-driver/src/conditions.rs create mode 100644 crates/chia-sdk-driver/src/spend.rs delete mode 100644 crates/chia-sdk-driver/src/spend_builder.rs delete mode 100644 crates/chia-sdk-parser/src/conditions.rs diff --git a/Cargo.lock b/Cargo.lock index 791a6824..13790fd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,7 +394,6 @@ dependencies = [ "chia-bls 0.9.0", "chia-protocol", "chia-puzzles", - "chia-sdk-parser", "chia-sdk-types", "clvm-traits", "clvmr", @@ -436,6 +435,7 @@ dependencies = [ name = "chia-sdk-types" version = "0.10.3" dependencies = [ + "anyhow", "chia-bls 0.9.0", "chia-protocol", "chia-puzzles", @@ -443,6 +443,7 @@ dependencies = [ "clvmr", "hex", "hex-literal", + "thiserror", ] [[package]] diff --git a/crates/chia-sdk-driver/Cargo.toml b/crates/chia-sdk-driver/Cargo.toml index a1b2f01d..e5bf2e59 100644 --- a/crates/chia-sdk-driver/Cargo.toml +++ b/crates/chia-sdk-driver/Cargo.toml @@ -23,9 +23,9 @@ clvm-utils = { workspace = true } clvmr = { workspace = true } thiserror = { workspace = true } chia-sdk-types = { workspace = true } -chia-sdk-test = { workspace = true } [dev-dependencies] +chia-sdk-test = { workspace = true } anyhow = { workspace = true } chia-consensus = { workspace = true } hex = { workspace = true } diff --git a/crates/chia-sdk-driver/src/conditions.rs b/crates/chia-sdk-driver/src/conditions.rs new file mode 100644 index 00000000..091a29dd --- /dev/null +++ b/crates/chia-sdk-driver/src/conditions.rs @@ -0,0 +1,170 @@ +use chia_protocol::{Bytes, Bytes32}; +use chia_sdk_types::conditions::{ + AssertBeforeHeightAbsolute, AssertBeforeHeightRelative, AssertBeforeSecondsAbsolute, + AssertBeforeSecondsRelative, AssertCoinAnnouncement, AssertHeightAbsolute, + AssertHeightRelative, AssertPuzzleAnnouncement, AssertSecondsAbsolute, AssertSecondsRelative, + Condition, CreateCoin, CreateCoinAnnouncement, CreatePuzzleAnnouncement, ReserveFee, +}; + +use clvm_traits::{ClvmEncoder, ToClvm, ToClvmError}; +use clvmr::{ + sha2::{Digest, Sha256}, + NodePtr, +}; + +#[derive(Debug, Default, Clone)] +#[must_use] +pub struct Conditions { + conditions: Vec, +} + +impl Conditions { + pub fn new() -> Self { + Self::default() + } + + pub fn condition(mut self, condition: Condition) -> Self { + self.conditions.push(condition); + self + } + + pub fn conditions(mut self, conditions: &[Condition]) -> Self { + self.conditions.extend_from_slice(conditions); + self + } + + pub fn extend(mut self, conditions: impl IntoIterator) -> Self { + self.conditions.extend(conditions); + self + } + + pub fn reserve_fee(self, fee: u64) -> Self { + self.condition(Condition::ReserveFee(ReserveFee::new(fee))) + } + + pub fn create_coin(self, puzzle_hash: Bytes32, amount: u64) -> Self { + self.condition(Condition::CreateCoin(CreateCoin::new(puzzle_hash, amount))) + } + + pub fn create_hinted_coin(self, puzzle_hash: Bytes32, amount: u64, hint: Bytes32) -> Self { + self.condition(Condition::CreateCoin(CreateCoin::with_hint( + puzzle_hash, + amount, + hint, + ))) + } + + pub fn create_coin_announcement(self, message: Bytes) -> Self { + self.condition(Condition::CreateCoinAnnouncement( + CreateCoinAnnouncement::new(message), + )) + } + + pub fn assert_raw_coin_announcement(self, announcement_id: Bytes32) -> Self { + self.condition(Condition::AssertCoinAnnouncement( + AssertCoinAnnouncement::new(announcement_id), + )) + } + + pub fn assert_coin_announcement(self, coin_id: Bytes32, message: impl AsRef<[u8]>) -> Self { + let mut announcement_id = Sha256::new(); + announcement_id.update(coin_id); + announcement_id.update(message); + self.assert_raw_coin_announcement(Bytes32::new(announcement_id.finalize().into())) + } + + pub fn create_puzzle_announcement(self, message: Bytes) -> Self { + self.condition(Condition::CreatePuzzleAnnouncement( + CreatePuzzleAnnouncement::new(message), + )) + } + + pub fn assert_raw_puzzle_announcement(self, announcement_id: Bytes32) -> Self { + self.condition(Condition::AssertPuzzleAnnouncement( + AssertPuzzleAnnouncement::new(announcement_id), + )) + } + + pub fn assert_puzzle_announcement( + self, + puzzle_hash: Bytes32, + message: impl AsRef<[u8]>, + ) -> Self { + let mut announcement_id = Sha256::new(); + announcement_id.update(puzzle_hash); + announcement_id.update(message); + self.assert_raw_puzzle_announcement(Bytes32::new(announcement_id.finalize().into())) + } + + pub fn assert_before_seconds_relative(self, seconds: u64) -> Self { + self.condition(Condition::AssertBeforeSecondsRelative( + AssertBeforeSecondsRelative::new(seconds), + )) + } + + pub fn assert_seconds_relative(self, seconds: u64) -> Self { + self.condition(Condition::AssertSecondsRelative( + AssertSecondsRelative::new(seconds), + )) + } + + pub fn assert_seconds_absolute(self, seconds: u64) -> Self { + self.condition(Condition::AssertSecondsAbsolute( + AssertSecondsAbsolute::new(seconds), + )) + } + + pub fn assert_before_seconds_absolute(self, seconds: u64) -> Self { + self.condition(Condition::AssertBeforeSecondsAbsolute( + AssertBeforeSecondsAbsolute::new(seconds), + )) + } + + pub fn assert_before_height_relative(self, height: u32) -> Self { + self.condition(Condition::AssertBeforeHeightRelative( + AssertBeforeHeightRelative::new(height), + )) + } + + pub fn assert_before_height_absolute(self, height: u32) -> Self { + self.condition(Condition::AssertBeforeHeightAbsolute( + AssertBeforeHeightAbsolute::new(height), + )) + } + + pub fn assert_height_relative(self, height: u32) -> Self { + self.condition(Condition::AssertHeightRelative(AssertHeightRelative::new( + height, + ))) + } + + pub fn assert_height_absolute(self, height: u32) -> Self { + self.condition(Condition::AssertHeightAbsolute(AssertHeightAbsolute::new( + height, + ))) + } +} + +impl AsRef<[Condition]> for Conditions { + fn as_ref(&self) -> &[Condition] { + &self.conditions + } +} + +impl IntoIterator for Conditions { + type Item = Condition; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.conditions.into_iter() + } +} + +impl ToClvm for Conditions { + fn to_clvm( + &self, + encoder: &mut impl ClvmEncoder, + ) -> Result { + self.conditions.to_clvm(encoder) + } +} diff --git a/crates/chia-sdk-driver/src/lib.rs b/crates/chia-sdk-driver/src/lib.rs index 023c9937..833cde9a 100644 --- a/crates/chia-sdk-driver/src/lib.rs +++ b/crates/chia-sdk-driver/src/lib.rs @@ -1,9 +1,11 @@ +mod conditions; mod puzzles; -mod spend_builder; +mod spend; mod spend_context; mod spend_error; +pub use conditions::*; pub use puzzles::*; -pub use spend_builder::*; +pub use spend::*; pub use spend_context::*; pub use spend_error::*; diff --git a/crates/chia-sdk-driver/src/puzzles.rs b/crates/chia-sdk-driver/src/puzzles.rs index 9ca73871..0d0d612c 100644 --- a/crates/chia-sdk-driver/src/puzzles.rs +++ b/crates/chia-sdk-driver/src/puzzles.rs @@ -1,10 +1,10 @@ -mod cat; +// mod cat; mod did; mod nft; mod singleton; mod standard; -pub use cat::*; +// pub use cat::*; pub use did::*; pub use nft::*; pub use singleton::*; diff --git a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs index 1d80f397..b4df5a52 100644 --- a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs +++ b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs @@ -1,283 +1,280 @@ -use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_puzzles::{ - cat::{CatArgs, CatSolution, CoinProof, CAT_PUZZLE_HASH}, - LineageProof, -}; -use chia_sdk_types::conditions::CreateCoin; -use clvm_utils::CurriedProgram; -use clvmr::NodePtr; - -use crate::{spend_builder::InnerSpend, SpendContext, SpendError}; - -#[derive(Debug, Default)] -#[must_use] -pub struct CatSpend { - asset_id: Bytes32, - cat_spends: Vec, -} - -#[derive(Debug)] -struct CatSpendItem { - coin: Coin, - inner_spend: InnerSpend, - lineage_proof: LineageProof, - extra_delta: i64, -} - -impl CatSpend { - pub const fn new(asset_id: Bytes32) -> Self { - Self { - asset_id, - cat_spends: Vec::new(), - } - } - - pub fn spend( - mut self, - coin: Coin, - inner_spend: InnerSpend, - lineage_proof: LineageProof, - extra_delta: i64, - ) -> Self { - self.cat_spends.push(CatSpendItem { - coin, - inner_spend, - lineage_proof, - extra_delta, - }); - self - } - - pub fn finish(self, ctx: &mut SpendContext<'_>) -> Result<(), SpendError> { - let cat_puzzle_ptr = ctx.cat_puzzle()?; - let len = self.cat_spends.len(); - - let mut total_delta = 0; - - for (index, item) in self.cat_spends.iter().enumerate() { - let CatSpendItem { - coin, - inner_spend, - lineage_proof, - extra_delta, - } = item; - - // Calculate the delta and add it to the subtotal. - let output = ctx.run(inner_spend.puzzle(), inner_spend.solution())?; - let conditions: Vec = ctx.extract(output)?; - - let create_coins = conditions - .into_iter() - .filter_map(|ptr| ctx.extract::(ptr).ok()); - - let delta = create_coins.fold( - i128::from(coin.amount) - i128::from(*extra_delta), - |delta, create_coin| delta - i128::from(create_coin.amount), - ); - - let prev_subtotal = total_delta; - total_delta += delta; - - // Find information of neighboring coins on the ring. - let prev_cat = &self.cat_spends[if index == 0 { len - 1 } else { index - 1 }]; - let next_cat = &self.cat_spends[if index == len - 1 { 0 } else { index + 1 }]; - - let puzzle_reveal = ctx.serialize(&CurriedProgram { - program: cat_puzzle_ptr, - args: CatArgs { - mod_hash: CAT_PUZZLE_HASH.into(), - asset_id: self.asset_id, - inner_puzzle: inner_spend.puzzle(), - }, - })?; - - let solution = ctx.serialize(&CatSolution { - inner_puzzle_solution: inner_spend.solution(), - lineage_proof: Some(*lineage_proof), - prev_coin_id: prev_cat.coin.coin_id(), - this_coin_info: *coin, - next_coin_proof: CoinProof { - parent_coin_info: next_cat.coin.parent_coin_info, - inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()).into(), - amount: next_cat.coin.amount, - }, - prev_subtotal: prev_subtotal.try_into()?, - extra_delta: *extra_delta, - })?; - - ctx.spend(CoinSpend::new(*coin, puzzle_reveal, solution)); - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use chia_puzzles::standard::StandardArgs; - use chia_sdk_test::{test_transaction, Simulator}; - use clvmr::Allocator; - - use crate::{ - puzzles::{IssueCat, StandardSpend}, - spend_builder::P2Spend, - }; - - use super::*; - - #[tokio::test] - async fn test_cat_spend_multi() -> anyhow::Result<()> { - let sim = Simulator::new().await?; - let peer = sim.connect().await?; - - let sk = sim.secret_key().await?; - let pk = sk.public_key(); - - let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); - let coin = sim.mint_coin(puzzle_hash, 6).await; - - let mut allocator = Allocator::new(); - let ctx = &mut SpendContext::new(&mut allocator); - - let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) - .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? - .create_hinted_coin(ctx, puzzle_hash, 2, puzzle_hash)? - .create_hinted_coin(ctx, puzzle_hash, 3, puzzle_hash)? - .single_issuance(ctx, 6)?; - - StandardSpend::new() - .chain(issue_cat) - .finish(ctx, coin, pk)?; - - let cat_puzzle_hash = - CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); - - CatSpend::new(issuance.asset_id) - .spend( - Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1), - StandardSpend::new() - .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? - .inner_spend(ctx, pk)?, - issuance.lineage_proof, - 0, - ) - .spend( - Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 2), - StandardSpend::new() - .create_hinted_coin(ctx, puzzle_hash, 2, puzzle_hash)? - .inner_spend(ctx, pk)?, - issuance.lineage_proof, - 0, - ) - .spend( - Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 3), - StandardSpend::new() - .create_hinted_coin(ctx, puzzle_hash, 3, puzzle_hash)? - .inner_spend(ctx, pk)?, - issuance.lineage_proof, - 0, - ) - .finish(ctx)?; - - test_transaction( - &peer, - ctx.take_spends(), - &[sk], - sim.config().genesis_challenge, - ) - .await; - - Ok(()) - } - - #[tokio::test] - async fn test_cat_spend() -> anyhow::Result<()> { - let sim = Simulator::new().await?; - let peer = sim.connect().await?; - - let sk = sim.secret_key().await?; - let pk = sk.public_key(); - - let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); - let coin = sim.mint_coin(puzzle_hash, 1).await; - - let mut allocator = Allocator::new(); - let ctx = &mut SpendContext::new(&mut allocator); - - let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) - .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? - .single_issuance(ctx, 1)?; - - StandardSpend::new() - .chain(issue_cat) - .finish(ctx, coin, pk)?; - - let inner_spend = StandardSpend::new() - .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? - .inner_spend(ctx, pk)?; - - let cat_puzzle_hash = - CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); - let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1); - - CatSpend::new(issuance.asset_id) - .spend(cat_coin, inner_spend, issuance.lineage_proof, 0) - .finish(ctx)?; - - test_transaction( - &peer, - ctx.take_spends(), - &[sk], - sim.config().genesis_challenge, - ) - .await; - - Ok(()) - } - - #[tokio::test] - async fn test_cat_melt() -> anyhow::Result<()> { - let sim = Simulator::new().await?; - let peer = sim.connect().await?; - - let sk = sim.secret_key().await?; - let pk = sk.public_key(); - - let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); - let coin = sim.mint_coin(puzzle_hash, 10000).await; - - let mut allocator = Allocator::new(); - let ctx = &mut SpendContext::new(&mut allocator); - - let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) - .create_hinted_coin(ctx, puzzle_hash, 10000, puzzle_hash)? - .multi_issuance(ctx, pk, 10000)?; - - StandardSpend::new() - .chain(issue_cat) - .finish(ctx, coin, pk)?; - - let inner_spend = StandardSpend::new() - .create_hinted_coin(ctx, puzzle_hash, 7000, puzzle_hash)? - .run_multi_issuance_tail(ctx, pk)? - .inner_spend(ctx, pk)?; - - let cat_puzzle_hash = - CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); - let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 10000); - - CatSpend::new(issuance.asset_id) - .spend(cat_coin, inner_spend, issuance.lineage_proof, -3000) - .finish(ctx)?; - - test_transaction( - &peer, - ctx.take_spends(), - &[sk], - sim.config().genesis_challenge, - ) - .await; - - Ok(()) - } -} +// use chia_protocol::{Bytes32, Coin, CoinSpend}; +// use chia_puzzles::{ +// cat::{CatArgs, CatSolution, CoinProof, CAT_PUZZLE_HASH}, +// LineageProof, +// }; +// use chia_sdk_types::conditions::CreateCoin; +// use clvm_utils::CurriedProgram; +// use clvmr::NodePtr; + +// use crate::{Spend, SpendContext, SpendError}; + +// #[derive(Debug, Default)] +// #[must_use] +// pub struct CatSpend { +// asset_id: Bytes32, +// cat_spends: Vec, +// } + +// #[derive(Debug)] +// struct CatSpendItem { +// coin: Coin, +// inner_spend: Spend, +// lineage_proof: LineageProof, +// extra_delta: i64, +// } + +// impl CatSpend { +// pub const fn new(asset_id: Bytes32) -> Self { +// Self { +// asset_id, +// cat_spends: Vec::new(), +// } +// } + +// pub fn spend( +// mut self, +// coin: Coin, +// inner_spend: Spend, +// lineage_proof: LineageProof, +// extra_delta: i64, +// ) -> Self { +// self.cat_spends.push(CatSpendItem { +// coin, +// inner_spend, +// lineage_proof, +// extra_delta, +// }); +// self +// } + +// pub fn finish(self, ctx: &mut SpendContext<'_>) -> Result<(), SpendError> { +// let cat_puzzle_ptr = ctx.cat_puzzle()?; +// let len = self.cat_spends.len(); + +// let mut total_delta = 0; + +// for (index, item) in self.cat_spends.iter().enumerate() { +// let CatSpendItem { +// coin, +// inner_spend, +// lineage_proof, +// extra_delta, +// } = item; + +// // Calculate the delta and add it to the subtotal. +// let output = ctx.run(inner_spend.puzzle(), inner_spend.solution())?; +// let conditions: Vec = ctx.extract(output)?; + +// let create_coins = conditions +// .into_iter() +// .filter_map(|ptr| ctx.extract::(ptr).ok()); + +// let delta = create_coins.fold( +// i128::from(coin.amount) - i128::from(*extra_delta), +// |delta, create_coin| delta - i128::from(create_coin.amount), +// ); + +// let prev_subtotal = total_delta; +// total_delta += delta; + +// // Find information of neighboring coins on the ring. +// let prev_cat = &self.cat_spends[if index == 0 { len - 1 } else { index - 1 }]; +// let next_cat = &self.cat_spends[if index == len - 1 { 0 } else { index + 1 }]; + +// let puzzle_reveal = ctx.serialize(&CurriedProgram { +// program: cat_puzzle_ptr, +// args: CatArgs { +// mod_hash: CAT_PUZZLE_HASH.into(), +// asset_id: self.asset_id, +// inner_puzzle: inner_spend.puzzle(), +// }, +// })?; + +// let solution = ctx.serialize(&CatSolution { +// inner_puzzle_solution: inner_spend.solution(), +// lineage_proof: Some(*lineage_proof), +// prev_coin_id: prev_cat.coin.coin_id(), +// this_coin_info: *coin, +// next_coin_proof: CoinProof { +// parent_coin_info: next_cat.coin.parent_coin_info, +// inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()).into(), +// amount: next_cat.coin.amount, +// }, +// prev_subtotal: prev_subtotal.try_into()?, +// extra_delta: *extra_delta, +// })?; + +// ctx.spend(CoinSpend::new(*coin, puzzle_reveal, solution)); +// } + +// Ok(()) +// } +// } + +// #[cfg(test)] +// mod tests { +// use chia_puzzles::standard::StandardArgs; +// use chia_sdk_test::{test_transaction, Simulator}; +// use clvmr::Allocator; + +// use crate::puzzles::{IssueCat, StandardSpend}; + +// use super::*; + +// #[tokio::test] +// async fn test_cat_spend_multi() -> anyhow::Result<()> { +// let sim = Simulator::new().await?; +// let peer = sim.connect().await?; + +// let sk = sim.secret_key().await?; +// let pk = sk.public_key(); + +// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); +// let coin = sim.mint_coin(puzzle_hash, 6).await; + +// let mut allocator = Allocator::new(); +// let ctx = &mut SpendContext::new(&mut allocator); + +// let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) +// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? +// .create_hinted_coin(ctx, puzzle_hash, 2, puzzle_hash)? +// .create_hinted_coin(ctx, puzzle_hash, 3, puzzle_hash)? +// .single_issuance(ctx, 6)?; + +// StandardSpend::new() +// .chain(issue_cat) +// .finish(ctx, coin, pk)?; + +// let cat_puzzle_hash = +// CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); + +// CatSpend::new(issuance.asset_id) +// .spend( +// Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1), +// StandardSpend::new() +// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? +// .inner_spend(ctx, pk)?, +// issuance.lineage_proof, +// 0, +// ) +// .spend( +// Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 2), +// StandardSpend::new() +// .create_hinted_coin(ctx, puzzle_hash, 2, puzzle_hash)? +// .inner_spend(ctx, pk)?, +// issuance.lineage_proof, +// 0, +// ) +// .spend( +// Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 3), +// StandardSpend::new() +// .create_hinted_coin(ctx, puzzle_hash, 3, puzzle_hash)? +// .inner_spend(ctx, pk)?, +// issuance.lineage_proof, +// 0, +// ) +// .finish(ctx)?; + +// test_transaction( +// &peer, +// ctx.take_spends(), +// &[sk], +// sim.config().genesis_challenge, +// ) +// .await; + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_cat_spend() -> anyhow::Result<()> { +// let sim = Simulator::new().await?; +// let peer = sim.connect().await?; + +// let sk = sim.secret_key().await?; +// let pk = sk.public_key(); + +// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); +// let coin = sim.mint_coin(puzzle_hash, 1).await; + +// let mut allocator = Allocator::new(); +// let ctx = &mut SpendContext::new(&mut allocator); + +// let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) +// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? +// .single_issuance(ctx, 1)?; + +// StandardSpend::new() +// .chain(issue_cat) +// .finish(ctx, coin, pk)?; + +// let inner_spend = StandardSpend::new() +// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? +// .inner_spend(ctx, pk)?; + +// let cat_puzzle_hash = +// CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); +// let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1); + +// CatSpend::new(issuance.asset_id) +// .spend(cat_coin, inner_spend, issuance.lineage_proof, 0) +// .finish(ctx)?; + +// test_transaction( +// &peer, +// ctx.take_spends(), +// &[sk], +// sim.config().genesis_challenge, +// ) +// .await; + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_cat_melt() -> anyhow::Result<()> { +// let sim = Simulator::new().await?; +// let peer = sim.connect().await?; + +// let sk = sim.secret_key().await?; +// let pk = sk.public_key(); + +// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); +// let coin = sim.mint_coin(puzzle_hash, 10000).await; + +// let mut allocator = Allocator::new(); +// let ctx = &mut SpendContext::new(&mut allocator); + +// let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) +// .create_hinted_coin(ctx, puzzle_hash, 10000, puzzle_hash)? +// .multi_issuance(ctx, pk, 10000)?; + +// StandardSpend::new() +// .chain(issue_cat) +// .finish(ctx, coin, pk)?; + +// let inner_spend = StandardSpend::new() +// .create_hinted_coin(ctx, puzzle_hash, 7000, puzzle_hash)? +// .run_multi_issuance_tail(ctx, pk)? +// .inner_spend(ctx, pk)?; + +// let cat_puzzle_hash = +// CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); +// let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 10000); + +// CatSpend::new(issuance.asset_id) +// .spend(cat_coin, inner_spend, issuance.lineage_proof, -3000) +// .finish(ctx)?; + +// test_transaction( +// &peer, +// ctx.take_spends(), +// &[sk], +// sim.config().genesis_challenge, +// ) +// .await; + +// Ok(()) +// } +// } diff --git a/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs b/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs index ef196bfc..32845b0d 100644 --- a/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs +++ b/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs @@ -1,218 +1,218 @@ -use chia_bls::PublicKey; -use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_puzzles::{ - cat::{ - CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, GenesisByCoinIdTailArgs, - CAT_PUZZLE_HASH, - }, - LineageProof, -}; -use chia_sdk_types::conditions::RunTail; -use clvm_traits::clvm_quote; -use clvm_utils::CurriedProgram; -use clvmr::NodePtr; - -use crate::{ - spend_builder::{P2Spend, SpendConditions}, - SpendContext, SpendError, -}; - -#[derive(Debug)] -#[must_use] -pub struct IssueCat { - parent_coin_id: Bytes32, - conditions: Vec, -} - -#[derive(Debug, Clone, Copy)] -pub struct CatIssuanceInfo { - pub asset_id: Bytes32, - pub lineage_proof: LineageProof, - pub eve_coin: Coin, -} - -impl IssueCat { - pub const fn new(parent_coin_id: Bytes32) -> Self { - Self { - parent_coin_id, - conditions: Vec::new(), - } - } - - pub fn single_issuance( - self, - ctx: &mut SpendContext<'_>, - amount: u64, - ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { - let tail_puzzle_ptr = ctx.genesis_by_coin_id_tail_puzzle()?; - - let tail = ctx.alloc(&CurriedProgram { - program: tail_puzzle_ptr, - args: GenesisByCoinIdTailArgs { - genesis_coin_id: self.parent_coin_id, - }, - })?; - let asset_id = ctx.tree_hash(tail).into(); - - self.raw_condition(ctx.alloc(&RunTail::new(tail, ()))?) - .finish_raw(ctx, asset_id, amount) - } - - pub fn multi_issuance( - self, - ctx: &mut SpendContext<'_>, - public_key: PublicKey, - amount: u64, - ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { - let tail_puzzle_ptr = ctx.everything_with_signature_tail_puzzle()?; - - let tail = ctx.alloc(&CurriedProgram { - program: tail_puzzle_ptr, - args: EverythingWithSignatureTailArgs { public_key }, - })?; - let asset_id = ctx.tree_hash(tail).into(); - - self.raw_condition(ctx.alloc(&RunTail::new(tail, ()))?) - .finish_raw(ctx, asset_id, amount) - } - - pub fn finish_raw( - self, - ctx: &mut SpendContext<'_>, - asset_id: Bytes32, - amount: u64, - ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { - let cat_puzzle_ptr = ctx.cat_puzzle()?; - - let inner_puzzle = ctx.alloc(&clvm_quote!(self.conditions))?; - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); - - let puzzle = ctx.alloc(&CurriedProgram { - program: cat_puzzle_ptr, - args: CatArgs { - mod_hash: CAT_PUZZLE_HASH.into(), - asset_id, - inner_puzzle, - }, - })?; - - let puzzle_hash = ctx.tree_hash(puzzle).into(); - let eve_coin = Coin::new(self.parent_coin_id, puzzle_hash, amount); - - let solution = ctx.serialize(&CatSolution { - inner_puzzle_solution: (), - lineage_proof: None, - prev_coin_id: eve_coin.coin_id(), - this_coin_info: eve_coin, - next_coin_proof: CoinProof { - parent_coin_info: self.parent_coin_id, - inner_puzzle_hash, - amount, - }, - prev_subtotal: 0, - extra_delta: 0, - })?; - - let puzzle_reveal = ctx.serialize(&puzzle)?; - ctx.spend(CoinSpend::new(eve_coin, puzzle_reveal, solution)); - - let chained_spend = - SpendConditions::new().create_hinted_coin(ctx, puzzle_hash, amount, puzzle_hash)?; - - let issuance_info = CatIssuanceInfo { - asset_id, - lineage_proof: LineageProof { - parent_parent_coin_id: eve_coin.parent_coin_info, - parent_inner_puzzle_hash: inner_puzzle_hash, - parent_amount: eve_coin.amount, - }, - eve_coin, - }; - - Ok((chained_spend, issuance_info)) - } -} - -impl P2Spend for IssueCat { - fn raw_condition(mut self, condition: NodePtr) -> Self { - self.conditions.push(condition); - self - } -} - -#[cfg(test)] -mod tests { - use chia_puzzles::standard::StandardArgs; - use chia_sdk_test::{test_transaction, Simulator}; - use clvmr::Allocator; - - use crate::puzzles::StandardSpend; - - use super::*; - - #[tokio::test] - async fn test_single_issuance_cat() -> anyhow::Result<()> { - let sim = Simulator::new().await?; - let peer = sim.connect().await?; - - let sk = sim.secret_key().await?; - let pk = sk.public_key(); - - let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); - let coin = sim.mint_coin(puzzle_hash, 1).await; - - let mut allocator = Allocator::new(); - let ctx = &mut SpendContext::new(&mut allocator); - - let (issue_cat, _cat_info) = IssueCat::new(coin.coin_id()) - .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? - .single_issuance(ctx, 1)?; - - StandardSpend::new() - .chain(issue_cat) - .finish(ctx, coin, pk)?; - - test_transaction( - &peer, - ctx.take_spends(), - &[sk], - sim.config().genesis_challenge, - ) - .await; - - Ok(()) - } - - #[tokio::test] - async fn test_multi_issuance_cat() -> anyhow::Result<()> { - let sim = Simulator::new().await?; - let peer = sim.connect().await?; - - let sk = sim.secret_key().await?; - let pk = sk.public_key(); - - let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); - let coin = sim.mint_coin(puzzle_hash, 1).await; - - let mut allocator = Allocator::new(); - let ctx = &mut SpendContext::new(&mut allocator); - - let (issue_cat, _cat_info) = IssueCat::new(coin.coin_id()) - .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? - .multi_issuance(ctx, pk, 1)?; - - StandardSpend::new() - .chain(issue_cat) - .finish(ctx, coin, pk)?; - - test_transaction( - &peer, - ctx.take_spends(), - &[sk], - sim.config().genesis_challenge, - ) - .await; - - Ok(()) - } -} +// use chia_bls::PublicKey; +// use chia_protocol::{Bytes32, Coin, CoinSpend}; +// use chia_puzzles::{ +// cat::{ +// CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, GenesisByCoinIdTailArgs, +// CAT_PUZZLE_HASH, +// }, +// LineageProof, +// }; +// use chia_sdk_types::conditions::RunTail; +// use clvm_traits::clvm_quote; +// use clvm_utils::CurriedProgram; +// use clvmr::NodePtr; + +// use crate::{ +// spend::{P2Spend, SpendConditions}, +// SpendContext, SpendError, +// }; + +// #[derive(Debug)] +// #[must_use] +// pub struct IssueCat { +// parent_coin_id: Bytes32, +// conditions: Vec, +// } + +// #[derive(Debug, Clone, Copy)] +// pub struct CatIssuanceInfo { +// pub asset_id: Bytes32, +// pub lineage_proof: LineageProof, +// pub eve_coin: Coin, +// } + +// impl IssueCat { +// pub const fn new(parent_coin_id: Bytes32) -> Self { +// Self { +// parent_coin_id, +// conditions: Vec::new(), +// } +// } + +// pub fn single_issuance( +// self, +// ctx: &mut SpendContext<'_>, +// amount: u64, +// ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { +// let tail_puzzle_ptr = ctx.genesis_by_coin_id_tail_puzzle()?; + +// let tail = ctx.alloc(&CurriedProgram { +// program: tail_puzzle_ptr, +// args: GenesisByCoinIdTailArgs { +// genesis_coin_id: self.parent_coin_id, +// }, +// })?; +// let asset_id = ctx.tree_hash(tail).into(); + +// self.raw_condition(ctx.alloc(&RunTail::new(tail, ()))?) +// .finish_raw(ctx, asset_id, amount) +// } + +// pub fn multi_issuance( +// self, +// ctx: &mut SpendContext<'_>, +// public_key: PublicKey, +// amount: u64, +// ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { +// let tail_puzzle_ptr = ctx.everything_with_signature_tail_puzzle()?; + +// let tail = ctx.alloc(&CurriedProgram { +// program: tail_puzzle_ptr, +// args: EverythingWithSignatureTailArgs { public_key }, +// })?; +// let asset_id = ctx.tree_hash(tail).into(); + +// self.raw_condition(ctx.alloc(&RunTail::new(tail, ()))?) +// .finish_raw(ctx, asset_id, amount) +// } + +// pub fn finish_raw( +// self, +// ctx: &mut SpendContext<'_>, +// asset_id: Bytes32, +// amount: u64, +// ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { +// let cat_puzzle_ptr = ctx.cat_puzzle()?; + +// let inner_puzzle = ctx.alloc(&clvm_quote!(self.conditions))?; +// let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); + +// let puzzle = ctx.alloc(&CurriedProgram { +// program: cat_puzzle_ptr, +// args: CatArgs { +// mod_hash: CAT_PUZZLE_HASH.into(), +// asset_id, +// inner_puzzle, +// }, +// })?; + +// let puzzle_hash = ctx.tree_hash(puzzle).into(); +// let eve_coin = Coin::new(self.parent_coin_id, puzzle_hash, amount); + +// let solution = ctx.serialize(&CatSolution { +// inner_puzzle_solution: (), +// lineage_proof: None, +// prev_coin_id: eve_coin.coin_id(), +// this_coin_info: eve_coin, +// next_coin_proof: CoinProof { +// parent_coin_info: self.parent_coin_id, +// inner_puzzle_hash, +// amount, +// }, +// prev_subtotal: 0, +// extra_delta: 0, +// })?; + +// let puzzle_reveal = ctx.serialize(&puzzle)?; +// ctx.spend(CoinSpend::new(eve_coin, puzzle_reveal, solution)); + +// let chained_spend = +// SpendConditions::new().create_hinted_coin(ctx, puzzle_hash, amount, puzzle_hash)?; + +// let issuance_info = CatIssuanceInfo { +// asset_id, +// lineage_proof: LineageProof { +// parent_parent_coin_id: eve_coin.parent_coin_info, +// parent_inner_puzzle_hash: inner_puzzle_hash, +// parent_amount: eve_coin.amount, +// }, +// eve_coin, +// }; + +// Ok((chained_spend, issuance_info)) +// } +// } + +// impl P2Spend for IssueCat { +// fn raw_condition(mut self, condition: NodePtr) -> Self { +// self.conditions.push(condition); +// self +// } +// } + +// #[cfg(test)] +// mod tests { +// use chia_puzzles::standard::StandardArgs; +// use chia_sdk_test::{test_transaction, Simulator}; +// use clvmr::Allocator; + +// use crate::puzzles::StandardSpend; + +// use super::*; + +// #[tokio::test] +// async fn test_single_issuance_cat() -> anyhow::Result<()> { +// let sim = Simulator::new().await?; +// let peer = sim.connect().await?; + +// let sk = sim.secret_key().await?; +// let pk = sk.public_key(); + +// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); +// let coin = sim.mint_coin(puzzle_hash, 1).await; + +// let mut allocator = Allocator::new(); +// let ctx = &mut SpendContext::new(&mut allocator); + +// let (issue_cat, _cat_info) = IssueCat::new(coin.coin_id()) +// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? +// .single_issuance(ctx, 1)?; + +// StandardSpend::new() +// .chain(issue_cat) +// .finish(ctx, coin, pk)?; + +// test_transaction( +// &peer, +// ctx.take_spends(), +// &[sk], +// sim.config().genesis_challenge, +// ) +// .await; + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_multi_issuance_cat() -> anyhow::Result<()> { +// let sim = Simulator::new().await?; +// let peer = sim.connect().await?; + +// let sk = sim.secret_key().await?; +// let pk = sk.public_key(); + +// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); +// let coin = sim.mint_coin(puzzle_hash, 1).await; + +// let mut allocator = Allocator::new(); +// let ctx = &mut SpendContext::new(&mut allocator); + +// let (issue_cat, _cat_info) = IssueCat::new(coin.coin_id()) +// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? +// .multi_issuance(ctx, pk, 1)?; + +// StandardSpend::new() +// .chain(issue_cat) +// .finish(ctx, coin, pk)?; + +// test_transaction( +// &peer, +// ctx.take_spends(), +// &[sk], +// sim.config().genesis_challenge, +// ) +// .await; + +// Ok(()) +// } +// } diff --git a/crates/chia-sdk-driver/src/puzzles/did.rs b/crates/chia-sdk-driver/src/puzzles/did.rs index eb37a8ad..78362051 100644 --- a/crates/chia-sdk-driver/src/puzzles/did.rs +++ b/crates/chia-sdk-driver/src/puzzles/did.rs @@ -6,10 +6,7 @@ pub use did_spend::*; #[cfg(test)] mod tests { - use crate::{ - puzzles::{Launcher, StandardSpend}, - SpendContext, - }; + use crate::{Launcher, SpendContext}; use super::*; @@ -32,12 +29,10 @@ mod tests { let ctx = &mut SpendContext::new(&mut allocator); let (launch_singleton, did_info) = Launcher::new(coin.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; - StandardSpend::new() - .chain(launch_singleton) - .finish(ctx, coin, pk)?; + ctx.spend_p2_coin(coin, pk, launch_singleton)?; test_transaction( &peer, diff --git a/crates/chia-sdk-driver/src/puzzles/did/create_did.rs b/crates/chia-sdk-driver/src/puzzles/did/create_did.rs index b5e28ccb..5f6880bf 100644 --- a/crates/chia-sdk-driver/src/puzzles/did/create_did.rs +++ b/crates/chia-sdk-driver/src/puzzles/did/create_did.rs @@ -11,9 +11,7 @@ use clvm_traits::ToClvm; use clvm_utils::{tree_hash_atom, CurriedProgram, ToTreeHash, TreeHash}; use clvmr::NodePtr; -use crate::{puzzles::SpendableLauncher, spend_builder::SpendConditions, SpendContext, SpendError}; - -use super::StandardDidSpend; +use crate::{Conditions, SpendContext, SpendError, SpendableLauncher}; pub trait CreateDid { fn create_eve_did( @@ -23,7 +21,7 @@ pub trait CreateDid { recovery_did_list_hash: Bytes32, num_verifications_required: u64, metadata: M, - ) -> Result<(SpendConditions, DidInfo), SpendError> + ) -> Result<(Conditions, DidInfo), SpendError> where M: ToClvm; @@ -34,14 +32,14 @@ pub trait CreateDid { num_verifications_required: u64, metadata: M, synthetic_key: PublicKey, - ) -> Result<(SpendConditions, DidInfo), SpendError> + ) -> Result<(Conditions, DidInfo), SpendError> where - M: ToClvm, + M: ToClvm + Clone, Self: Sized, { let inner_puzzle_hash = CurriedProgram { program: STANDARD_PUZZLE_HASH, - args: StandardArgs { synthetic_key }, + args: StandardArgs::new(synthetic_key), } .tree_hash() .into(); @@ -54,9 +52,7 @@ pub trait CreateDid { metadata, )?; - let did_info = StandardDidSpend::new() - .recreate() - .finish(ctx, synthetic_key, did_info)?; + let did_info = ctx.spend_standard_did(&did_info, synthetic_key, Conditions::new())?; Ok((create_did, did_info)) } @@ -65,7 +61,7 @@ pub trait CreateDid { self, ctx: &mut SpendContext<'_>, synthetic_key: PublicKey, - ) -> Result<(SpendConditions, DidInfo<()>), SpendError> + ) -> Result<(Conditions, DidInfo<()>), SpendError> where Self: Sized, { @@ -81,7 +77,7 @@ impl CreateDid for SpendableLauncher { recovery_did_list_hash: Bytes32, num_verifications_required: u64, metadata: M, - ) -> Result<(SpendConditions, DidInfo), SpendError> + ) -> Result<(Conditions, DidInfo), SpendError> where M: ToClvm, { diff --git a/crates/chia-sdk-driver/src/puzzles/did/did_spend.rs b/crates/chia-sdk-driver/src/puzzles/did/did_spend.rs index 529e0f4b..668fd8d4 100644 --- a/crates/chia-sdk-driver/src/puzzles/did/did_spend.rs +++ b/crates/chia-sdk-driver/src/puzzles/did/did_spend.rs @@ -1,8 +1,7 @@ -use chia_bls::PublicKey; use chia_protocol::{Coin, CoinSpend}; use chia_puzzles::{ did::{DidArgs, DidSolution}, - singleton::{SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, + singleton::SingletonStruct, LineageProof, Proof, }; use chia_sdk_types::puzzles::DidInfo; @@ -10,111 +9,34 @@ use clvm_traits::ToClvm; use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use crate::{ - puzzles::{spend_singleton, StandardSpend}, - spend_builder::{InnerSpend, P2Spend, SpendConditions}, - SpendContext, SpendError, -}; - -#[derive(Debug, Clone, Copy)] -pub struct NoDidOutput; - -#[derive(Debug, Clone, Copy)] -pub enum DidOutput { - Recreate, -} +use crate::{puzzles::spend_singleton, Conditions, Spend, SpendContext, SpendError}; -#[derive(Debug, Clone)] -#[must_use] -pub struct StandardDidSpend { - standard_spend: StandardSpend, - output: T, -} +pub fn recreate_did(mut did_info: DidInfo) -> (Conditions, DidInfo) { + let conditions = Conditions::new().create_hinted_coin( + did_info.did_inner_puzzle_hash, + did_info.coin.amount, + did_info.p2_puzzle_hash, + ); -impl StandardDidSpend { - pub fn chain(mut self, chained: SpendConditions) -> Self { - self.standard_spend = self.standard_spend.chain(chained); - self - } -} + did_info.proof = Proof::Lineage(LineageProof { + parent_parent_coin_id: did_info.coin.parent_coin_info, + parent_inner_puzzle_hash: did_info.did_inner_puzzle_hash, + parent_amount: did_info.coin.amount, + }); -impl P2Spend for StandardDidSpend { - fn raw_condition(mut self, condition: NodePtr) -> Self { - self.standard_spend = self.standard_spend.raw_condition(condition); - self - } -} + did_info.coin = Coin::new( + did_info.coin.coin_id(), + did_info.coin.puzzle_hash, + did_info.coin.amount, + ); -impl Default for StandardDidSpend { - fn default() -> Self { - Self { - output: NoDidOutput, - standard_spend: StandardSpend::new(), - } - } + (conditions, did_info) } -impl StandardDidSpend { - pub fn new() -> Self { - Self::default() - } - - pub fn recreate(self) -> StandardDidSpend { - StandardDidSpend { - standard_spend: self.standard_spend, - output: DidOutput::Recreate, - } - } -} - -impl StandardDidSpend { - pub fn finish( - self, - ctx: &mut SpendContext<'_>, - synthetic_key: PublicKey, - mut did_info: DidInfo, - ) -> Result, SpendError> - where - M: ToClvm, - { - let spend = match self.output { - DidOutput::Recreate => self.standard_spend.create_hinted_coin( - ctx, - did_info.did_inner_puzzle_hash, - did_info.coin.amount, - did_info.p2_puzzle_hash, - )?, - }; - - let inner_spend = spend.inner_spend(ctx, synthetic_key)?; - let did_spend = raw_did_spend(ctx, &did_info, inner_spend)?; - - ctx.spend(did_spend); - - match self.output { - DidOutput::Recreate => { - did_info.proof = Proof::Lineage(LineageProof { - parent_parent_coin_id: did_info.coin.parent_coin_info, - parent_inner_puzzle_hash: did_info.did_inner_puzzle_hash, - parent_amount: did_info.coin.amount, - }); - - did_info.coin = Coin::new( - did_info.coin.coin_id(), - did_info.coin.puzzle_hash, - did_info.coin.amount, - ); - } - } - - Ok(did_info) - } -} - -pub fn raw_did_spend( +pub fn did_spend( ctx: &mut SpendContext<'_>, did_info: &DidInfo, - inner_spend: InnerSpend, + inner_spend: Spend, ) -> Result where M: ToClvm, @@ -127,25 +49,19 @@ where inner_puzzle: inner_spend.puzzle(), recovery_did_list_hash: did_info.recovery_did_list_hash, num_verifications_required: did_info.num_verifications_required, - singleton_struct: SingletonStruct { - mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), - launcher_id: did_info.launcher_id, - launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - }, + singleton_struct: SingletonStruct::new(did_info.launcher_id), metadata: &did_info.metadata, }, })?; let solution = ctx.alloc(&DidSolution::InnerSpend(inner_spend.solution()))?; - let did_spend = InnerSpend::new(puzzle, solution); - spend_singleton( ctx, did_info.coin, did_info.launcher_id, did_info.proof, - did_spend, + Spend::new(puzzle, solution), ) } @@ -174,17 +90,13 @@ mod tests { let ctx = &mut SpendContext::new(&mut allocator); let (create_did, mut did_info) = Launcher::new(coin.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; - StandardSpend::new() - .chain(create_did) - .finish(ctx, coin, pk)?; + ctx.spend_p2_coin(coin, pk, create_did)?; for _ in 0..10 { - did_info = StandardDidSpend::new() - .recreate() - .finish(ctx, pk, did_info)?; + did_info = ctx.spend_standard_did(&did_info, pk, Conditions::new())?; } test_transaction( diff --git a/crates/chia-sdk-driver/src/puzzles/nft/mint_nft.rs b/crates/chia-sdk-driver/src/puzzles/nft/mint_nft.rs index 5dec031a..5088ff99 100644 --- a/crates/chia-sdk-driver/src/puzzles/nft/mint_nft.rs +++ b/crates/chia-sdk-driver/src/puzzles/nft/mint_nft.rs @@ -1,27 +1,15 @@ use chia_bls::PublicKey; use chia_protocol::Bytes32; use chia_puzzles::{ - nft::{ - NftOwnershipLayerArgs, NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs, - NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - NFT_ROYALTY_TRANSFER_PUZZLE_HASH, NFT_STATE_LAYER_PUZZLE_HASH, - }, - singleton::SingletonStruct, - standard::{StandardArgs, STANDARD_PUZZLE_HASH}, + nft::{NftOwnershipLayerArgs, NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs}, + standard::StandardArgs, EveProof, Proof, }; -use chia_sdk_types::puzzles::NftInfo; +use chia_sdk_types::{conditions::NewNftOwner, puzzles::NftInfo}; use clvm_traits::ToClvm; -use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use clvmr::NodePtr; -use crate::{ - puzzles::SpendableLauncher, - spend_builder::{P2Spend, SpendConditions}, - SpendContext, SpendError, -}; - -use super::StandardNftSpend; +use crate::{puzzles::SpendableLauncher, Conditions, SpendContext, SpendError}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StandardMint { @@ -47,7 +35,7 @@ pub trait MintNft { metadata: M, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, - ) -> Result<(SpendConditions, NftInfo), SpendError> + ) -> Result<(Conditions, NftInfo), SpendError> where M: ToClvm; @@ -55,21 +43,14 @@ pub trait MintNft { self, ctx: &mut SpendContext<'_>, mint: StandardMint, - ) -> Result<(SpendConditions, NftInfo), SpendError> + ) -> Result<(Conditions, NftInfo), SpendError> where - M: ToClvm, + M: ToClvm + Clone, Self: Sized, { - let inner_puzzle_hash = CurriedProgram { - program: STANDARD_PUZZLE_HASH, - args: StandardArgs { - synthetic_key: mint.synthetic_key, - }, - } - .tree_hash() - .into(); - - let (mut mint_nft, nft_info) = self.mint_eve_nft( + let inner_puzzle_hash = StandardArgs::curry_tree_hash(mint.synthetic_key).into(); + + let (mint_nft, nft_info) = self.mint_eve_nft( ctx, inner_puzzle_hash, mint.metadata, @@ -77,20 +58,21 @@ pub trait MintNft { mint.royalty_percentage, )?; - let mut nft_spend = StandardNftSpend::new(); - - if let Some(owner_did) = mint.owner_did { - nft_spend = nft_spend.new_owner(owner_did.did_id, owner_did.did_inner_puzzle_hash); - } - - let (nft_spend, nft_info) = - nft_spend - .transfer(mint.owner_puzzle_hash) - .finish(ctx, mint.synthetic_key, nft_info)?; - - mint_nft.extend(nft_spend); + let (spend_eve, nft_info) = ctx.spend_standard_nft( + &nft_info, + mint.synthetic_key, + mint.owner_puzzle_hash, + mint.owner_did.map(|owner_did| { + NewNftOwner::new( + Some(owner_did.did_id), + Vec::new(), + Some(owner_did.did_inner_puzzle_hash), + ) + }), + Conditions::new(), + )?; - Ok((mint_nft, nft_info)) + Ok((mint_nft.extend(spend_eve), nft_info)) } } @@ -102,49 +84,30 @@ impl MintNft for SpendableLauncher { metadata: M, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, - ) -> Result<(SpendConditions, NftInfo), SpendError> + ) -> Result<(Conditions, NftInfo), SpendError> where M: ToClvm, { let metadata_ptr = ctx.alloc(&metadata)?; let metadata_hash = ctx.tree_hash(metadata_ptr); - let transfer_program = CurriedProgram { - program: NFT_ROYALTY_TRANSFER_PUZZLE_HASH, - args: NftRoyaltyTransferPuzzleArgs { - singleton_struct: SingletonStruct::new(self.coin().coin_id()), - royalty_puzzle_hash, - trade_price_percentage: royalty_percentage, - }, - }; + let transfer_program = NftRoyaltyTransferPuzzleArgs::curry_tree_hash( + self.coin().coin_id(), + royalty_puzzle_hash, + royalty_percentage, + ); - let ownership_layer = CurriedProgram { - program: NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - args: NftOwnershipLayerArgs { - mod_hash: NFT_OWNERSHIP_LAYER_PUZZLE_HASH.into(), - current_owner: None, - transfer_program, - inner_puzzle: TreeHash::from(p2_puzzle_hash), - }, - }; + let ownership_layer = + NftOwnershipLayerArgs::curry_tree_hash(None, transfer_program, p2_puzzle_hash.into()); - let nft_inner_puzzle_hash = CurriedProgram { - program: NFT_STATE_LAYER_PUZZLE_HASH, - args: NftStateLayerArgs { - mod_hash: NFT_STATE_LAYER_PUZZLE_HASH.into(), - metadata: metadata_hash, - metadata_updater_puzzle_hash: NFT_METADATA_UPDATER_PUZZLE_HASH.into(), - inner_puzzle: ownership_layer, - }, - } - .tree_hash() - .into(); + let nft_inner_puzzle_hash = + NftStateLayerArgs::curry_tree_hash(metadata_hash, ownership_layer).into(); let launcher_coin = self.coin(); let (mut chained_spend, eve_coin) = self.spend(ctx, nft_inner_puzzle_hash, ())?; - chained_spend = chained_spend - .create_puzzle_announcement(ctx, launcher_coin.coin_id().to_vec().into())?; + chained_spend = + chained_spend.create_puzzle_announcement(launcher_coin.coin_id().to_vec().into()); let proof = Proof::Eve(EveProof { parent_coin_info: launcher_coin.parent_coin_info, @@ -169,10 +132,7 @@ impl MintNft for SpendableLauncher { #[cfg(test)] mod tests { - use crate::{ - puzzles::{CreateDid, IntermediateLauncher, Launcher, StandardDidSpend, StandardSpend}, - spend_builder::P2Spend, - }; + use crate::puzzles::{CreateDid, IntermediateLauncher, Launcher}; use super::*; @@ -195,12 +155,10 @@ mod tests { let ctx = &mut SpendContext::new(&mut allocator); let (create_did, did_info) = Launcher::new(coin.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; - StandardSpend::new() - .chain(create_did) - .finish(ctx, coin, pk)?; + ctx.spend_p2_coin(coin, pk, create_did)?; let mint = StandardMint { metadata: (), @@ -214,21 +172,21 @@ mod tests { }), }; - let _did_info = StandardDidSpend::new() - .chain( - IntermediateLauncher::new(did_info.coin.coin_id(), 0, 2) - .create(ctx)? - .mint_standard_nft(ctx, mint.clone())? - .0, - ) - .chain( - IntermediateLauncher::new(did_info.coin.coin_id(), 1, 2) - .create(ctx)? - .mint_standard_nft(ctx, mint.clone())? - .0, - ) - .recreate() - .finish(ctx, pk, did_info)?; + let mint_1 = IntermediateLauncher::new(did_info.coin.coin_id(), 0, 2) + .create(ctx)? + .mint_standard_nft(ctx, mint.clone())? + .0; + + let mint_2 = IntermediateLauncher::new(did_info.coin.coin_id(), 1, 2) + .create(ctx)? + .mint_standard_nft(ctx, mint.clone())? + .0; + + let _did_info = ctx.spend_standard_did( + &did_info, + pk, + Conditions::new().extend(mint_1).extend(mint_2), + )?; test_transaction( &peer, @@ -256,17 +214,15 @@ mod tests { let ctx = &mut SpendContext::new(&mut allocator); let (create_did, did_info) = Launcher::new(coin.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; - StandardSpend::new() - .chain(create_did) - .finish(ctx, coin, pk)?; + ctx.spend_p2_coin(coin, pk, create_did)?; 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_now(ctx)?; + Launcher::new(intermediate_coin.coin_id(), 1).create_now(); let mint = StandardMint { metadata: (), @@ -281,16 +237,8 @@ mod tests { }; let (mint_nft, _nft_info) = launcher.mint_standard_nft(ctx, mint)?; - - StandardDidSpend::new() - .chain(mint_nft) - .create_coin(ctx, puzzle_hash, 0)? - .recreate() - .finish(ctx, pk, did_info)?; - - StandardSpend::new() - .chain(create_launcher) - .finish(ctx, intermediate_coin, pk)?; + ctx.spend_standard_did(&did_info, pk, mint_nft.create_coin(puzzle_hash, 0))?; + ctx.spend_p2_coin(intermediate_coin, pk, create_launcher)?; test_transaction( &peer, @@ -318,17 +266,15 @@ mod tests { let ctx = &mut SpendContext::new(&mut allocator); let (create_did, did_info) = Launcher::new(coin.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; - StandardSpend::new() - .chain(create_did) - .finish(ctx, coin, pk)?; + ctx.spend_p2_coin(coin, pk, create_did)?; 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_now(ctx)?; + Launcher::new(intermediate_coin.coin_id(), 1).create_now(); let mint = StandardMint { metadata: (), @@ -344,19 +290,10 @@ mod tests { let (mint_nft, _nft_info) = launcher.mint_standard_nft(ctx, mint)?; - let did_info = StandardDidSpend::new() - .create_coin(ctx, puzzle_hash, 0)? - .recreate() - .finish(ctx, pk, did_info)?; - - StandardDidSpend::new() - .chain(mint_nft) - .recreate() - .finish(ctx, pk, did_info)?; - - StandardSpend::new() - .chain(create_launcher) - .finish(ctx, intermediate_coin, pk)?; + let did_info = + ctx.spend_standard_did(&did_info, pk, Conditions::new().create_coin(puzzle_hash, 0))?; + ctx.spend_standard_did(&did_info, pk, mint_nft)?; + ctx.spend_p2_coin(intermediate_coin, pk, create_launcher)?; test_transaction( &peer, diff --git a/crates/chia-sdk-driver/src/puzzles/nft/nft_spend.rs b/crates/chia-sdk-driver/src/puzzles/nft/nft_spend.rs index 5ecebbea..b3ce5825 100644 --- a/crates/chia-sdk-driver/src/puzzles/nft/nft_spend.rs +++ b/crates/chia-sdk-driver/src/puzzles/nft/nft_spend.rs @@ -1,4 +1,3 @@ -use chia_bls::PublicKey; use chia_protocol::{Bytes32, Coin, CoinSpend}; use chia_puzzles::{ nft::{ @@ -9,7 +8,7 @@ use chia_puzzles::{ LineageProof, Proof, }; use chia_sdk_types::{ - conditions::{AssertPuzzleAnnouncement, NewNftOwner}, + conditions::{Condition, NewNftOwner}, puzzles::NftInfo, }; use clvm_traits::{clvm_list, ToClvm}; @@ -19,183 +18,92 @@ use clvmr::{ NodePtr, }; -use crate::{ - puzzles::{spend_singleton, StandardSpend}, - spend_builder::{InnerSpend, P2Spend, SpendConditions}, - SpendContext, SpendError, -}; - -#[derive(Debug, Clone, Copy)] -pub struct NoNftOutput; - -#[derive(Debug, Clone, Copy)] -pub enum NftOutput { - SamePuzzleHash, - NewPuzzleHash { puzzle_hash: Bytes32 }, -} +use crate::{spend_singleton, Conditions, Spend, SpendContext, SpendError}; #[derive(Debug, Clone)] -#[must_use] -pub struct StandardNftSpend { - standard_spend: StandardSpend, - output: T, - new_owner: Option, -} - -impl Default for StandardNftSpend { - fn default() -> Self { - Self { - output: NoNftOutput, - standard_spend: StandardSpend::new(), - new_owner: None, - } - } +pub struct TransferNft { + pub did_conditions: Conditions, + pub p2_conditions: Conditions, + pub output: NftInfo, } -impl StandardNftSpend { - pub fn new() -> Self { - Self::default() - } - - pub fn update(self) -> StandardNftSpend { - StandardNftSpend { - standard_spend: self.standard_spend, - output: NftOutput::SamePuzzleHash, - new_owner: self.new_owner, - } - } - - pub fn transfer(self, puzzle_hash: Bytes32) -> StandardNftSpend { - StandardNftSpend { - standard_spend: self.standard_spend, - output: NftOutput::NewPuzzleHash { puzzle_hash }, - new_owner: self.new_owner, - } - } -} - -impl StandardNftSpend { - pub fn new_owner(mut self, did_id: Bytes32, did_inner_puzzle_hash: Bytes32) -> Self { - self.new_owner = Some(NewNftOwner { - new_owner: Some(did_id), - trade_prices_list: Vec::new(), - new_did_p2_puzzle_hash: Some(did_inner_puzzle_hash), - }); - self - } - - pub fn reset_owner(mut self) -> Self { - self.new_owner = Some(NewNftOwner { - new_owner: None, - trade_prices_list: Vec::new(), - new_did_p2_puzzle_hash: None, - }); - self - } - - pub fn chain(mut self, chained: SpendConditions) -> Self { - self.standard_spend = self.standard_spend.chain(chained); - self - } -} - -impl P2Spend for StandardNftSpend { - fn raw_condition(mut self, condition: NodePtr) -> Self { - self.standard_spend = self.standard_spend.raw_condition(condition); - self +pub fn transfer_nft( + ctx: &mut SpendContext<'_>, + mut nft_info: NftInfo, + p2_puzzle_hash: Bytes32, + new_owner: Option, +) -> Result, SpendError> +where + M: ToClvm, +{ + let mut did_conditions = Conditions::new(); + let mut p2_conditions = + Conditions::new().create_hinted_coin(p2_puzzle_hash, nft_info.coin.amount, p2_puzzle_hash); + + if let Some(new_owner) = &new_owner { + let new_nft_owner_args = ctx.alloc(&clvm_list!( + new_owner.new_owner, + new_owner.trade_prices_list.clone(), + new_owner.new_did_p2_puzzle_hash + ))?; + + let mut hasher = Sha256::new(); + hasher.update(nft_info.coin.puzzle_hash); + hasher.update([0xad, 0x4c]); + hasher.update(ctx.tree_hash(new_nft_owner_args)); + let announcement_id = Bytes32::new(hasher.finalize().into()); + + did_conditions = did_conditions.assert_raw_puzzle_announcement(announcement_id); + p2_conditions = p2_conditions.condition(Condition::Other(ctx.alloc(new_owner)?)); } -} - -impl StandardNftSpend { - pub fn finish( - mut self, - ctx: &mut SpendContext<'_>, - synthetic_key: PublicKey, - mut nft_info: NftInfo, - ) -> Result<(SpendConditions, NftInfo), SpendError> - where - M: ToClvm, - { - let mut parent = SpendConditions::default(); - - let p2_puzzle_hash = match self.output { - NftOutput::SamePuzzleHash => nft_info.p2_puzzle_hash, - NftOutput::NewPuzzleHash { puzzle_hash } => puzzle_hash, - }; - - if let Some(new_owner) = &self.new_owner { - self.standard_spend = self.standard_spend.raw_condition(ctx.alloc(new_owner)?); - - let new_nft_owner_args = ctx.alloc(&clvm_list!( - new_owner.new_owner, - new_owner.trade_prices_list.clone(), - new_owner.new_did_p2_puzzle_hash - ))?; - - let mut announcement_id = Sha256::new(); - announcement_id.update(nft_info.coin.puzzle_hash); - announcement_id.update([0xad, 0x4c]); - announcement_id.update(ctx.tree_hash(new_nft_owner_args)); - - parent = parent.raw_condition(ctx.alloc(&AssertPuzzleAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize().into()), - })?); - } - - let inner_spend = self - .standard_spend - .create_hinted_coin(ctx, p2_puzzle_hash, nft_info.coin.amount, p2_puzzle_hash)? - .inner_spend(ctx, synthetic_key)?; - - let nft_spend = raw_nft_spend(ctx, &nft_info, inner_spend)?; - ctx.spend(nft_spend); - - nft_info.current_owner = self - .new_owner - .map_or(nft_info.current_owner, |value| value.new_owner); - - let metadata_ptr = ctx.alloc(&nft_info.metadata)?; - - let transfer_program = NftRoyaltyTransferPuzzleArgs::curry_tree_hash( - nft_info.launcher_id, - nft_info.royalty_puzzle_hash, - nft_info.royalty_percentage, - ); - - let ownership_layer = NftOwnershipLayerArgs::curry_tree_hash( - nft_info.current_owner, - transfer_program, - p2_puzzle_hash.into(), - ); - - let state_layer = - NftStateLayerArgs::curry_tree_hash(ctx.tree_hash(metadata_ptr), ownership_layer); - - let singleton_puzzle_hash = - SingletonArgs::curry_tree_hash(nft_info.launcher_id, state_layer); - nft_info.proof = Proof::Lineage(LineageProof { - parent_parent_coin_id: nft_info.coin.parent_coin_info, - parent_inner_puzzle_hash: nft_info.nft_inner_puzzle_hash, - parent_amount: nft_info.coin.amount, - }); + nft_info.current_owner = new_owner.map_or(nft_info.current_owner, |value| value.new_owner); - nft_info.coin = Coin::new( - nft_info.coin.coin_id(), - singleton_puzzle_hash.into(), - nft_info.coin.amount, - ); + let metadata_ptr = ctx.alloc(&nft_info.metadata)?; - nft_info.nft_inner_puzzle_hash = state_layer.into(); - - Ok((parent, nft_info)) - } + let transfer_program = NftRoyaltyTransferPuzzleArgs::curry_tree_hash( + nft_info.launcher_id, + nft_info.royalty_puzzle_hash, + nft_info.royalty_percentage, + ); + + let ownership_layer = NftOwnershipLayerArgs::curry_tree_hash( + nft_info.current_owner, + transfer_program, + p2_puzzle_hash.into(), + ); + + let state_layer = + NftStateLayerArgs::curry_tree_hash(ctx.tree_hash(metadata_ptr), ownership_layer); + + let singleton_puzzle_hash = SingletonArgs::curry_tree_hash(nft_info.launcher_id, state_layer); + + nft_info.proof = Proof::Lineage(LineageProof { + parent_parent_coin_id: nft_info.coin.parent_coin_info, + parent_inner_puzzle_hash: nft_info.nft_inner_puzzle_hash, + parent_amount: nft_info.coin.amount, + }); + + nft_info.coin = Coin::new( + nft_info.coin.coin_id(), + singleton_puzzle_hash.into(), + nft_info.coin.amount, + ); + + nft_info.nft_inner_puzzle_hash = state_layer.into(); + nft_info.p2_puzzle_hash = p2_puzzle_hash; + + Ok(TransferNft { + did_conditions, + p2_conditions, + output: nft_info, + }) } -pub fn raw_nft_spend( +pub fn nft_spend( ctx: &mut SpendContext<'_>, nft_info: &NftInfo, - inner_spend: InnerSpend, + inner_spend: Spend, ) -> Result where M: ToClvm, @@ -228,8 +136,8 @@ where pub fn spend_nft_state_layer( ctx: &mut SpendContext<'_>, metadata: M, - inner_spend: InnerSpend, -) -> Result + inner_spend: Spend, +) -> Result where M: ToClvm, { @@ -244,15 +152,15 @@ where inner_solution: inner_spend.solution(), })?; - Ok(InnerSpend::new(puzzle, solution)) + Ok(Spend::new(puzzle, solution)) } pub fn spend_nft_ownership_layer

( ctx: &mut SpendContext<'_>, current_owner: Option, transfer_program: P, - inner_spend: InnerSpend, -) -> Result + inner_spend: Spend, +) -> Result where P: ToClvm, { @@ -267,14 +175,13 @@ where inner_solution: inner_spend.solution(), })?; - Ok(InnerSpend::new(puzzle, solution)) + Ok(Spend::new(puzzle, solution)) } #[cfg(test)] mod tests { use crate::puzzles::{ - CreateDid, IntermediateLauncher, Launcher, MintNft, OwnerDid, StandardDidSpend, - StandardMint, + CreateDid, IntermediateLauncher, Launcher, MintNft, OwnerDid, StandardMint, }; use super::*; @@ -298,12 +205,10 @@ mod tests { let ctx = &mut SpendContext::new(&mut allocator); let (create_did, did_info) = Launcher::new(coin.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; - StandardSpend::new() - .chain(create_did) - .finish(ctx, coin, pk)?; + ctx.spend_p2_coin(coin, pk, create_did)?; let (mint_nft, mut nft_info) = IntermediateLauncher::new(did_info.coin.coin_id(), 0, 1) .create(ctx)? @@ -322,25 +227,26 @@ mod tests { }, )?; - let mut did_info = StandardDidSpend::new() - .chain(mint_nft) - .recreate() - .finish(ctx, pk, did_info)?; + let mut did_info = ctx.spend_standard_did(&did_info, pk, mint_nft)?; for i in 0..5 { - let mut spend = StandardNftSpend::new().update(); - - if i % 2 == 0 { - spend = spend.new_owner(did_info.launcher_id, did_info.did_inner_puzzle_hash); - } - - let (nft_spend, new_nft_info) = spend.finish(ctx, pk, nft_info)?; + let (spend_nft, new_nft_info) = ctx.spend_standard_nft( + &nft_info, + pk, + nft_info.p2_puzzle_hash, + if i % 2 == 0 { + Some(NewNftOwner::new( + Some(did_info.launcher_id), + Vec::new(), + Some(did_info.did_inner_puzzle_hash), + )) + } else { + None + }, + Conditions::new(), + )?; nft_info = new_nft_info; - - did_info = StandardDidSpend::new() - .chain(nft_spend) - .recreate() - .finish(ctx, pk, did_info)?; + did_info = ctx.spend_standard_did(&did_info, pk, spend_nft)?; } test_transaction( diff --git a/crates/chia-sdk-driver/src/puzzles/singleton/intermediate_launcher.rs b/crates/chia-sdk-driver/src/puzzles/singleton/intermediate_launcher.rs index 785d4bbf..2d5ca6af 100644 --- a/crates/chia-sdk-driver/src/puzzles/singleton/intermediate_launcher.rs +++ b/crates/chia-sdk-driver/src/puzzles/singleton/intermediate_launcher.rs @@ -8,10 +8,7 @@ use clvmr::{ Allocator, }; -use crate::{ - spend_builder::{P2Spend, SpendConditions}, - SpendContext, SpendError, -}; +use crate::{Conditions, SpendContext, SpendError}; use super::SpendableLauncher; @@ -63,7 +60,7 @@ impl IntermediateLauncher { /// Spends the intermediate coin to create the launcher coin. pub fn create(self, ctx: &mut SpendContext<'_>) -> Result { - let mut parent = SpendConditions::new(); + let mut parent = Conditions::new(); let intermediate_puzzle = ctx.nft_intermediate_launcher()?; @@ -72,12 +69,12 @@ impl IntermediateLauncher { args: NftIntermediateLauncherArgs::new(self.mint_number, self.mint_total), })?; - parent = parent.create_coin(ctx, self.intermediate_coin.puzzle_hash, 0)?; + parent = parent.create_coin(self.intermediate_coin.puzzle_hash, 0); let puzzle_reveal = ctx.serialize(&puzzle)?; let solution = ctx.serialize(&())?; - ctx.spend(CoinSpend::new( + ctx.insert_coin_spend(CoinSpend::new( self.intermediate_coin, puzzle_reveal, solution, @@ -87,15 +84,12 @@ impl IntermediateLauncher { index_message.update(usize_to_bytes(self.mint_number)); index_message.update(usize_to_bytes(self.mint_total)); - parent = parent.assert_coin_announcement( - ctx, - self.intermediate_coin.coin_id(), - index_message.finalize(), - )?; - Ok(SpendableLauncher::with_parent_conditions( self.launcher_coin, - parent, + parent.assert_coin_announcement( + self.intermediate_coin.coin_id(), + index_message.finalize(), + ), )) } } diff --git a/crates/chia-sdk-driver/src/puzzles/singleton/launcher.rs b/crates/chia-sdk-driver/src/puzzles/singleton/launcher.rs index 499eb2da..920c4f2f 100644 --- a/crates/chia-sdk-driver/src/puzzles/singleton/launcher.rs +++ b/crates/chia-sdk-driver/src/puzzles/singleton/launcher.rs @@ -3,10 +3,7 @@ use chia_protocol::{Bytes32, Coin}; use chia_puzzles::singleton::SINGLETON_LAUNCHER_PUZZLE_HASH; -use crate::{ - spend_builder::{P2Spend, SpendConditions}, - SpendContext, SpendError, -}; +use crate::Conditions; use super::SpendableLauncher; @@ -39,33 +36,24 @@ impl Launcher { /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin. /// By default, no hint is used when creating the coin. To specify a hint, use [`Launcher::create_hinted`]. - pub fn create(self, ctx: &mut SpendContext<'_>) -> Result { - Ok(SpendableLauncher::with_parent_conditions( + pub fn create(self) -> SpendableLauncher { + SpendableLauncher::with_parent_conditions( self.coin, - SpendConditions::new().create_coin( - ctx, - SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - self.coin.amount, - )?, - )) + Conditions::new().create_coin(SINGLETON_LAUNCHER_PUZZLE_HASH.into(), self.coin.amount), + ) } /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin. /// The created launcher coin will be hinted to make identifying it easier later. - pub fn create_hinted( - self, - ctx: &mut SpendContext<'_>, - hint: Bytes32, - ) -> Result { - Ok(SpendableLauncher::with_parent_conditions( + pub fn create_hinted(self, hint: Bytes32) -> SpendableLauncher { + SpendableLauncher::with_parent_conditions( self.coin, - SpendConditions::new().create_hinted_coin( - ctx, + Conditions::new().create_hinted_coin( SINGLETON_LAUNCHER_PUZZLE_HASH.into(), self.coin.amount, hint, - )?, - )) + ), + ) } /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin. @@ -73,18 +61,11 @@ impl Launcher { /// /// This method is used to create the launcher coin immediately from the parent, then spend it later attached to any coin spend. /// For example, this is useful for minting NFTs from intermediate coins created with an earlier instance of a DID. - pub fn create_now( - self, - ctx: &mut SpendContext<'_>, - ) -> Result<(SpendConditions, SpendableLauncher), SpendError> { - Ok(( - SpendConditions::new().create_coin( - ctx, - SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - self.coin.amount, - )?, - SpendableLauncher::with_parent_conditions(self.coin, SpendConditions::new()), - )) + pub fn create_now(self) -> (Conditions, SpendableLauncher) { + ( + Conditions::new().create_coin(SINGLETON_LAUNCHER_PUZZLE_HASH.into(), self.coin.amount), + SpendableLauncher::with_parent_conditions(self.coin, Conditions::new()), + ) } /// The parent coin specified when constructing the [`Launcher`] will create the launcher coin. @@ -92,19 +73,14 @@ impl Launcher { /// /// This method is used to create the launcher coin immediately from the parent, then spend it later attached to any coin spend. /// For example, this is useful for minting NFTs from intermediate coins created with an earlier instance of a DID. - pub fn create_hinted_now( - self, - ctx: &mut SpendContext<'_>, - hint: Bytes32, - ) -> Result<(SpendConditions, SpendableLauncher), SpendError> { - Ok(( - SpendConditions::new().create_hinted_coin( - ctx, + pub fn create_hinted_now(self, hint: Bytes32) -> (Conditions, SpendableLauncher) { + ( + Conditions::new().create_hinted_coin( SINGLETON_LAUNCHER_PUZZLE_HASH.into(), self.coin.amount, hint, - )?, - SpendableLauncher::with_parent_conditions(self.coin, SpendConditions::new()), - )) + ), + SpendableLauncher::with_parent_conditions(self.coin, Conditions::new()), + ) } } diff --git a/crates/chia-sdk-driver/src/puzzles/singleton/singleton_spend.rs b/crates/chia-sdk-driver/src/puzzles/singleton/singleton_spend.rs index efcd5f64..045752e6 100644 --- a/crates/chia-sdk-driver/src/puzzles/singleton/singleton_spend.rs +++ b/crates/chia-sdk-driver/src/puzzles/singleton/singleton_spend.rs @@ -1,34 +1,24 @@ use chia_protocol::{Bytes32, Coin, CoinSpend}; use chia_puzzles::{ - singleton::{ - SingletonArgs, SingletonSolution, SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, - SINGLETON_TOP_LAYER_PUZZLE_HASH, - }, + singleton::{SingletonArgs, SingletonSolution}, Proof, }; use clvm_utils::CurriedProgram; -use crate::{spend_builder::InnerSpend, SpendContext, SpendError}; +use crate::{Spend, SpendContext, SpendError}; pub fn spend_singleton( ctx: &mut SpendContext<'_>, coin: Coin, launcher_id: Bytes32, lineage_proof: Proof, - inner_spend: InnerSpend, + inner_spend: Spend, ) -> Result { let singleton_puzzle = ctx.singleton_top_layer()?; let puzzle_reveal = ctx.serialize(&CurriedProgram { program: singleton_puzzle, - args: SingletonArgs { - singleton_struct: SingletonStruct { - mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), - launcher_id, - launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - }, - inner_puzzle: inner_spend.puzzle(), - }, + args: SingletonArgs::new(launcher_id, inner_spend.puzzle()), })?; let solution = ctx.serialize(&SingletonSolution { diff --git a/crates/chia-sdk-driver/src/puzzles/singleton/spendable_launcher.rs b/crates/chia-sdk-driver/src/puzzles/singleton/spendable_launcher.rs index 6fa86a77..6f8020fc 100644 --- a/crates/chia-sdk-driver/src/puzzles/singleton/spendable_launcher.rs +++ b/crates/chia-sdk-driver/src/puzzles/singleton/spendable_launcher.rs @@ -2,31 +2,27 @@ use chia_protocol::{Bytes32, Coin, CoinSpend, Program}; use chia_puzzles::singleton::{ - LauncherSolution, SingletonArgs, SingletonStruct, SINGLETON_LAUNCHER_PUZZLE, - SINGLETON_TOP_LAYER_PUZZLE_HASH, + LauncherSolution, SingletonArgs, SINGLETON_LAUNCHER_PUZZLE, SINGLETON_TOP_LAYER_PUZZLE_HASH, }; use clvm_traits::{clvm_list, ToClvm}; use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use clvmr::NodePtr; -use crate::{ - spend_builder::{P2Spend, SpendConditions}, - SpendContext, SpendError, -}; +use crate::{Conditions, SpendContext, SpendError}; /// A singleton launcher that is ready to be spent to create the eve singleton. See [`crate::Launcher`] for more information. #[must_use] #[derive(Debug, Clone)] pub struct SpendableLauncher { coin: Coin, - parent: SpendConditions, + parent: Conditions, } impl SpendableLauncher { /// Creates a new [`SpendableLauncher`] with the specified launcher coin and parent conditions. /// This is used internally by [`crate::Launcher::create`] and [`crate::IntermediateLauncher::create`]. /// You should not need to use this directly. - pub fn with_parent_conditions(coin: Coin, parent: SpendConditions) -> Self { + pub fn with_parent_conditions(coin: Coin, parent: Conditions) -> Self { Self { coin, parent } } @@ -43,16 +39,16 @@ impl SpendableLauncher { ctx: &mut SpendContext<'_>, singleton_inner_puzzle_hash: Bytes32, key_value_list: T, - ) -> Result<(SpendConditions, Coin), SpendError> + ) -> Result<(Conditions, Coin), SpendError> where T: ToClvm, { let singleton_puzzle_hash = CurriedProgram { program: SINGLETON_TOP_LAYER_PUZZLE_HASH, - args: SingletonArgs { - singleton_struct: SingletonStruct::new(self.coin.coin_id()), - inner_puzzle: TreeHash::from(singleton_inner_puzzle_hash), - }, + args: SingletonArgs::new( + self.coin.coin_id(), + TreeHash::from(singleton_inner_puzzle_hash), + ), } .tree_hash() .into(); @@ -64,9 +60,9 @@ impl SpendableLauncher { ))?; let eve_message_hash = ctx.tree_hash(eve_message); - self.parent = - self.parent - .assert_coin_announcement(ctx, self.coin.coin_id(), eve_message_hash)?; + self.parent = self + .parent + .assert_coin_announcement(self.coin.coin_id(), eve_message_hash); let solution = ctx.serialize(&LauncherSolution { singleton_puzzle_hash, @@ -74,7 +70,7 @@ impl SpendableLauncher { key_value_list, })?; - ctx.spend(CoinSpend::new( + ctx.insert_coin_spend(CoinSpend::new( self.coin, Program::from(SINGLETON_LAUNCHER_PUZZLE.to_vec()), solution, diff --git a/crates/chia-sdk-driver/src/puzzles/standard.rs b/crates/chia-sdk-driver/src/puzzles/standard.rs index 7cbeb716..20bf6744 100644 --- a/crates/chia-sdk-driver/src/puzzles/standard.rs +++ b/crates/chia-sdk-driver/src/puzzles/standard.rs @@ -1,72 +1,24 @@ use chia_bls::PublicKey; -use chia_protocol::{Coin, CoinSpend}; use chia_puzzles::standard::{StandardArgs, StandardSolution}; -use clvm_traits::clvm_quote; use clvm_utils::CurriedProgram; -use clvmr::NodePtr; -use crate::{ - spend_builder::{InnerSpend, P2Spend, SpendConditions}, - SpendContext, SpendError, -}; +use crate::{Conditions, Spend, SpendContext, SpendError}; -#[derive(Debug, Default, Clone)] -#[must_use] -pub struct StandardSpend { - conditions: Vec, -} - -impl StandardSpend { - pub fn new() -> Self { - Self::default() - } +pub fn p2_spend( + ctx: &mut SpendContext<'_>, + synthetic_key: PublicKey, + conditions: Conditions, +) -> Result { + let standard_puzzle = ctx.standard_puzzle()?; - #[allow(clippy::needless_pass_by_value)] - pub fn chain(mut self, chained: SpendConditions) -> Self { - self.conditions.extend(chained.parent_conditions()); - self - } + let puzzle = ctx.alloc(&CurriedProgram { + program: standard_puzzle, + args: StandardArgs::new(synthetic_key), + })?; - pub fn inner_spend( - self, - ctx: &mut SpendContext<'_>, - synthetic_key: PublicKey, - ) -> Result { - let standard_puzzle = ctx.standard_puzzle()?; + let solution = ctx.alloc(&StandardSolution::from_conditions(conditions))?; - let puzzle = ctx.alloc(&CurriedProgram { - program: standard_puzzle, - args: StandardArgs { synthetic_key }, - })?; - - let solution = ctx.alloc(&StandardSolution { - original_public_key: None, - delegated_puzzle: clvm_quote!(self.conditions), - solution: (), - })?; - - Ok(InnerSpend::new(puzzle, solution)) - } - - pub fn finish( - self, - ctx: &mut SpendContext<'_>, - coin: Coin, - synthetic_key: PublicKey, - ) -> Result<(), SpendError> { - let inner_spend = self.inner_spend(ctx, synthetic_key)?; - let puzzle_reveal = ctx.serialize(&inner_spend.puzzle())?; - let solution = ctx.serialize(&inner_spend.solution())?; - ctx.spend(CoinSpend::new(coin, puzzle_reveal, solution)); - Ok(()) - } -} - -impl P2Spend for StandardSpend { - fn raw_condition(mut self, condition: NodePtr) -> Self { - self.conditions.push(condition); - self - } + Ok(Spend::new(puzzle, solution)) } #[cfg(test)] @@ -90,9 +42,7 @@ mod tests { let mut allocator = Allocator::new(); let ctx = &mut SpendContext::new(&mut allocator); - StandardSpend::new() - .create_coin(ctx, puzzle_hash, 1)? - .finish(ctx, coin, pk)?; + ctx.spend_p2_coin(coin, pk, Conditions::new().create_coin(puzzle_hash, 1))?; test_transaction( &peer, diff --git a/crates/chia-sdk-driver/src/spend.rs b/crates/chia-sdk-driver/src/spend.rs new file mode 100644 index 00000000..1e911bea --- /dev/null +++ b/crates/chia-sdk-driver/src/spend.rs @@ -0,0 +1,22 @@ +use clvmr::NodePtr; + +#[derive(Debug, Clone, Copy)] +#[must_use] +pub struct Spend { + puzzle: NodePtr, + solution: NodePtr, +} + +impl Spend { + pub fn new(puzzle: NodePtr, solution: NodePtr) -> Self { + Self { puzzle, solution } + } + + pub fn puzzle(&self) -> NodePtr { + self.puzzle + } + + pub fn solution(&self) -> NodePtr { + self.solution + } +} diff --git a/crates/chia-sdk-driver/src/spend_builder.rs b/crates/chia-sdk-driver/src/spend_builder.rs deleted file mode 100644 index c98ec5a6..00000000 --- a/crates/chia-sdk-driver/src/spend_builder.rs +++ /dev/null @@ -1,259 +0,0 @@ -use chia_bls::PublicKey; -use chia_protocol::{Bytes, Bytes32}; -use chia_puzzles::cat::{EverythingWithSignatureTailArgs, GenesisByCoinIdTailArgs}; -use chia_sdk_types::conditions::{ - AssertBeforeHeightAbsolute, AssertBeforeHeightRelative, AssertBeforeSecondsAbsolute, - AssertBeforeSecondsRelative, AssertCoinAnnouncement, AssertHeightAbsolute, - AssertHeightRelative, AssertPuzzleAnnouncement, AssertSecondsAbsolute, AssertSecondsRelative, - CreateCoin, CreateCoinAnnouncement, CreatePuzzleAnnouncement, ReserveFee, RunTail, -}; -use clvm_traits::ToClvm; -use clvm_utils::CurriedProgram; -use clvmr::{ - sha2::{Digest, Sha256}, - NodePtr, -}; - -use crate::{SpendContext, SpendError}; - -pub trait P2Spend: Sized { - #[must_use] - fn raw_condition(self, condition: NodePtr) -> Self; - - fn reserve_fee(self, ctx: &mut SpendContext<'_>, fee: u64) -> Result { - Ok(self.raw_condition(ctx.alloc(&ReserveFee { amount: fee })?)) - } - - fn create_coin( - self, - ctx: &mut SpendContext<'_>, - puzzle_hash: Bytes32, - amount: u64, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&CreateCoin::new(puzzle_hash, amount))?)) - } - - fn create_hinted_coin( - self, - ctx: &mut SpendContext<'_>, - puzzle_hash: Bytes32, - amount: u64, - hint: Bytes32, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&CreateCoin::with_hint(puzzle_hash, amount, hint))?)) - } - - fn create_coin_announcement( - self, - ctx: &mut SpendContext<'_>, - message: Bytes, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&CreateCoinAnnouncement { message })?)) - } - - fn assert_raw_coin_announcement( - self, - ctx: &mut SpendContext<'_>, - announcement_id: Bytes32, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertCoinAnnouncement { announcement_id })?)) - } - - fn assert_coin_announcement( - self, - ctx: &mut SpendContext<'_>, - coin_id: Bytes32, - message: impl AsRef<[u8]>, - ) -> Result { - let mut announcement_id = Sha256::new(); - announcement_id.update(coin_id); - announcement_id.update(message); - self.assert_raw_coin_announcement(ctx, Bytes32::new(announcement_id.finalize().into())) - } - - fn create_puzzle_announcement( - self, - ctx: &mut SpendContext<'_>, - message: Bytes, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&CreatePuzzleAnnouncement { message })?)) - } - - fn assert_raw_puzzle_announcement( - self, - ctx: &mut SpendContext<'_>, - announcement_id: Bytes32, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertPuzzleAnnouncement { announcement_id })?)) - } - - fn assert_puzzle_announcement( - self, - ctx: &mut SpendContext<'_>, - puzzle_hash: Bytes32, - message: impl AsRef<[u8]>, - ) -> Result { - let mut announcement_id = Sha256::new(); - announcement_id.update(puzzle_hash); - announcement_id.update(message); - self.assert_raw_puzzle_announcement(ctx, Bytes32::new(announcement_id.finalize().into())) - } - - fn assert_before_seconds_relative( - self, - ctx: &mut SpendContext<'_>, - seconds: u64, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertBeforeSecondsRelative { seconds })?)) - } - - fn assert_seconds_relative( - self, - ctx: &mut SpendContext<'_>, - seconds: u64, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertSecondsRelative { seconds })?)) - } - - fn assert_before_seconds_absolute( - self, - ctx: &mut SpendContext<'_>, - seconds: u64, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertBeforeSecondsAbsolute { seconds })?)) - } - - fn assert_seconds_absolute( - self, - ctx: &mut SpendContext<'_>, - seconds: u64, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertSecondsAbsolute { seconds })?)) - } - - fn assert_before_height_relative( - self, - ctx: &mut SpendContext<'_>, - height: u32, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertBeforeHeightRelative { height })?)) - } - - fn assert_height_relative( - self, - ctx: &mut SpendContext<'_>, - height: u32, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertHeightRelative { height })?)) - } - - fn assert_before_height_absolute( - self, - ctx: &mut SpendContext<'_>, - height: u32, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertBeforeHeightAbsolute { height })?)) - } - - fn assert_height_absolute( - self, - ctx: &mut SpendContext<'_>, - height: u32, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&AssertHeightAbsolute { height })?)) - } - - fn run_single_issuance_tail( - self, - ctx: &mut SpendContext<'_>, - genesis_coin_id: Bytes32, - ) -> Result { - let genesis_by_coin_id_tail_puzzle = ctx.genesis_by_coin_id_tail_puzzle()?; - - self.run_custom_tail( - ctx, - CurriedProgram { - program: genesis_by_coin_id_tail_puzzle, - args: GenesisByCoinIdTailArgs::new(genesis_coin_id), - }, - (), - ) - } - - fn run_multi_issuance_tail( - self, - ctx: &mut SpendContext<'_>, - issuance_key: PublicKey, - ) -> Result { - let everything_with_signature_tail_puzzle = ctx.everything_with_signature_tail_puzzle()?; - - self.run_custom_tail( - ctx, - CurriedProgram { - program: everything_with_signature_tail_puzzle, - args: EverythingWithSignatureTailArgs::new(issuance_key), - }, - (), - ) - } - - fn run_custom_tail( - self, - ctx: &mut SpendContext<'_>, - tail_program: impl ToClvm, - tail_solution: impl ToClvm, - ) -> Result { - Ok(self.raw_condition(ctx.alloc(&RunTail { - program: tail_program, - solution: tail_solution, - })?)) - } -} - -#[derive(Debug, Default, Clone)] -#[must_use] -pub struct SpendConditions { - conditions: Vec, -} - -impl SpendConditions { - pub fn new() -> Self { - Self::default() - } - - pub fn extend(&mut self, other: Self) { - self.conditions.extend(other.conditions); - } - - pub fn parent_conditions(&self) -> &[NodePtr] { - &self.conditions - } -} - -impl P2Spend for SpendConditions { - fn raw_condition(mut self, condition: NodePtr) -> Self { - self.conditions.push(condition); - self - } -} - -#[derive(Debug, Clone, Copy)] -#[must_use] -pub struct InnerSpend { - puzzle: NodePtr, - solution: NodePtr, -} - -impl InnerSpend { - pub const fn new(puzzle: NodePtr, solution: NodePtr) -> Self { - Self { puzzle, solution } - } - - pub const fn puzzle(&self) -> NodePtr { - self.puzzle - } - - pub const fn solution(&self) -> NodePtr { - self.solution - } -} diff --git a/crates/chia-sdk-driver/src/spend_context.rs b/crates/chia-sdk-driver/src/spend_context.rs index ddb484a4..1067be6c 100644 --- a/crates/chia-sdk-driver/src/spend_context.rs +++ b/crates/chia-sdk-driver/src/spend_context.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use chia_protocol::{CoinSpend, Program}; +use chia_bls::PublicKey; +use chia_protocol::{Bytes32, Coin, CoinSpend, Program}; use chia_puzzles::{ cat::{ CAT_PUZZLE, CAT_PUZZLE_HASH, EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, @@ -20,11 +21,18 @@ use chia_puzzles::{ }, standard::{STANDARD_PUZZLE, STANDARD_PUZZLE_HASH}, }; -use clvm_traits::{FromNodePtr, ToNodePtr}; +use chia_sdk_types::{ + conditions::NewNftOwner, + puzzles::{DidInfo, NftInfo}, +}; +use clvm_traits::{FromNodePtr, ToClvm, ToNodePtr}; use clvm_utils::{tree_hash, TreeHash}; use clvmr::{run_program, serde::node_from_bytes, Allocator, ChiaDialect, NodePtr}; -use crate::spend_error::SpendError; +use crate::{ + did_spend, nft_spend, p2_spend, recreate_did, spend_error::SpendError, transfer_nft, + Conditions, Spend, +}; /// A wrapper around `Allocator` that caches puzzles and simplifies coin spending. #[derive(Debug)] @@ -65,10 +73,18 @@ impl<'a> SpendContext<'a> { } /// Add a [`CoinSpend`] to the list. - pub fn spend(&mut self, coin_spend: CoinSpend) { + pub fn insert_coin_spend(&mut self, coin_spend: CoinSpend) { self.coin_spends.push(coin_spend); } + /// Serializes a [`Spend`] and adds it to the list of coin spends. + pub fn spend(&mut self, coin: Coin, spend: Spend) -> Result<(), SpendError> { + let puzzle_reveal = self.serialize(&spend.puzzle())?; + let solution = self.serialize(&spend.solution())?; + self.insert_coin_spend(CoinSpend::new(coin, puzzle_reveal, solution)); + Ok(()) + } + /// Allocate a new node and return its pointer. pub fn alloc(&mut self, value: &T) -> Result where @@ -207,4 +223,55 @@ impl<'a> SpendContext<'a> { Ok(puzzle) } } + + /// Spend a standard p2 coin. + pub fn spend_p2_coin( + &mut self, + coin: Coin, + synthetic_key: PublicKey, + conditions: Conditions, + ) -> Result<(), SpendError> { + let p2_spend = p2_spend(self, synthetic_key, conditions)?; + self.spend(coin, p2_spend) + } + + /// Spend a DID coin with a standard p2 inner puzzle. + pub fn spend_standard_did( + &mut self, + did_info: &DidInfo, + synthetic_key: PublicKey, + extra_conditions: Conditions, + ) -> Result, SpendError> + where + M: ToClvm + Clone, + { + let (conditions, new_did_info) = recreate_did(did_info.clone()); + let p2_spend = p2_spend(self, synthetic_key, conditions.extend(extra_conditions))?; + let did_spend = did_spend(self, did_info, p2_spend)?; + self.insert_coin_spend(did_spend); + Ok(new_did_info) + } + + /// Spend an NFT coin with a standard p2 inner puzzle. + pub fn spend_standard_nft( + &mut self, + nft_info: &NftInfo, + synthetic_key: PublicKey, + p2_puzzle_hash: Bytes32, + new_owner: Option, + extra_conditions: Conditions, + ) -> Result<(Conditions, NftInfo), SpendError> + where + M: ToClvm + Clone, + { + let transfer = transfer_nft(self, nft_info.clone(), p2_puzzle_hash, new_owner)?; + let p2_spend = p2_spend( + self, + synthetic_key, + transfer.p2_conditions.extend(extra_conditions), + )?; + let nft_spend = nft_spend(self, nft_info, p2_spend)?; + self.insert_coin_spend(nft_spend); + Ok((transfer.did_conditions, transfer.output)) + } } diff --git a/crates/chia-sdk-offers/src/offer_builder.rs b/crates/chia-sdk-offers/src/offer_builder.rs index 9b62354d..d8ef3135 100644 --- a/crates/chia-sdk-offers/src/offer_builder.rs +++ b/crates/chia-sdk-offers/src/offer_builder.rs @@ -203,7 +203,7 @@ mod tests { offer::{PaymentWithoutMemos, SETTLEMENT_PAYMENTS_PUZZLE_HASH}, standard::StandardArgs, }; - use chia_sdk_driver::{P2Spend, StandardSpend}; + use chia_sdk_driver::Conditions; use chia_sdk_test::{sign_transaction, Simulator}; use clvmr::Allocator; @@ -239,10 +239,13 @@ mod tests { })], )?; - StandardSpend::new() - .raw_condition(ctx.alloc(&announcement)?) - .create_coin(ctx, SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), a.amount)? - .finish(ctx, a, a_public_key)?; + ctx.spend_p2_coin( + a, + a_public_key, + Conditions::new() + .assert_raw_puzzle_announcement(announcement.announcement_id) + .create_coin(SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), a.amount), + )?; let coin_spends = ctx.take_spends(); let signature = sign_transaction( @@ -263,10 +266,13 @@ mod tests { })], )?; - StandardSpend::new() - .raw_condition(ctx.alloc(&announcement)?) - .create_coin(ctx, SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), b.amount)? - .finish(ctx, b, b_public_key)?; + ctx.spend_p2_coin( + b, + b_public_key, + Conditions::new() + .assert_raw_puzzle_announcement(announcement.announcement_id) + .create_coin(SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), b.amount), + )?; let coin_spends = ctx.take_spends(); let signature = sign_transaction( diff --git a/crates/chia-sdk-offers/src/settlement.rs b/crates/chia-sdk-offers/src/settlement.rs index 368458af..2e84d62e 100644 --- a/crates/chia-sdk-offers/src/settlement.rs +++ b/crates/chia-sdk-offers/src/settlement.rs @@ -1,6 +1,6 @@ use chia_protocol::{Coin, CoinSpend}; use chia_puzzles::offer::{NotarizedPayment, SettlementPaymentsSolution}; -use chia_sdk_driver::{InnerSpend, SpendContext, SpendError}; +use chia_sdk_driver::{Spend, SpendContext, SpendError}; #[derive(Debug, Clone)] #[must_use] @@ -13,19 +13,19 @@ impl SettlementSpend { Self { notarized_payments } } - pub fn inner_spend(self, ctx: &mut SpendContext<'_>) -> Result { + pub fn inner_spend(self, ctx: &mut SpendContext<'_>) -> Result { let puzzle = ctx.settlement_payments_puzzle()?; let solution = ctx.alloc(&SettlementPaymentsSolution { notarized_payments: self.notarized_payments, })?; - Ok(InnerSpend::new(puzzle, solution)) + Ok(Spend::new(puzzle, solution)) } pub fn finish(self, ctx: &mut SpendContext<'_>, coin: Coin) -> Result<(), SpendError> { let inner_spend = self.inner_spend(ctx)?; let puzzle_reveal = ctx.serialize(&inner_spend.puzzle())?; let solution = ctx.serialize(&inner_spend.solution())?; - ctx.spend(CoinSpend::new(coin, puzzle_reveal, solution)); + ctx.insert_coin_spend(CoinSpend::new(coin, puzzle_reveal, solution)); Ok(()) } } diff --git a/crates/chia-sdk-parser/src/conditions.rs b/crates/chia-sdk-parser/src/conditions.rs deleted file mode 100644 index dc26fc8e..00000000 --- a/crates/chia-sdk-parser/src/conditions.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::collections::HashSet; - -use chia_protocol::{Coin, CoinSpend}; -use chia_sdk_types::conditions::Condition; -use clvm_traits::{FromClvm, ToNodePtr}; -use clvmr::{reduction::Reduction, Allocator, NodePtr}; - -use crate::ParseError; - -pub fn parse_conditions( - allocator: &mut Allocator, - conditions: NodePtr, -) -> Result>, ParseError> { - Vec::::from_clvm(allocator, conditions)? - .into_iter() - .map(|condition| Ok(Condition::from_clvm(allocator, condition)?)) - .collect() -} - -pub fn run_puzzle( - allocator: &mut Allocator, - puzzle: NodePtr, - solution: NodePtr, -) -> Result { - let Reduction(_cost, output) = clvmr::run_program( - allocator, - &clvmr::ChiaDialect::new(0), - puzzle, - solution, - 11_000_000_000, - )?; - Ok(output) -} - -pub fn puzzle_conditions( - allocator: &mut Allocator, - puzzle: NodePtr, - solution: NodePtr, -) -> Result>, ParseError> { - let output = run_puzzle(allocator, puzzle, solution)?; - parse_conditions(allocator, output) -} - -pub fn non_ephemeral_coins(coin_spends: &[CoinSpend]) -> Result, ParseError> { - let mut allocator = Allocator::new(); - let mut created_coins = HashSet::new(); - - for coin_spend in coin_spends { - let puzzle = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; - let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; - let conditions = puzzle_conditions(&mut allocator, puzzle, solution)?; - - for condition in conditions { - if let Condition::CreateCoin(create_coin) = condition { - created_coins.insert(Coin::new( - coin_spend.coin.coin_id(), - create_coin.puzzle_hash, - create_coin.amount, - )); - } - } - } - - let non_ephemeral = coin_spends - .iter() - .map(|cs| cs.coin) - .filter(|coin| !created_coins.contains(coin)) - .collect(); - - Ok(non_ephemeral) -} - -#[cfg(test)] -mod tests { - use chia_protocol::{Bytes32, Program}; - use chia_sdk_types::conditions::CreateCoin; - use clvm_traits::{FromNodePtr, ToClvm}; - - use super::*; - - #[test] - fn test_non_ephemeral_coins() -> anyhow::Result<()> { - let mut allocator = Allocator::new(); - - let coins: Vec = (0..3) - .map(|amount| Coin::new(Bytes32::default(), Bytes32::default(), amount)) - .collect(); - - let puzzle = 1.to_clvm(&mut allocator)?; - let puzzle_reveal = Program::from_node_ptr(&allocator, puzzle)?; - let identity_solution = Program::from_node_ptr(&allocator, NodePtr::NIL)?; - - let mut coin_spends = Vec::new(); - - for i in 0..3 { - let create_coin = CreateCoin::new(Bytes32::new([i; 32]), u64::from(i)); - let solution = [&create_coin].to_clvm(&mut allocator)?; - - coin_spends.push(CoinSpend::new( - Coin::new( - coins[i as usize].coin_id(), - create_coin.puzzle_hash, - create_coin.amount, - ), - puzzle_reveal.clone(), - identity_solution.clone(), - )); - - coin_spends.push(CoinSpend::new( - coins[i as usize], - puzzle_reveal.clone(), - Program::from_node_ptr(&allocator, solution)?, - )); - } - - let non_ephemeral_coins = non_ephemeral_coins(&coin_spends)?; - assert_eq!(non_ephemeral_coins, coins); - - Ok(()) - } -} diff --git a/crates/chia-sdk-parser/src/error.rs b/crates/chia-sdk-parser/src/error.rs index 1b748c08..092f7d05 100644 --- a/crates/chia-sdk-parser/src/error.rs +++ b/crates/chia-sdk-parser/src/error.rs @@ -1,7 +1,9 @@ +use chia_sdk_types::conditions::ConditionError; use clvm_traits::{FromClvmError, ToClvmError}; use clvmr::reduction::EvalErr; use thiserror::Error; +// todo #[derive(Debug, Error)] pub enum ParseError { #[error("failed to serialize clvm value: {0}")] @@ -10,6 +12,9 @@ pub enum ParseError { #[error("failed to deserialize clvm value: {0}")] FromClvm(#[from] FromClvmError), + #[error("failed to parse conditions: {0}")] + Conditions(#[from] ConditionError), + #[error("clvm eval error: {0}")] Eval(#[from] EvalErr), diff --git a/crates/chia-sdk-parser/src/lib.rs b/crates/chia-sdk-parser/src/lib.rs index 8a365bec..a4f20cf4 100644 --- a/crates/chia-sdk-parser/src/lib.rs +++ b/crates/chia-sdk-parser/src/lib.rs @@ -1,9 +1,7 @@ -mod conditions; mod curried_puzzle; mod error; mod puzzles; -pub use conditions::*; pub use curried_puzzle::*; pub use error::*; pub use puzzles::*; diff --git a/crates/chia-sdk-parser/src/puzzles/cat.rs b/crates/chia-sdk-parser/src/puzzles/cat.rs index 865c0742..7b5e21b8 100644 --- a/crates/chia-sdk-parser/src/puzzles/cat.rs +++ b/crates/chia-sdk-parser/src/puzzles/cat.rs @@ -1,154 +1,153 @@ -use chia_protocol::{Bytes32, Coin}; -use chia_puzzles::{ - cat::{CatArgs, CatSolution, CAT_PUZZLE_HASH}, - LineageProof, -}; -use chia_sdk_types::{ - conditions::{Condition, CreateCoin}, - puzzles::CatInfo, -}; -use clvm_traits::FromClvm; -use clvmr::{Allocator, NodePtr}; - -use crate::{puzzle_conditions, ParseError, Puzzle}; - -#[derive(Debug, Clone, Copy)] -pub struct CatPuzzle { - pub asset_id: Bytes32, - pub inner_puzzle: Puzzle, -} - -impl CatPuzzle { - pub fn parse(allocator: &Allocator, puzzle: &Puzzle) -> Result, ParseError> { - let Some(puzzle) = puzzle.as_curried() else { - return Ok(None); - }; - - if puzzle.mod_hash != CAT_PUZZLE_HASH { - return Ok(None); - } - - let args = CatArgs::::from_clvm(allocator, puzzle.args)?; - - if args.mod_hash != CAT_PUZZLE_HASH.into() { - return Err(ParseError::InvalidModHash); - } - - Ok(Some(CatPuzzle { - asset_id: args.asset_id, - inner_puzzle: Puzzle::parse(allocator, args.inner_puzzle), - })) - } - - pub fn p2_outputs( - &self, - allocator: &mut Allocator, - solution: NodePtr, - ) -> Result, ParseError> { - let solution = CatSolution::::from_clvm(allocator, solution)?; - - let conditions = puzzle_conditions( - allocator, - self.inner_puzzle.ptr(), - solution.inner_puzzle_solution, - )?; - - let create_coins = conditions - .into_iter() - .filter_map(|condition| match condition { - Condition::CreateCoin(create_coin) => Some(create_coin), - _ => None, - }) - .collect(); - - Ok(create_coins) - } - - pub fn child_coin_info( - &self, - allocator: &mut Allocator, - parent_coin: Coin, - child_coin: Coin, - solution: NodePtr, - ) -> Result { - let create_coin = self - .p2_outputs(allocator, solution)? - .into_iter() - .find(|create_coin| { - let cat_puzzle_hash = - CatArgs::curry_tree_hash(self.asset_id, create_coin.puzzle_hash.into()); - - cat_puzzle_hash == child_coin.puzzle_hash.into() - && create_coin.amount == child_coin.amount - }) - .ok_or(ParseError::MissingChild)?; - - Ok(CatInfo { - asset_id: self.asset_id, - p2_puzzle_hash: create_coin.puzzle_hash, - coin: child_coin, - lineage_proof: LineageProof { - parent_parent_coin_id: parent_coin.parent_coin_info, - parent_inner_puzzle_hash: self.inner_puzzle.curried_puzzle_hash().into(), - parent_amount: parent_coin.amount, - }, - }) - } -} - -#[cfg(test)] -mod tests { - use chia_bls::PublicKey; - use chia_protocol::Coin; - use chia_puzzles::standard::StandardArgs; - use chia_sdk_driver::{IssueCat, P2Spend, SpendContext, StandardSpend}; - use clvm_traits::ToNodePtr; - - use super::*; - - #[test] - fn test_parse_cat() -> anyhow::Result<()> { - let mut allocator = Allocator::new(); - let ctx = &mut SpendContext::new(&mut allocator); - - let pk = PublicKey::default(); - let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); - let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); - - let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) - .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? - .multi_issuance(ctx, pk, 1)?; - - let cat_puzzle_hash = CatArgs::curry_tree_hash(issuance_info.asset_id, puzzle_hash.into()); - - let cat_info = CatInfo { - asset_id: issuance_info.asset_id, - p2_puzzle_hash: puzzle_hash, - coin: Coin::new(issuance_info.eve_coin.coin_id(), cat_puzzle_hash.into(), 1), - lineage_proof: issuance_info.lineage_proof, - }; - - StandardSpend::new() - .chain(issue_cat) - .finish(ctx, parent, pk)?; - - let coin_spends = ctx.take_spends(); - - let coin_spend = coin_spends - .into_iter() - .find(|cs| cs.coin.coin_id() == issuance_info.eve_coin.coin_id()) - .unwrap(); - - let puzzle_ptr = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; - let solution_ptr = coin_spend.solution.to_node_ptr(&mut allocator)?; - - let puzzle = Puzzle::parse(&allocator, puzzle_ptr); - let cat = CatPuzzle::parse(&allocator, &puzzle)?.expect("not a cat puzzle"); - let parsed_cat_info = - cat.child_coin_info(&mut allocator, coin_spend.coin, cat_info.coin, solution_ptr)?; - - assert_eq!(parsed_cat_info, cat_info); - - Ok(()) - } -} +// use chia_protocol::{Bytes32, Coin}; +// use chia_puzzles::{ +// cat::{CatArgs, CatSolution, CAT_PUZZLE_HASH}, +// LineageProof, +// }; +// use chia_sdk_types::{ +// conditions::{Condition, CreateCoin}, +// puzzles::CatInfo, +// }; +// use clvm_traits::FromClvm; +// use clvmr::{Allocator, NodePtr}; + +// use crate::{puzzle_conditions, ParseError, Puzzle}; + +// #[derive(Debug, Clone, Copy)] +// pub struct CatPuzzle { +// pub asset_id: Bytes32, +// pub inner_puzzle: Puzzle, +// } + +// impl CatPuzzle { +// pub fn parse(allocator: &Allocator, puzzle: &Puzzle) -> Result, ParseError> { +// let Some(puzzle) = puzzle.as_curried() else { +// return Ok(None); +// }; + +// if puzzle.mod_hash != CAT_PUZZLE_HASH { +// return Ok(None); +// } + +// let args = CatArgs::::from_clvm(allocator, puzzle.args)?; + +// if args.mod_hash != CAT_PUZZLE_HASH.into() { +// return Err(ParseError::InvalidModHash); +// } + +// Ok(Some(CatPuzzle { +// asset_id: args.asset_id, +// inner_puzzle: Puzzle::parse(allocator, args.inner_puzzle), +// })) +// } + +// pub fn p2_outputs( +// &self, +// allocator: &mut Allocator, +// solution: NodePtr, +// ) -> Result, ParseError> { +// let solution = CatSolution::::from_clvm(allocator, solution)?; + +// let conditions = puzzle_conditions( +// allocator, +// self.inner_puzzle.ptr(), +// solution.inner_puzzle_solution, +// )?; + +// let create_coins = conditions +// .into_iter() +// .filter_map(|condition| match condition { +// Condition::CreateCoin(create_coin) => Some(create_coin), +// _ => None, +// }) +// .collect(); + +// Ok(create_coins) +// } + +// pub fn child_coin_info( +// &self, +// allocator: &mut Allocator, +// parent_coin: Coin, +// child_coin: Coin, +// solution: NodePtr, +// ) -> Result { +// let create_coin = self +// .p2_outputs(allocator, solution)? +// .into_iter() +// .find(|create_coin| { +// let cat_puzzle_hash = +// CatArgs::curry_tree_hash(self.asset_id, create_coin.puzzle_hash.into()); + +// cat_puzzle_hash == child_coin.puzzle_hash.into() +// && create_coin.amount == child_coin.amount +// }) +// .ok_or(ParseError::MissingChild)?; + +// Ok(CatInfo { +// asset_id: self.asset_id, +// p2_puzzle_hash: create_coin.puzzle_hash, +// coin: child_coin, +// lineage_proof: LineageProof { +// parent_parent_coin_id: parent_coin.parent_coin_info, +// parent_inner_puzzle_hash: self.inner_puzzle.curried_puzzle_hash().into(), +// parent_amount: parent_coin.amount, +// }, +// }) +// } +// } + +// #[cfg(test)] +// mod tests { +// use chia_bls::PublicKey; +// use chia_protocol::Coin; +// use chia_puzzles::standard::StandardArgs; +// use clvm_traits::ToNodePtr; + +// use super::*; + +// #[test] +// fn test_parse_cat() -> anyhow::Result<()> { +// let mut allocator = Allocator::new(); +// let ctx = &mut SpendContext::new(&mut allocator); + +// let pk = PublicKey::default(); +// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); +// let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); + +// let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) +// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? +// .multi_issuance(ctx, pk, 1)?; + +// let cat_puzzle_hash = CatArgs::curry_tree_hash(issuance_info.asset_id, puzzle_hash.into()); + +// let cat_info = CatInfo { +// asset_id: issuance_info.asset_id, +// p2_puzzle_hash: puzzle_hash, +// coin: Coin::new(issuance_info.eve_coin.coin_id(), cat_puzzle_hash.into(), 1), +// lineage_proof: issuance_info.lineage_proof, +// }; + +// StandardSpend::new() +// .chain(issue_cat) +// .finish(ctx, parent, pk)?; + +// let coin_spends = ctx.take_spends(); + +// let coin_spend = coin_spends +// .into_iter() +// .find(|cs| cs.coin.coin_id() == issuance_info.eve_coin.coin_id()) +// .unwrap(); + +// let puzzle_ptr = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; +// let solution_ptr = coin_spend.solution.to_node_ptr(&mut allocator)?; + +// let puzzle = Puzzle::parse(&allocator, puzzle_ptr); +// let cat = CatPuzzle::parse(&allocator, &puzzle)?.expect("not a cat puzzle"); +// let parsed_cat_info = +// cat.child_coin_info(&mut allocator, coin_spend.coin, cat_info.coin, solution_ptr)?; + +// assert_eq!(parsed_cat_info, cat_info); + +// Ok(()) +// } +// } diff --git a/crates/chia-sdk-parser/src/puzzles/did.rs b/crates/chia-sdk-parser/src/puzzles/did.rs index 763594e2..6623a021 100644 --- a/crates/chia-sdk-parser/src/puzzles/did.rs +++ b/crates/chia-sdk-parser/src/puzzles/did.rs @@ -5,14 +5,14 @@ use chia_puzzles::{ Proof, }; use chia_sdk_types::{ - conditions::{Condition, CreateCoin}, + conditions::{puzzle_conditions, Condition, CreateCoin}, puzzles::DidInfo, }; use clvm_traits::FromClvm; use clvm_utils::{tree_hash, CurriedProgram, ToTreeHash, TreeHash}; use clvmr::{Allocator, NodePtr}; -use crate::{puzzle_conditions, ParseError, Puzzle, SingletonPuzzle}; +use crate::{ParseError, Puzzle, SingletonPuzzle}; #[derive(Debug, Clone, Copy)] pub struct DidPuzzle { @@ -129,7 +129,7 @@ mod tests { use chia_bls::PublicKey; use chia_protocol::Coin; use chia_puzzles::{singleton::SingletonSolution, standard::StandardArgs}; - use chia_sdk_driver::{CreateDid, Launcher, SpendContext, StandardSpend}; + use chia_sdk_driver::{CreateDid, Launcher, SpendContext}; use clvm_traits::ToNodePtr; #[test] @@ -142,12 +142,10 @@ mod tests { let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; - StandardSpend::new() - .chain(create_did) - .finish(ctx, parent, pk)?; + ctx.spend_p2_coin(parent, pk, create_did)?; let coin_spends = ctx.take_spends(); diff --git a/crates/chia-sdk-parser/src/puzzles/nft.rs b/crates/chia-sdk-parser/src/puzzles/nft.rs index abd99cdb..17f8252a 100644 --- a/crates/chia-sdk-parser/src/puzzles/nft.rs +++ b/crates/chia-sdk-parser/src/puzzles/nft.rs @@ -10,14 +10,14 @@ use chia_puzzles::{ Proof, }; use chia_sdk_types::{ - conditions::{Condition, CreateCoin, NewNftOwner}, + conditions::{puzzle_conditions, Condition, CreateCoin, NewNftOwner}, puzzles::NftInfo, }; use clvm_traits::FromClvm; use clvm_utils::tree_hash; use clvmr::{Allocator, NodePtr}; -use crate::{puzzle_conditions, ParseError, Puzzle, SingletonPuzzle}; +use crate::{ParseError, Puzzle, SingletonPuzzle}; #[derive(Debug, Clone, Copy)] pub struct NftPuzzle { @@ -184,9 +184,7 @@ mod tests { use chia_bls::PublicKey; use chia_protocol::{Bytes32, Coin}; use chia_puzzles::{singleton::SingletonSolution, standard::StandardArgs}; - use chia_sdk_driver::{ - CreateDid, Launcher, MintNft, OwnerDid, SpendContext, StandardMint, StandardSpend, - }; + use chia_sdk_driver::{CreateDid, Launcher, MintNft, OwnerDid, SpendContext, StandardMint}; use clvm_traits::ToNodePtr; #[test] @@ -199,11 +197,11 @@ mod tests { let parent = Coin::new(Bytes32::default(), puzzle_hash, 2); let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) - .create(ctx)? + .create() .create_standard_did(ctx, pk)?; let (mint_nft, nft_info) = Launcher::new(did_info.coin.coin_id(), 1) - .create(ctx)? + .create() .mint_standard_nft( ctx, StandardMint { @@ -219,10 +217,7 @@ mod tests { }, )?; - StandardSpend::new() - .chain(create_did) - .chain(mint_nft) - .finish(ctx, parent, pk)?; + ctx.spend_p2_coin(parent, pk, create_did.extend(mint_nft))?; let coin_spends = ctx.take_spends(); diff --git a/crates/chia-sdk-signer/Cargo.toml b/crates/chia-sdk-signer/Cargo.toml index ed57bbef..bb36ba45 100644 --- a/crates/chia-sdk-signer/Cargo.toml +++ b/crates/chia-sdk-signer/Cargo.toml @@ -21,7 +21,6 @@ clvm-traits = { workspace = true } clvmr = { workspace = true } thiserror = { workspace = true } chia-sdk-types = { workspace = true } -chia-sdk-parser = { workspace = true } [dev-dependencies] chia-puzzles = { workspace = true } diff --git a/crates/chia-sdk-signer/src/error.rs b/crates/chia-sdk-signer/src/error.rs index 85199a84..3377b45d 100644 --- a/crates/chia-sdk-signer/src/error.rs +++ b/crates/chia-sdk-signer/src/error.rs @@ -1,11 +1,11 @@ -use chia_sdk_parser::ParseError; +use chia_sdk_types::conditions::ConditionError; use clvm_traits::ToClvmError; use thiserror::Error; #[derive(Debug, Error)] pub enum SignerError { - #[error("parse error: {0}")] - Parse(#[from] ParseError), + #[error("condition error: {0}")] + Condition(#[from] ConditionError), #[error("clvm error")] ToClvm(#[from] ToClvmError), diff --git a/crates/chia-sdk-signer/src/required_signature.rs b/crates/chia-sdk-signer/src/required_signature.rs index 61d3df02..2feb560c 100644 --- a/crates/chia-sdk-signer/src/required_signature.rs +++ b/crates/chia-sdk-signer/src/required_signature.rs @@ -1,9 +1,6 @@ -#![allow(clippy::missing_const_for_fn)] - use chia_bls::PublicKey; use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend}; -use chia_sdk_parser::puzzle_conditions; -use chia_sdk_types::conditions::{AggSig, AggSigKind, Condition}; +use chia_sdk_types::conditions::{puzzle_conditions, AggSig, AggSigKind, Condition}; use clvm_traits::ToNodePtr; use clvmr::{ sha2::{Digest, Sha256}, diff --git a/crates/chia-sdk-types/Cargo.toml b/crates/chia-sdk-types/Cargo.toml index 9879c4a2..5a6df55e 100644 --- a/crates/chia-sdk-types/Cargo.toml +++ b/crates/chia-sdk-types/Cargo.toml @@ -20,7 +20,9 @@ chia-protocol = { workspace = true } chia-puzzles = { workspace = true } clvm-traits = { workspace = true } clvmr = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] hex = { workspace = true } hex-literal = { workspace = true } +anyhow = { workspace = true } diff --git a/crates/chia-sdk-types/src/conditions.rs b/crates/chia-sdk-types/src/conditions.rs index b1216240..f833034d 100644 --- a/crates/chia-sdk-types/src/conditions.rs +++ b/crates/chia-sdk-types/src/conditions.rs @@ -1,3 +1,13 @@ +use std::collections::HashSet; + +use chia_protocol::{Coin, CoinSpend}; +use clvm_traits::{FromClvm, FromClvmError, ToClvm, ToClvmError, ToNodePtr}; +use clvmr::{ + reduction::{EvalErr, Reduction}, + Allocator, NodePtr, +}; +use thiserror::Error; + mod agg_sig; mod announcements; mod coin_info; @@ -14,8 +24,17 @@ pub use output::*; pub use puzzles::*; pub use time::*; -use clvm_traits::{FromClvm, ToClvm}; -use clvmr::NodePtr; +#[derive(Debug, Error)] +pub enum ConditionError { + #[error("eval error: {0}")] + Eval(#[from] EvalErr), + + #[error("to clvm error: {0}")] + ToClvm(#[from] ToClvmError), + + #[error("from clvm error: {0}")] + FromClvm(#[from] FromClvmError), +} #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[clvm(transparent)] @@ -48,3 +67,115 @@ pub enum Condition { Softfork(Softfork), Other(T), } + +pub fn parse_conditions( + allocator: &mut Allocator, + conditions: NodePtr, +) -> Result>, ConditionError> { + Vec::::from_clvm(allocator, conditions)? + .into_iter() + .map(|condition| Ok(Condition::from_clvm(allocator, condition)?)) + .collect() +} + +pub fn run_puzzle( + allocator: &mut Allocator, + puzzle: NodePtr, + solution: NodePtr, +) -> Result { + let Reduction(_cost, output) = clvmr::run_program( + allocator, + &clvmr::ChiaDialect::new(0), + puzzle, + solution, + 11_000_000_000, + )?; + Ok(output) +} + +pub fn puzzle_conditions( + allocator: &mut Allocator, + puzzle: NodePtr, + solution: NodePtr, +) -> Result>, ConditionError> { + let output = run_puzzle(allocator, puzzle, solution)?; + parse_conditions(allocator, output) +} + +pub fn non_ephemeral_coins(coin_spends: &[CoinSpend]) -> Result, ConditionError> { + let mut allocator = Allocator::new(); + let mut created_coins = HashSet::new(); + + for coin_spend in coin_spends { + let puzzle = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; + let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; + let conditions = puzzle_conditions(&mut allocator, puzzle, solution)?; + + for condition in conditions { + if let Condition::CreateCoin(create_coin) = condition { + created_coins.insert(Coin::new( + coin_spend.coin.coin_id(), + create_coin.puzzle_hash, + create_coin.amount, + )); + } + } + } + + let non_ephemeral = coin_spends + .iter() + .map(|cs| cs.coin) + .filter(|coin| !created_coins.contains(coin)) + .collect(); + + Ok(non_ephemeral) +} + +#[cfg(test)] +mod tests { + use super::*; + + use chia_protocol::{Bytes32, Program}; + use clvm_traits::{FromNodePtr, ToClvm}; + + #[test] + fn test_non_ephemeral_coins() -> anyhow::Result<()> { + let mut allocator = Allocator::new(); + + let coins: Vec = (0..3) + .map(|amount| Coin::new(Bytes32::default(), Bytes32::default(), amount)) + .collect(); + + let puzzle = 1.to_clvm(&mut allocator)?; + let puzzle_reveal = Program::from_node_ptr(&allocator, puzzle)?; + let identity_solution = Program::from_node_ptr(&allocator, NodePtr::NIL)?; + + let mut coin_spends = Vec::new(); + + for i in 0..3 { + let create_coin = CreateCoin::new(Bytes32::new([i; 32]), u64::from(i)); + let solution = [&create_coin].to_clvm(&mut allocator)?; + + coin_spends.push(CoinSpend::new( + Coin::new( + coins[i as usize].coin_id(), + create_coin.puzzle_hash, + create_coin.amount, + ), + puzzle_reveal.clone(), + identity_solution.clone(), + )); + + coin_spends.push(CoinSpend::new( + coins[i as usize], + puzzle_reveal.clone(), + Program::from_node_ptr(&allocator, solution)?, + )); + } + + let non_ephemeral_coins = non_ephemeral_coins(&coin_spends)?; + assert_eq!(non_ephemeral_coins, coins); + + Ok(()) + } +} From b970ff6c5a4a179e8dc4d4fb45476decaf734398 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 9 Jun 2024 20:16:34 -0400 Subject: [PATCH 2/4] CAT stuff --- crates/chia-sdk-driver/src/puzzles.rs | 4 +- .../src/puzzles/cat/cat_spend.rs | 583 +++++++++--------- .../src/puzzles/cat/issue_cat.rs | 428 +++++++------ crates/chia-sdk-parser/src/puzzles/cat.rs | 307 ++++----- 4 files changed, 669 insertions(+), 653 deletions(-) diff --git a/crates/chia-sdk-driver/src/puzzles.rs b/crates/chia-sdk-driver/src/puzzles.rs index 0d0d612c..9ca73871 100644 --- a/crates/chia-sdk-driver/src/puzzles.rs +++ b/crates/chia-sdk-driver/src/puzzles.rs @@ -1,10 +1,10 @@ -// mod cat; +mod cat; mod did; mod nft; mod singleton; mod standard; -// pub use cat::*; +pub use cat::*; pub use did::*; pub use nft::*; pub use singleton::*; diff --git a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs index b4df5a52..4db0edd2 100644 --- a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs +++ b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs @@ -1,280 +1,303 @@ -// use chia_protocol::{Bytes32, Coin, CoinSpend}; -// use chia_puzzles::{ -// cat::{CatArgs, CatSolution, CoinProof, CAT_PUZZLE_HASH}, -// LineageProof, -// }; -// use chia_sdk_types::conditions::CreateCoin; -// use clvm_utils::CurriedProgram; -// use clvmr::NodePtr; - -// use crate::{Spend, SpendContext, SpendError}; - -// #[derive(Debug, Default)] -// #[must_use] -// pub struct CatSpend { -// asset_id: Bytes32, -// cat_spends: Vec, -// } - -// #[derive(Debug)] -// struct CatSpendItem { -// coin: Coin, -// inner_spend: Spend, -// lineage_proof: LineageProof, -// extra_delta: i64, -// } - -// impl CatSpend { -// pub const fn new(asset_id: Bytes32) -> Self { -// Self { -// asset_id, -// cat_spends: Vec::new(), -// } -// } - -// pub fn spend( -// mut self, -// coin: Coin, -// inner_spend: Spend, -// lineage_proof: LineageProof, -// extra_delta: i64, -// ) -> Self { -// self.cat_spends.push(CatSpendItem { -// coin, -// inner_spend, -// lineage_proof, -// extra_delta, -// }); -// self -// } - -// pub fn finish(self, ctx: &mut SpendContext<'_>) -> Result<(), SpendError> { -// let cat_puzzle_ptr = ctx.cat_puzzle()?; -// let len = self.cat_spends.len(); - -// let mut total_delta = 0; - -// for (index, item) in self.cat_spends.iter().enumerate() { -// let CatSpendItem { -// coin, -// inner_spend, -// lineage_proof, -// extra_delta, -// } = item; - -// // Calculate the delta and add it to the subtotal. -// let output = ctx.run(inner_spend.puzzle(), inner_spend.solution())?; -// let conditions: Vec = ctx.extract(output)?; - -// let create_coins = conditions -// .into_iter() -// .filter_map(|ptr| ctx.extract::(ptr).ok()); - -// let delta = create_coins.fold( -// i128::from(coin.amount) - i128::from(*extra_delta), -// |delta, create_coin| delta - i128::from(create_coin.amount), -// ); - -// let prev_subtotal = total_delta; -// total_delta += delta; - -// // Find information of neighboring coins on the ring. -// let prev_cat = &self.cat_spends[if index == 0 { len - 1 } else { index - 1 }]; -// let next_cat = &self.cat_spends[if index == len - 1 { 0 } else { index + 1 }]; - -// let puzzle_reveal = ctx.serialize(&CurriedProgram { -// program: cat_puzzle_ptr, -// args: CatArgs { -// mod_hash: CAT_PUZZLE_HASH.into(), -// asset_id: self.asset_id, -// inner_puzzle: inner_spend.puzzle(), -// }, -// })?; - -// let solution = ctx.serialize(&CatSolution { -// inner_puzzle_solution: inner_spend.solution(), -// lineage_proof: Some(*lineage_proof), -// prev_coin_id: prev_cat.coin.coin_id(), -// this_coin_info: *coin, -// next_coin_proof: CoinProof { -// parent_coin_info: next_cat.coin.parent_coin_info, -// inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()).into(), -// amount: next_cat.coin.amount, -// }, -// prev_subtotal: prev_subtotal.try_into()?, -// extra_delta: *extra_delta, -// })?; - -// ctx.spend(CoinSpend::new(*coin, puzzle_reveal, solution)); -// } - -// Ok(()) -// } -// } - -// #[cfg(test)] -// mod tests { -// use chia_puzzles::standard::StandardArgs; -// use chia_sdk_test::{test_transaction, Simulator}; -// use clvmr::Allocator; - -// use crate::puzzles::{IssueCat, StandardSpend}; - -// use super::*; - -// #[tokio::test] -// async fn test_cat_spend_multi() -> anyhow::Result<()> { -// let sim = Simulator::new().await?; -// let peer = sim.connect().await?; - -// let sk = sim.secret_key().await?; -// let pk = sk.public_key(); - -// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); -// let coin = sim.mint_coin(puzzle_hash, 6).await; - -// let mut allocator = Allocator::new(); -// let ctx = &mut SpendContext::new(&mut allocator); - -// let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) -// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? -// .create_hinted_coin(ctx, puzzle_hash, 2, puzzle_hash)? -// .create_hinted_coin(ctx, puzzle_hash, 3, puzzle_hash)? -// .single_issuance(ctx, 6)?; - -// StandardSpend::new() -// .chain(issue_cat) -// .finish(ctx, coin, pk)?; - -// let cat_puzzle_hash = -// CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); - -// CatSpend::new(issuance.asset_id) -// .spend( -// Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1), -// StandardSpend::new() -// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? -// .inner_spend(ctx, pk)?, -// issuance.lineage_proof, -// 0, -// ) -// .spend( -// Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 2), -// StandardSpend::new() -// .create_hinted_coin(ctx, puzzle_hash, 2, puzzle_hash)? -// .inner_spend(ctx, pk)?, -// issuance.lineage_proof, -// 0, -// ) -// .spend( -// Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 3), -// StandardSpend::new() -// .create_hinted_coin(ctx, puzzle_hash, 3, puzzle_hash)? -// .inner_spend(ctx, pk)?, -// issuance.lineage_proof, -// 0, -// ) -// .finish(ctx)?; - -// test_transaction( -// &peer, -// ctx.take_spends(), -// &[sk], -// sim.config().genesis_challenge, -// ) -// .await; - -// Ok(()) -// } - -// #[tokio::test] -// async fn test_cat_spend() -> anyhow::Result<()> { -// let sim = Simulator::new().await?; -// let peer = sim.connect().await?; - -// let sk = sim.secret_key().await?; -// let pk = sk.public_key(); - -// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); -// let coin = sim.mint_coin(puzzle_hash, 1).await; - -// let mut allocator = Allocator::new(); -// let ctx = &mut SpendContext::new(&mut allocator); - -// let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) -// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? -// .single_issuance(ctx, 1)?; - -// StandardSpend::new() -// .chain(issue_cat) -// .finish(ctx, coin, pk)?; - -// let inner_spend = StandardSpend::new() -// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? -// .inner_spend(ctx, pk)?; - -// let cat_puzzle_hash = -// CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); -// let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1); - -// CatSpend::new(issuance.asset_id) -// .spend(cat_coin, inner_spend, issuance.lineage_proof, 0) -// .finish(ctx)?; - -// test_transaction( -// &peer, -// ctx.take_spends(), -// &[sk], -// sim.config().genesis_challenge, -// ) -// .await; - -// Ok(()) -// } - -// #[tokio::test] -// async fn test_cat_melt() -> anyhow::Result<()> { -// let sim = Simulator::new().await?; -// let peer = sim.connect().await?; - -// let sk = sim.secret_key().await?; -// let pk = sk.public_key(); - -// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); -// let coin = sim.mint_coin(puzzle_hash, 10000).await; - -// let mut allocator = Allocator::new(); -// let ctx = &mut SpendContext::new(&mut allocator); - -// let (issue_cat, issuance) = IssueCat::new(coin.coin_id()) -// .create_hinted_coin(ctx, puzzle_hash, 10000, puzzle_hash)? -// .multi_issuance(ctx, pk, 10000)?; - -// StandardSpend::new() -// .chain(issue_cat) -// .finish(ctx, coin, pk)?; - -// let inner_spend = StandardSpend::new() -// .create_hinted_coin(ctx, puzzle_hash, 7000, puzzle_hash)? -// .run_multi_issuance_tail(ctx, pk)? -// .inner_spend(ctx, pk)?; - -// let cat_puzzle_hash = -// CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); -// let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 10000); - -// CatSpend::new(issuance.asset_id) -// .spend(cat_coin, inner_spend, issuance.lineage_proof, -3000) -// .finish(ctx)?; - -// test_transaction( -// &peer, -// ctx.take_spends(), -// &[sk], -// sim.config().genesis_challenge, -// ) -// .await; - -// Ok(()) -// } -// } +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_puzzles::{ + cat::{CatArgs, CatSolution, CoinProof, CAT_PUZZLE_HASH}, + LineageProof, +}; +use chia_sdk_types::conditions::CreateCoin; +use clvm_utils::CurriedProgram; +use clvmr::NodePtr; + +use crate::{Spend, SpendContext, SpendError}; + +#[derive(Debug, Default)] +#[must_use] +pub struct CatSpend { + asset_id: Bytes32, + cat_spends: Vec, +} + +#[derive(Debug)] +struct CatSpendItem { + coin: Coin, + inner_spend: Spend, + lineage_proof: LineageProof, + extra_delta: i64, +} + +impl CatSpend { + pub const fn new(asset_id: Bytes32) -> Self { + Self { + asset_id, + cat_spends: Vec::new(), + } + } + + pub fn spend( + mut self, + coin: Coin, + inner_spend: Spend, + lineage_proof: LineageProof, + extra_delta: i64, + ) -> Self { + self.cat_spends.push(CatSpendItem { + coin, + inner_spend, + lineage_proof, + extra_delta, + }); + self + } + + pub fn finish(self, ctx: &mut SpendContext<'_>) -> Result<(), SpendError> { + let cat_puzzle_ptr = ctx.cat_puzzle()?; + let len = self.cat_spends.len(); + + let mut total_delta = 0; + + for (index, item) in self.cat_spends.iter().enumerate() { + let CatSpendItem { + coin, + inner_spend, + lineage_proof, + extra_delta, + } = item; + + // Calculate the delta and add it to the subtotal. + let output = ctx.run(inner_spend.puzzle(), inner_spend.solution())?; + let conditions: Vec = ctx.extract(output)?; + + let create_coins = conditions + .into_iter() + .filter_map(|ptr| ctx.extract::(ptr).ok()); + + let delta = create_coins.fold( + i128::from(coin.amount) - i128::from(*extra_delta), + |delta, create_coin| delta - i128::from(create_coin.amount), + ); + + let prev_subtotal = total_delta; + total_delta += delta; + + // Find information of neighboring coins on the ring. + let prev_cat = &self.cat_spends[if index == 0 { len - 1 } else { index - 1 }]; + let next_cat = &self.cat_spends[if index == len - 1 { 0 } else { index + 1 }]; + + let puzzle_reveal = ctx.serialize(&CurriedProgram { + program: cat_puzzle_ptr, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + asset_id: self.asset_id, + inner_puzzle: inner_spend.puzzle(), + }, + })?; + + let solution = ctx.serialize(&CatSolution { + inner_puzzle_solution: inner_spend.solution(), + lineage_proof: Some(*lineage_proof), + prev_coin_id: prev_cat.coin.coin_id(), + this_coin_info: *coin, + next_coin_proof: CoinProof { + parent_coin_info: next_cat.coin.parent_coin_info, + inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()).into(), + amount: next_cat.coin.amount, + }, + prev_subtotal: prev_subtotal.try_into()?, + extra_delta: *extra_delta, + })?; + + ctx.insert_coin_spend(CoinSpend::new(*coin, puzzle_reveal, solution)); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use chia_puzzles::{cat::EverythingWithSignatureTailArgs, standard::StandardArgs}; + use chia_sdk_test::{test_transaction, Simulator}; + use chia_sdk_types::conditions::{Condition, RunTail}; + use clvmr::Allocator; + + use crate::{p2_spend, puzzles::IssueCat, Conditions}; + + use super::*; + + #[tokio::test] + async fn test_cat_spend_multi() -> anyhow::Result<()> { + let sim = Simulator::new().await?; + let peer = sim.connect().await?; + + let sk = sim.secret_key().await?; + let pk = sk.public_key(); + + let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); + let coin = sim.mint_coin(puzzle_hash, 6).await; + + let mut allocator = Allocator::new(); + let ctx = &mut SpendContext::new(&mut allocator); + + let (issue_cat, issuance) = IssueCat::new( + coin.coin_id(), + Conditions::new() + .create_hinted_coin(puzzle_hash, 1, puzzle_hash) + .create_hinted_coin(puzzle_hash, 2, puzzle_hash) + .create_hinted_coin(puzzle_hash, 3, puzzle_hash), + ) + .single_issuance(ctx, 6)?; + + ctx.spend_p2_coin(coin, pk, issue_cat)?; + + let cat_puzzle_hash = + CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); + + CatSpend::new(issuance.asset_id) + .spend( + Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1), + p2_spend( + ctx, + pk, + Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), + )?, + issuance.lineage_proof, + 0, + ) + .spend( + Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 2), + p2_spend( + ctx, + pk, + Conditions::new().create_hinted_coin(puzzle_hash, 2, puzzle_hash), + )?, + issuance.lineage_proof, + 0, + ) + .spend( + Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 3), + p2_spend( + ctx, + pk, + Conditions::new().create_hinted_coin(puzzle_hash, 3, puzzle_hash), + )?, + issuance.lineage_proof, + 0, + ) + .finish(ctx)?; + + test_transaction( + &peer, + ctx.take_spends(), + &[sk], + sim.config().genesis_challenge, + ) + .await; + + Ok(()) + } + + #[tokio::test] + async fn test_cat_spend() -> anyhow::Result<()> { + let sim = Simulator::new().await?; + let peer = sim.connect().await?; + + let sk = sim.secret_key().await?; + let pk = sk.public_key(); + + let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); + let coin = sim.mint_coin(puzzle_hash, 1).await; + + let mut allocator = Allocator::new(); + let ctx = &mut SpendContext::new(&mut allocator); + + let (issue_cat, issuance) = IssueCat::new( + coin.coin_id(), + Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), + ) + .single_issuance(ctx, 1)?; + + ctx.spend_p2_coin(coin, pk, issue_cat)?; + + let cat_puzzle_hash = + CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); + let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1); + + CatSpend::new(issuance.asset_id) + .spend( + cat_coin, + p2_spend( + ctx, + pk, + Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), + )?, + issuance.lineage_proof, + 0, + ) + .finish(ctx)?; + + test_transaction( + &peer, + ctx.take_spends(), + &[sk], + sim.config().genesis_challenge, + ) + .await; + + Ok(()) + } + + #[tokio::test] + async fn test_cat_melt() -> anyhow::Result<()> { + let sim = Simulator::new().await?; + let peer = sim.connect().await?; + + let sk = sim.secret_key().await?; + let pk = sk.public_key(); + + let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); + let coin = sim.mint_coin(puzzle_hash, 10000).await; + + let mut allocator = Allocator::new(); + let ctx = &mut SpendContext::new(&mut allocator); + + let (issue_cat, issuance) = IssueCat::new( + coin.coin_id(), + Conditions::new().create_hinted_coin(puzzle_hash, 10000, puzzle_hash), + ) + .multi_issuance(ctx, pk, 10000)?; + + ctx.spend_p2_coin(coin, pk, issue_cat)?; + + let tail = ctx.everything_with_signature_tail_puzzle()?; + let tail_program = ctx.alloc(&CurriedProgram { + program: tail, + args: EverythingWithSignatureTailArgs::new(pk), + })?; + let run_tail = Condition::Other(ctx.alloc(&RunTail::new(tail_program, ()))?); + + let inner_spend = p2_spend( + ctx, + pk, + Conditions::new() + .create_hinted_coin(puzzle_hash, 7000, puzzle_hash) + .condition(run_tail), + )?; + + let cat_puzzle_hash = + CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); + let cat_coin = Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 10000); + + CatSpend::new(issuance.asset_id) + .spend(cat_coin, inner_spend, issuance.lineage_proof, -3000) + .finish(ctx)?; + + test_transaction( + &peer, + ctx.take_spends(), + &[sk], + sim.config().genesis_challenge, + ) + .await; + + Ok(()) + } +} diff --git a/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs b/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs index 32845b0d..284182b8 100644 --- a/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs +++ b/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs @@ -1,218 +1,210 @@ -// use chia_bls::PublicKey; -// use chia_protocol::{Bytes32, Coin, CoinSpend}; -// use chia_puzzles::{ -// cat::{ -// CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, GenesisByCoinIdTailArgs, -// CAT_PUZZLE_HASH, -// }, -// LineageProof, -// }; -// use chia_sdk_types::conditions::RunTail; -// use clvm_traits::clvm_quote; -// use clvm_utils::CurriedProgram; -// use clvmr::NodePtr; - -// use crate::{ -// spend::{P2Spend, SpendConditions}, -// SpendContext, SpendError, -// }; - -// #[derive(Debug)] -// #[must_use] -// pub struct IssueCat { -// parent_coin_id: Bytes32, -// conditions: Vec, -// } - -// #[derive(Debug, Clone, Copy)] -// pub struct CatIssuanceInfo { -// pub asset_id: Bytes32, -// pub lineage_proof: LineageProof, -// pub eve_coin: Coin, -// } - -// impl IssueCat { -// pub const fn new(parent_coin_id: Bytes32) -> Self { -// Self { -// parent_coin_id, -// conditions: Vec::new(), -// } -// } - -// pub fn single_issuance( -// self, -// ctx: &mut SpendContext<'_>, -// amount: u64, -// ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { -// let tail_puzzle_ptr = ctx.genesis_by_coin_id_tail_puzzle()?; - -// let tail = ctx.alloc(&CurriedProgram { -// program: tail_puzzle_ptr, -// args: GenesisByCoinIdTailArgs { -// genesis_coin_id: self.parent_coin_id, -// }, -// })?; -// let asset_id = ctx.tree_hash(tail).into(); - -// self.raw_condition(ctx.alloc(&RunTail::new(tail, ()))?) -// .finish_raw(ctx, asset_id, amount) -// } - -// pub fn multi_issuance( -// self, -// ctx: &mut SpendContext<'_>, -// public_key: PublicKey, -// amount: u64, -// ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { -// let tail_puzzle_ptr = ctx.everything_with_signature_tail_puzzle()?; - -// let tail = ctx.alloc(&CurriedProgram { -// program: tail_puzzle_ptr, -// args: EverythingWithSignatureTailArgs { public_key }, -// })?; -// let asset_id = ctx.tree_hash(tail).into(); - -// self.raw_condition(ctx.alloc(&RunTail::new(tail, ()))?) -// .finish_raw(ctx, asset_id, amount) -// } - -// pub fn finish_raw( -// self, -// ctx: &mut SpendContext<'_>, -// asset_id: Bytes32, -// amount: u64, -// ) -> Result<(SpendConditions, CatIssuanceInfo), SpendError> { -// let cat_puzzle_ptr = ctx.cat_puzzle()?; - -// let inner_puzzle = ctx.alloc(&clvm_quote!(self.conditions))?; -// let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); - -// let puzzle = ctx.alloc(&CurriedProgram { -// program: cat_puzzle_ptr, -// args: CatArgs { -// mod_hash: CAT_PUZZLE_HASH.into(), -// asset_id, -// inner_puzzle, -// }, -// })?; - -// let puzzle_hash = ctx.tree_hash(puzzle).into(); -// let eve_coin = Coin::new(self.parent_coin_id, puzzle_hash, amount); - -// let solution = ctx.serialize(&CatSolution { -// inner_puzzle_solution: (), -// lineage_proof: None, -// prev_coin_id: eve_coin.coin_id(), -// this_coin_info: eve_coin, -// next_coin_proof: CoinProof { -// parent_coin_info: self.parent_coin_id, -// inner_puzzle_hash, -// amount, -// }, -// prev_subtotal: 0, -// extra_delta: 0, -// })?; - -// let puzzle_reveal = ctx.serialize(&puzzle)?; -// ctx.spend(CoinSpend::new(eve_coin, puzzle_reveal, solution)); - -// let chained_spend = -// SpendConditions::new().create_hinted_coin(ctx, puzzle_hash, amount, puzzle_hash)?; - -// let issuance_info = CatIssuanceInfo { -// asset_id, -// lineage_proof: LineageProof { -// parent_parent_coin_id: eve_coin.parent_coin_info, -// parent_inner_puzzle_hash: inner_puzzle_hash, -// parent_amount: eve_coin.amount, -// }, -// eve_coin, -// }; - -// Ok((chained_spend, issuance_info)) -// } -// } - -// impl P2Spend for IssueCat { -// fn raw_condition(mut self, condition: NodePtr) -> Self { -// self.conditions.push(condition); -// self -// } -// } - -// #[cfg(test)] -// mod tests { -// use chia_puzzles::standard::StandardArgs; -// use chia_sdk_test::{test_transaction, Simulator}; -// use clvmr::Allocator; - -// use crate::puzzles::StandardSpend; - -// use super::*; - -// #[tokio::test] -// async fn test_single_issuance_cat() -> anyhow::Result<()> { -// let sim = Simulator::new().await?; -// let peer = sim.connect().await?; - -// let sk = sim.secret_key().await?; -// let pk = sk.public_key(); - -// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); -// let coin = sim.mint_coin(puzzle_hash, 1).await; - -// let mut allocator = Allocator::new(); -// let ctx = &mut SpendContext::new(&mut allocator); - -// let (issue_cat, _cat_info) = IssueCat::new(coin.coin_id()) -// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? -// .single_issuance(ctx, 1)?; - -// StandardSpend::new() -// .chain(issue_cat) -// .finish(ctx, coin, pk)?; - -// test_transaction( -// &peer, -// ctx.take_spends(), -// &[sk], -// sim.config().genesis_challenge, -// ) -// .await; - -// Ok(()) -// } - -// #[tokio::test] -// async fn test_multi_issuance_cat() -> anyhow::Result<()> { -// let sim = Simulator::new().await?; -// let peer = sim.connect().await?; - -// let sk = sim.secret_key().await?; -// let pk = sk.public_key(); - -// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); -// let coin = sim.mint_coin(puzzle_hash, 1).await; - -// let mut allocator = Allocator::new(); -// let ctx = &mut SpendContext::new(&mut allocator); - -// let (issue_cat, _cat_info) = IssueCat::new(coin.coin_id()) -// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? -// .multi_issuance(ctx, pk, 1)?; - -// StandardSpend::new() -// .chain(issue_cat) -// .finish(ctx, coin, pk)?; - -// test_transaction( -// &peer, -// ctx.take_spends(), -// &[sk], -// sim.config().genesis_challenge, -// ) -// .await; - -// Ok(()) -// } -// } +use chia_bls::PublicKey; +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_puzzles::{ + cat::{ + CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, GenesisByCoinIdTailArgs, + CAT_PUZZLE_HASH, + }, + LineageProof, +}; +use chia_sdk_types::conditions::{Condition, RunTail}; +use clvm_traits::clvm_quote; +use clvm_utils::CurriedProgram; + +use crate::{Conditions, SpendContext, SpendError}; + +#[derive(Debug)] +#[must_use] +pub struct IssueCat { + parent_coin_id: Bytes32, + conditions: Conditions, +} + +#[derive(Debug, Clone, Copy)] +pub struct CatIssuanceInfo { + pub asset_id: Bytes32, + pub lineage_proof: LineageProof, + pub eve_coin: Coin, +} + +impl IssueCat { + pub fn new(parent_coin_id: Bytes32, conditions: Conditions) -> Self { + Self { + parent_coin_id, + conditions, + } + } + + pub fn single_issuance( + mut self, + ctx: &mut SpendContext<'_>, + amount: u64, + ) -> Result<(Conditions, CatIssuanceInfo), SpendError> { + let tail_puzzle_ptr = ctx.genesis_by_coin_id_tail_puzzle()?; + + let tail = ctx.alloc(&CurriedProgram { + program: tail_puzzle_ptr, + args: GenesisByCoinIdTailArgs { + genesis_coin_id: self.parent_coin_id, + }, + })?; + let asset_id = ctx.tree_hash(tail).into(); + + self.conditions = self + .conditions + .condition(Condition::Other(ctx.alloc(&RunTail::new(tail, ()))?)); + + self.finish_raw(ctx, asset_id, amount) + } + + pub fn multi_issuance( + mut self, + ctx: &mut SpendContext<'_>, + public_key: PublicKey, + amount: u64, + ) -> Result<(Conditions, CatIssuanceInfo), SpendError> { + let tail_puzzle_ptr = ctx.everything_with_signature_tail_puzzle()?; + + let tail = ctx.alloc(&CurriedProgram { + program: tail_puzzle_ptr, + args: EverythingWithSignatureTailArgs { public_key }, + })?; + let asset_id = ctx.tree_hash(tail).into(); + + self.conditions = self + .conditions + .condition(Condition::Other(ctx.alloc(&RunTail::new(tail, ()))?)); + + self.finish_raw(ctx, asset_id, amount) + } + + pub fn finish_raw( + self, + ctx: &mut SpendContext<'_>, + asset_id: Bytes32, + amount: u64, + ) -> Result<(Conditions, CatIssuanceInfo), SpendError> { + let cat_puzzle_ptr = ctx.cat_puzzle()?; + + let inner_puzzle = ctx.alloc(&clvm_quote!(self.conditions))?; + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); + + let puzzle = ctx.alloc(&CurriedProgram { + program: cat_puzzle_ptr, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + asset_id, + inner_puzzle, + }, + })?; + + let puzzle_hash = ctx.tree_hash(puzzle).into(); + let eve_coin = Coin::new(self.parent_coin_id, puzzle_hash, amount); + + let solution = ctx.serialize(&CatSolution { + inner_puzzle_solution: (), + lineage_proof: None, + prev_coin_id: eve_coin.coin_id(), + this_coin_info: eve_coin, + next_coin_proof: CoinProof { + parent_coin_info: self.parent_coin_id, + inner_puzzle_hash, + amount, + }, + prev_subtotal: 0, + extra_delta: 0, + })?; + + let puzzle_reveal = ctx.serialize(&puzzle)?; + ctx.insert_coin_spend(CoinSpend::new(eve_coin, puzzle_reveal, solution)); + + let chained_spend = Conditions::new().create_hinted_coin(puzzle_hash, amount, puzzle_hash); + + let issuance_info = CatIssuanceInfo { + asset_id, + lineage_proof: LineageProof { + parent_parent_coin_id: eve_coin.parent_coin_info, + parent_inner_puzzle_hash: inner_puzzle_hash, + parent_amount: eve_coin.amount, + }, + eve_coin, + }; + + Ok((chained_spend, issuance_info)) + } +} + +#[cfg(test)] +mod tests { + use chia_puzzles::standard::StandardArgs; + use chia_sdk_test::{test_transaction, Simulator}; + use clvmr::Allocator; + + use super::*; + + #[tokio::test] + async fn test_single_issuance_cat() -> anyhow::Result<()> { + let sim = Simulator::new().await?; + let peer = sim.connect().await?; + + let sk = sim.secret_key().await?; + let pk = sk.public_key(); + + let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); + let coin = sim.mint_coin(puzzle_hash, 1).await; + + let mut allocator = Allocator::new(); + let ctx = &mut SpendContext::new(&mut allocator); + + let (issue_cat, _cat_info) = IssueCat::new( + coin.coin_id(), + Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), + ) + .single_issuance(ctx, 1)?; + + ctx.spend_p2_coin(coin, pk, issue_cat)?; + + test_transaction( + &peer, + ctx.take_spends(), + &[sk], + sim.config().genesis_challenge, + ) + .await; + + Ok(()) + } + + #[tokio::test] + async fn test_multi_issuance_cat() -> anyhow::Result<()> { + let sim = Simulator::new().await?; + let peer = sim.connect().await?; + + let sk = sim.secret_key().await?; + let pk = sk.public_key(); + + let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); + let coin = sim.mint_coin(puzzle_hash, 1).await; + + let mut allocator = Allocator::new(); + let ctx = &mut SpendContext::new(&mut allocator); + + let (issue_cat, _cat_info) = IssueCat::new( + coin.coin_id(), + Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), + ) + .multi_issuance(ctx, pk, 1)?; + + ctx.spend_p2_coin(coin, pk, issue_cat)?; + + test_transaction( + &peer, + ctx.take_spends(), + &[sk], + sim.config().genesis_challenge, + ) + .await; + + Ok(()) + } +} diff --git a/crates/chia-sdk-parser/src/puzzles/cat.rs b/crates/chia-sdk-parser/src/puzzles/cat.rs index 7b5e21b8..da54a13a 100644 --- a/crates/chia-sdk-parser/src/puzzles/cat.rs +++ b/crates/chia-sdk-parser/src/puzzles/cat.rs @@ -1,153 +1,154 @@ -// use chia_protocol::{Bytes32, Coin}; -// use chia_puzzles::{ -// cat::{CatArgs, CatSolution, CAT_PUZZLE_HASH}, -// LineageProof, -// }; -// use chia_sdk_types::{ -// conditions::{Condition, CreateCoin}, -// puzzles::CatInfo, -// }; -// use clvm_traits::FromClvm; -// use clvmr::{Allocator, NodePtr}; - -// use crate::{puzzle_conditions, ParseError, Puzzle}; - -// #[derive(Debug, Clone, Copy)] -// pub struct CatPuzzle { -// pub asset_id: Bytes32, -// pub inner_puzzle: Puzzle, -// } - -// impl CatPuzzle { -// pub fn parse(allocator: &Allocator, puzzle: &Puzzle) -> Result, ParseError> { -// let Some(puzzle) = puzzle.as_curried() else { -// return Ok(None); -// }; - -// if puzzle.mod_hash != CAT_PUZZLE_HASH { -// return Ok(None); -// } - -// let args = CatArgs::::from_clvm(allocator, puzzle.args)?; - -// if args.mod_hash != CAT_PUZZLE_HASH.into() { -// return Err(ParseError::InvalidModHash); -// } - -// Ok(Some(CatPuzzle { -// asset_id: args.asset_id, -// inner_puzzle: Puzzle::parse(allocator, args.inner_puzzle), -// })) -// } - -// pub fn p2_outputs( -// &self, -// allocator: &mut Allocator, -// solution: NodePtr, -// ) -> Result, ParseError> { -// let solution = CatSolution::::from_clvm(allocator, solution)?; - -// let conditions = puzzle_conditions( -// allocator, -// self.inner_puzzle.ptr(), -// solution.inner_puzzle_solution, -// )?; - -// let create_coins = conditions -// .into_iter() -// .filter_map(|condition| match condition { -// Condition::CreateCoin(create_coin) => Some(create_coin), -// _ => None, -// }) -// .collect(); - -// Ok(create_coins) -// } - -// pub fn child_coin_info( -// &self, -// allocator: &mut Allocator, -// parent_coin: Coin, -// child_coin: Coin, -// solution: NodePtr, -// ) -> Result { -// let create_coin = self -// .p2_outputs(allocator, solution)? -// .into_iter() -// .find(|create_coin| { -// let cat_puzzle_hash = -// CatArgs::curry_tree_hash(self.asset_id, create_coin.puzzle_hash.into()); - -// cat_puzzle_hash == child_coin.puzzle_hash.into() -// && create_coin.amount == child_coin.amount -// }) -// .ok_or(ParseError::MissingChild)?; - -// Ok(CatInfo { -// asset_id: self.asset_id, -// p2_puzzle_hash: create_coin.puzzle_hash, -// coin: child_coin, -// lineage_proof: LineageProof { -// parent_parent_coin_id: parent_coin.parent_coin_info, -// parent_inner_puzzle_hash: self.inner_puzzle.curried_puzzle_hash().into(), -// parent_amount: parent_coin.amount, -// }, -// }) -// } -// } - -// #[cfg(test)] -// mod tests { -// use chia_bls::PublicKey; -// use chia_protocol::Coin; -// use chia_puzzles::standard::StandardArgs; -// use clvm_traits::ToNodePtr; - -// use super::*; - -// #[test] -// fn test_parse_cat() -> anyhow::Result<()> { -// let mut allocator = Allocator::new(); -// let ctx = &mut SpendContext::new(&mut allocator); - -// let pk = PublicKey::default(); -// let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); -// let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); - -// let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) -// .create_hinted_coin(ctx, puzzle_hash, 1, puzzle_hash)? -// .multi_issuance(ctx, pk, 1)?; - -// let cat_puzzle_hash = CatArgs::curry_tree_hash(issuance_info.asset_id, puzzle_hash.into()); - -// let cat_info = CatInfo { -// asset_id: issuance_info.asset_id, -// p2_puzzle_hash: puzzle_hash, -// coin: Coin::new(issuance_info.eve_coin.coin_id(), cat_puzzle_hash.into(), 1), -// lineage_proof: issuance_info.lineage_proof, -// }; - -// StandardSpend::new() -// .chain(issue_cat) -// .finish(ctx, parent, pk)?; - -// let coin_spends = ctx.take_spends(); - -// let coin_spend = coin_spends -// .into_iter() -// .find(|cs| cs.coin.coin_id() == issuance_info.eve_coin.coin_id()) -// .unwrap(); - -// let puzzle_ptr = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; -// let solution_ptr = coin_spend.solution.to_node_ptr(&mut allocator)?; - -// let puzzle = Puzzle::parse(&allocator, puzzle_ptr); -// let cat = CatPuzzle::parse(&allocator, &puzzle)?.expect("not a cat puzzle"); -// let parsed_cat_info = -// cat.child_coin_info(&mut allocator, coin_spend.coin, cat_info.coin, solution_ptr)?; - -// assert_eq!(parsed_cat_info, cat_info); - -// Ok(()) -// } -// } +use chia_protocol::{Bytes32, Coin}; +use chia_puzzles::{ + cat::{CatArgs, CatSolution, CAT_PUZZLE_HASH}, + LineageProof, +}; +use chia_sdk_types::{ + conditions::{puzzle_conditions, Condition, CreateCoin}, + puzzles::CatInfo, +}; +use clvm_traits::FromClvm; +use clvmr::{Allocator, NodePtr}; + +use crate::{ParseError, Puzzle}; + +#[derive(Debug, Clone, Copy)] +pub struct CatPuzzle { + pub asset_id: Bytes32, + pub inner_puzzle: Puzzle, +} + +impl CatPuzzle { + pub fn parse(allocator: &Allocator, puzzle: &Puzzle) -> Result, ParseError> { + let Some(puzzle) = puzzle.as_curried() else { + return Ok(None); + }; + + if puzzle.mod_hash != CAT_PUZZLE_HASH { + return Ok(None); + } + + let args = CatArgs::::from_clvm(allocator, puzzle.args)?; + + if args.mod_hash != CAT_PUZZLE_HASH.into() { + return Err(ParseError::InvalidModHash); + } + + Ok(Some(CatPuzzle { + asset_id: args.asset_id, + inner_puzzle: Puzzle::parse(allocator, args.inner_puzzle), + })) + } + + pub fn p2_outputs( + &self, + allocator: &mut Allocator, + solution: NodePtr, + ) -> Result, ParseError> { + let solution = CatSolution::::from_clvm(allocator, solution)?; + + let conditions = puzzle_conditions( + allocator, + self.inner_puzzle.ptr(), + solution.inner_puzzle_solution, + )?; + + let create_coins = conditions + .into_iter() + .filter_map(|condition| match condition { + Condition::CreateCoin(create_coin) => Some(create_coin), + _ => None, + }) + .collect(); + + Ok(create_coins) + } + + pub fn child_coin_info( + &self, + allocator: &mut Allocator, + parent_coin: Coin, + child_coin: Coin, + solution: NodePtr, + ) -> Result { + let create_coin = self + .p2_outputs(allocator, solution)? + .into_iter() + .find(|create_coin| { + let cat_puzzle_hash = + CatArgs::curry_tree_hash(self.asset_id, create_coin.puzzle_hash.into()); + + cat_puzzle_hash == child_coin.puzzle_hash.into() + && create_coin.amount == child_coin.amount + }) + .ok_or(ParseError::MissingChild)?; + + Ok(CatInfo { + asset_id: self.asset_id, + p2_puzzle_hash: create_coin.puzzle_hash, + coin: child_coin, + lineage_proof: LineageProof { + parent_parent_coin_id: parent_coin.parent_coin_info, + parent_inner_puzzle_hash: self.inner_puzzle.curried_puzzle_hash().into(), + parent_amount: parent_coin.amount, + }, + }) + } +} + +#[cfg(test)] +mod tests { + use chia_bls::PublicKey; + use chia_protocol::Coin; + use chia_puzzles::standard::StandardArgs; + use chia_sdk_driver::{Conditions, IssueCat, SpendContext}; + use clvm_traits::ToNodePtr; + + use super::*; + + #[test] + fn test_parse_cat() -> anyhow::Result<()> { + let mut allocator = Allocator::new(); + let ctx = &mut SpendContext::new(&mut allocator); + + let pk = PublicKey::default(); + let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); + let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); + + let (issue_cat, issuance_info) = IssueCat::new( + parent.coin_id(), + Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), + ) + .multi_issuance(ctx, pk, 1)?; + + let cat_puzzle_hash = CatArgs::curry_tree_hash(issuance_info.asset_id, puzzle_hash.into()); + + let cat_info = CatInfo { + asset_id: issuance_info.asset_id, + p2_puzzle_hash: puzzle_hash, + coin: Coin::new(issuance_info.eve_coin.coin_id(), cat_puzzle_hash.into(), 1), + lineage_proof: issuance_info.lineage_proof, + }; + + ctx.spend_p2_coin(parent, pk, issue_cat)?; + + let coin_spends = ctx.take_spends(); + + let coin_spend = coin_spends + .into_iter() + .find(|cs| cs.coin.coin_id() == issuance_info.eve_coin.coin_id()) + .unwrap(); + + let puzzle_ptr = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; + let solution_ptr = coin_spend.solution.to_node_ptr(&mut allocator)?; + + let puzzle = Puzzle::parse(&allocator, puzzle_ptr); + let cat = CatPuzzle::parse(&allocator, &puzzle)?.expect("not a cat puzzle"); + let parsed_cat_info = + cat.child_coin_info(&mut allocator, coin_spend.coin, cat_info.coin, solution_ptr)?; + + assert_eq!(parsed_cat_info, cat_info); + + Ok(()) + } +} From 258ef34a9b136a405f2fa5b6e676463a83bdeafc Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 9 Jun 2024 20:43:59 -0400 Subject: [PATCH 3/4] Finish CATs --- .../src/puzzles/cat/cat_spend.rs | 24 +- .../src/puzzles/cat/issue_cat.rs | 234 +++++++++--------- crates/chia-sdk-parser/src/puzzles/cat.rs | 10 +- 3 files changed, 136 insertions(+), 132 deletions(-) diff --git a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs index 4db0edd2..385f29d8 100644 --- a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs +++ b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs @@ -119,7 +119,7 @@ mod tests { use chia_sdk_types::conditions::{Condition, RunTail}; use clvmr::Allocator; - use crate::{p2_spend, puzzles::IssueCat, Conditions}; + use crate::{issue_cat_from_coin, issue_cat_from_key, p2_spend, Conditions}; use super::*; @@ -137,14 +137,15 @@ mod tests { let mut allocator = Allocator::new(); let ctx = &mut SpendContext::new(&mut allocator); - let (issue_cat, issuance) = IssueCat::new( + let (issue_cat, issuance) = issue_cat_from_coin( + ctx, coin.coin_id(), + 6, Conditions::new() .create_hinted_coin(puzzle_hash, 1, puzzle_hash) .create_hinted_coin(puzzle_hash, 2, puzzle_hash) .create_hinted_coin(puzzle_hash, 3, puzzle_hash), - ) - .single_issuance(ctx, 6)?; + )?; ctx.spend_p2_coin(coin, pk, issue_cat)?; @@ -209,11 +210,12 @@ mod tests { let mut allocator = Allocator::new(); let ctx = &mut SpendContext::new(&mut allocator); - let (issue_cat, issuance) = IssueCat::new( + let (issue_cat, issuance) = issue_cat_from_coin( + ctx, coin.coin_id(), + 1, Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), - ) - .single_issuance(ctx, 1)?; + )?; ctx.spend_p2_coin(coin, pk, issue_cat)?; @@ -259,11 +261,13 @@ mod tests { let mut allocator = Allocator::new(); let ctx = &mut SpendContext::new(&mut allocator); - let (issue_cat, issuance) = IssueCat::new( + let (issue_cat, issuance) = issue_cat_from_key( + ctx, coin.coin_id(), + pk, + 10000, Conditions::new().create_hinted_coin(puzzle_hash, 10000, puzzle_hash), - ) - .multi_issuance(ctx, pk, 10000)?; + )?; ctx.spend_p2_coin(coin, pk, issue_cat)?; diff --git a/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs b/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs index 284182b8..663ce918 100644 --- a/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs +++ b/crates/chia-sdk-driver/src/puzzles/cat/issue_cat.rs @@ -7,131 +7,126 @@ use chia_puzzles::{ }, LineageProof, }; -use chia_sdk_types::conditions::{Condition, RunTail}; -use clvm_traits::clvm_quote; +use chia_sdk_types::conditions::RunTail; +use clvm_traits::{clvm_quote, ToClvm}; use clvm_utils::CurriedProgram; +use clvmr::NodePtr; use crate::{Conditions, SpendContext, SpendError}; -#[derive(Debug)] -#[must_use] -pub struct IssueCat { - parent_coin_id: Bytes32, - conditions: Conditions, -} - #[derive(Debug, Clone, Copy)] -pub struct CatIssuanceInfo { +pub struct IssueCat { pub asset_id: Bytes32, pub lineage_proof: LineageProof, pub eve_coin: Coin, } -impl IssueCat { - pub fn new(parent_coin_id: Bytes32, conditions: Conditions) -> Self { - Self { - parent_coin_id, - conditions, - } - } - - pub fn single_issuance( - mut self, - ctx: &mut SpendContext<'_>, - amount: u64, - ) -> Result<(Conditions, CatIssuanceInfo), SpendError> { - let tail_puzzle_ptr = ctx.genesis_by_coin_id_tail_puzzle()?; - - let tail = ctx.alloc(&CurriedProgram { - program: tail_puzzle_ptr, - args: GenesisByCoinIdTailArgs { - genesis_coin_id: self.parent_coin_id, - }, - })?; - let asset_id = ctx.tree_hash(tail).into(); - - self.conditions = self - .conditions - .condition(Condition::Other(ctx.alloc(&RunTail::new(tail, ()))?)); - - self.finish_raw(ctx, asset_id, amount) - } +pub fn issue_cat_from_coin( + ctx: &mut SpendContext<'_>, + parent_coin_id: Bytes32, + amount: u64, + extra_conditions: Conditions, +) -> Result<(Conditions, IssueCat), SpendError> { + let tail_puzzle_ptr = ctx.genesis_by_coin_id_tail_puzzle()?; + let tail = ctx.alloc(&CurriedProgram { + program: tail_puzzle_ptr, + args: GenesisByCoinIdTailArgs::new(parent_coin_id), + })?; + let asset_id = ctx.tree_hash(tail).into(); + + issue_cat( + ctx, + parent_coin_id, + asset_id, + amount, + RunTail::new(tail, ()), + extra_conditions, + ) +} - pub fn multi_issuance( - mut self, - ctx: &mut SpendContext<'_>, - public_key: PublicKey, - amount: u64, - ) -> Result<(Conditions, CatIssuanceInfo), SpendError> { - let tail_puzzle_ptr = ctx.everything_with_signature_tail_puzzle()?; - - let tail = ctx.alloc(&CurriedProgram { - program: tail_puzzle_ptr, - args: EverythingWithSignatureTailArgs { public_key }, - })?; - let asset_id = ctx.tree_hash(tail).into(); - - self.conditions = self - .conditions - .condition(Condition::Other(ctx.alloc(&RunTail::new(tail, ()))?)); - - self.finish_raw(ctx, asset_id, amount) - } +pub fn issue_cat_from_key( + ctx: &mut SpendContext<'_>, + parent_coin_id: Bytes32, + public_key: PublicKey, + amount: u64, + extra_conditions: Conditions, +) -> Result<(Conditions, IssueCat), SpendError> { + let tail_puzzle_ptr = ctx.everything_with_signature_tail_puzzle()?; + let tail = ctx.alloc(&CurriedProgram { + program: tail_puzzle_ptr, + args: EverythingWithSignatureTailArgs::new(public_key), + })?; + let asset_id = ctx.tree_hash(tail).into(); + + issue_cat( + ctx, + parent_coin_id, + asset_id, + amount, + RunTail::new(tail, ()), + extra_conditions, + ) +} - pub fn finish_raw( - self, - ctx: &mut SpendContext<'_>, - asset_id: Bytes32, - amount: u64, - ) -> Result<(Conditions, CatIssuanceInfo), SpendError> { - let cat_puzzle_ptr = ctx.cat_puzzle()?; - - let inner_puzzle = ctx.alloc(&clvm_quote!(self.conditions))?; - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); - - let puzzle = ctx.alloc(&CurriedProgram { - program: cat_puzzle_ptr, - args: CatArgs { - mod_hash: CAT_PUZZLE_HASH.into(), - asset_id, - inner_puzzle, - }, - })?; - - let puzzle_hash = ctx.tree_hash(puzzle).into(); - let eve_coin = Coin::new(self.parent_coin_id, puzzle_hash, amount); - - let solution = ctx.serialize(&CatSolution { - inner_puzzle_solution: (), - lineage_proof: None, - prev_coin_id: eve_coin.coin_id(), - this_coin_info: eve_coin, - next_coin_proof: CoinProof { - parent_coin_info: self.parent_coin_id, - inner_puzzle_hash, - amount, - }, - prev_subtotal: 0, - extra_delta: 0, - })?; - - let puzzle_reveal = ctx.serialize(&puzzle)?; - ctx.insert_coin_spend(CoinSpend::new(eve_coin, puzzle_reveal, solution)); - - let chained_spend = Conditions::new().create_hinted_coin(puzzle_hash, amount, puzzle_hash); - - let issuance_info = CatIssuanceInfo { +pub fn issue_cat( + ctx: &mut SpendContext<'_>, + parent_coin_id: Bytes32, + asset_id: Bytes32, + amount: u64, + run_tail: RunTail, + extra_conditions: Conditions, +) -> Result<(Conditions, IssueCat), SpendError> +where + P: ToClvm, + S: ToClvm, +{ + let cat_puzzle_ptr = ctx.cat_puzzle()?; + + let inner_puzzle = ctx.alloc(&clvm_quote!((run_tail, extra_conditions)))?; + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); + + let puzzle = ctx.alloc(&CurriedProgram { + program: cat_puzzle_ptr, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), asset_id, - lineage_proof: LineageProof { - parent_parent_coin_id: eve_coin.parent_coin_info, - parent_inner_puzzle_hash: inner_puzzle_hash, - parent_amount: eve_coin.amount, - }, - eve_coin, - }; - - Ok((chained_spend, issuance_info)) - } + inner_puzzle, + }, + })?; + + let puzzle_hash = ctx.tree_hash(puzzle).into(); + let eve_coin = Coin::new(parent_coin_id, puzzle_hash, amount); + + let solution = ctx.serialize(&CatSolution { + inner_puzzle_solution: (), + lineage_proof: None, + prev_coin_id: eve_coin.coin_id(), + this_coin_info: eve_coin, + next_coin_proof: CoinProof { + parent_coin_info: parent_coin_id, + inner_puzzle_hash, + amount, + }, + prev_subtotal: 0, + extra_delta: 0, + })?; + + let puzzle_reveal = ctx.serialize(&puzzle)?; + ctx.insert_coin_spend(CoinSpend::new(eve_coin, puzzle_reveal, solution)); + + let chained_spend = Conditions::new().create_hinted_coin(puzzle_hash, amount, puzzle_hash); + + let issuance_info = IssueCat { + asset_id, + lineage_proof: LineageProof { + parent_parent_coin_id: eve_coin.parent_coin_info, + parent_inner_puzzle_hash: inner_puzzle_hash, + parent_amount: eve_coin.amount, + }, + eve_coin, + }; + + Ok((chained_spend, issuance_info)) } #[cfg(test)] @@ -156,11 +151,12 @@ mod tests { let mut allocator = Allocator::new(); let ctx = &mut SpendContext::new(&mut allocator); - let (issue_cat, _cat_info) = IssueCat::new( + let (issue_cat, _cat_info) = issue_cat_from_coin( + ctx, coin.coin_id(), + 1, Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), - ) - .single_issuance(ctx, 1)?; + )?; ctx.spend_p2_coin(coin, pk, issue_cat)?; @@ -189,11 +185,13 @@ mod tests { let mut allocator = Allocator::new(); let ctx = &mut SpendContext::new(&mut allocator); - let (issue_cat, _cat_info) = IssueCat::new( + let (issue_cat, _cat_info) = issue_cat_from_key( + ctx, coin.coin_id(), + pk, + 1, Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), - ) - .multi_issuance(ctx, pk, 1)?; + )?; ctx.spend_p2_coin(coin, pk, issue_cat)?; diff --git a/crates/chia-sdk-parser/src/puzzles/cat.rs b/crates/chia-sdk-parser/src/puzzles/cat.rs index da54a13a..317161f5 100644 --- a/crates/chia-sdk-parser/src/puzzles/cat.rs +++ b/crates/chia-sdk-parser/src/puzzles/cat.rs @@ -101,7 +101,7 @@ mod tests { use chia_bls::PublicKey; use chia_protocol::Coin; use chia_puzzles::standard::StandardArgs; - use chia_sdk_driver::{Conditions, IssueCat, SpendContext}; + use chia_sdk_driver::{issue_cat_from_key, Conditions, SpendContext}; use clvm_traits::ToNodePtr; use super::*; @@ -115,11 +115,13 @@ mod tests { let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); - let (issue_cat, issuance_info) = IssueCat::new( + let (issue_cat, issuance_info) = issue_cat_from_key( + ctx, parent.coin_id(), + pk, + 1, Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), - ) - .multi_issuance(ctx, pk, 1)?; + )?; let cat_puzzle_hash = CatArgs::curry_tree_hash(issuance_info.asset_id, puzzle_hash.into()); From aaf56bfc46dcb0764fa1047c563b3e5f655d5896 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 9 Jun 2024 20:56:54 -0400 Subject: [PATCH 4/4] Refactor p2_spend --- crates/chia-sdk-driver/src/conditions.rs | 57 +++++++++++++++++++ crates/chia-sdk-driver/src/puzzles.rs | 2 - .../src/puzzles/cat/cat_spend.rs | 45 ++++++--------- .../chia-sdk-driver/src/puzzles/standard.rs | 57 ------------------- crates/chia-sdk-driver/src/spend_context.rs | 18 +++--- 5 files changed, 83 insertions(+), 96 deletions(-) delete mode 100644 crates/chia-sdk-driver/src/puzzles/standard.rs diff --git a/crates/chia-sdk-driver/src/conditions.rs b/crates/chia-sdk-driver/src/conditions.rs index 091a29dd..2e8d9022 100644 --- a/crates/chia-sdk-driver/src/conditions.rs +++ b/crates/chia-sdk-driver/src/conditions.rs @@ -1,4 +1,6 @@ +use chia_bls::PublicKey; use chia_protocol::{Bytes, Bytes32}; +use chia_puzzles::standard::{StandardArgs, StandardSolution}; use chia_sdk_types::conditions::{ AssertBeforeHeightAbsolute, AssertBeforeHeightRelative, AssertBeforeSecondsAbsolute, AssertBeforeSecondsRelative, AssertCoinAnnouncement, AssertHeightAbsolute, @@ -7,11 +9,14 @@ use chia_sdk_types::conditions::{ }; use clvm_traits::{ClvmEncoder, ToClvm, ToClvmError}; +use clvm_utils::CurriedProgram; use clvmr::{ sha2::{Digest, Sha256}, NodePtr, }; +use crate::{Spend, SpendContext, SpendError}; + #[derive(Debug, Default, Clone)] #[must_use] pub struct Conditions { @@ -143,6 +148,23 @@ impl Conditions { height, ))) } + + pub fn p2_spend( + self, + ctx: &mut SpendContext<'_>, + synthetic_key: PublicKey, + ) -> Result { + let standard_puzzle = ctx.standard_puzzle()?; + + let puzzle = ctx.alloc(&CurriedProgram { + program: standard_puzzle, + args: StandardArgs::new(synthetic_key), + })?; + + let solution = ctx.alloc(&StandardSolution::from_conditions(self))?; + + Ok(Spend::new(puzzle, solution)) + } } impl AsRef<[Condition]> for Conditions { @@ -168,3 +190,38 @@ impl ToClvm for Conditions { self.conditions.to_clvm(encoder) } } + +#[cfg(test)] +mod tests { + use chia_sdk_test::{test_transaction, Simulator}; + use clvmr::Allocator; + + use super::*; + + #[tokio::test] + async fn test_standard_spend() -> anyhow::Result<()> { + let sim = Simulator::new().await?; + let peer = sim.connect().await?; + + let sk = sim.secret_key().await?; + let pk = sk.public_key(); + + let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); + let coin = sim.mint_coin(puzzle_hash, 1).await; + + let mut allocator = Allocator::new(); + let ctx = &mut SpendContext::new(&mut allocator); + + ctx.spend_p2_coin(coin, pk, Conditions::new().create_coin(puzzle_hash, 1))?; + + test_transaction( + &peer, + ctx.take_spends(), + &[sk], + sim.config().genesis_challenge, + ) + .await; + + Ok(()) + } +} diff --git a/crates/chia-sdk-driver/src/puzzles.rs b/crates/chia-sdk-driver/src/puzzles.rs index 9ca73871..37af4c12 100644 --- a/crates/chia-sdk-driver/src/puzzles.rs +++ b/crates/chia-sdk-driver/src/puzzles.rs @@ -2,10 +2,8 @@ mod cat; mod did; mod nft; mod singleton; -mod standard; pub use cat::*; pub use did::*; pub use nft::*; pub use singleton::*; -pub use standard::*; diff --git a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs index 385f29d8..a65b061d 100644 --- a/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs +++ b/crates/chia-sdk-driver/src/puzzles/cat/cat_spend.rs @@ -119,7 +119,7 @@ mod tests { use chia_sdk_types::conditions::{Condition, RunTail}; use clvmr::Allocator; - use crate::{issue_cat_from_coin, issue_cat_from_key, p2_spend, Conditions}; + use crate::{issue_cat_from_coin, issue_cat_from_key, Conditions}; use super::*; @@ -155,31 +155,25 @@ mod tests { CatSpend::new(issuance.asset_id) .spend( Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 1), - p2_spend( - ctx, - pk, - Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), - )?, + Conditions::new() + .create_hinted_coin(puzzle_hash, 1, puzzle_hash) + .p2_spend(ctx, pk)?, issuance.lineage_proof, 0, ) .spend( Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 2), - p2_spend( - ctx, - pk, - Conditions::new().create_hinted_coin(puzzle_hash, 2, puzzle_hash), - )?, + Conditions::new() + .create_hinted_coin(puzzle_hash, 2, puzzle_hash) + .p2_spend(ctx, pk)?, issuance.lineage_proof, 0, ) .spend( Coin::new(issuance.eve_coin.coin_id(), cat_puzzle_hash, 3), - p2_spend( - ctx, - pk, - Conditions::new().create_hinted_coin(puzzle_hash, 3, puzzle_hash), - )?, + Conditions::new() + .create_hinted_coin(puzzle_hash, 3, puzzle_hash) + .p2_spend(ctx, pk)?, issuance.lineage_proof, 0, ) @@ -226,11 +220,9 @@ mod tests { CatSpend::new(issuance.asset_id) .spend( cat_coin, - p2_spend( - ctx, - pk, - Conditions::new().create_hinted_coin(puzzle_hash, 1, puzzle_hash), - )?, + Conditions::new() + .create_hinted_coin(puzzle_hash, 1, puzzle_hash) + .p2_spend(ctx, pk)?, issuance.lineage_proof, 0, ) @@ -278,13 +270,10 @@ mod tests { })?; let run_tail = Condition::Other(ctx.alloc(&RunTail::new(tail_program, ()))?); - let inner_spend = p2_spend( - ctx, - pk, - Conditions::new() - .create_hinted_coin(puzzle_hash, 7000, puzzle_hash) - .condition(run_tail), - )?; + let inner_spend = Conditions::new() + .create_hinted_coin(puzzle_hash, 7000, puzzle_hash) + .condition(run_tail) + .p2_spend(ctx, pk)?; let cat_puzzle_hash = CatArgs::curry_tree_hash(issuance.asset_id, puzzle_hash.into()).into(); diff --git a/crates/chia-sdk-driver/src/puzzles/standard.rs b/crates/chia-sdk-driver/src/puzzles/standard.rs deleted file mode 100644 index 20bf6744..00000000 --- a/crates/chia-sdk-driver/src/puzzles/standard.rs +++ /dev/null @@ -1,57 +0,0 @@ -use chia_bls::PublicKey; -use chia_puzzles::standard::{StandardArgs, StandardSolution}; -use clvm_utils::CurriedProgram; - -use crate::{Conditions, Spend, SpendContext, SpendError}; - -pub fn p2_spend( - ctx: &mut SpendContext<'_>, - synthetic_key: PublicKey, - conditions: Conditions, -) -> Result { - let standard_puzzle = ctx.standard_puzzle()?; - - let puzzle = ctx.alloc(&CurriedProgram { - program: standard_puzzle, - args: StandardArgs::new(synthetic_key), - })?; - - let solution = ctx.alloc(&StandardSolution::from_conditions(conditions))?; - - Ok(Spend::new(puzzle, solution)) -} - -#[cfg(test)] -mod tests { - use chia_sdk_test::{test_transaction, Simulator}; - use clvmr::Allocator; - - use super::*; - - #[tokio::test] - async fn test_standard_spend() -> anyhow::Result<()> { - let sim = Simulator::new().await?; - let peer = sim.connect().await?; - - let sk = sim.secret_key().await?; - let pk = sk.public_key(); - - let puzzle_hash = StandardArgs::curry_tree_hash(pk).into(); - let coin = sim.mint_coin(puzzle_hash, 1).await; - - let mut allocator = Allocator::new(); - let ctx = &mut SpendContext::new(&mut allocator); - - ctx.spend_p2_coin(coin, pk, Conditions::new().create_coin(puzzle_hash, 1))?; - - test_transaction( - &peer, - ctx.take_spends(), - &[sk], - sim.config().genesis_challenge, - ) - .await; - - Ok(()) - } -} diff --git a/crates/chia-sdk-driver/src/spend_context.rs b/crates/chia-sdk-driver/src/spend_context.rs index 1067be6c..be97a1fe 100644 --- a/crates/chia-sdk-driver/src/spend_context.rs +++ b/crates/chia-sdk-driver/src/spend_context.rs @@ -30,8 +30,7 @@ use clvm_utils::{tree_hash, TreeHash}; use clvmr::{run_program, serde::node_from_bytes, Allocator, ChiaDialect, NodePtr}; use crate::{ - did_spend, nft_spend, p2_spend, recreate_did, spend_error::SpendError, transfer_nft, - Conditions, Spend, + did_spend, nft_spend, recreate_did, spend_error::SpendError, transfer_nft, Conditions, Spend, }; /// A wrapper around `Allocator` that caches puzzles and simplifies coin spending. @@ -231,7 +230,7 @@ impl<'a> SpendContext<'a> { synthetic_key: PublicKey, conditions: Conditions, ) -> Result<(), SpendError> { - let p2_spend = p2_spend(self, synthetic_key, conditions)?; + let p2_spend = conditions.p2_spend(self, synthetic_key)?; self.spend(coin, p2_spend) } @@ -246,7 +245,9 @@ impl<'a> SpendContext<'a> { M: ToClvm + Clone, { let (conditions, new_did_info) = recreate_did(did_info.clone()); - let p2_spend = p2_spend(self, synthetic_key, conditions.extend(extra_conditions))?; + let p2_spend = conditions + .extend(extra_conditions) + .p2_spend(self, synthetic_key)?; let did_spend = did_spend(self, did_info, p2_spend)?; self.insert_coin_spend(did_spend); Ok(new_did_info) @@ -265,11 +266,10 @@ impl<'a> SpendContext<'a> { M: ToClvm + Clone, { let transfer = transfer_nft(self, nft_info.clone(), p2_puzzle_hash, new_owner)?; - let p2_spend = p2_spend( - self, - synthetic_key, - transfer.p2_conditions.extend(extra_conditions), - )?; + let p2_spend = transfer + .p2_conditions + .extend(extra_conditions) + .p2_spend(self, synthetic_key)?; let nft_spend = nft_spend(self, nft_info, p2_spend)?; self.insert_coin_spend(nft_spend); Ok((transfer.did_conditions, transfer.output))