From 6adfdcf6a07396c1ff1795cd0ee07ccb0ff85e75 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 25 Apr 2024 01:19:37 -0400 Subject: [PATCH 01/25] Abstract and publicize intermediate launcher logic --- src/lib.rs | 9 ++++++++- src/spends/nft.rs | 40 +++++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 495cc4f8..5b16ddc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,8 @@ pub use spends::*; pub use ssl::*; pub use wallet::*; -fn trim_leading_zeros(mut slice: &[u8]) -> &[u8] { +/// Removes the leading zeros from a CLVM atom. +pub fn trim_leading_zeros(mut slice: &[u8]) -> &[u8] { while (!slice.is_empty()) && (slice[0] == 0) { if slice.len() > 1 && (slice[1] & 0x80 == 0x80) { break; @@ -32,6 +33,12 @@ fn trim_leading_zeros(mut slice: &[u8]) -> &[u8] { slice } +/// Converts a `usize` to an atom in CLVM format, with leading zeros trimmed. +pub fn usize_to_bytes(amount: usize) -> Vec { + let bytes: Vec = amount.to_be_bytes().into(); + trim_leading_zeros(bytes.as_slice()).to_vec() +} + #[cfg(test)] mod testing { use std::str::FromStr; diff --git a/src/spends/nft.rs b/src/spends/nft.rs index 506cf473..aeca0d08 100644 --- a/src/spends/nft.rs +++ b/src/spends/nft.rs @@ -22,7 +22,7 @@ use clvmr::{ }; use crate::{ - trim_leading_zeros, AssertCoinAnnouncement, AssertPuzzleAnnouncement, CreateCoinWithMemos, + usize_to_bytes, AssertCoinAnnouncement, AssertPuzzleAnnouncement, CreateCoinWithMemos, CreateCoinWithoutMemos, CreatePuzzleAnnouncement, NewNftOwner, SpendContext, SpendError, }; @@ -106,6 +106,8 @@ pub fn mint_nfts( synthetic_key: PublicKey, did_id: Bytes32, did_inner_puzzle_hash: Bytes32, + mint_start_index: usize, + mint_total: usize, ) -> Result { let mut coin_spends = Vec::new(); let mut outputs = Vec::new(); @@ -122,14 +124,16 @@ pub fn mint_nfts( args: StandardArgs { synthetic_key }, })?; - let mint_total = inputs.len(); - - for (mint_index, input) in inputs.into_iter().enumerate() { + for (i, input) in inputs.into_iter().enumerate() { let mut parent_conditions = Vec::new(); // Create the intermediate launcher. - let intermediate_spend = - spend_new_intermediate_launcher(ctx, input.parent_coin_id, mint_index, mint_total)?; + let intermediate_spend = spend_new_intermediate_launcher( + ctx, + input.parent_coin_id, + mint_start_index + i, + mint_total, + )?; let intermediate_id = intermediate_spend.coin.coin_id(); parent_conditions.push(ctx.alloc(CreateCoinWithoutMemos { @@ -137,13 +141,12 @@ pub fn mint_nfts( amount: intermediate_spend.coin.amount, })?); - let mut index_message = Sha256::new(); - index_message.update(usize_to_bytes(mint_index)); - index_message.update(usize_to_bytes(mint_total)); - let mut announcement_id = Sha256::new(); announcement_id.update(intermediate_id); - announcement_id.update(index_message.finalize()); + announcement_id.update(intermediate_launcher_message( + mint_start_index + i, + mint_total, + )); parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { announcement_id: Bytes::new(announcement_id.finalize().to_vec()), @@ -287,7 +290,11 @@ pub fn mint_nfts( }) } -fn spend_new_intermediate_launcher( +/// Creates a new intermediate launcher for a given parent coin id. +/// The intermediate launcher is used to create a new launcher coin. +/// You can use the output `CoinSpend` to spend the singleton launcher coin. +/// The parent coin will need to create a coin with the returned puzzle hash and amount. +pub fn spend_new_intermediate_launcher( ctx: &mut SpendContext, parent_coin_id: Bytes32, index: usize, @@ -315,7 +322,10 @@ fn spend_new_intermediate_launcher( )) } -fn usize_to_bytes(amount: usize) -> Vec { - let bytes: Vec = amount.to_be_bytes().into(); - trim_leading_zeros(bytes.as_slice()).to_vec() +/// Calculates the announcement message to assert from the intermediate launcher. +pub fn intermediate_launcher_message(index: usize, total: usize) -> Bytes32 { + let mut index_message = Sha256::new(); + index_message.update(usize_to_bytes(index)); + index_message.update(usize_to_bytes(total)); + Bytes32::new(index_message.finalize().into()) } From d2d1079a5014d476efeb14f4477ad534ff8706fd Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 25 Apr 2024 01:23:20 -0400 Subject: [PATCH 02/25] Use Bytes32 for AGG_SIG_ME --- src/wallet/required_signature.rs | 20 ++++++++++---------- src/wallet/signer.rs | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/wallet/required_signature.rs b/src/wallet/required_signature.rs index 35b14be1..47a75c14 100644 --- a/src/wallet/required_signature.rs +++ b/src/wallet/required_signature.rs @@ -1,5 +1,5 @@ use chia_bls::PublicKey; -use chia_protocol::{Bytes, Coin, CoinSpend, SpendBundle}; +use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend, SpendBundle}; use clvm_traits::{FromClvm, FromClvmError}; use clvmr::{allocator::NodePtr, reduction::EvalErr, Allocator}; use sha2::{digest::FixedOutput, Digest, Sha256}; @@ -25,12 +25,12 @@ pub struct RequiredSignature { public_key: PublicKey, raw_message: Bytes, appended_info: Vec, - domain_string: Option<[u8; 32]>, + domain_string: Option, } impl RequiredSignature { /// Converts a known AggSig condition to a `RequiredSignature` if possible. - pub fn from_condition(coin: &Coin, condition: AggSig, agg_sig_me: [u8; 32]) -> Self { + pub fn from_condition(coin: &Coin, condition: AggSig, agg_sig_me: Bytes32) -> Self { let mut hasher = Sha256::new(); hasher.update(agg_sig_me); @@ -90,7 +90,7 @@ impl RequiredSignature { public_key, raw_message: message, appended_info, - domain_string: Some(hasher.finalize_fixed().into()), + domain_string: Some(Bytes32::new(hasher.finalize_fixed().into())), } } @@ -100,7 +100,7 @@ impl RequiredSignature { pub fn from_coin_spend( allocator: &mut Allocator, coin_spend: &CoinSpend, - agg_sig_me: [u8; 32], + agg_sig_me: Bytes32, ) -> Result, ConditionError> { let output = coin_spend .puzzle_reveal @@ -125,7 +125,7 @@ impl RequiredSignature { pub fn from_spend_bundle( allocator: &mut Allocator, spend_bundle: &SpendBundle, - agg_sig_me: [u8; 32], + agg_sig_me: Bytes32, ) -> Result, ConditionError> { let mut required_signatures = Vec::new(); for coin_spend in &spend_bundle.coin_spends { @@ -150,7 +150,7 @@ impl RequiredSignature { } /// The domain string that is appended to the condition's message. - pub fn domain_string(&self) -> Option<[u8; 32]> { + pub fn domain_string(&self) -> Option { self.domain_string } @@ -159,7 +159,7 @@ impl RequiredSignature { let mut message = Vec::from(self.raw_message.as_ref()); message.extend(&self.appended_info); if let Some(domain_string) = self.domain_string { - message.extend(domain_string); + message.extend(domain_string.to_bytes()); } message } @@ -183,7 +183,7 @@ mod tests { #[test] fn test_messages() { let coin = Coin::new(Bytes32::from([1; 32]), Bytes32::from([2; 32]), 3); - let agg_sig_data = [4u8; 32]; + let agg_sig_data = Bytes32::new([4u8; 32]); let public_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); @@ -265,7 +265,7 @@ mod tests { message.extend(required.raw_message()); message.extend(required.appended_info()); if let Some(domain_string) = required.domain_string() { - message.extend(domain_string); + message.extend(domain_string.to_bytes()); } assert_eq!(hex::encode(message), hex::encode(required.final_message())); diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 64a0afc3..4246a3be 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -101,7 +101,8 @@ mod tests { ) -> Signature { let mut a = Allocator::new(); let required_signatures = - RequiredSignature::from_coin_spend(&mut a, coin_spend, AGG_SIG_ME).unwrap(); + RequiredSignature::from_coin_spend(&mut a, coin_spend, Bytes32::new(AGG_SIG_ME)) + .unwrap(); let mut aggregated_signature = Signature::default(); From 4c5705b24ce345592937d6f2a75bbcfd5f8eb6cc Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 26 Apr 2024 11:41:10 -0400 Subject: [PATCH 03/25] Add WalletSimulator --- Cargo.lock | 119 +++- Cargo.toml | 7 +- src/wallet.rs | 2 + src/wallet/wallet_simulator.rs | 989 +++++++++++++++++++++++++++++++++ 4 files changed, 1114 insertions(+), 3 deletions(-) create mode 100644 src/wallet/wallet_simulator.rs diff --git a/Cargo.lock b/Cargo.lock index b12ab6e7..6a17e1d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -391,16 +400,21 @@ dependencies = [ "chia-consensus", "chia-protocol", "chia-ssl", + "chia-traits 0.7.0", "chia-wallet", "clvm-traits", "clvm-utils", "clvmr", + "futures-channel", + "futures-util", "hex", "hex-literal", + "indexmap", "native-tls", "once_cell", "rand", "rand_chacha", + "rstest", "serde", "serde_json", "sha2 0.9.9", @@ -796,6 +810,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -863,12 +892,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1027,9 +1063,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -1561,6 +1597,41 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "rfc6979" version = "0.4.0" @@ -1606,6 +1677,35 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rstest" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.48", + "unicode-ident", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1618,6 +1718,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -1698,6 +1807,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" version = "1.0.197" diff --git a/Cargo.toml b/Cargo.toml index e222c23f..311a7b82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,15 @@ sqlx = { version = "0.7.4", features = ["runtime-tokio", "sqlite"], optional = t hex-literal = "0.4.1" serde_json = "1.0.115" serde = { version = "1.0.197", features = ["derive"] } +futures-util = "0.3.30" +futures-channel = "0.3.30" +chia-traits = "0.7.0" +chia-consensus = "0.7.0" +indexmap = "2.2.6" [dev-dependencies] bip39 = "2.0.0" -chia-consensus = "0.7.0" once_cell = "1.19.0" +rstest = "0.19.0" sqlx = { version = "0.7.4", features = ["runtime-tokio", "sqlite"] } tokio = { version = "1.33.0", features = ["full"] } diff --git a/src/wallet.rs b/src/wallet.rs index dcc3c5f9..d71a093f 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,7 +1,9 @@ mod coin_selection; mod required_signature; mod signer; +mod wallet_simulator; pub use coin_selection::*; pub use required_signature::*; pub use signer::*; +pub use wallet_simulator::*; diff --git a/src/wallet/wallet_simulator.rs b/src/wallet/wallet_simulator.rs new file mode 100644 index 00000000..2324f6da --- /dev/null +++ b/src/wallet/wallet_simulator.rs @@ -0,0 +1,989 @@ +use std::{collections::HashMap, net::SocketAddr, sync::Arc}; + +use chia_client::Peer; +use chia_consensus::gen::{ + conditions::EmptyVisitor, + owned_conditions::OwnedSpendBundleConditions, + run_block_generator::run_block_generator, + solution_generator::solution_generator, + validation_error::{ErrorCode, ValidationErr}, +}; +use chia_protocol::{ + Bytes, Bytes32, Coin, CoinState, CoinStateUpdate, Message, NewPeakWallet, Program, + ProtocolMessageTypes, PuzzleSolutionResponse, RegisterForCoinUpdates, RegisterForPhUpdates, + RejectPuzzleSolution, RequestChildren, RequestPuzzleSolution, RespondChildren, + RespondPuzzleSolution, RespondToCoinUpdates, RespondToPhUpdates, SendTransaction, SpendBundle, + TransactionAck, +}; +use chia_traits::Streamable; +use clvmr::{Allocator, NodePtr, MEMPOOL_MODE}; +use futures_channel::mpsc::{unbounded, UnboundedSender}; +use futures_util::{future, pin_mut, SinkExt, StreamExt, TryStreamExt}; +use indexmap::{IndexMap, IndexSet}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; +use sha2::{Digest, Sha256}; +use tokio::{ + net::{TcpListener, TcpStream}, + sync::Mutex, + task::JoinHandle, +}; +use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage}; + +type PeerMapInner = HashMap>; +type PeerMap = Arc>; + +/// A very limited full node simulator that can be used to test specifically wallet functionality. +/// It's not guaranteed to be fully accurate, and is only to be able to test wallet code efficiently. +pub struct WalletSimulator { + rng: Mutex, + addr: SocketAddr, + join_handle: JoinHandle<()>, + data: Arc>, +} + +#[derive(Default)] +struct Data { + block_height: u32, + coin_states: IndexMap, + hinted_coins: IndexMap>, + puzzle_subscriptions: IndexMap>, + coin_subscriptions: IndexMap>, + puzzle_and_solutions: IndexMap, +} + +impl WalletSimulator { + /// The `AGG_SIG_ME_ADDIITONAL_DATA` constant for the wallet simulator. + pub const AGG_SIG_ME: [u8; 32] = [42; 32]; + + /// Create a new wallet simulator and start listening for connections in the background. + pub async fn new() -> Self { + let addr = "127.0.0.1:0"; + let peer_map = PeerMap::new(Mutex::new(HashMap::new())); + let try_socket = TcpListener::bind(addr).await; + let listener = try_socket.unwrap_or_else(|_| panic!("failed to bind to `{addr}`")); + let addr = listener.local_addr().unwrap(); + + let data = Arc::new(Mutex::new(Data::default())); + let join_handle = tokio::spawn(listen_for_connections(peer_map, listener, data.clone())); + + Self { + rng: Mutex::new(ChaCha8Rng::seed_from_u64(0)), + addr, + join_handle, + data, + } + } + + /// Resets all of the simulator data to default. + pub async fn reset(&self) { + let mut data = self.data.lock().await; + *data = Data::default(); + } + + /// Generate a new coin with the given puzzle hash and amount. + pub async fn generate_coin(&self, puzzle_hash: Bytes32, amount: u64) -> CoinState { + let mut data = self.data.lock().await; + + let bytes = self.rng.lock().await.gen(); + let parent_coin_info = Bytes32::new(bytes); + + let coin = Coin { + parent_coin_info, + puzzle_hash, + amount, + }; + let coin_id = coin.coin_id(); + let coin_state = CoinState::new(coin, None, Some(data.block_height)); + + data.coin_states.insert(coin_id, coin_state.clone()); + coin_state + } + + /// Connects a WebSocket peer to the wallet simulator. + pub async fn peer(&self) -> Peer { + let (ws, _) = connect_async(format!("ws://{}", self.addr)) + .await + .expect("failed to connect to websocket server"); + Peer::new(ws) + } +} + +impl Drop for WalletSimulator { + fn drop(&mut self) { + self.join_handle.abort(); + } +} + +async fn listen_for_connections(peer_map: PeerMap, listener: TcpListener, data: Arc>) { + while let Ok((stream, addr)) = listener.accept().await { + tokio::spawn(handle_connection( + peer_map.clone(), + stream, + addr, + data.clone(), + )); + } +} + +async fn handle_connection( + peer_map: PeerMap, + raw_stream: TcpStream, + addr: SocketAddr, + data: Arc>, +) { + let ws_stream = tokio_tungstenite::accept_async(raw_stream) + .await + .expect("failed to accept websocket connection"); + + let (tx, rx) = unbounded(); + peer_map.lock().await.insert(addr, tx); + + let (sink, stream) = ws_stream.split(); + + let broadcast_incoming = stream.try_for_each(|message| { + let peer_map = peer_map.clone(); + let data = data.clone(); + + async move { + let message = Message::from_bytes(&message.into_data()).unwrap(); + + match message.msg_type { + ProtocolMessageTypes::SendTransaction => { + let tx = SendTransaction::from_bytes(message.data.as_ref()).unwrap(); + let spend_bundle = tx.transaction; + + let mut allocator = Allocator::new(); + let gen = solution_generator( + spend_bundle + .coin_spends + .iter() + .cloned() + .map(|spend| (spend.coin, spend.puzzle_reveal, spend.solution)), + ) + .unwrap(); + + let transaction_id = spend_bundle.name(); + + let error = match run_block_generator::<&[u8], EmptyVisitor>( + &mut allocator, + &gen, + &[], + 11_000_000_000, + MEMPOOL_MODE, + ) { + Ok(conds) => { + let conds = OwnedSpendBundleConditions::from(&allocator, conds); + process_spend_bundle(peer_map.clone(), conds, data, spend_bundle) + .await + .err() + } + Err(error) => Some(error), + }; + + let body = match error { + Some(error) => { + TransactionAck::new(transaction_id, 3, Some(error.to_string())) + } + None => TransactionAck::new(transaction_id, 1, None), + } + .to_bytes() + .unwrap(); + + let response = Message { + msg_type: ProtocolMessageTypes::TransactionAck, + data: body.into(), + id: message.id, + } + .to_bytes() + .unwrap(); + + peer_map + .lock() + .await + .get_mut(&addr) + .unwrap() + .send(response.into()) + .await + .unwrap(); + } + ProtocolMessageTypes::RegisterForCoinUpdates => { + let request = + RegisterForCoinUpdates::from_bytes(message.data.as_ref()).unwrap(); + + let mut coin_states = Vec::new(); + let mut data = data.lock().await; + + for coin_id in request.coin_ids.iter() { + if let Some(coin_state) = data.coin_states.get(coin_id) { + coin_states.push(coin_state.clone()); + } + + data.coin_subscriptions + .entry(addr) + .or_default() + .insert(*coin_id); + } + + let response = Message { + msg_type: ProtocolMessageTypes::RespondToCoinUpdates, + data: RespondToCoinUpdates { + coin_ids: request.coin_ids, + min_height: request.min_height, + coin_states, + } + .to_bytes() + .unwrap() + .into(), + id: message.id, + } + .to_bytes() + .unwrap(); + + peer_map + .lock() + .await + .get_mut(&addr) + .unwrap() + .send(response.into()) + .await + .unwrap(); + } + ProtocolMessageTypes::RegisterForPhUpdates => { + let request = RegisterForPhUpdates::from_bytes(message.data.as_ref()).unwrap(); + + let mut coin_states = IndexMap::new(); + let mut data = data.lock().await; + + for (coin_id, coin_state) in data.coin_states.iter() { + if request.puzzle_hashes.contains(&coin_state.coin.puzzle_hash) { + coin_states.insert(*coin_id, data.coin_states[coin_id].clone()); + } + } + + for puzzle_hash in request.puzzle_hashes.iter() { + if let Some(coin_state) = data.coin_states.get(puzzle_hash) { + coin_states.insert(coin_state.coin.coin_id(), coin_state.clone()); + } + + if let Some(hinted_coins) = + data.hinted_coins.get(&Bytes::new(puzzle_hash.to_vec())) + { + for coin_id in hinted_coins.iter() { + coin_states.insert(*coin_id, data.coin_states[coin_id].clone()); + } + } + + data.puzzle_subscriptions + .entry(addr) + .or_default() + .insert(*puzzle_hash); + } + + let response = Message { + msg_type: ProtocolMessageTypes::RespondToPhUpdates, + data: RespondToPhUpdates { + puzzle_hashes: request.puzzle_hashes, + min_height: request.min_height, + coin_states: coin_states.into_values().collect(), + } + .to_bytes() + .unwrap() + .into(), + id: message.id, + } + .to_bytes() + .unwrap(); + + peer_map + .lock() + .await + .get_mut(&addr) + .unwrap() + .send(response.into()) + .await + .unwrap(); + } + ProtocolMessageTypes::RequestPuzzleSolution => { + let request = RequestPuzzleSolution::from_bytes(message.data.as_ref()).unwrap(); + let data = data.lock().await; + + let matches_height = data + .coin_states + .get(&request.coin_name) + .map_or(false, |cs| cs.spent_height == Some(request.height)); + + let response = match data.puzzle_and_solutions.get(&request.coin_name).cloned() + { + Some((puzzle, solution)) if matches_height => Message { + msg_type: ProtocolMessageTypes::RespondPuzzleSolution, + data: RespondPuzzleSolution::new(PuzzleSolutionResponse::new( + request.coin_name, + request.height, + puzzle, + solution, + )) + .to_bytes() + .unwrap() + .into(), + id: message.id, + } + .to_bytes() + .unwrap(), + _ => Message { + msg_type: ProtocolMessageTypes::RejectPuzzleSolution, + data: RejectPuzzleSolution::new(request.coin_name, request.height) + .to_bytes() + .unwrap() + .into(), + id: message.id, + } + .to_bytes() + .unwrap(), + }; + + peer_map + .lock() + .await + .get_mut(&addr) + .unwrap() + .send(response.into()) + .await + .unwrap(); + } + ProtocolMessageTypes::RequestChildren => { + let request = RequestChildren::from_bytes(message.data.as_ref()).unwrap(); + let data = data.lock().await; + + let coin_states: Vec = data + .coin_states + .iter() + .filter(|(_, cs)| cs.coin.parent_coin_info == request.coin_name) + .map(|(_, cs)| cs.clone()) + .collect(); + + let response = Message { + msg_type: ProtocolMessageTypes::RespondChildren, + data: RespondChildren::new(coin_states).to_bytes().unwrap().into(), + id: message.id, + } + .to_bytes() + .unwrap(); + + peer_map + .lock() + .await + .get_mut(&addr) + .unwrap() + .send(response.into()) + .await + .unwrap(); + } + _ => unimplemented!( + "unsupported message type for wallet simulator: {:?}", + message.msg_type + ), + } + + Ok(()) + } + }); + + let receive_from_others = rx.map(Ok).forward(sink); + + pin_mut!(broadcast_incoming, receive_from_others); + future::select(broadcast_incoming, receive_from_others).await; + + peer_map.lock().await.remove(&addr); +} + +async fn process_spend_bundle( + peer_map: PeerMap, + conds: OwnedSpendBundleConditions, + data: Arc>, + spend_bundle: SpendBundle, +) -> Result<(), ValidationErr> { + let data = &mut data.lock().await; + + let mut removed_coins = IndexMap::new(); + let mut added_coins = IndexMap::new(); + let mut added_hints: IndexMap> = IndexMap::new(); + let mut puzzles_and_solutions = IndexMap::new(); + + for coin_spend in spend_bundle.coin_spends.into_iter() { + puzzles_and_solutions.insert( + coin_spend.coin.coin_id(), + (coin_spend.puzzle_reveal, coin_spend.solution), + ); + } + + // Calculate additions and removals. + for spend in conds.spends.iter() { + for new_coin in spend.create_coin.iter() { + let coin = Coin { + parent_coin_info: spend.coin_id, + puzzle_hash: new_coin.0, + amount: new_coin.1, + }; + + let coin_id = coin.coin_id(); + + let coin_state = CoinState { + coin, + spent_height: None, + created_height: Some(data.block_height), + }; + + added_coins.insert(coin_id, coin_state); + + if let Some(hint) = new_coin.2.clone() { + added_hints.entry(hint).or_default().insert(coin_id); + } + } + + let coin_state = data + .coin_states + .get(&spend.coin_id) + .cloned() + .unwrap_or_else(|| CoinState { + coin: Coin { + parent_coin_info: spend.parent_id, + puzzle_hash: spend.puzzle_hash, + amount: spend.coin_amount, + }, + created_height: Some(data.block_height), + spent_height: None, + }); + + removed_coins.insert(spend.coin_id, coin_state); + } + + // Validate removals. + for coin_state in removed_coins.values_mut() { + let height = data.block_height; + let coin_id = coin_state.coin.coin_id(); + + if !data.coin_states.contains_key(&coin_id) && !added_coins.contains_key(&coin_id) { + return Err(ValidationErr(NodePtr::NIL, ErrorCode::UnknownUnspent)); + } + + if coin_state.spent_height.is_some() { + return Err(ValidationErr(NodePtr::NIL, ErrorCode::DoubleSpend)); + } + + coin_state.spent_height = Some(height); + } + + // Update the coin data. + let mut updates = added_coins.clone(); + updates.extend(removed_coins); + data.block_height += 1; + data.coin_states.extend(updates.clone()); + data.hinted_coins.extend(added_hints.clone()); + data.puzzle_and_solutions.extend(puzzles_and_solutions); + + // Calculate a deterministic but fake header hash. + let mut hasher = Sha256::new(); + hasher.update(data.block_height.to_be_bytes()); + let header_hash = Bytes32::new(hasher.finalize().into()); + + let mut peers = peer_map.lock().await; + + // Send updates to peers. + for (&addr, peer) in peers.iter_mut() { + let mut peer_updates = IndexSet::new(); + + let coin_subscriptions = data + .coin_subscriptions + .get(&addr) + .cloned() + .unwrap_or_default(); + + let puzzle_subscriptions = data + .puzzle_subscriptions + .get(&addr) + .cloned() + .unwrap_or_default(); + + for (hint, coins) in added_hints.iter() { + let Ok(hint) = hint.to_vec().try_into() else { + continue; + }; + let hint = Bytes32::new(hint); + + if puzzle_subscriptions.contains(&hint) { + peer_updates.extend( + coins + .iter() + .map(|coin_id| data.coin_states[coin_id].clone()), + ); + } + } + + for coin_id in updates.keys() { + if coin_subscriptions.contains(coin_id) { + peer_updates.insert(data.coin_states[coin_id].clone()); + } + + if puzzle_subscriptions.contains(&data.coin_states[coin_id].coin.puzzle_hash) { + peer_updates.insert(data.coin_states[coin_id].clone()); + } + } + + let new_peak = Message { + msg_type: ProtocolMessageTypes::NewPeakWallet, + id: None, + data: NewPeakWallet::new(header_hash, data.block_height, 0, data.block_height) + .to_bytes() + .unwrap() + .into(), + } + .to_bytes() + .unwrap(); + + peer.send(new_peak.into()).await.unwrap(); + + if !peer_updates.is_empty() { + let update = Message { + msg_type: ProtocolMessageTypes::CoinStateUpdate, + id: None, + data: CoinStateUpdate::new( + data.block_height, + data.block_height, + header_hash, + peer_updates.into_iter().collect(), + ) + .to_bytes() + .unwrap() + .into(), + } + .to_bytes() + .unwrap(); + + peer.send(update.into()).await.unwrap(); + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use chia_bls::{sign, Signature}; + use chia_client::PeerEvent; + use chia_protocol::{CoinSpend, SpendBundle}; + use chia_wallet::{standard::DEFAULT_HIDDEN_PUZZLE_HASH, DeriveSynthetic}; + + use crate::{ + testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, RequiredSignature, + SpendContext, + }; + + use super::*; + + fn sign_bundle(spend_bundle: &mut SpendBundle) { + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + + let mut a = Allocator::new(); + + let required_signatures = RequiredSignature::from_spend_bundle( + &mut a, + spend_bundle, + Bytes32::new(WalletSimulator::AGG_SIG_ME), + ) + .unwrap(); + + let mut aggregated_signature = Signature::default(); + + for required_signature in required_signatures { + aggregated_signature += &sign(&sk, &required_signature.final_message()); + } + } + + #[tokio::test] + async fn test_coin_lineage_many_blocks() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut a = Allocator::new(); + let mut ctx = SpendContext::new(&mut a); + + let puzzle = ctx.alloc(1).unwrap(); + let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_reveal = ctx.serialize(puzzle).unwrap(); + + let mut coin = sim.generate_coin(puzzle_hash, 1000).await.coin; + + for _ in 0..1000 { + let solution = ctx + .serialize([CreateCoinWithoutMemos { + puzzle_hash, + amount: coin.amount - 1, + }]) + .unwrap(); + + let coin_spend = CoinSpend::new(coin.clone(), puzzle_reveal.clone(), solution); + + let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); + sign_bundle(&mut spend_bundle); + + let transaction_id = spend_bundle.name(); + let ack = peer.send_transaction(spend_bundle.clone()).await.unwrap(); + assert_eq!(ack, TransactionAck::new(transaction_id, 1, None)); + + coin = Coin { + parent_coin_info: coin.coin_id(), + puzzle_hash, + amount: coin.amount - 1, + }; + + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack.txid, transaction_id); + assert_eq!(ack.status, 3); + } + } + + #[tokio::test] + async fn test_spend_unknown_coin() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut a = Allocator::new(); + let mut ctx = SpendContext::new(&mut a); + + let puzzle = ctx.alloc(1).unwrap(); + let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_reveal = ctx.serialize(puzzle).unwrap(); + + let solution = ctx + .serialize([CreateCoinWithoutMemos { + puzzle_hash, + amount: 1000, + }]) + .unwrap(); + + let coin_spend = CoinSpend::new( + Coin { + parent_coin_info: Bytes32::new([42; 32]), + puzzle_hash, + amount: 1000, + }, + puzzle_reveal, + solution, + ); + + let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); + sign_bundle(&mut spend_bundle); + + let transaction_id = spend_bundle.name(); + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack.txid, transaction_id); + assert_eq!(ack.status, 3); + } + + #[tokio::test] + async fn test_coin_subscriptions() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut a = Allocator::new(); + let mut ctx = SpendContext::new(&mut a); + + let puzzle = ctx.alloc(1).unwrap(); + let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_reveal = ctx.serialize(puzzle).unwrap(); + + let mut cs = sim.generate_coin(puzzle_hash, 1000).await; + + // Subscribe and request initial state. + let results = peer + .register_for_coin_updates(vec![cs.coin.coin_id()], 0) + .await + .unwrap(); + + assert_eq!(results, vec![cs.clone()]); + + // The initial state should still be the same. + let results = peer + .register_for_coin_updates(vec![cs.coin.coin_id()], 0) + .await + .unwrap(); + + assert_eq!(results, vec![cs.clone()]); + + let mut receiver = peer.receiver().resubscribe(); + + while receiver.try_recv().is_ok() {} + + // Spend the coin. + let solution = ctx + .serialize([CreateCoinWithoutMemos { + puzzle_hash, + amount: cs.coin.amount - 1, + }]) + .unwrap(); + + let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal, solution); + + let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); + sign_bundle(&mut spend_bundle); + + let transaction_id = spend_bundle.name(); + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack, TransactionAck::new(transaction_id, 1, None)); + + // We should have gotten a new peak and an update. + // But the coin is spent now. + cs.spent_height = Some(0); + + let event = receiver.recv().await.unwrap(); + assert!(matches!(event, PeerEvent::NewPeakWallet(..))); + + let event = receiver.recv().await.unwrap(); + match event { + PeerEvent::CoinStateUpdate(update) => { + assert_eq!(update.items, vec![cs]); + } + _ => panic!("unexpected event: {:?}", event), + } + } + + #[tokio::test] + async fn test_puzzle_subscriptions() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut a = Allocator::new(); + let mut ctx = SpendContext::new(&mut a); + + let puzzle = ctx.alloc(1).unwrap(); + let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_reveal = ctx.serialize(puzzle).unwrap(); + + let mut cs = sim.generate_coin(puzzle_hash, 1000).await; + + // Subscribe and request initial state. + let results = peer + .register_for_ph_updates(vec![cs.coin.puzzle_hash], 0) + .await + .unwrap(); + + assert_eq!(results, vec![cs.clone()]); + + // The initial state should still be the same. + let results = peer + .register_for_ph_updates(vec![cs.coin.puzzle_hash], 0) + .await + .unwrap(); + + assert_eq!(results, vec![cs.clone()]); + + let mut receiver = peer.receiver().resubscribe(); + + while receiver.try_recv().is_ok() {} + + // Spend the coin. + let solution = ctx + .serialize([CreateCoinWithoutMemos { + puzzle_hash, + amount: cs.coin.amount - 1, + }]) + .unwrap(); + + let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal, solution); + + let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); + sign_bundle(&mut spend_bundle); + + let transaction_id = spend_bundle.name(); + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack, TransactionAck::new(transaction_id, 1, None)); + + // We should have gotten a new peak and an update. + // But the coin is spent now. + cs.spent_height = Some(0); + let new_cs = CoinState { + coin: Coin { + parent_coin_info: cs.coin.coin_id(), + puzzle_hash, + amount: cs.coin.amount - 1, + }, + created_height: Some(0), + spent_height: None, + }; + + let event = receiver.recv().await.unwrap(); + assert!(matches!(event, PeerEvent::NewPeakWallet(..))); + + let event = receiver.recv().await.unwrap(); + match event { + PeerEvent::CoinStateUpdate(update) => { + let items = update.items.into_iter().collect::>(); + let expected = vec![cs, new_cs].into_iter().collect::>(); + assert_eq!(items, expected); + } + _ => panic!("unexpected event: {:?}", event), + } + } + + #[tokio::test] + async fn test_hint_subscriptions() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut a = Allocator::new(); + let mut ctx = SpendContext::new(&mut a); + + let puzzle = ctx.alloc(1).unwrap(); + let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_reveal = ctx.serialize(puzzle).unwrap(); + + let cs = sim.generate_coin(puzzle_hash, 1000).await; + + let hint = Bytes32::new([34; 32]); + + // Subscribe and request initial state. + let results = peer.register_for_ph_updates(vec![hint], 0).await.unwrap(); + assert!(results.is_empty()); + + let mut receiver = peer.receiver().resubscribe(); + + while receiver.try_recv().is_ok() {} + + // Spend the coin. + let solution = ctx + .serialize([CreateCoinWithMemos { + puzzle_hash, + amount: cs.coin.amount - 1, + memos: vec![hint.to_bytes().to_vec().into()], + }]) + .unwrap(); + + let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal, solution); + + let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); + sign_bundle(&mut spend_bundle); + + let transaction_id = spend_bundle.name(); + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack, TransactionAck::new(transaction_id, 1, None)); + + // We should have gotten a new peak and an update. + let event = receiver.recv().await.unwrap(); + assert!(matches!(event, PeerEvent::NewPeakWallet(..))); + + let event = receiver.recv().await.unwrap(); + match event { + PeerEvent::CoinStateUpdate(update) => { + let new_cs = CoinState { + coin: Coin { + parent_coin_info: cs.coin.coin_id(), + puzzle_hash, + amount: cs.coin.amount - 1, + }, + created_height: Some(0), + spent_height: None, + }; + + assert_eq!(update.items, vec![new_cs]); + } + _ => panic!("unexpected event: {:?}", event), + } + } + + #[tokio::test] + async fn test_puzzle_solution() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut a = Allocator::new(); + let mut ctx = SpendContext::new(&mut a); + + let puzzle = ctx.alloc(1).unwrap(); + let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_reveal = ctx.serialize(puzzle).unwrap(); + + let cs = sim.generate_coin(puzzle_hash, 1000).await; + + let solution = ctx + .serialize([CreateCoinWithoutMemos { + puzzle_hash, + amount: cs.coin.amount - 1, + }]) + .unwrap(); + + let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal.clone(), solution.clone()); + + let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); + sign_bundle(&mut spend_bundle); + + let transaction_id = spend_bundle.name(); + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack, TransactionAck::new(transaction_id, 1, None)); + + let mut receiver = peer.receiver().resubscribe(); + + while receiver.try_recv().is_ok() {} + + let response = peer + .request_puzzle_and_solution(cs.coin.coin_id(), 0) + .await + .unwrap(); + + assert_eq!( + response, + PuzzleSolutionResponse::new(cs.coin.coin_id(), 0, puzzle_reveal, solution) + ); + } + + #[tokio::test] + async fn test_request_children() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut a = Allocator::new(); + let mut ctx = SpendContext::new(&mut a); + + let puzzle = ctx.alloc(1).unwrap(); + let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_reveal = ctx.serialize(puzzle).unwrap(); + + let cs = sim.generate_coin(puzzle_hash, 1000).await; + + let solution = ctx + .serialize([CreateCoinWithoutMemos { + puzzle_hash, + amount: cs.coin.amount - 1, + }]) + .unwrap(); + + let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal.clone(), solution.clone()); + + let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); + sign_bundle(&mut spend_bundle); + + let transaction_id = spend_bundle.name(); + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack, TransactionAck::new(transaction_id, 1, None)); + + let mut receiver = peer.receiver().resubscribe(); + + while receiver.try_recv().is_ok() {} + + let response = peer.request_children(cs.coin.coin_id()).await.unwrap(); + + let new_cs = CoinState { + coin: Coin { + parent_coin_info: cs.coin.coin_id(), + puzzle_hash, + amount: cs.coin.amount - 1, + }, + created_height: Some(0), + spent_height: None, + }; + + assert_eq!(response, vec![new_cs]); + } +} From be124e2226e2bbec22461b009f177d2c994103d6 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 30 Apr 2024 19:40:47 -0400 Subject: [PATCH 04/25] Initial move and offers --- Cargo.lock | 32 +++ Cargo.toml | 3 +- src/lib.rs | 1 - src/spends.rs | 240 +----------------- src/spends/did.rs | 29 --- src/spends/puzzles.rs | 13 + src/spends/{ => puzzles}/cat.rs | 0 src/spends/puzzles/did.rs | 238 +++++++++++++++++ src/spends/{ => puzzles}/nft.rs | 130 +++++----- src/spends/puzzles/offer.rs | 5 + src/spends/puzzles/offer/offer_compression.rs | 182 +++++++++++++ .../puzzles/offer/settlement_payments.rs | 93 +++++++ src/spends/puzzles/singleton.rs | 36 +++ src/spends/{ => puzzles}/standard.rs | 0 src/spends/spend_context.rs | 172 +++++++++++++ src/spends/spend_error.rs | 19 ++ src/wallet/wallet_simulator.rs | 101 +++++--- 17 files changed, 930 insertions(+), 364 deletions(-) delete mode 100644 src/spends/did.rs create mode 100644 src/spends/puzzles.rs rename src/spends/{ => puzzles}/cat.rs (100%) create mode 100644 src/spends/puzzles/did.rs rename src/spends/{ => puzzles}/nft.rs (78%) create mode 100644 src/spends/puzzles/offer.rs create mode 100644 src/spends/puzzles/offer/offer_compression.rs create mode 100644 src/spends/puzzles/offer/settlement_payments.rs create mode 100644 src/spends/puzzles/singleton.rs rename src/spends/{ => puzzles}/standard.rs (100%) create mode 100644 src/spends/spend_context.rs create mode 100644 src/spends/spend_error.rs diff --git a/Cargo.lock b/Cargo.lock index 6a17e1d5..d8399908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,7 @@ dependencies = [ "clvm-traits", "clvm-utils", "clvmr", + "flate2", "futures-channel", "futures-util", "hex", @@ -545,6 +546,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -769,6 +779,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +[[package]] +name = "flate2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" +dependencies = [ + "crc32fast", + "libz-sys", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.0" @@ -1141,6 +1162,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-sys" +version = "1.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 311a7b82..0128af64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,10 +34,11 @@ hex-literal = "0.4.1" serde_json = "1.0.115" serde = { version = "1.0.197", features = ["derive"] } futures-util = "0.3.30" -futures-channel = "0.3.30" +futures-channel = { version = "0.3.30", features = ["sink"] } chia-traits = "0.7.0" chia-consensus = "0.7.0" indexmap = "2.2.6" +flate2 = { version = "1.0.29", features = ["zlib"] } [dev-dependencies] bip39 = "2.0.0" diff --git a/src/lib.rs b/src/lib.rs index 5b16ddc0..042603c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -#![deny(missing_docs)] #![doc = include_str!("../README.md")] mod address; diff --git a/src/spends.rs b/src/spends.rs index 8dde5a70..2946a581 100644 --- a/src/spends.rs +++ b/src/spends.rs @@ -1,235 +1,7 @@ -use std::collections::HashMap; +mod puzzles; +mod spend_context; +mod spend_error; -use chia_protocol::{Bytes32, Program, SpendBundle}; -use chia_wallet::{ - cat::{CAT_PUZZLE, CAT_PUZZLE_HASH, EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE}, - did::{DID_INNER_PUZZLE, DID_INNER_PUZZLE_HASH}, - nft::{ - NFT_INTERMEDIATE_LAUNCHER_PUZZLE, NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, - NFT_OWNERSHIP_LAYER_PUZZLE, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, NFT_ROYALTY_TRANSFER_PUZZLE, - NFT_ROYALTY_TRANSFER_PUZZLE_HASH, NFT_STATE_LAYER_PUZZLE, NFT_STATE_LAYER_PUZZLE_HASH, - }, - singleton::{ - SINGLETON_LAUNCHER_PUZZLE, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE, - SINGLETON_TOP_LAYER_PUZZLE_HASH, - }, - standard::{STANDARD_PUZZLE, STANDARD_PUZZLE_HASH}, -}; -use clvm_traits::{FromClvmError, FromNodePtr, ToClvmError, ToNodePtr}; -use clvm_utils::tree_hash; -use clvmr::{ - reduction::EvalErr, run_program, serde::node_from_bytes, Allocator, ChiaDialect, NodePtr, -}; -use hex_literal::hex; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -mod cat; -mod did; -mod nft; -mod standard; - -pub use cat::*; -pub use did::*; -pub use nft::*; -pub use standard::*; - -/// Errors that can occur when spending a coin. -#[derive(Debug, Error)] -pub enum SpendError { - /// An error occurred while converting to clvm. - #[error("to clvm error: {0}")] - ToClvm(#[from] ToClvmError), - - /// An error occurred while converting from clvm. - #[error("from clvm error: {0}")] - FromClvm(#[from] FromClvmError), - - /// An error occurred while evaluating a program. - #[error("eval error: {0}")] - Eval(#[from] EvalErr), -} - -/// A wrapper around `Allocator` that caches puzzles and simplifies coin spending. -pub struct SpendContext<'a> { - allocator: &'a mut Allocator, - puzzles: HashMap<[u8; 32], NodePtr>, -} - -impl<'a> SpendContext<'a> { - /// Create a new `SpendContext` from an `Allocator` reference. - pub fn new(allocator: &'a mut Allocator) -> Self { - Self { - allocator, - puzzles: HashMap::new(), - } - } - - /// Allocate a new node and return its pointer. - pub fn alloc(&mut self, value: T) -> Result - where - T: ToNodePtr, - { - Ok(value.to_node_ptr(self.allocator)?) - } - - /// Extract a value from a node pointer. - pub fn extract(&self, ptr: NodePtr) -> Result - where - T: FromNodePtr, - { - Ok(T::from_node_ptr(self.allocator, ptr)?) - } - - /// Compute the tree hash of a node pointer. - pub fn tree_hash(&self, ptr: NodePtr) -> Bytes32 { - Bytes32::new(tree_hash(self.allocator, ptr)) - } - - /// Run a puzzle with a solution and return the result. - pub fn run(&mut self, puzzle: NodePtr, solution: NodePtr) -> Result { - let result = run_program( - self.allocator, - &ChiaDialect::new(0), - puzzle, - solution, - u64::MAX, - )?; - Ok(result.1) - } - - /// Serialize a value and return a `Program`. - pub fn serialize(&mut self, value: T) -> Result - where - T: ToNodePtr, - { - let ptr = value.to_node_ptr(self.allocator)?; - Ok(Program::from_node_ptr(self.allocator, ptr)?) - } - - /// Allocate the standard puzzle and return its pointer. - pub fn standard_puzzle(&mut self) -> NodePtr { - self.puzzle(&STANDARD_PUZZLE_HASH, &STANDARD_PUZZLE) - } - - /// Allocate the CAT puzzle and return its pointer. - pub fn cat_puzzle(&mut self) -> NodePtr { - self.puzzle(&CAT_PUZZLE_HASH, &CAT_PUZZLE) - } - - /// Allocate the DID inner puzzle and return its pointer. - pub fn did_inner_puzzle(&mut self) -> NodePtr { - self.puzzle(&DID_INNER_PUZZLE_HASH, &DID_INNER_PUZZLE) - } - - /// Allocate the NFT intermediate launcher puzzle and return its pointer. - pub fn nft_intermediate_launcher(&mut self) -> NodePtr { - self.puzzle( - &NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, - &NFT_INTERMEDIATE_LAUNCHER_PUZZLE, - ) - } - - /// Allocate the NFT royalty transfer puzzle and return its pointer. - pub fn nft_royalty_transfer(&mut self) -> NodePtr { - self.puzzle( - &NFT_ROYALTY_TRANSFER_PUZZLE_HASH, - &NFT_ROYALTY_TRANSFER_PUZZLE, - ) - } - - /// Allocate the NFT ownership layer puzzle and return its pointer. - pub fn nft_ownership_layer(&mut self) -> NodePtr { - self.puzzle( - &NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - &NFT_OWNERSHIP_LAYER_PUZZLE, - ) - } - - /// Allocate the NFT state layer puzzle and return its pointer. - pub fn nft_state_layer(&mut self) -> NodePtr { - self.puzzle(&NFT_STATE_LAYER_PUZZLE_HASH, &NFT_STATE_LAYER_PUZZLE) - } - - /// Allocate the singleton top layer puzzle and return its pointer. - pub fn singleton_top_layer(&mut self) -> NodePtr { - self.puzzle( - &SINGLETON_TOP_LAYER_PUZZLE_HASH, - &SINGLETON_TOP_LAYER_PUZZLE, - ) - } - - /// Allocate the singleton launcher puzzle and return its pointer. - pub fn singleton_launcher(&mut self) -> NodePtr { - self.puzzle(&SINGLETON_LAUNCHER_PUZZLE_HASH, &SINGLETON_LAUNCHER_PUZZLE) - } - - /// Allocate the EverythingWithSignature TAIL puzzle and return its pointer. - pub fn everything_with_signature_tail_puzzle(&mut self) -> NodePtr { - // todo: add constant to chia_rs - self.puzzle( - &hex!("1720d13250a7c16988eaf530331cefa9dd57a76b2c82236bec8bbbff91499b89"), - &EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, - ) - } - - /// Preload a puzzle into the cache. - pub fn preload(&mut self, puzzle_hash: [u8; 32], ptr: NodePtr) { - self.puzzles.insert(puzzle_hash, ptr); - } - - /// Get a puzzle from the cache or allocate a new one. - pub fn puzzle(&mut self, puzzle_hash: &[u8; 32], puzzle_bytes: &[u8]) -> NodePtr { - if let Some(puzzle) = self.puzzles.get(puzzle_bytes) { - *puzzle - } else { - let puzzle = node_from_bytes(self.allocator, puzzle_bytes).unwrap(); - self.puzzles.insert(*puzzle_hash, puzzle); - puzzle - } - } -} - -#[derive(Serialize, Deserialize)] -struct CoinJson { - parent_coin_info: String, - puzzle_hash: String, - amount: u64, -} - -#[derive(Serialize, Deserialize)] -struct CoinSpendJson { - coin: CoinJson, - puzzle_reveal: String, - solution: String, -} - -#[derive(Serialize, Deserialize)] -struct SpendBundleJson { - coin_spends: Vec, - aggregated_signature: String, -} - -/// Dump a `SpendBundle` to a JSON string. -pub fn dump_spend_bundle(bundle: &SpendBundle) -> String { - let mut coin_spends = Vec::new(); - - for coin_spend in &bundle.coin_spends { - coin_spends.push(CoinSpendJson { - coin: CoinJson { - parent_coin_info: format!("0x{}", hex::encode(coin_spend.coin.parent_coin_info)), - puzzle_hash: format!("0x{}", hex::encode(coin_spend.coin.puzzle_hash)), - amount: coin_spend.coin.amount, - }, - puzzle_reveal: hex::encode(&coin_spend.puzzle_reveal), - solution: hex::encode(&coin_spend.solution), - }); - } - - let json = SpendBundleJson { - coin_spends, - aggregated_signature: hex::encode(bundle.aggregated_signature.to_bytes()), - }; - - serde_json::to_string(&json).unwrap() -} +pub use puzzles::*; +pub use spend_context::*; +pub use spend_error::*; diff --git a/src/spends/did.rs b/src/spends/did.rs deleted file mode 100644 index ba079563..00000000 --- a/src/spends/did.rs +++ /dev/null @@ -1,29 +0,0 @@ -use chia_protocol::{Coin, CoinSpend, Program}; -use chia_wallet::{did::DidSolution, singleton::SingletonSolution, Proof}; -use clvm_traits::ToClvm; -use clvmr::NodePtr; - -use crate::{standard_solution, SpendContext, SpendError}; - -/// Spend a standard DID coin (a DID singleton with the standard transaction inner puzzle). -pub fn spend_did( - ctx: &mut SpendContext, - coin: Coin, - puzzle_reveal: Program, - proof: Proof, - conditions: T, -) -> Result -where - T: ToClvm, -{ - let p2_solution = standard_solution(conditions); - let did_solution = DidSolution::InnerSpend(p2_solution); - - let solution = ctx.serialize(SingletonSolution { - proof, - amount: coin.amount, - inner_solution: did_solution, - })?; - - Ok(CoinSpend::new(coin, puzzle_reveal, solution)) -} diff --git a/src/spends/puzzles.rs b/src/spends/puzzles.rs new file mode 100644 index 00000000..79f67065 --- /dev/null +++ b/src/spends/puzzles.rs @@ -0,0 +1,13 @@ +mod cat; +mod did; +mod nft; +mod offer; +mod singleton; +mod standard; + +pub use cat::*; +pub use did::*; +pub use nft::*; +pub use offer::*; +pub use singleton::*; +pub use standard::*; diff --git a/src/spends/cat.rs b/src/spends/puzzles/cat.rs similarity index 100% rename from src/spends/cat.rs rename to src/spends/puzzles/cat.rs diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs new file mode 100644 index 00000000..df4fb977 --- /dev/null +++ b/src/spends/puzzles/did.rs @@ -0,0 +1,238 @@ +use chia_bls::PublicKey; +use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend, Program}; +use chia_wallet::{ + did::{DidArgs, DidSolution}, + singleton::{ + LauncherSolution, SingletonArgs, SingletonSolution, SingletonStruct, + SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, + }, + standard::StandardArgs, + EveProof, Proof, +}; +use clvm_traits::{clvm_list, ToClvm}; +use clvm_utils::CurriedProgram; +use clvmr::NodePtr; +use sha2::{Digest, Sha256}; + +use crate::{ + create_launcher, standard_solution, AssertCoinAnnouncement, CreateCoinWithMemos, SpendContext, + SpendError, +}; + +/// The output of a DID mint. +pub struct DidCreation { + /// The conditions that must be output from the parent to make this DID creation valid. + pub parent_conditions: Vec, + /// The coin spends required to fulfill the DID creation. + pub coin_spends: Vec, + /// The launcher id of the newly created DID. + pub did_id: Bytes32, + /// The inner puzzle hash of the DID. + pub did_inner_puzzle_hash: Bytes32, + /// The DID coin. + pub coin: Coin, + /// The DID puzzle reveal. + pub puzzle_reveal: Program, +} + +/// Creates a new DID singleton. +pub fn create_did( + ctx: &mut SpendContext, + parent_coin_id: Bytes32, + synthetic_key: PublicKey, + owner_puzzle_hash: Bytes32, +) -> Result { + let standard_puzzle = ctx.standard_puzzle(); + let launcher_puzzle = ctx.singleton_launcher(); + let singleton_puzzle = ctx.singleton_top_layer(); + let did_puzzle = ctx.did_inner_puzzle(); + + let mut coin_spends = Vec::new(); + + let launcher = create_launcher(ctx, parent_coin_id)?; + let launcher_id = launcher.coin.coin_id(); + let mut parent_conditions = launcher.parent_conditions; + + let singleton_struct = SingletonStruct { + mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), + launcher_id, + launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + }; + + let p2 = CurriedProgram { + program: standard_puzzle, + args: StandardArgs { synthetic_key }, + }; + + let did = ctx.alloc(CurriedProgram { + program: did_puzzle, + args: DidArgs { + inner_puzzle: p2, + recovery_did_list_hash: ctx.tree_hash(NodePtr::NIL), + num_verifications_required: 1, + singleton_struct: singleton_struct.clone(), + metadata: (), + }, + })?; + + let did_inner_puzzle_hash = ctx.tree_hash(did); + + let singleton = ctx.alloc(CurriedProgram { + program: singleton_puzzle, + args: SingletonArgs { + singleton_struct, + inner_puzzle: did, + }, + })?; + + let eve_puzzle_hash = ctx.tree_hash(singleton); + + let eve_message = ctx.alloc(clvm_list!(eve_puzzle_hash, 1, ()))?; + let eve_message_hash = ctx.tree_hash(eve_message); + + let mut announcement_id = Sha256::new(); + announcement_id.update(launcher_id); + announcement_id.update(eve_message_hash); + + parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { + announcement_id: Bytes::new(announcement_id.finalize().to_vec()), + })?); + + // Spend the launcher coin. + let launcher_puzzle_reveal = ctx.serialize(launcher_puzzle)?; + let launcher_solution = ctx.serialize(LauncherSolution { + singleton_puzzle_hash: eve_puzzle_hash, + amount: 1, + key_value_list: (), + })?; + + coin_spends.push(CoinSpend::new( + launcher.coin, + launcher_puzzle_reveal, + launcher_solution, + )); + + // Spend the eve coin. + let eve_coin = Coin::new(launcher_id, eve_puzzle_hash, 1); + + let eve_proof = Proof::Eve(EveProof { + parent_coin_info: parent_coin_id, + amount: 1, + }); + + let eve_puzzle_reveal = ctx.serialize(singleton)?; + + let eve_coin_spend = spend_did( + ctx, + eve_coin.clone(), + eve_puzzle_reveal.clone(), + eve_proof, + clvm_list!(CreateCoinWithMemos { + puzzle_hash: did_inner_puzzle_hash, + amount: 1, + memos: vec![Bytes::new(owner_puzzle_hash.to_vec())], + },), + )?; + + coin_spends.push(eve_coin_spend); + + Ok(DidCreation { + parent_conditions, + coin_spends, + did_id: launcher_id, + did_inner_puzzle_hash, + coin: Coin::new(eve_coin.coin_id(), eve_puzzle_hash, 1), + puzzle_reveal: eve_puzzle_reveal, + }) +} + +/// Spend a standard DID coin (a DID singleton with the standard transaction inner puzzle). +pub fn spend_did( + ctx: &mut SpendContext, + coin: Coin, + puzzle_reveal: Program, + proof: Proof, + conditions: T, +) -> Result +where + T: ToClvm, +{ + let p2_solution = standard_solution(conditions); + let did_solution = DidSolution::InnerSpend(p2_solution); + + let solution = ctx.serialize(SingletonSolution { + proof, + amount: coin.amount, + inner_solution: did_solution, + })?; + + Ok(CoinSpend::new(coin, puzzle_reveal, solution)) +} + +#[cfg(test)] +mod tests { + use chia_bls::{sign, Signature}; + use chia_protocol::SpendBundle; + use chia_wallet::{ + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, + }; + use clvmr::Allocator; + + use crate::{spend_standard_coin, testing::SECRET_KEY, RequiredSignature, WalletSimulator}; + + use super::*; + + #[tokio::test] + async fn test_create_did() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk); + + let parent = sim.generate_coin(puzzle_hash.into(), 1).await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let did_creation = create_did( + &mut ctx, + parent.coin.coin_id(), + pk.clone(), + puzzle_hash.into(), + ) + .unwrap(); + + let mut coin_spends = did_creation.coin_spends; + + coin_spends.push( + spend_standard_coin(&mut ctx, parent.coin, pk, did_creation.parent_conditions).unwrap(), + ); + + let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); + + let required_signatures = RequiredSignature::from_spend_bundle( + &mut allocator, + &spend_bundle, + WalletSimulator::AGG_SIG_ME.into(), + ) + .unwrap(); + + for required in required_signatures { + spend_bundle.aggregated_signature += &sign(&sk, required.final_message()); + } + + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + // Make sure the DID was created. + let found_coins = peer + .register_for_ph_updates(vec![puzzle_hash.into()], 0) + .await + .unwrap(); + assert_eq!(found_coins.len(), 2); + } +} diff --git a/src/spends/nft.rs b/src/spends/puzzles/nft.rs similarity index 78% rename from src/spends/nft.rs rename to src/spends/puzzles/nft.rs index aeca0d08..307d6aa1 100644 --- a/src/spends/nft.rs +++ b/src/spends/puzzles/nft.rs @@ -68,7 +68,7 @@ where } /// The information required to mint an NFT. -pub struct MintInput { +pub struct MintInput { /// The owner puzzle hash of the newly minted NFT. pub owner_puzzle_hash: Bytes32, /// The puzzle hash to send royalties to when trading the NFT. @@ -76,9 +76,7 @@ pub struct MintInput { /// The percentage royalty to send to the royalty puzzle hash. pub royalty_percentage: u16, /// The NFT metadata. - pub metadata: NodePtr, - /// The parent coin to spend. - pub parent_coin_id: Bytes32, + pub metadata: M, /// The amount of the launcher coin and subsequent NFT coin. pub amount: u64, } @@ -87,31 +85,27 @@ pub struct MintInput { pub struct BulkMint { /// The coin spends for the NFT bulk mint. pub coin_spends: Vec, - /// The new NFT outputs. - pub outputs: Vec, -} - -/// The output of a single NFT mint. -pub struct MintOutput { + /// The new NFT launcher ids. + pub launcher_ids: Vec, /// The conditions that must be output from the parent to make this mint valid. pub parent_conditions: Vec, - /// The launcher id of the newly minted NFT. - pub launcher_id: Bytes32, } /// Bulk mints a set of NFTs. -pub fn mint_nfts( +#[allow(clippy::too_many_arguments)] +pub fn mint_nfts( ctx: &mut SpendContext, - inputs: Vec, + inputs: Vec>, + parent_coin_id: Bytes32, synthetic_key: PublicKey, did_id: Bytes32, did_inner_puzzle_hash: Bytes32, mint_start_index: usize, mint_total: usize, -) -> Result { - let mut coin_spends = Vec::new(); - let mut outputs = Vec::new(); - +) -> Result +where + M: ToClvm, +{ let standard_puzzle = ctx.standard_puzzle(); let royalty_transfer_puzzle = ctx.nft_royalty_transfer(); let ownership_puzzle = ctx.nft_ownership_layer(); @@ -124,44 +118,22 @@ pub fn mint_nfts( args: StandardArgs { synthetic_key }, })?; - for (i, input) in inputs.into_iter().enumerate() { - let mut parent_conditions = Vec::new(); + let mut coin_spends = Vec::new(); + let mut launcher_ids = Vec::new(); + let mut parent_conditions = Vec::new(); + for (i, input) in inputs.into_iter().enumerate() { // Create the intermediate launcher. - let intermediate_spend = spend_new_intermediate_launcher( - ctx, - input.parent_coin_id, - mint_start_index + i, - mint_total, - )?; - let intermediate_id = intermediate_spend.coin.coin_id(); + let intermediate_launcher = + create_intermediate_launcher(ctx, parent_coin_id, mint_start_index + i, mint_total)?; - parent_conditions.push(ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash: intermediate_spend.coin.puzzle_hash, - amount: intermediate_spend.coin.amount, - })?); + let intermediate_id = intermediate_launcher.coin_spend.coin.coin_id(); + let launcher_id = intermediate_launcher.launcher_coin.coin_id(); - let mut announcement_id = Sha256::new(); - announcement_id.update(intermediate_id); - announcement_id.update(intermediate_launcher_message( - mint_start_index + i, - mint_total, - )); - - parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes::new(announcement_id.finalize().to_vec()), - })?); - - coin_spends.push(intermediate_spend); + parent_conditions.extend(intermediate_launcher.parent_conditions); + coin_spends.push(intermediate_launcher.coin_spend); // Construct the eve NFT. - let launcher_coin = Coin::new( - intermediate_id, - SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - input.amount, - ); - let launcher_id = launcher_coin.coin_id(); - parent_conditions.push(ctx.alloc(CreatePuzzleAnnouncement { message: launcher_id.to_vec().into(), })?); @@ -231,7 +203,7 @@ pub fn mint_nfts( })?; coin_spends.push(CoinSpend::new( - launcher_coin, + intermediate_launcher.launcher_coin, launcher_puzzle_reveal, launcher_solution, )); @@ -278,28 +250,33 @@ pub fn mint_nfts( })?); // Finalize the output. - outputs.push(MintOutput { - parent_conditions, - launcher_id, - }); + launcher_ids.push(launcher_id); } Ok(BulkMint { coin_spends, - outputs, + launcher_ids, + parent_conditions, }) } -/// Creates a new intermediate launcher for a given parent coin id. -/// The intermediate launcher is used to create a new launcher coin. -/// You can use the output `CoinSpend` to spend the singleton launcher coin. -/// The parent coin will need to create a coin with the returned puzzle hash and amount. -pub fn spend_new_intermediate_launcher( +/// Information required to create and spend a new intermediate launcher. +pub struct IntermediateLauncher { + /// The coin spend for the new intermediate launcher. + pub coin_spend: CoinSpend, + /// The conditions that must be output from the parent to make this intermediate launcher valid. + pub parent_conditions: Vec, + /// The final launcher coin. + pub launcher_coin: Coin, +} + +/// Creates and spends a new intermediate launcher coin. +pub fn create_intermediate_launcher( ctx: &mut SpendContext, parent_coin_id: Bytes32, index: usize, total: usize, -) -> Result { +) -> Result { let intermediate_puzzle = ctx.nft_intermediate_launcher(); let puzzle = ctx.alloc(CurriedProgram { @@ -315,17 +292,34 @@ pub fn spend_new_intermediate_launcher( let puzzle_hash = ctx.tree_hash(puzzle); - Ok(CoinSpend::new( + let intermediate_spend = CoinSpend::new( Coin::new(parent_coin_id, puzzle_hash, 0), puzzle_reveal, solution, - )) -} + ); + + let intermediate_id = intermediate_spend.coin.coin_id(); + + let mut parent_conditions = vec![ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: intermediate_spend.coin.puzzle_hash, + amount: intermediate_spend.coin.amount, + })?]; -/// Calculates the announcement message to assert from the intermediate launcher. -pub fn intermediate_launcher_message(index: usize, total: usize) -> Bytes32 { let mut index_message = Sha256::new(); index_message.update(usize_to_bytes(index)); index_message.update(usize_to_bytes(total)); - Bytes32::new(index_message.finalize().into()) + + let mut announcement_id = Sha256::new(); + announcement_id.update(intermediate_id); + announcement_id.update(index_message.finalize()); + + parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { + announcement_id: Bytes::new(announcement_id.finalize().to_vec()), + })?); + + Ok(IntermediateLauncher { + coin_spend: intermediate_spend, + parent_conditions, + launcher_coin: Coin::new(intermediate_id, SINGLETON_LAUNCHER_PUZZLE_HASH.into(), 1), + }) } diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs new file mode 100644 index 00000000..60663417 --- /dev/null +++ b/src/spends/puzzles/offer.rs @@ -0,0 +1,5 @@ +mod offer_compression; +mod settlement_payments; + +pub use offer_compression::*; +pub use settlement_payments::*; diff --git a/src/spends/puzzles/offer/offer_compression.rs b/src/spends/puzzles/offer/offer_compression.rs new file mode 100644 index 00000000..fbe3f024 --- /dev/null +++ b/src/spends/puzzles/offer/offer_compression.rs @@ -0,0 +1,182 @@ +use std::{ + array::TryFromSliceError, + io::{self, ErrorKind, Read}, +}; + +use chia_wallet::{ + cat::{CAT_PUZZLE, CAT_PUZZLE_V1}, + nft::{ + NFT_METADATA_UPDATER_PUZZLE, NFT_OWNERSHIP_LAYER_PUZZLE, NFT_ROYALTY_TRANSFER_PUZZLE, + NFT_STATE_LAYER_PUZZLE, + }, + offer::{SETTLEMENT_PAYMENTS_PUZZLE, SETTLEMENT_PAYMENTS_PUZZLE_V1}, + singleton::SINGLETON_TOP_LAYER_PUZZLE, + standard::STANDARD_PUZZLE, +}; +use flate2::{ + read::{ZlibDecoder, ZlibEncoder}, + Compress, Compression, Decompress, FlushDecompress, +}; +use thiserror::Error; + +macro_rules! define_compression_versions { + ( $( $version:expr => $( $bytes:expr ),+ ; )+ ) => { + fn zdict_for_version(version: u16) -> Vec { + let mut bytes = Vec::new(); + $( if version >= $version { + $( bytes.extend_from_slice(&$bytes); )+ + } )+ + bytes + } + + /// Returns the required compression version for the given puzzle reveals. + pub fn required_compression_version(puzzles: Vec>) -> u16 { + let mut required_version = MIN_VERSION; + $( { + $( if required_version < $version && puzzles.iter().any(|puzzle| puzzle == &$bytes) { + required_version = $version; + } )+ + } )+ + required_version + } + }; +} + +const MIN_VERSION: u16 = 6; +const MAX_VERSION: u16 = 6; + +define_compression_versions!( + 1 => STANDARD_PUZZLE, CAT_PUZZLE_V1; + 2 => SETTLEMENT_PAYMENTS_PUZZLE_V1; + 3 => SINGLETON_TOP_LAYER_PUZZLE, NFT_STATE_LAYER_PUZZLE, + NFT_OWNERSHIP_LAYER_PUZZLE, NFT_METADATA_UPDATER_PUZZLE, + NFT_ROYALTY_TRANSFER_PUZZLE; + 4 => CAT_PUZZLE; + 5 => SETTLEMENT_PAYMENTS_PUZZLE; + 6 => [0; 0]; // Purposefully break backwards compatibility. +); + +/// An error than can occur while decompressing an offer. +#[derive(Debug, Error)] +pub enum DecompressionError { + /// An io error. + #[error("io error: {0}")] + Io(#[from] io::Error), + + /// An error that occurred while trying to convert a slice to an array. + #[error("{0}")] + TryFromSlice(#[from] TryFromSliceError), + + /// The input is missing the version prefix. + #[error("missing version prefix")] + MissingVersionPrefix, + + /// The version is unsupported. + #[error("unsupported version")] + UnsupportedVersion, +} + +/// Decompresses an offer spend bundle. +pub fn decompress_offer(bytes: &[u8]) -> Result, DecompressionError> { + let version_bytes: [u8; 2] = bytes + .get(0..2) + .ok_or(DecompressionError::MissingVersionPrefix)? + .try_into()?; + + let version = u16::from_be_bytes(version_bytes); + + if version > MAX_VERSION { + return Err(DecompressionError::UnsupportedVersion); + } + + let zdict = zdict_for_version(version); + + Ok(decompress(&bytes[2..], &zdict)?) +} + +/// Compresses an offer spend bundle. +pub fn compress_offer(bytes: &[u8], version: u16) -> io::Result> { + let mut output = version.to_be_bytes().to_vec(); + let zdict = zdict_for_version(version); + output.extend(compress(bytes, &zdict)?); + Ok(output) +} + +fn decompress(input: &[u8], zdict: &[u8]) -> io::Result> { + let mut decompress = Decompress::new(true); + if decompress + .decompress(input, &mut [], FlushDecompress::Finish) + .is_ok() + { + return Err(io::Error::new( + ErrorKind::Unsupported, + "cannot decompress uncompressed input", + )); + } + decompress.set_dictionary(zdict)?; + let i = decompress.total_in(); + let mut decoder = ZlibDecoder::new_with_decompress(&input[i as usize..], decompress); + let mut output = Vec::new(); + decoder.read_to_end(&mut output)?; + Ok(output) +} + +fn compress(input: &[u8], zdict: &[u8]) -> io::Result> { + let mut compress = Compress::new(Compression::new(6), true); + compress.set_dictionary(zdict)?; + let mut encoder = ZlibEncoder::new_with_compress(input, compress); + let mut output = Vec::new(); + encoder.read_to_end(&mut output)?; + Ok(output) +} + +#[cfg(test)] +mod tests { + use chia_protocol::SpendBundle; + use chia_traits::Streamable; + use hex::ToHex; + use hex_literal::hex; + + use super::*; + + #[test] + fn test_compression() { + for version in MIN_VERSION..=MAX_VERSION { + let output = compress_offer(&DECOMPRESSED_OFFER_HEX, version).unwrap(); + + assert_eq!( + output.encode_hex::(), + COMPRESSED_OFFER_HEX.encode_hex::() + ); + } + } + + #[test] + fn test_decompression() { + for _ in MIN_VERSION..=MAX_VERSION { + let output = decompress_offer(&COMPRESSED_OFFER_HEX).unwrap(); + + assert_eq!( + output.encode_hex::(), + DECOMPRESSED_OFFER_HEX.encode_hex::() + ); + } + } + + #[test] + fn parse_spend_bundle() { + SpendBundle::from_bytes(&DECOMPRESSED_OFFER_HEX).unwrap(); + } + + const COMPRESSED_OFFER_HEX: [u8; 1225] = hex!( + " + 000678bb1ce2864b63606060622000f234ef6ae4725635979d975e6263fb68c9b687879b720f54fbcf0ace3de319c33d09a60ede4e1dadfd476bffd1da7fb4f61fadfd476bffff0355fb830bf705e6fb3e27bc6b6d34de3637326a42ee9158c6f501e673d706c6ed0cecda5d23ab550555c6f445a3f9b7b1852187eac9c0f7675608bcd9265e74f3e9ad2c5d5faed4893b3aeba055c5688b8288ee3f234466c1f5c59bc3dfd96dbcdc5aa327e6fd6ee9e99e8f172eb31fde71c2cdb66561eca3c7af804aa6d4443f0cd2fa1f6eb6577ba710cb9fc5ad697cf64be7596f5ffcae607e50c3fcc2ffade21e652f18885009b273febfbd99c12d7de60d5c57358d8e1dbbc795a9b0a6d4e9ea910ef9d007a50deed925613fb414ea5ca30f2eaff9a0f4cff964c7a693676d2fdd7448ba1514b7f1505130b419038f6678188fae18185d31008ef9d1150330c121b3620092b617d4af320ade7ff7e2837ba739d936dd4972df7de47c427f8449acb8d5ede4175b6ae6ff5ff0728acd3aa766d7c7f97cbeba7667cf17fb3d3bd6da671653ffe68cd6bc97befbad17bcff50aae419907020ed5bbed19a43cbebacec8aab4e75af5b727199df92b3076d57a2d611ff47d729d02e018cae5318beeb14c0097ec102dff9e165fe214bec1c0cf6fdf66d60ca6b67955cb52cd964dd0f03ff58e6c98760f9a2f4ff81c88c929282622b7dfda4c4b4eca2d4cca48ca28ad2a2ca34a3f4f2caf4e28ad4ec9c8cc4f492dc948a92e2fce4c2b2acacaa94c24a3393a2ac9432e3a294bc94c2bc7413bdcc82b462bdbcb492e292fca2c4f454bd9cccbc6cd0b044c602f5de9732e26edb78b5277a5bf09af17d9edccab5d452c6f09dabf2ba47667dbce6ffff37e596fe5f087344416271496a52669e5e727eae7e5162b97e88a98f7b8153aa71034861c682c008a6f8681599bc832d9d45963116a61e53e2d30e2672ff3a706f25fbb6b8de04a0aae23cd0fc567109ac665ef0afcbdb4f7dd1a359bef6c7cfcd0daaba7deaccae6d6b174fb0396fbd7279e6a5ebdd584a9ed1651e28096c7499c7e8320f02eef93fbacc83d878a2cf320f48d97774f5abcaf5ab18a443d4ef2f3871dee954bec5d716e98a9f9b0f0515db6dbaa7ae0655f6b0ab32d0e47262d7295619afd882af4b7c76f1e9aee7c830e0e672d73b14f53d0f4b1139ba9c627439c550584ef19fce3d98058b9d16dabff3fbb2ebedec9fe9db2f4f375cbab5f77ef5c4d939f939dfee2e5e3af10c4419176ca0075bc61ab6db1bc1066fe87931a1f4d68ffcc0c247870433ef3c8b78eae9b54e6642cd87e78eb9c71faadd158fdb9f67bae7e5b740d35b9d53ea1e8a9e428c8ba150ff09c7d9ff0561ed81a593f6ef60faee68c15eebbe42397dcad515b981b24d816a1e7a96d3e7f2805d061abc013b18d87803316003670b08cddd413202b01bb4e0fcfe7b6f637c16ddb3e5feaab0f3b4f7f6122bd3d9979e2decb838e7f9fd3f8a979f830c27a8086496fdff0552f1cc53263ead78fe2ffafe9edf3b5e6e49a98cf874ffa813df0ae3b5f6bf2a5bf361f9b569fdd32dedbf5a8e9cbbe35da7cd7dfda2a038fff584f2522b734be70949afef545888cc3df25eee72421443edffd2dfd3bef2a9b15e5e3ed3e34ca2e57efe075e923e3badde58d8258b35873ebebb69fd156571cbe7f20fa6a4fbea7c0f5772da3e554d190018bbefd7 + " + ); + + const DECOMPRESSED_OFFER_HEX: [u8; 7390] = hex!( + " + 0000000200000000000000000000000000000000000000000000000000000000000000006e29dd286d097a8376cf1ba43c3de2a4b6e1c3826dc07b4f9a536dcc495c0b920000000000000000ff02ffff01ff02ffff01ff02ff5effff04ff02ffff04ffff04ff05ffff04ffff0bff34ff0580ffff04ff0bff80808080ffff04ffff02ff17ff2f80ffff04ff5fffff04ffff02ff2effff04ff02ffff04ff17ff80808080ffff04ffff02ff2affff04ff02ffff04ff82027fffff04ff82057fffff04ff820b7fff808080808080ffff04ff81bfffff04ff82017fffff04ff8202ffffff04ff8205ffffff04ff820bffff80808080808080808080808080ffff04ffff01ffffffff3d46ff02ff333cffff0401ff01ff81cb02ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff7cffff0bff34ff2480ffff0bff7cffff0bff7cffff0bff34ff2c80ff0980ffff0bff7cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ffff02ffff03ff0bffff01ff02ffff03ffff09ffff02ff2effff04ff02ffff04ff13ff80808080ff820b9f80ffff01ff02ff56ffff04ff02ffff04ffff02ff13ffff04ff5fffff04ff17ffff04ff2fffff04ff81bfffff04ff82017fffff04ff1bff8080808080808080ffff04ff82017fff8080808080ffff01ff088080ff0180ffff01ff02ffff03ff17ffff01ff02ffff03ffff20ff81bf80ffff0182017fffff01ff088080ff0180ffff01ff088080ff018080ff0180ff04ffff04ff05ff2780ffff04ffff10ff0bff5780ff778080ffffff02ffff03ff05ffff01ff02ffff03ffff09ffff02ffff03ffff09ff11ff5880ffff0159ff8080ff0180ffff01818f80ffff01ff02ff26ffff04ff02ffff04ff0dffff04ff0bffff04ffff04ff81b9ff82017980ff808080808080ffff01ff02ff7affff04ff02ffff04ffff02ffff03ffff09ff11ff5880ffff01ff04ff58ffff04ffff02ff76ffff04ff02ffff04ff13ffff04ff29ffff04ffff0bff34ff5b80ffff04ff2bff80808080808080ff398080ffff01ff02ffff03ffff09ff11ff7880ffff01ff02ffff03ffff20ffff02ffff03ffff09ffff0121ffff0dff298080ffff01ff02ffff03ffff09ffff0cff29ff80ff3480ff5c80ffff01ff0101ff8080ff0180ff8080ff018080ffff0109ffff01ff088080ff0180ffff010980ff018080ff0180ffff04ffff02ffff03ffff09ff11ff5880ffff0159ff8080ff0180ffff04ffff02ff26ffff04ff02ffff04ff0dffff04ff0bffff04ff17ff808080808080ff80808080808080ff0180ffff01ff04ff80ffff04ff80ff17808080ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff56ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ff0bff7cffff0bff34ff2880ffff0bff7cffff0bff7cffff0bff34ff2c80ff0580ffff0bff7cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff04ffff04ff30ffff04ff5fff808080ffff02ff7effff04ff02ffff04ffff04ffff04ff2fff0580ffff04ff5fff82017f8080ffff04ffff02ff26ffff04ff02ffff04ff0bffff04ff05ffff01ff808080808080ffff04ff17ffff04ff81bfffff04ff82017fffff04ffff02ff2affff04ff02ffff04ff8204ffffff04ffff02ff76ffff04ff02ffff04ff09ffff04ff820affffff04ffff0bff34ff2d80ffff04ff15ff80808080808080ffff04ff8216ffff808080808080ffff04ff8205ffffff04ff820bffff808080808080808080808080ff02ff5affff04ff02ffff04ff5fffff04ff3bffff04ffff02ffff03ff17ffff01ff09ff2dffff02ff2affff04ff02ffff04ff27ffff04ffff02ff76ffff04ff02ffff04ff29ffff04ff57ffff04ffff0bff34ff81b980ffff04ff59ff80808080808080ffff04ff81b7ff80808080808080ff8080ff0180ffff04ff17ffff04ff05ffff04ff8202ffffff04ffff04ffff04ff78ffff04ffff0eff5cffff02ff2effff04ff02ffff04ffff04ff2fffff04ff82017fff808080ff8080808080ff808080ffff04ffff04ff20ffff04ffff0bff81bfff5cffff02ff2effff04ff02ffff04ffff04ff15ffff04ffff10ff82017fffff11ff8202dfff2b80ff8202ff80ff808080ff8080808080ff808080ff138080ff80808080808080808080ff018080ffff04ffff01a037bef360ee858133b69d595a906dc45d01af50379dad515eb9518abb7c1d2a7affff04ffff01a002f42883fb3338310825c951efcca810ecb61772d9e5da6a2d4d0a6591b8897effff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff0180808080ffffa0d7a3b357ee3eb1d3857c2e164beea5cb8cf1d0d307c3b8c8463d84a15de2e3eaffffa0947c5be1522aff5736bd2bb91204fca385660e3fa59e3bb7a3ee709f52809f71ff85174876e800ffffa0947c5be1522aff5736bd2bb91204fca385660e3fa59e3bb7a3ee709f52809f71808080809ffebd6953848e37800ad52932c6c6de0a6920ac7542d5c4881f55e07580476b7456f82a207e455bc1a77cf022fe43c988b2c9cd3dd2d94062da525eb1c272530000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0e9943cae428345e36f0e4d2d3ecdcf734ee6c6858e365c7feccc2a9ee94dbf3ba0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffff75ffc05968747470733a2f2f6261666b726569626872787572796632677779677378656b6c686167746d647874736f6371766a6a7a6471793634726a64763372646e64716e67342e697066732e6e667473746f726167652e6c696e6b2f80ffff68a0278de91c1746b60d2b914b380d360ef393850aa5391c31ee4523aee2368e0d37ffff826d75ffa168747470733a2f2f706173746562696e2e636f6d2f7261772f54354c477042653380ffff826d68a05158025f5b241c6ec1848972395c383548945f66c1610bfac0dea907b65e8d60ffff82736e01ffff8273740180ffff04ffff01a0fe8a4b4e27a2e29a4d3fc7ce9d527adbcaccbab6ada3903ccf3ba9a769d2d78bffff04ffff01ff02ffff01ff02ffff01ff02ff26ffff04ff02ffff04ff05ffff04ff17ffff04ff0bffff04ffff02ff2fff5f80ff80808080808080ffff04ffff01ffffff82ad4cff0233ffff3e04ff81f601ffffff0102ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff32ffff0bff3cff3480ffff0bff32ffff0bff32ffff0bff3cff2280ff0980ffff0bff32ff0bffff0bff3cff8080808080ff8080808080ffff010b80ff0180ff04ffff04ff38ffff04ffff02ff36ffff04ff02ffff04ff05ffff04ff27ffff04ffff02ff2effff04ff02ffff04ffff02ffff03ff81afffff0181afffff010b80ff0180ff80808080ffff04ffff0bff3cff4f80ffff04ffff0bff3cff0580ff8080808080808080ff378080ff82016f80ffffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff2fffff01ff80ff808080808080808080ff0bff32ffff0bff3cff2880ffff0bff32ffff0bff32ffff0bff3cff2280ff0580ffff0bff32ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080ffff0bff3cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff5fffff01ff02ffff03ffff09ff82011fff3880ffff01ff02ffff03ffff09ffff18ff82059f80ff3c80ffff01ff02ffff03ffff20ff81bf80ffff01ff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff82019fffff04ff82017fff80808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff808080808080808080808080ff0180ffff01ff02ffff03ffff09ff82011fff2c80ffff01ff02ffff03ffff20ff82017f80ffff01ff04ffff04ff24ffff04ffff0eff10ffff02ff2effff04ff02ffff04ff82019fff8080808080ff808080ffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ffff02ff0bffff04ff17ffff04ff2fffff04ff82019fff8080808080ff8080808080808080808080ffff01ff088080ff0180ffff01ff02ffff03ffff09ff82011fff2480ffff01ff02ffff03ffff20ffff02ffff03ffff09ffff0122ffff0dff82029f8080ffff01ff02ffff03ffff09ffff0cff82029fff80ffff010280ff1080ffff01ff0101ff8080ff0180ff8080ff018080ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff8080808080808080808080ffff01ff088080ff0180ffff01ff04ff819fffff02ff3effff04ff02ffff04ff05ffff04ff0bffff04ff17ffff04ff2fffff04ff81dfffff04ff81bfffff04ff82017fff808080808080808080808080ff018080ff018080ff0180ffff01ff02ff3affff04ff02ffff04ff05ffff04ff0bffff04ff81bfffff04ffff02ffff03ff82017fffff0182017fffff01ff02ff0bffff04ff17ffff04ff2fffff01ff808080808080ff0180ff8080808080808080ff0180ff018080ffff04ffff01a0c5abea79afaa001b5427dfa0c8cf42ca6f38f5841b78f9b3c252733eb2de2726ffff04ffff01a0e18a795134d3618aca051c4a5d70f5a44cba0e2daf0868300b0a472ec25af76effff04ffff01ff02ffff01ff02ffff01ff02ffff03ff81bfffff01ff04ff82013fffff04ff80ffff04ffff02ffff03ffff22ff82013fffff20ffff09ff82013fff2f808080ffff01ff04ffff04ff10ffff04ffff0bffff02ff2effff04ff02ffff04ff09ffff04ff8205bfffff04ffff02ff3effff04ff02ffff04ffff04ff09ffff04ff82013fff1d8080ff80808080ff808080808080ff1580ff808080ffff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff8202bfffff04ff15ff8080808080808080ffff01ff02ff16ffff04ff02ffff04ff0bffff04ff17ffff04ff8202bfffff04ff15ff8080808080808080ff0180ff80808080ffff01ff04ff2fffff01ff80ff80808080ff0180ffff04ffff01ffffff3f02ff04ff0101ffff822710ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04ff0dffff04ffff0bff2affff0bff2cff1480ffff0bff2affff0bff2affff0bff2cff3c80ff0980ffff0bff2aff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ff17ffff01ff04ffff04ff10ffff04ffff0bff81a7ffff02ff3effff04ff02ffff04ffff04ff2fffff04ffff04ff05ffff04ffff05ffff14ffff12ff47ff0b80ff128080ffff04ffff04ff05ff8080ff80808080ff808080ff8080808080ff808080ffff02ff16ffff04ff02ffff04ff05ffff04ff0bffff04ff37ffff04ff2fff8080808080808080ff8080ff0180ffff0bff2affff0bff2cff1880ffff0bff2affff0bff2affff0bff2cff3c80ff0580ffff0bff2affff02ff3affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0e9943cae428345e36f0e4d2d3ecdcf734ee6c6858e365c7feccc2a9ee94dbf3ba0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01a0a342a13fee4ef4baed9bf967b7d39731a5b58ddf7b919b6c6f6cf6dda3a591ccffff04ffff010aff0180808080ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b08ce89075daf86f5171e2c21169dce658e5494aae1c907cf0e7416dc7e126dd175ebf6e35bce9f65135da89947ee115caff018080ff018080808080ff018080808080ff01808080ffffa0e9943cae428345e36f0e4d2d3ecdcf734ee6c6858e365c7feccc2a9ee94dbf3bffa05687517592bfb802f74138077d47a8236794d5a86d511d825126482e39979d0cff0180ff01ffffffff80ffff01ffff81f6ff80ffffff85174876e800ffa06e29dd286d097a8376cf1ba43c3de2a4b6e1c3826dc07b4f9a536dcc495c0b928080ff8080ffff33ffa0cfbfdeed5c4ca2de3d0bf520b9cb4bb7743a359bd2e6a188d19ce7dffc21d3e7ff01ffffa0cfbfdeed5c4ca2de3d0bf520b9cb4bb7743a359bd2e6a188d19ce7dffc21d3e78080ffff3fffa01a5f039491e578e7fe5bdfbcfbb8e9b4647958f2dfc5420ea833ad3ffa79856f8080ff808080808082afe5b487fa84c4cedc4b7e2b0bd7d111170fd76077753a3739439062ebdc7838149dc4ef1ed3605a007dff75fb96f50e2605d3a79948cc6139bf0fe04a194cb93aec383e63168355e3ddb2afd4231739e71fe094674d2cf7572242b7952623 + " + ); +} diff --git a/src/spends/puzzles/offer/settlement_payments.rs b/src/spends/puzzles/offer/settlement_payments.rs new file mode 100644 index 00000000..336d4d27 --- /dev/null +++ b/src/spends/puzzles/offer/settlement_payments.rs @@ -0,0 +1,93 @@ +use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend}; +use clvm_traits::{FromClvm, ToClvm}; +use clvmr::NodePtr; +use sha2::{digest::FixedOutput, Digest, Sha256}; + +use crate::{SpendContext, SpendError}; + +#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +pub struct SettlementPaymentsSolution { + pub notarized_payments: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(tuple)] +pub struct NotarizedPayment { + pub nonce: Bytes32, + pub payments: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(tuple, untagged)] +pub enum Payment { + WithoutMemos(PaymentWithoutMemos), + WithMemos(PaymentWithMemos), +} + +#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +pub struct PaymentWithoutMemos { + pub puzzle_hash: Bytes32, + pub amount: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] +#[clvm(list)] +pub struct PaymentWithMemos { + pub puzzle_hash: Bytes32, + pub amount: u64, + pub memos: Vec, +} + +pub fn calculate_nonce( + ctx: &mut SpendContext, + mut offered_coin_ids: Vec, +) -> Result { + offered_coin_ids.sort(); + let offered_coin_ids = ctx.alloc(offered_coin_ids)?; + Ok(ctx.tree_hash(offered_coin_ids)) +} + +pub struct OfferRequest { + pub coin_spend: CoinSpend, + pub puzzle_announcement_id: Bytes32, +} + +pub fn request_offer_payments( + ctx: &mut SpendContext, + nonce: Bytes32, + requested_puzzle: NodePtr, + requested_payments: Vec, +) -> Result { + let puzzle_reveal = ctx.serialize(requested_puzzle)?; + let puzzle_hash = ctx.tree_hash(requested_puzzle); + + let notarized_payment = NotarizedPayment { + nonce, + payments: requested_payments, + }; + + let settlement_solution = ctx.serialize(SettlementPaymentsSolution { + notarized_payments: vec![notarized_payment.clone()], + })?; + + let coin_spend = CoinSpend::new( + Coin::new(Bytes32::default(), puzzle_hash, 0), + puzzle_reveal, + settlement_solution, + ); + + let notarized_payment_ptr = ctx.alloc(notarized_payment)?; + let notarized_payment_hash = ctx.tree_hash(notarized_payment_ptr); + + let mut hasher = Sha256::new(); + hasher.update(puzzle_hash); + hasher.update(notarized_payment_hash); + let puzzle_announcement_id = Bytes32::new(hasher.finalize_fixed().into()); + + Ok(OfferRequest { + coin_spend, + puzzle_announcement_id, + }) +} diff --git a/src/spends/puzzles/singleton.rs b/src/spends/puzzles/singleton.rs new file mode 100644 index 00000000..4fd079eb --- /dev/null +++ b/src/spends/puzzles/singleton.rs @@ -0,0 +1,36 @@ +use chia_protocol::{Bytes32, Coin}; +use chia_wallet::singleton::SINGLETON_LAUNCHER_PUZZLE_HASH; +use clvmr::NodePtr; + +use crate::{CreateCoinWithoutMemos, CreatePuzzleAnnouncement, SpendContext, SpendError}; + +/// The information required to create a new singleton launcher. +pub struct Launcher { + /// The conditions that must be output from the parent to make this singleton launcher valid. + pub parent_conditions: Vec, + /// The singleton launcher coin. + pub coin: Coin, +} + +/// Creates a new singleton launcher coin. +pub fn create_launcher( + ctx: &mut SpendContext, + parent_coin_id: Bytes32, +) -> Result { + let mut parent_conditions = vec![ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + amount: 1, + })?]; + + let launcher_coin = Coin::new(parent_coin_id, SINGLETON_LAUNCHER_PUZZLE_HASH.into(), 1); + let launcher_id = launcher_coin.coin_id(); + + parent_conditions.push(ctx.alloc(CreatePuzzleAnnouncement { + message: launcher_id.to_vec().into(), + })?); + + Ok(Launcher { + parent_conditions, + coin: launcher_coin, + }) +} diff --git a/src/spends/standard.rs b/src/spends/puzzles/standard.rs similarity index 100% rename from src/spends/standard.rs rename to src/spends/puzzles/standard.rs diff --git a/src/spends/spend_context.rs b/src/spends/spend_context.rs new file mode 100644 index 00000000..0493ed3f --- /dev/null +++ b/src/spends/spend_context.rs @@ -0,0 +1,172 @@ +use std::collections::HashMap; + +use chia_protocol::{Bytes32, Program}; +use chia_wallet::{ + cat::{CAT_PUZZLE, CAT_PUZZLE_HASH, EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE}, + did::{DID_INNER_PUZZLE, DID_INNER_PUZZLE_HASH}, + nft::{ + NFT_INTERMEDIATE_LAUNCHER_PUZZLE, NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, + NFT_OWNERSHIP_LAYER_PUZZLE, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, NFT_ROYALTY_TRANSFER_PUZZLE, + NFT_ROYALTY_TRANSFER_PUZZLE_HASH, NFT_STATE_LAYER_PUZZLE, NFT_STATE_LAYER_PUZZLE_HASH, + }, + offer::{SETTLEMENT_PAYMENTS_PUZZLE, SETTLEMENT_PAYMENTS_PUZZLE_HASH}, + singleton::{ + SINGLETON_LAUNCHER_PUZZLE, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE, + SINGLETON_TOP_LAYER_PUZZLE_HASH, + }, + standard::{STANDARD_PUZZLE, STANDARD_PUZZLE_HASH}, +}; +use clvm_traits::{FromNodePtr, ToNodePtr}; +use clvm_utils::tree_hash; +use clvmr::{run_program, serde::node_from_bytes, Allocator, ChiaDialect, NodePtr}; +use hex_literal::hex; + +use crate::SpendError; + +/// A wrapper around `Allocator` that caches puzzles and simplifies coin spending. +pub struct SpendContext<'a> { + allocator: &'a mut Allocator, + puzzles: HashMap<[u8; 32], NodePtr>, +} + +impl<'a> SpendContext<'a> { + /// Create a new `SpendContext` from an `Allocator` reference. + pub fn new(allocator: &'a mut Allocator) -> Self { + Self { + allocator, + puzzles: HashMap::new(), + } + } + + /// Allocate a new node and return its pointer. + pub fn alloc(&mut self, value: T) -> Result + where + T: ToNodePtr, + { + Ok(value.to_node_ptr(self.allocator)?) + } + + /// Extract a value from a node pointer. + pub fn extract(&self, ptr: NodePtr) -> Result + where + T: FromNodePtr, + { + Ok(T::from_node_ptr(self.allocator, ptr)?) + } + + /// Compute the tree hash of a node pointer. + pub fn tree_hash(&self, ptr: NodePtr) -> Bytes32 { + Bytes32::new(tree_hash(self.allocator, ptr)) + } + + /// Run a puzzle with a solution and return the result. + pub fn run(&mut self, puzzle: NodePtr, solution: NodePtr) -> Result { + let result = run_program( + self.allocator, + &ChiaDialect::new(0), + puzzle, + solution, + u64::MAX, + )?; + Ok(result.1) + } + + /// Serialize a value and return a `Program`. + pub fn serialize(&mut self, value: T) -> Result + where + T: ToNodePtr, + { + let ptr = value.to_node_ptr(self.allocator)?; + Ok(Program::from_node_ptr(self.allocator, ptr)?) + } + + /// Allocate the standard puzzle and return its pointer. + pub fn standard_puzzle(&mut self) -> NodePtr { + self.puzzle(&STANDARD_PUZZLE_HASH, &STANDARD_PUZZLE) + } + + /// Allocate the CAT puzzle and return its pointer. + pub fn cat_puzzle(&mut self) -> NodePtr { + self.puzzle(&CAT_PUZZLE_HASH, &CAT_PUZZLE) + } + + /// Allocate the DID inner puzzle and return its pointer. + pub fn did_inner_puzzle(&mut self) -> NodePtr { + self.puzzle(&DID_INNER_PUZZLE_HASH, &DID_INNER_PUZZLE) + } + + /// Allocate the NFT intermediate launcher puzzle and return its pointer. + pub fn nft_intermediate_launcher(&mut self) -> NodePtr { + self.puzzle( + &NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, + &NFT_INTERMEDIATE_LAUNCHER_PUZZLE, + ) + } + + /// Allocate the NFT royalty transfer puzzle and return its pointer. + pub fn nft_royalty_transfer(&mut self) -> NodePtr { + self.puzzle( + &NFT_ROYALTY_TRANSFER_PUZZLE_HASH, + &NFT_ROYALTY_TRANSFER_PUZZLE, + ) + } + + /// Allocate the NFT ownership layer puzzle and return its pointer. + pub fn nft_ownership_layer(&mut self) -> NodePtr { + self.puzzle( + &NFT_OWNERSHIP_LAYER_PUZZLE_HASH, + &NFT_OWNERSHIP_LAYER_PUZZLE, + ) + } + + /// Allocate the NFT state layer puzzle and return its pointer. + pub fn nft_state_layer(&mut self) -> NodePtr { + self.puzzle(&NFT_STATE_LAYER_PUZZLE_HASH, &NFT_STATE_LAYER_PUZZLE) + } + + /// Allocate the singleton top layer puzzle and return its pointer. + pub fn singleton_top_layer(&mut self) -> NodePtr { + self.puzzle( + &SINGLETON_TOP_LAYER_PUZZLE_HASH, + &SINGLETON_TOP_LAYER_PUZZLE, + ) + } + + /// Allocate the singleton launcher puzzle and return its pointer. + pub fn singleton_launcher(&mut self) -> NodePtr { + self.puzzle(&SINGLETON_LAUNCHER_PUZZLE_HASH, &SINGLETON_LAUNCHER_PUZZLE) + } + + /// Allocate the EverythingWithSignature TAIL puzzle and return its pointer. + pub fn everything_with_signature_tail_puzzle(&mut self) -> NodePtr { + // todo: add constant to chia_rs + self.puzzle( + &hex!("1720d13250a7c16988eaf530331cefa9dd57a76b2c82236bec8bbbff91499b89"), + &EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, + ) + } + + /// Allocate the settlement payments puzzle and return its pointer. + pub fn settlement_payments_puzzle(&mut self) -> NodePtr { + self.puzzle( + &SETTLEMENT_PAYMENTS_PUZZLE_HASH, + &SETTLEMENT_PAYMENTS_PUZZLE, + ) + } + + /// Preload a puzzle into the cache. + pub fn preload(&mut self, puzzle_hash: [u8; 32], ptr: NodePtr) { + self.puzzles.insert(puzzle_hash, ptr); + } + + /// Get a puzzle from the cache or allocate a new one. + pub fn puzzle(&mut self, puzzle_hash: &[u8; 32], puzzle_bytes: &[u8]) -> NodePtr { + if let Some(puzzle) = self.puzzles.get(puzzle_bytes) { + *puzzle + } else { + let puzzle = node_from_bytes(self.allocator, puzzle_bytes).unwrap(); + self.puzzles.insert(*puzzle_hash, puzzle); + puzzle + } + } +} diff --git a/src/spends/spend_error.rs b/src/spends/spend_error.rs new file mode 100644 index 00000000..f1e24a25 --- /dev/null +++ b/src/spends/spend_error.rs @@ -0,0 +1,19 @@ +use clvm_traits::{FromClvmError, ToClvmError}; +use clvmr::reduction::EvalErr; +use thiserror::Error; + +/// Errors that can occur when spending a coin. +#[derive(Debug, Error)] +pub enum SpendError { + /// An error occurred while converting to clvm. + #[error("to clvm error: {0}")] + ToClvm(#[from] ToClvmError), + + /// An error occurred while converting from clvm. + #[error("from clvm error: {0}")] + FromClvm(#[from] FromClvmError), + + /// An error occurred while evaluating a program. + #[error("eval error: {0}")] + Eval(#[from] EvalErr), +} diff --git a/src/wallet/wallet_simulator.rs b/src/wallet/wallet_simulator.rs index 2324f6da..3f018b22 100644 --- a/src/wallet/wallet_simulator.rs +++ b/src/wallet/wallet_simulator.rs @@ -1,5 +1,10 @@ -use std::{collections::HashMap, net::SocketAddr, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + net::SocketAddr, + sync::Arc, +}; +use chia_bls::aggregate_verify; use chia_client::Peer; use chia_consensus::gen::{ conditions::EmptyVisitor, @@ -30,6 +35,8 @@ use tokio::{ }; use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage}; +use crate::RequiredSignature; + type PeerMapInner = HashMap>; type PeerMap = Arc>; @@ -153,37 +160,15 @@ async fn handle_connection( let tx = SendTransaction::from_bytes(message.data.as_ref()).unwrap(); let spend_bundle = tx.transaction; - let mut allocator = Allocator::new(); - let gen = solution_generator( - spend_bundle - .coin_spends - .iter() - .cloned() - .map(|spend| (spend.coin, spend.puzzle_reveal, spend.solution)), - ) - .unwrap(); - let transaction_id = spend_bundle.name(); - let error = match run_block_generator::<&[u8], EmptyVisitor>( - &mut allocator, - &gen, - &[], - 11_000_000_000, - MEMPOOL_MODE, - ) { - Ok(conds) => { - let conds = OwnedSpendBundleConditions::from(&allocator, conds); - process_spend_bundle(peer_map.clone(), conds, data, spend_bundle) - .await - .err() - } - Err(error) => Some(error), - }; + let error = process_spend_bundle(peer_map.clone(), data, spend_bundle) + .await + .err(); let body = match error { Some(error) => { - TransactionAck::new(transaction_id, 3, Some(error.to_string())) + TransactionAck::new(transaction_id, 3, Some(format!("{:?}", error.1))) } None => TransactionAck::new(transaction_id, 1, None), } @@ -399,10 +384,65 @@ async fn handle_connection( async fn process_spend_bundle( peer_map: PeerMap, - conds: OwnedSpendBundleConditions, data: Arc>, spend_bundle: SpendBundle, ) -> Result<(), ValidationErr> { + let mut allocator = Allocator::new(); + let gen = solution_generator( + spend_bundle + .coin_spends + .iter() + .cloned() + .map(|spend| (spend.coin, spend.puzzle_reveal, spend.solution)), + ) + .unwrap(); + + let conds = run_block_generator::<&[u8], EmptyVisitor>( + &mut allocator, + &gen, + &[], + 11_000_000_000, + MEMPOOL_MODE, + )?; + + let conds = OwnedSpendBundleConditions::from(&allocator, conds); + + let cond_puzzle_hashes = conds + .spends + .iter() + .map(|s| s.puzzle_hash) + .collect::>(); + + let bundle_puzzle_hashes = spend_bundle + .coin_spends + .iter() + .map(|s| s.coin.puzzle_hash) + .collect::>(); + + if cond_puzzle_hashes != bundle_puzzle_hashes { + return Err(ValidationErr(NodePtr::NIL, ErrorCode::InvalidSpendBundle)); + } + + let required_signatures = RequiredSignature::from_spend_bundle( + &mut allocator, + &spend_bundle, + WalletSimulator::AGG_SIG_ME.into(), + ) + .unwrap(); + + if !aggregate_verify( + &spend_bundle.aggregated_signature, + required_signatures + .into_iter() + .map(|r| (r.public_key().clone(), r.final_message())) + .collect::>(), + ) { + return Err(ValidationErr( + NodePtr::NIL, + ErrorCode::BadAggregateSignature, + )); + } + let data = &mut data.lock().await; let mut removed_coins = IndexMap::new(); @@ -459,11 +499,10 @@ async fn process_spend_bundle( } // Validate removals. - for coin_state in removed_coins.values_mut() { + for (coin_id, coin_state) in removed_coins.iter_mut() { let height = data.block_height; - let coin_id = coin_state.coin.coin_id(); - if !data.coin_states.contains_key(&coin_id) && !added_coins.contains_key(&coin_id) { + if !data.coin_states.contains_key(coin_id) && !added_coins.contains_key(coin_id) { return Err(ValidationErr(NodePtr::NIL, ErrorCode::UnknownUnspent)); } From 9a80658023391feef55a9ba220556635a8865c8a Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 2 May 2024 10:52:52 -0400 Subject: [PATCH 05/25] Temp --- .gitignore | 1 + src/address.rs | 5 +- src/condition.rs | 4 +- src/spends/puzzles/cat.rs | 104 +++++- src/spends/puzzles/did.rs | 8 +- src/spends/puzzles/nft.rs | 6 +- src/spends/puzzles/offer.rs | 295 ++++++++++++++++++ src/spends/puzzles/offer/offer_compression.rs | 41 ++- src/spends/puzzles/offer/offer_encoding.rs | 43 +++ .../puzzles/offer/settlement_payments.rs | 2 +- src/wallet/required_signature.rs | 8 +- src/wallet/wallet_simulator.rs | 9 +- 12 files changed, 491 insertions(+), 35 deletions(-) create mode 100644 src/spends/puzzles/offer/offer_encoding.rs diff --git a/.gitignore b/.gitignore index f9fa99e1..ca46687c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target *.sqlite +*.DS_Store diff --git a/src/address.rs b/src/address.rs index a1c4a7e4..7156f7e9 100644 --- a/src/address.rs +++ b/src/address.rs @@ -5,10 +5,6 @@ use thiserror::Error; /// Errors you can get while trying to decode an address. #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum AddressError { - /// The wrong HRP prefix was used. - #[error("invalid prefix `{0}`")] - InvalidPrefix(String), - /// The address was encoded as bech32, rather than bech32m. #[error("encoding is not bech32m")] InvalidFormat, @@ -54,6 +50,7 @@ pub fn encode_puzzle_hash(puzzle_hash: [u8; 32], include_0x: bool) -> String { /// Decodes an address into a puzzle hash and HRP prefix. pub fn decode_address(address: &str) -> Result<([u8; 32], String), AddressError> { let (hrp, data, variant) = bech32::decode(address)?; + if variant != Variant::Bech32m { return Err(AddressError::InvalidFormat); } diff --git a/src/condition.rs b/src/condition.rs index 5ac64c0c..f8bdda7f 100644 --- a/src/condition.rs +++ b/src/condition.rs @@ -117,9 +117,9 @@ condition!(CreateCoinWithoutMemos, 51, { puzzle_hash: Bytes32, amount: u64 }); condition!(CreateCoinWithMemos, 51, { puzzle_hash: Bytes32, amount: u64, memos: Vec }); condition!(ReserveFee, 52, { amount: u64 }); condition!(CreateCoinAnnouncement, 60, { message: Bytes }); -condition!(AssertCoinAnnouncement, 61, { announcement_id: Bytes }); +condition!(AssertCoinAnnouncement, 61, { announcement_id: Bytes32 }); condition!(CreatePuzzleAnnouncement, 62, { message: Bytes }); -condition!(AssertPuzzleAnnouncement, 63, { announcement_id: Bytes }); +condition!(AssertPuzzleAnnouncement, 63, { announcement_id: Bytes32 }); condition!(AssertConcurrentSpend, 64, { coin_id: Bytes32 }); condition!(AssertConcurrentPuzzle, 65, { puzzle_hash: Bytes32 }); condition!(AssertMyCoinId, 70, { coin_id: Bytes32 }); diff --git a/src/spends/puzzles/cat.rs b/src/spends/puzzles/cat.rs index 65b7ba19..b701d134 100644 --- a/src/spends/puzzles/cat.rs +++ b/src/spends/puzzles/cat.rs @@ -1,13 +1,16 @@ use chia_bls::PublicKey; use chia_protocol::{Bytes32, Coin, CoinSpend}; use chia_wallet::{ - cat::{CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, CAT_PUZZLE_HASH}, + cat::{ + CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, CAT_PUZZLE_HASH, + EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, + }, standard::{StandardArgs, StandardSolution}, LineageProof, }; -use clvm_traits::{clvm_quote, destructure_tuple, match_tuple, MatchByte, ToClvm}; -use clvm_utils::CurriedProgram; -use clvmr::NodePtr; +use clvm_traits::{clvm_quote, destructure_tuple, match_tuple, MatchByte, ToClvm, ToNodePtr}; +use clvm_utils::{tree_hash, CurriedProgram}; +use clvmr::{serde::node_from_bytes, Allocator, NodePtr}; use crate::{RunTail, SpendContext, SpendError}; @@ -102,16 +105,34 @@ pub fn spend_cat_coins( Ok(coin_spends) } +pub fn sig_cat_asset_id(public_key: PublicKey) -> Bytes32 { + let a = &mut Allocator::new(); + let tail_mod = node_from_bytes(a, &EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE).unwrap(); + + let ptr = CurriedProgram { + program: tail_mod, + args: EverythingWithSignatureTailArgs { public_key }, + } + .to_node_ptr(a) + .unwrap(); + + Bytes32::new(tree_hash(a, ptr)) +} + /// The information required to create and spend an eve CAT coin. pub struct EveSpend { /// The full puzzle hash of the eve CAT coin. pub puzzle_hash: Bytes32, /// The coin spend for the eve CAT. pub coin_spend: CoinSpend, + /// The eve CAT coin id. + pub eve_coin_id: Bytes32, + /// The eve CAT inner puzzle hash. + pub eve_inner_puzzle_hash: Bytes32, } /// Constructs a coin spend to issue more of an `EverythingWithSignature` CAT. -pub fn issue_cat_everything_with_signature( +pub fn issue_sig_cat( ctx: &mut SpendContext, public_key: PublicKey, parent_coin_id: Bytes32, @@ -182,22 +203,24 @@ where })?; let puzzle_reveal = ctx.serialize(puzzle)?; - let coin_spend = CoinSpend::new(coin, puzzle_reveal, solution); + let coin_spend = CoinSpend::new(coin.clone(), puzzle_reveal, solution); Ok(EveSpend { puzzle_hash, coin_spend, + eve_coin_id: coin.coin_id(), + eve_inner_puzzle_hash: inner_puzzle_hash, }) } #[cfg(test)] mod tests { - use chia_bls::derive_keys::master_to_wallet_unhardened; + use chia_bls::{derive_keys::master_to_wallet_unhardened, sign, Signature}; use chia_consensus::gen::{ conditions::EmptyVisitor, run_block_generator::run_block_generator, solution_generator::solution_generator, }; - use chia_protocol::{Bytes32, Program}; + use chia_protocol::{Bytes32, Program, SpendBundle}; use chia_wallet::{ cat::cat_puzzle_hash, standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, @@ -206,10 +229,73 @@ mod tests { use clvmr::{serde::node_to_bytes, Allocator}; use hex_literal::hex; - use crate::{testing::SECRET_KEY, CreateCoinWithoutMemos}; + use crate::{ + spend_standard_coin, testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, + RequiredSignature, WalletSimulator, + }; use super::*; + #[tokio::test] + async fn test_cat_issuance() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let parent = sim.generate_coin(puzzle_hash, 1000).await; + + let issue_cat = issue_sig_cat( + &mut ctx, + pk.clone(), + parent.coin.coin_id(), + 1000, + [CreateCoinWithMemos { + puzzle_hash, + amount: 1000, + memos: vec![puzzle_hash.to_vec().into()], + }], + ) + .unwrap(); + + let xch_spend = spend_standard_coin( + &mut ctx, + parent.coin, + pk, + [CreateCoinWithoutMemos { + puzzle_hash: issue_cat.puzzle_hash, + amount: 1000, + }], + ) + .unwrap(); + + let coin_spends = vec![issue_cat.coin_spend, xch_spend]; + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + ) + .unwrap(); + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); + + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + } + #[test] fn test_cat_spend() { let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index df4fb977..298bb8cb 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -12,7 +12,7 @@ use chia_wallet::{ use clvm_traits::{clvm_list, ToClvm}; use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use sha2::{Digest, Sha256}; +use sha2::{digest::FixedOutput, Digest, Sha256}; use crate::{ create_launcher, standard_solution, AssertCoinAnnouncement, CreateCoinWithMemos, SpendContext, @@ -95,7 +95,7 @@ pub fn create_did( announcement_id.update(eve_message_hash); parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes::new(announcement_id.finalize().to_vec()), + announcement_id: Bytes32::new(announcement_id.finalize_fixed().into()), })?); // Spend the launcher coin. @@ -213,9 +213,9 @@ mod tests { let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); - let required_signatures = RequiredSignature::from_spend_bundle( + let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, - &spend_bundle, + &spend_bundle.coin_spends, WalletSimulator::AGG_SIG_ME.into(), ) .unwrap(); diff --git a/src/spends/puzzles/nft.rs b/src/spends/puzzles/nft.rs index 307d6aa1..a72f824e 100644 --- a/src/spends/puzzles/nft.rs +++ b/src/spends/puzzles/nft.rs @@ -191,7 +191,7 @@ where announcement_id.update(eve_message_hash); parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes::new(announcement_id.finalize().to_vec()), + announcement_id: Bytes32::new(announcement_id.finalize().into()), })?); // Spend the launcher coin. @@ -246,7 +246,7 @@ where announcement_id.update(ctx.tree_hash(new_nft_owner_args)); parent_conditions.push(ctx.alloc(AssertPuzzleAnnouncement { - announcement_id: Bytes::new(announcement_id.finalize().to_vec()), + announcement_id: Bytes32::new(announcement_id.finalize().into()), })?); // Finalize the output. @@ -314,7 +314,7 @@ pub fn create_intermediate_launcher( announcement_id.update(index_message.finalize()); parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes::new(announcement_id.finalize().to_vec()), + announcement_id: Bytes32::new(announcement_id.finalize().into()), })?); Ok(IntermediateLauncher { diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index 60663417..264850c5 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -1,5 +1,300 @@ mod offer_compression; +mod offer_encoding; mod settlement_payments; pub use offer_compression::*; +pub use offer_encoding::*; pub use settlement_payments::*; + +use chia_protocol::{CoinSpend, SpendBundle}; + +#[derive(Debug, Clone)] +pub struct Offer { + offered_spend_bundle: SpendBundle, + requested_payment_spends: Vec, +} + +impl From for Offer { + fn from(spend_bundle: SpendBundle) -> Self { + let (requested_payment_spends, coin_spends): (_, Vec<_>) = spend_bundle + .coin_spends + .into_iter() + .partition(|coin_spend| { + coin_spend + .coin + .parent_coin_info + .iter() + .all(|byte| *byte == 0) + }); + + let offered_spend_bundle = SpendBundle::new(coin_spends, spend_bundle.aggregated_signature); + + Self { + offered_spend_bundle, + requested_payment_spends, + } + } +} + +impl From for SpendBundle { + fn from(offer: Offer) -> Self { + let mut spend_bundle = offer.offered_spend_bundle; + + spend_bundle + .coin_spends + .extend(offer.requested_payment_spends); + + spend_bundle + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use chia_bls::{sign, DerivableKey, SecretKey, Signature}; + use chia_protocol::{Bytes32, Coin, SpendBundle}; + use chia_wallet::{ + cat::{cat_puzzle_hash, CatArgs, CAT_PUZZLE_HASH}, + offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH, + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, LineageProof, + }; + use clvm_utils::CurriedProgram; + use clvmr::Allocator; + use hex_literal::hex; + + use crate::{ + issue_sig_cat, sig_cat_asset_id, spend_cat_coins, spend_standard_coin, testing::SECRET_KEY, + AssertPuzzleAnnouncement, CatSpend, CreateCoinWithMemos, CreateCoinWithoutMemos, + RequiredSignature, SpendContext, WalletSimulator, + }; + + fn sk1() -> SecretKey { + SECRET_KEY + .derive_unhardened(0) + .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) + } + + fn sk2() -> SecretKey { + SECRET_KEY + .derive_unhardened(1) + .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) + } + + fn sign_tx(required_signatures: Vec) -> Signature { + let sk1 = sk1(); + let sk2 = sk2(); + + let pk1 = sk1.public_key(); + let pk2 = sk2.public_key(); + + let mut aggregated_signature = Signature::default(); + + for req in required_signatures { + if req.public_key() == &pk1 { + let sig = sign(&sk1, &req.final_message()); + aggregated_signature += &sig; + } else if req.public_key() == &pk2 { + let sig = sign(&sk2, &req.final_message()); + aggregated_signature += &sig; + } else { + panic!("unexpected public key"); + } + } + + aggregated_signature + } + + #[tokio::test] + async fn test_chia_offer() { + let offer = "offer1qqr83wcuu2rykcmqvpsxvgqqemhmlaekcenaz02ma6hs5w600dhjlvfjn477nkwz369h88kll73h37fefnwk3qqnz8s0lle07zdk52smt8k62xtc53n9hxt4gpn4p0qp27dka6wa40d7djd0mrn86p4gvngckfmt877hr0h3uzekcus20afa0xwx54l3vhmtttsajt047dh5dnv4llk7c5nl84aczcgwktrmuezkmqfv2du9x5h4l9v8g6lx2ynpn068d07an2fl4r308aexve0nrj0h7gxv9g3dk78h4l7nzhlu0sk9vm8h6m7xzetn44em6ml6gwgkctnsancnkwwqyfctf7fkmf7pkmd735md735mdl5wj6d5l9rtlesmhpc97c97aaeu9w7kgm3k6uer5vnwg85fv4wp7cp7wakkphxwczhd6ddjtf24qe0t56jj7h8dcwsmjcq6wlte6p5aeytj8n870eftrwezkzjrjuv0frexu6z4s9wvzr0824uxpqeh9jl0y2w09nsxdp4a0fzwelftr7wz6xqlvnnmewgucrtmrsmrglz20wnt46a0evatzt0084jtt369w2kvszx7pqsvdzqrfk079usdf7hmmuxx5wgs2crn948xl7py7cgt0d4wvdg6g5kanvwh0j048ay4chdnw8fsh8av3u247kpzpd3a5qc447llpv9xea270hg55yyvdlsz3w6e946dkl40jkr0acntya6880nwl2j9uks8f88079evuxkyeeg4h5jl9amvmncskjevzu7nk5mll7nue34kunaffmuhmg7shl30mryt2dmjxmlm8l7wjcmlwjwlvdx48zugm26arer2urmpj7e7yltv3xvpnh0lqluphgm07r8psudp2f6l9runwl4enqf2xfnd7ds607fh46jfvfumdeyq2q3dy02ve74lspgyxzm2ryc5dmfmenur2h8ultcanyuw6464483m0m3vdzu5r8mzrhmx4s24qwtqktur34pg2qupqz5y5hehvtpfwq5hfu4gzw0l6dh0ehkled97xtdu0lquhdhhuhg7w3q62y7clzen99tzmsmuyuk8esaan5w4xdam33r0amjxhkf3hlj4mcm3ga93jdekkwez8k3hu70cl2tnn443n4nd95004jlf7tyltwl7s26u9v79gfnznt75a06lmxvkptwnanuh6axqucwmhx6zfn8d7y8ldx26l33ytl6m4kxwgp0dsr4yml4hxr7l5qzdlmlgjr5hmx9shrq4pghtq4pnnluke3cjmr0wc4h2ctc79l5tgu2hlz5q4fk07tac4xaw7ewdl2ug8dgvllvhatt642kzhym5hjy78rx00nn5wg3t60km4sxpxhzhjcn2sffxd2ljuw449cfndeah8kluh2wmg00yxzhandnuhh4044ekwanejnaujwhtll4egdx6wqa8za4mwejkmxn0h46mzand7qjxvts5vrah4x94tk0dkg7hfglawvd9d02lla24v5neegdzlldk3ll7v8wdq5q8kgq4fcnu54uz"; + let offer_data = decode_offer(offer).unwrap(); + let spend_bundle = decompress_offer(&offer_data).unwrap(); + + panic!("{:#?}", &spend_bundle); + } + + #[tokio::test] + async fn test_offer() { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let pk1 = sk1().public_key(); + let pk2 = sk2().public_key(); + + let ph1 = Bytes32::new(standard_puzzle_hash(&pk1)); + let ph2 = Bytes32::new(standard_puzzle_hash(&pk2)); + + let xch1 = sim.generate_coin(ph1, 1000).await; + + // Issue CAT. + let issue_cat = issue_sig_cat( + &mut ctx, + pk1.clone(), + xch1.coin.coin_id(), + 1000, + [CreateCoinWithMemos { + puzzle_hash: ph1, + amount: 1000, + memos: vec![ph1.to_vec().into()], + }], + ) + .unwrap(); + + let spend_xch = spend_standard_coin( + &mut ctx, + xch1.coin.clone(), + pk1.clone(), + [CreateCoinWithoutMemos { + puzzle_hash: issue_cat.puzzle_hash, + amount: 1000, + }], + ) + .unwrap(); + + let mut spend_bundle = + SpendBundle::new(vec![issue_cat.coin_spend, spend_xch], Signature::default()); + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &spend_bundle.coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + ) + .unwrap(); + + spend_bundle.aggregated_signature = sign_tx(required_signatures); + + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + // Create offer for CAT coin. + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let asset_id = sig_cat_asset_id(pk1.clone()); + let cat1_ph = cat_puzzle_hash(asset_id.into(), ph1.into()).into(); + let cat1 = Coin::new(issue_cat.eve_coin_id, cat1_ph, 1000); + + let xch2 = sim.generate_coin(ph2, 1000).await.coin; + + let nonce = calculate_nonce(&mut ctx, vec![cat1.coin_id(), xch2.coin_id()]).unwrap(); + + let cat_puzzle = ctx.cat_puzzle(); + let offer_puzzle = ctx.settlement_payments_puzzle(); + + let requested_cat = ctx + .alloc(CurriedProgram { + program: cat_puzzle, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: asset_id, + inner_puzzle: offer_puzzle, + }, + }) + .unwrap(); + + let requested = request_offer_payments( + &mut ctx, + nonce, + requested_cat, + vec![Payment::WithMemos(PaymentWithMemos { + puzzle_hash: ph2, + amount: 1000, + memos: vec![ph2.to_vec().into()], + })], + ) + .unwrap(); + + let conditions = [ + ctx.alloc(CreateCoinWithMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount: 1000, + memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], + }) + .unwrap(), + ctx.alloc(AssertPuzzleAnnouncement { + announcement_id: requested.puzzle_announcement_id, + }) + .unwrap(), + ]; + + let xch_spend = spend_standard_coin(&mut ctx, xch2, pk2, conditions).unwrap(); + + let aggregated_signature = sign_tx( + RequiredSignature::from_coin_spend( + &mut allocator, + &xch_spend, + WalletSimulator::AGG_SIG_ME.into(), + ) + .unwrap(), + ); + + let spend_bundle = + SpendBundle::new(vec![requested.coin_spend, xch_spend], aggregated_signature); + + let offer_data = compress_offer(spend_bundle).unwrap(); + + assert_eq!(hex::encode(&offer_data), hex::encode(EXAMPLE_OFFER_DATA)); + + // Decompress offer and accept it (we know the only requested payment is the CAT). + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let offer = Offer::from(decompress_offer(&offer_data).unwrap()); + + let mut spend_bundle = offer.offered_spend_bundle; + + let conditions = ctx + .alloc(vec![CreateCoinWithMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount: 1000, + memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], + }]) + .unwrap(); + + let cat_spend = spend_cat_coins( + &mut ctx, + asset_id, + &[CatSpend { + coin: cat1, + synthetic_key: pk1, + conditions, + p2_puzzle_hash: ph1, + extra_delta: 0, + lineage_proof: LineageProof { + parent_coin_info: xch1.coin.coin_id(), + inner_puzzle_hash: issue_cat.eve_inner_puzzle_hash, + amount: 1000, + }, + }], + ) + .unwrap() + .remove(0); + + spend_bundle.aggregated_signature += &sign_tx( + RequiredSignature::from_coin_spend( + &mut allocator, + &cat_spend, + WalletSimulator::AGG_SIG_ME.into(), + ) + .unwrap(), + ); + + spend_bundle.coin_spends.push(cat_spend); + + let ack = peer.send_transaction(spend_bundle).await.unwrap(); + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + } + + pub const EXAMPLE_OFFER_DATA: [u8; 535] = hex!( + " + 000678bb1ce2864b63606060622000726f47eb64fdea99caadb02b31f402df8f25f7274d2b562a6948cc62fff2cf7ddb5f983a783b75b4f61fadfd476bffd1da7fb4f61fadfdff0f54ed0f2edc1798effb9cf0aeb5d178dbdcc8a809b9476219d70798cf5d1b18b733b06b778dac5615541913a35ffc9c6b79778a6a3bea8e75083c0f7c30f34513ebfadb7c2aa5b367b2666442ab8ad1160511dd7f4688cc02b7736e8f8b26afbc77f7c16e9be7278c6b264f78aa9ec179f0eca3ecab768cec8cb7804afa2aebba6ef2c85f0c9c7f24382974f3652bc11b5ddf7e3afdec79dd9f35df5ba8ef7f13f30b2294812cfca0127770d376c987dd1384d9edc2ad7ace7a8779fabdb8ad9dfd30e45bca1d3f1e4266409a3040eb901b31c379a11ed8e00d4d87387be4baff3c61be5d2b7e6fd6ef33212f4e9877764cb40e2bb8b7dda3f0ef31813b077856a4f4cfcfb635d173b2132d026b6d80a4f6ff0bceefbff736c667d13d5beeaf0a3b4f7b6f2fb1329d7de9d9c28e8b739edfffa378f93934fe082903b9c6feff0213d765d75a5c6bf8f4fda66cb799c45174c24ff6ade792bdcea68b2326fbdcf27e0f0e9f29af827f9a54bc9f9dd411f939abe88e5de7b13501129a0106ae9eb78d97d65bd43d916f543fa1abad2b76ec958e7c91813ef7f4c0b4876bee6ed9b4e4e86cd5cd2bacd61f7bb6a2a13e67dff2e902ae0b3acf773818f84cde55fb6fc7f6b2905dd676df5f0300bd5673c3 + " + ); +} diff --git a/src/spends/puzzles/offer/offer_compression.rs b/src/spends/puzzles/offer/offer_compression.rs index fbe3f024..ca9f4021 100644 --- a/src/spends/puzzles/offer/offer_compression.rs +++ b/src/spends/puzzles/offer/offer_compression.rs @@ -3,6 +3,8 @@ use std::{ io::{self, ErrorKind, Read}, }; +use chia_protocol::SpendBundle; +use chia_traits::Streamable; use chia_wallet::{ cat::{CAT_PUZZLE, CAT_PUZZLE_V1}, nft::{ @@ -74,10 +76,20 @@ pub enum DecompressionError { /// The version is unsupported. #[error("unsupported version")] UnsupportedVersion, + + /// A streamable error. + #[error("streamable error: {0}")] + Streamable(#[from] chia_traits::Error), } /// Decompresses an offer spend bundle. -pub fn decompress_offer(bytes: &[u8]) -> Result, DecompressionError> { +pub fn decompress_offer(bytes: &[u8]) -> Result { + let decompressed_bytes = decompress_offer_bytes(bytes)?; + Ok(SpendBundle::from_bytes(&decompressed_bytes)?) +} + +/// Decompresses an offer spend bundle into bytes. +pub fn decompress_offer_bytes(bytes: &[u8]) -> Result, DecompressionError> { let version_bytes: [u8; 2] = bytes .get(0..2) .ok_or(DecompressionError::MissingVersionPrefix)? @@ -94,8 +106,29 @@ pub fn decompress_offer(bytes: &[u8]) -> Result, DecompressionError> { Ok(decompress(&bytes[2..], &zdict)?) } +#[derive(Debug, Error)] +pub enum CompressionError { + #[error("io error: {0}")] + Io(#[from] io::Error), + #[error("streamable error: {0}")] + Streamable(#[from] chia_traits::Error), +} + /// Compresses an offer spend bundle. -pub fn compress_offer(bytes: &[u8], version: u16) -> io::Result> { +pub fn compress_offer(spend_bundle: SpendBundle) -> Result, CompressionError> { + let bytes = spend_bundle.to_bytes()?; + let version = required_compression_version( + spend_bundle + .coin_spends + .into_iter() + .map(|cs| cs.puzzle_reveal.to_vec()) + .collect(), + ); + Ok(compress_offer_bytes(&bytes, version)?) +} + +/// Compresses an offer spend bundle from bytes. +pub fn compress_offer_bytes(bytes: &[u8], version: u16) -> io::Result> { let mut output = version.to_be_bytes().to_vec(); let zdict = zdict_for_version(version); output.extend(compress(bytes, &zdict)?); @@ -142,7 +175,7 @@ mod tests { #[test] fn test_compression() { for version in MIN_VERSION..=MAX_VERSION { - let output = compress_offer(&DECOMPRESSED_OFFER_HEX, version).unwrap(); + let output = compress_offer_bytes(&DECOMPRESSED_OFFER_HEX, version).unwrap(); assert_eq!( output.encode_hex::(), @@ -154,7 +187,7 @@ mod tests { #[test] fn test_decompression() { for _ in MIN_VERSION..=MAX_VERSION { - let output = decompress_offer(&COMPRESSED_OFFER_HEX).unwrap(); + let output = decompress_offer_bytes(&COMPRESSED_OFFER_HEX).unwrap(); assert_eq!( output.encode_hex::(), diff --git a/src/spends/puzzles/offer/offer_encoding.rs b/src/spends/puzzles/offer/offer_encoding.rs new file mode 100644 index 00000000..1ed00e5c --- /dev/null +++ b/src/spends/puzzles/offer/offer_encoding.rs @@ -0,0 +1,43 @@ +use bech32::{u5, Variant}; +use thiserror::Error; + +/// Errors you can get while trying to decode an offer. +#[derive(Error, Debug, Clone, PartialEq, Eq)] +pub enum DecodeOfferError { + /// The wrong HRP prefix was used. + #[error("invalid prefix `{0}`")] + InvalidPrefix(String), + + /// The address was encoded as bech32, rather than bech32m. + #[error("encoding is not bech32m")] + InvalidFormat, + + /// An error occured while trying to decode the address. + #[error("error when decoding address: {0}")] + Decode(#[from] bech32::Error), +} + +/// Decodes an offer into bytes. +pub fn decode_offer(offer: &str) -> Result, DecodeOfferError> { + let (hrp, data, variant) = bech32::decode(offer)?; + + if variant != Variant::Bech32m { + return Err(DecodeOfferError::InvalidFormat); + } + + if hrp.as_str() != "offer" { + return Err(DecodeOfferError::InvalidPrefix(hrp)); + } + + Ok(bech32::convert_bits(&data, 5, 8, false)?) +} + +/// Encodes an offer. +pub fn encode_offer(offer: &[u8]) -> Result { + let data = bech32::convert_bits(offer, 8, 5, true) + .unwrap() + .into_iter() + .map(u5::try_from_u8) + .collect::, bech32::Error>>()?; + bech32::encode("offer1", data, Variant::Bech32m) +} diff --git a/src/spends/puzzles/offer/settlement_payments.rs b/src/spends/puzzles/offer/settlement_payments.rs index 336d4d27..a185aec3 100644 --- a/src/spends/puzzles/offer/settlement_payments.rs +++ b/src/spends/puzzles/offer/settlement_payments.rs @@ -6,7 +6,7 @@ use sha2::{digest::FixedOutput, Digest, Sha256}; use crate::{SpendContext, SpendError}; #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] -#[clvm(list)] +#[clvm(tuple)] pub struct SettlementPaymentsSolution { pub notarized_payments: Vec, } diff --git a/src/wallet/required_signature.rs b/src/wallet/required_signature.rs index 47a75c14..7787feda 100644 --- a/src/wallet/required_signature.rs +++ b/src/wallet/required_signature.rs @@ -1,5 +1,5 @@ use chia_bls::PublicKey; -use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend, SpendBundle}; +use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend}; use clvm_traits::{FromClvm, FromClvmError}; use clvmr::{allocator::NodePtr, reduction::EvalErr, Allocator}; use sha2::{digest::FixedOutput, Digest, Sha256}; @@ -122,13 +122,13 @@ impl RequiredSignature { /// Calculates the required signatures for a spend bundle. /// All of these signatures are aggregated together should /// sufficient, unless secp keys are used as well. - pub fn from_spend_bundle( + pub fn from_coin_spends( allocator: &mut Allocator, - spend_bundle: &SpendBundle, + coin_spends: &[CoinSpend], agg_sig_me: Bytes32, ) -> Result, ConditionError> { let mut required_signatures = Vec::new(); - for coin_spend in &spend_bundle.coin_spends { + for coin_spend in coin_spends { required_signatures.extend(Self::from_coin_spend(allocator, coin_spend, agg_sig_me)?); } Ok(required_signatures) diff --git a/src/wallet/wallet_simulator.rs b/src/wallet/wallet_simulator.rs index 3f018b22..7e7bbcde 100644 --- a/src/wallet/wallet_simulator.rs +++ b/src/wallet/wallet_simulator.rs @@ -388,6 +388,7 @@ async fn process_spend_bundle( spend_bundle: SpendBundle, ) -> Result<(), ValidationErr> { let mut allocator = Allocator::new(); + let gen = solution_generator( spend_bundle .coin_spends @@ -423,9 +424,9 @@ async fn process_spend_bundle( return Err(ValidationErr(NodePtr::NIL, ErrorCode::InvalidSpendBundle)); } - let required_signatures = RequiredSignature::from_spend_bundle( + let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, - &spend_bundle, + &spend_bundle.coin_spends, WalletSimulator::AGG_SIG_ME.into(), ) .unwrap(); @@ -627,9 +628,9 @@ mod tests { let mut a = Allocator::new(); - let required_signatures = RequiredSignature::from_spend_bundle( + let required_signatures = RequiredSignature::from_coin_spends( &mut a, - spend_bundle, + &spend_bundle.coin_spends, Bytes32::new(WalletSimulator::AGG_SIG_ME), ) .unwrap(); From c94cfbc1d8111601cde78ece16bb517c67f5daec Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 2 May 2024 12:19:29 -0400 Subject: [PATCH 06/25] Start adding spend builder --- src/spends.rs | 2 + src/spends/puzzles/cat.rs | 171 +++++++++++++++++++-------------- src/spends/puzzles/offer.rs | 42 +++----- src/spends/puzzles/standard.rs | 75 ++++++++------- src/spends/spend_builder.rs | 18 ++++ 5 files changed, 172 insertions(+), 136 deletions(-) create mode 100644 src/spends/spend_builder.rs diff --git a/src/spends.rs b/src/spends.rs index 2946a581..023c9937 100644 --- a/src/spends.rs +++ b/src/spends.rs @@ -1,7 +1,9 @@ mod puzzles; +mod spend_builder; mod spend_context; mod spend_error; pub use puzzles::*; +pub use spend_builder::*; pub use spend_context::*; pub use spend_error::*; diff --git a/src/spends/puzzles/cat.rs b/src/spends/puzzles/cat.rs index b701d134..40751eed 100644 --- a/src/spends/puzzles/cat.rs +++ b/src/spends/puzzles/cat.rs @@ -12,7 +12,73 @@ use clvm_traits::{clvm_quote, destructure_tuple, match_tuple, MatchByte, ToClvm, use clvm_utils::{tree_hash, CurriedProgram}; use clvmr::{serde::node_from_bytes, Allocator, NodePtr}; -use crate::{RunTail, SpendContext, SpendError}; +use crate::{BaseSpend, ChainedSpend, CreateCoinWithMemos, RunTail, SpendContext, SpendError}; + +pub struct IssueCat<'a, 'b> { + ctx: &'a mut SpendContext<'b>, + parent_coin_id: Bytes32, + conditions: Vec, + coin_spends: Vec, +} + +pub struct IssuanceInfo { + pub asset_id: Bytes32, + pub eve_coin: Coin, + pub eve_inner_puzzle_hash: Bytes32, +} + +impl<'a, 'b> IssueCat<'a, 'b> { + pub fn new(ctx: &'a mut SpendContext<'b>, parent_coin_id: Bytes32) -> Self { + Self { + ctx, + parent_coin_id, + conditions: Vec::new(), + coin_spends: Vec::with_capacity(1), + } + } + + pub fn multi_issuance( + self, + public_key: PublicKey, + amount: u64, + ) -> Result<(ChainedSpend, IssuanceInfo), SpendError> { + let ctx = self.ctx; + 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); + + create_and_spend_eve_cat( + ctx, + self.parent_coin_id, + asset_id, + amount, + ( + RunTail { + program: tail, + solution: NodePtr::NIL, + }, + self.conditions, + ), + ) + } +} + +impl<'a, 'b> BaseSpend for IssueCat<'a, 'b> { + fn chain(mut self, chained_spend: ChainedSpend) -> Self { + self.conditions.extend(chained_spend.parent_conditions); + self.coin_spends.extend(chained_spend.coin_spends); + self + } + + fn condition(mut self, condition: impl ToClvm) -> Result { + self.conditions.push(self.ctx.alloc(condition)?); + Ok(self) + } +} /// The information required to spend a CAT coin. /// This assumes that the inner puzzle is a standard transaction. @@ -119,47 +185,6 @@ pub fn sig_cat_asset_id(public_key: PublicKey) -> Bytes32 { Bytes32::new(tree_hash(a, ptr)) } -/// The information required to create and spend an eve CAT coin. -pub struct EveSpend { - /// The full puzzle hash of the eve CAT coin. - pub puzzle_hash: Bytes32, - /// The coin spend for the eve CAT. - pub coin_spend: CoinSpend, - /// The eve CAT coin id. - pub eve_coin_id: Bytes32, - /// The eve CAT inner puzzle hash. - pub eve_inner_puzzle_hash: Bytes32, -} - -/// Constructs a coin spend to issue more of an `EverythingWithSignature` CAT. -pub fn issue_sig_cat( - ctx: &mut SpendContext, - public_key: PublicKey, - parent_coin_id: Bytes32, - amount: u64, - conditions: T, -) -> Result -where - T: ToClvm, -{ - 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); - - let run_tail = RunTail { - program: tail, - solution: NodePtr::NIL, - }; - - let conditions = (run_tail, conditions); - - create_and_spend_eve_cat(ctx, parent_coin_id, asset_id, amount, conditions) -} - /// Creates an eve CAT coin and spends it. pub fn create_and_spend_eve_cat( ctx: &mut SpendContext, @@ -167,7 +192,7 @@ pub fn create_and_spend_eve_cat( asset_id: Bytes32, amount: u64, conditions: T, -) -> Result +) -> Result<(ChainedSpend, IssuanceInfo), SpendError> where T: ToClvm, { @@ -205,12 +230,22 @@ where let puzzle_reveal = ctx.serialize(puzzle)?; let coin_spend = CoinSpend::new(coin.clone(), puzzle_reveal, solution); - Ok(EveSpend { - puzzle_hash, - coin_spend, - eve_coin_id: coin.coin_id(), + let chained_spend = ChainedSpend { + coin_spends: vec![coin_spend], + parent_conditions: vec![ctx.alloc(CreateCoinWithMemos { + puzzle_hash, + amount, + memos: vec![puzzle_hash.to_vec().into()], + })?], + }; + + let issuance_info = IssuanceInfo { + asset_id, + eve_coin: coin, eve_inner_puzzle_hash: inner_puzzle_hash, - }) + }; + + Ok((chained_spend, issuance_info)) } #[cfg(test)] @@ -230,8 +265,8 @@ mod tests { use hex_literal::hex; use crate::{ - spend_standard_coin, testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, - RequiredSignature, WalletSimulator, + testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, RequiredSignature, + StandardSpend, WalletSimulator, }; use super::*; @@ -246,35 +281,25 @@ mod tests { let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); - let parent = sim.generate_coin(puzzle_hash, 1000).await; + let xch_coin = sim.generate_coin(puzzle_hash, 1).await.coin; - let issue_cat = issue_sig_cat( - &mut ctx, - pk.clone(), - parent.coin.coin_id(), - 1000, - [CreateCoinWithMemos { + let (issue_cat, _cat_info) = IssueCat::new(&mut ctx, xch_coin.coin_id()) + .condition(CreateCoinWithMemos { puzzle_hash, - amount: 1000, + amount: 1, memos: vec![puzzle_hash.to_vec().into()], - }], - ) - .unwrap(); - - let xch_spend = spend_standard_coin( - &mut ctx, - parent.coin, - pk, - [CreateCoinWithoutMemos { - puzzle_hash: issue_cat.puzzle_hash, - amount: 1000, - }], - ) - .unwrap(); + }) + .unwrap() + .multi_issuance(pk.clone(), 1) + .unwrap(); - let coin_spends = vec![issue_cat.coin_spend, xch_spend]; + let coin_spends = StandardSpend::new(&mut ctx, xch_coin) + .chain(issue_cat) + .finish(pk) + .unwrap(); let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index 264850c5..8639c526 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -65,9 +65,9 @@ mod tests { use hex_literal::hex; use crate::{ - issue_sig_cat, sig_cat_asset_id, spend_cat_coins, spend_standard_coin, testing::SECRET_KEY, - AssertPuzzleAnnouncement, CatSpend, CreateCoinWithMemos, CreateCoinWithoutMemos, - RequiredSignature, SpendContext, WalletSimulator, + sig_cat_asset_id, spend_cat_coins, spend_standard_coin, testing::SECRET_KEY, + AssertPuzzleAnnouncement, BaseSpend, CatSpend, CreateCoinWithMemos, IssueCat, + RequiredSignature, SpendContext, StandardSpend, WalletSimulator, }; fn sk1() -> SecretKey { @@ -132,32 +132,22 @@ mod tests { let xch1 = sim.generate_coin(ph1, 1000).await; // Issue CAT. - let issue_cat = issue_sig_cat( - &mut ctx, - pk1.clone(), - xch1.coin.coin_id(), - 1000, - [CreateCoinWithMemos { + let (issue_cat, cat_info) = IssueCat::new(&mut ctx, xch1.coin.coin_id()) + .condition(CreateCoinWithMemos { puzzle_hash: ph1, amount: 1000, memos: vec![ph1.to_vec().into()], - }], - ) - .unwrap(); + }) + .unwrap() + .multi_issuance(pk1.clone(), 1000) + .unwrap(); - let spend_xch = spend_standard_coin( - &mut ctx, - xch1.coin.clone(), - pk1.clone(), - [CreateCoinWithoutMemos { - puzzle_hash: issue_cat.puzzle_hash, - amount: 1000, - }], - ) - .unwrap(); + let coin_spends = StandardSpend::new(&mut ctx, xch1.coin.clone()) + .chain(issue_cat) + .finish(pk1.clone()) + .unwrap(); - let mut spend_bundle = - SpendBundle::new(vec![issue_cat.coin_spend, spend_xch], Signature::default()); + let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, @@ -178,7 +168,7 @@ mod tests { let asset_id = sig_cat_asset_id(pk1.clone()); let cat1_ph = cat_puzzle_hash(asset_id.into(), ph1.into()).into(); - let cat1 = Coin::new(issue_cat.eve_coin_id, cat1_ph, 1000); + let cat1 = Coin::new(cat_info.eve_coin.coin_id(), cat1_ph, 1000); let xch2 = sim.generate_coin(ph2, 1000).await.coin; @@ -268,7 +258,7 @@ mod tests { extra_delta: 0, lineage_proof: LineageProof { parent_coin_info: xch1.coin.coin_id(), - inner_puzzle_hash: issue_cat.eve_inner_puzzle_hash, + inner_puzzle_hash: cat_info.eve_inner_puzzle_hash, amount: 1000, }, }], diff --git a/src/spends/puzzles/standard.rs b/src/spends/puzzles/standard.rs index 08696564..056d1b04 100644 --- a/src/spends/puzzles/standard.rs +++ b/src/spends/puzzles/standard.rs @@ -5,7 +5,44 @@ use clvm_traits::{clvm_quote, ToClvm}; use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use crate::{SpendContext, SpendError}; +use crate::{BaseSpend, ChainedSpend, SpendContext, SpendError}; + +pub struct StandardSpend<'a, 'b> { + ctx: &'a mut SpendContext<'b>, + coin: Coin, + coin_spends: Vec, + conditions: Vec, +} + +impl<'a, 'b> StandardSpend<'a, 'b> { + pub fn new(ctx: &'a mut SpendContext<'b>, coin: Coin) -> Self { + Self { + ctx, + coin, + coin_spends: Vec::with_capacity(1), + conditions: Vec::new(), + } + } + + pub fn finish(mut self, synthetic_key: PublicKey) -> Result, SpendError> { + let coin_spend = spend_standard_coin(self.ctx, self.coin, synthetic_key, self.conditions)?; + self.coin_spends.push(coin_spend); + Ok(self.coin_spends) + } +} + +impl<'a, 'b> BaseSpend for StandardSpend<'a, 'b> { + fn chain(mut self, chained_spend: ChainedSpend) -> Self { + self.conditions.extend(chained_spend.parent_conditions); + self.coin_spends.extend(chained_spend.coin_spends); + self + } + + fn condition(mut self, condition: impl ToClvm) -> Result { + self.conditions.push(self.ctx.alloc(condition)?); + Ok(self) + } +} /// Constructs a solution for the standard puzzle, given a list of condition. /// This assumes no hidden puzzle is being used in this spend. @@ -39,42 +76,6 @@ where Ok(CoinSpend::new(coin, puzzle_reveal, serialized_solution)) } -/// A coin and its corresponding public key. -pub struct StandardSpend { - /// The coin being spent. - pub coin: Coin, - - /// The public key corresponding to the coin. - pub synthetic_key: PublicKey, -} - -/// Spends a set of standard transaction coins. -pub fn spend_standard_coins( - ctx: &mut SpendContext, - standard_spends: Vec, - conditions: T, -) -> Result, SpendError> -where - T: ToClvm, -{ - let mut coin_spends = Vec::new(); - - let conditions = ctx.alloc(conditions)?; - - for (i, spend) in standard_spends.into_iter().enumerate() { - // todo: add announcements - let coin_spend = spend_standard_coin( - ctx, - spend.coin, - spend.synthetic_key, - if i == 0 { conditions } else { NodePtr::NIL }, - )?; - coin_spends.push(coin_spend); - } - - Ok(coin_spends) -} - #[cfg(test)] mod tests { use chia_bls::derive_keys::master_to_wallet_unhardened; diff --git a/src/spends/spend_builder.rs b/src/spends/spend_builder.rs new file mode 100644 index 00000000..31a86f20 --- /dev/null +++ b/src/spends/spend_builder.rs @@ -0,0 +1,18 @@ +use chia_protocol::CoinSpend; +use clvm_traits::ToClvm; +use clvmr::NodePtr; + +use crate::SpendError; + +pub trait BaseSpend { + fn chain(self, chained_spend: ChainedSpend) -> Self; + fn condition(self, condition: impl ToClvm) -> Result + where + Self: Sized; +} + +#[must_use = "The contents of a chained spend must be used."] +pub struct ChainedSpend { + pub coin_spends: Vec, + pub parent_conditions: Vec, +} From 87d42647950ae9c4b2ecb7784831df99a81d9558 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Thu, 2 May 2024 14:49:39 -0500 Subject: [PATCH 07/25] Next --- Cargo.lock | 5 +- Cargo.toml | 1 + src/spends/puzzles/cat.rs | 2 +- src/spends/puzzles/offer.rs | 141 +++++++----------- src/spends/puzzles/offer/offer_builder.rs | 141 ++++++++++++++++++ .../puzzles/offer/settlement_payments.rs | 58 +------ src/spends/spend_builder.rs | 38 ++++- src/spends/spend_context.rs | 5 + 8 files changed, 241 insertions(+), 150 deletions(-) create mode 100644 src/spends/puzzles/offer/offer_builder.rs diff --git a/Cargo.lock b/Cargo.lock index d8399908..15001372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,9 +47,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arbitrary" @@ -393,6 +393,7 @@ dependencies = [ name = "chia-wallet-sdk" version = "0.7.1" dependencies = [ + "anyhow", "bech32", "bip39", "chia-bls 0.7.0", diff --git a/Cargo.toml b/Cargo.toml index 0128af64..75972f90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ indexmap = "2.2.6" flate2 = { version = "1.0.29", features = ["zlib"] } [dev-dependencies] +anyhow = "1.0.82" bip39 = "2.0.0" once_cell = "1.19.0" rstest = "0.19.0" diff --git a/src/spends/puzzles/cat.rs b/src/spends/puzzles/cat.rs index 40751eed..b52f6be1 100644 --- a/src/spends/puzzles/cat.rs +++ b/src/spends/puzzles/cat.rs @@ -171,7 +171,7 @@ pub fn spend_cat_coins( Ok(coin_spends) } -pub fn sig_cat_asset_id(public_key: PublicKey) -> Bytes32 { +pub fn multi_issuance_asset_id(public_key: PublicKey) -> Bytes32 { let a = &mut Allocator::new(); let tail_mod = node_from_bytes(a, &EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE).unwrap(); diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index 8639c526..7702791c 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -1,7 +1,9 @@ +mod offer_builder; mod offer_compression; mod offer_encoding; mod settlement_payments; +pub use offer_builder::*; pub use offer_compression::*; pub use offer_encoding::*; pub use settlement_payments::*; @@ -55,18 +57,16 @@ mod tests { use chia_bls::{sign, DerivableKey, SecretKey, Signature}; use chia_protocol::{Bytes32, Coin, SpendBundle}; use chia_wallet::{ - cat::{cat_puzzle_hash, CatArgs, CAT_PUZZLE_HASH}, + cat::cat_puzzle_hash, offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH, standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, DeriveSynthetic, LineageProof, }; - use clvm_utils::CurriedProgram; use clvmr::Allocator; use hex_literal::hex; use crate::{ - sig_cat_asset_id, spend_cat_coins, spend_standard_coin, testing::SECRET_KEY, - AssertPuzzleAnnouncement, BaseSpend, CatSpend, CreateCoinWithMemos, IssueCat, + spend_cat_coins, testing::SECRET_KEY, BaseSpend, CatSpend, CreateCoinWithMemos, IssueCat, RequiredSignature, SpendContext, StandardSpend, WalletSimulator, }; @@ -116,7 +116,7 @@ mod tests { } #[tokio::test] - async fn test_offer() { + async fn test_offer() -> anyhow::Result<()> { let sim = WalletSimulator::new().await; let peer = sim.peer().await; @@ -137,15 +137,12 @@ mod tests { puzzle_hash: ph1, amount: 1000, memos: vec![ph1.to_vec().into()], - }) - .unwrap() - .multi_issuance(pk1.clone(), 1000) - .unwrap(); + })? + .multi_issuance(pk1.clone(), 1000)?; let coin_spends = StandardSpend::new(&mut ctx, xch1.coin.clone()) .chain(issue_cat) - .finish(pk1.clone()) - .unwrap(); + .finish(pk1.clone())?; let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); @@ -153,12 +150,11 @@ mod tests { &mut allocator, &spend_bundle.coin_spends, WalletSimulator::AGG_SIG_ME.into(), - ) - .unwrap(); + )?; spend_bundle.aggregated_signature = sign_tx(required_signatures); - let ack = peer.send_transaction(spend_bundle).await.unwrap(); + let ack = peer.send_transaction(spend_bundle).await?; assert_eq!(ack.error, None); assert_eq!(ack.status, 1); @@ -166,68 +162,39 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let asset_id = sig_cat_asset_id(pk1.clone()); - let cat1_ph = cat_puzzle_hash(asset_id.into(), ph1.into()).into(); + let cat1_ph = cat_puzzle_hash(cat_info.asset_id.into(), ph1.into()).into(); let cat1 = Coin::new(cat_info.eve_coin.coin_id(), cat1_ph, 1000); let xch2 = sim.generate_coin(ph2, 1000).await.coin; - let nonce = calculate_nonce(&mut ctx, vec![cat1.coin_id(), xch2.coin_id()]).unwrap(); + let requests = OfferBuilder::new(&mut ctx, vec![cat1.coin_id(), xch2.coin_id()]) + .request_cat_payments( + cat_info.asset_id, + vec![Payment::WithMemos(PaymentWithMemos { + puzzle_hash: ph2, + amount: 1000, + memos: vec![ph2.to_vec().into()], + })], + )? + .finish(); - let cat_puzzle = ctx.cat_puzzle(); - let offer_puzzle = ctx.settlement_payments_puzzle(); + let mut coin_spends = requests.coin_spends; - let requested_cat = ctx - .alloc(CurriedProgram { - program: cat_puzzle, - args: CatArgs { - mod_hash: CAT_PUZZLE_HASH.into(), - tail_program_hash: asset_id, - inner_puzzle: offer_puzzle, - }, - }) - .unwrap(); + let xch_spends = StandardSpend::new(&mut ctx, xch2) + .settlement_coin(1000)? + .conditions(requests.assertions)? + .finish(pk2)?; - let requested = request_offer_payments( - &mut ctx, - nonce, - requested_cat, - vec![Payment::WithMemos(PaymentWithMemos { - puzzle_hash: ph2, - amount: 1000, - memos: vec![ph2.to_vec().into()], - })], - ) - .unwrap(); - - let conditions = [ - ctx.alloc(CreateCoinWithMemos { - puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), - amount: 1000, - memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], - }) - .unwrap(), - ctx.alloc(AssertPuzzleAnnouncement { - announcement_id: requested.puzzle_announcement_id, - }) - .unwrap(), - ]; - - let xch_spend = spend_standard_coin(&mut ctx, xch2, pk2, conditions).unwrap(); - - let aggregated_signature = sign_tx( - RequiredSignature::from_coin_spend( - &mut allocator, - &xch_spend, - WalletSimulator::AGG_SIG_ME.into(), - ) - .unwrap(), - ); - - let spend_bundle = - SpendBundle::new(vec![requested.coin_spend, xch_spend], aggregated_signature); - - let offer_data = compress_offer(spend_bundle).unwrap(); + let aggregated_signature = sign_tx(RequiredSignature::from_coin_spends( + &mut allocator, + &xch_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?); + + coin_spends.extend(xch_spends); + + let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); + let offer_data = compress_offer(spend_bundle)?; assert_eq!(hex::encode(&offer_data), hex::encode(EXAMPLE_OFFER_DATA)); @@ -235,21 +202,19 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let offer = Offer::from(decompress_offer(&offer_data).unwrap()); + let offer = Offer::from(decompress_offer(&offer_data)?); let mut spend_bundle = offer.offered_spend_bundle; - let conditions = ctx - .alloc(vec![CreateCoinWithMemos { - puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), - amount: 1000, - memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], - }]) - .unwrap(); + let conditions = ctx.alloc(vec![CreateCoinWithMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount: 1000, + memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], + }])?; let cat_spend = spend_cat_coins( &mut ctx, - asset_id, + cat_info.asset_id, &[CatSpend { coin: cat1, synthetic_key: pk1, @@ -262,24 +227,22 @@ mod tests { amount: 1000, }, }], - ) - .unwrap() + )? .remove(0); - spend_bundle.aggregated_signature += &sign_tx( - RequiredSignature::from_coin_spend( - &mut allocator, - &cat_spend, - WalletSimulator::AGG_SIG_ME.into(), - ) - .unwrap(), - ); + spend_bundle.aggregated_signature += &sign_tx(RequiredSignature::from_coin_spend( + &mut allocator, + &cat_spend, + WalletSimulator::AGG_SIG_ME.into(), + )?); spend_bundle.coin_spends.push(cat_spend); - let ack = peer.send_transaction(spend_bundle).await.unwrap(); + let ack = peer.send_transaction(spend_bundle).await?; assert_eq!(ack.error, None); assert_eq!(ack.status, 1); + + Ok(()) } pub const EXAMPLE_OFFER_DATA: [u8; 535] = hex!( diff --git a/src/spends/puzzles/offer/offer_builder.rs b/src/spends/puzzles/offer/offer_builder.rs new file mode 100644 index 00000000..bf6610be --- /dev/null +++ b/src/spends/puzzles/offer/offer_builder.rs @@ -0,0 +1,141 @@ +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_wallet::{ + cat::{cat_puzzle_hash, CatArgs, CAT_PUZZLE_HASH}, + offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH, +}; +use clvm_utils::{tree_hash_atom, tree_hash_pair, CurriedProgram}; +use clvmr::NodePtr; +use sha2::{digest::FixedOutput, Digest, Sha256}; + +use crate::{ + AssertPuzzleAnnouncement, NotarizedPayment, Payment, SettlementPaymentsSolution, SpendContext, + SpendError, +}; + +pub struct OfferRequests { + pub coin_spends: Vec, + pub assertions: Vec, +} + +pub struct OfferBuilder<'a, 'b> { + ctx: &'a mut SpendContext<'b>, + nonce: Bytes32, + coin_spends: Vec, + assertions: Vec, +} + +impl<'a, 'b> OfferBuilder<'a, 'b> { + pub fn new(ctx: &'a mut SpendContext<'b>, offered_coin_ids: Vec) -> Self { + let nonce = calculate_nonce(offered_coin_ids); + Self::from_nonce(ctx, nonce) + } + + pub fn from_nonce(ctx: &'a mut SpendContext<'b>, nonce: Bytes32) -> Self { + Self { + ctx, + nonce, + coin_spends: Vec::new(), + assertions: Vec::new(), + } + } + + pub fn request_xch_payments(self, payments: Vec) -> Result { + let puzzle = self.ctx.standard_puzzle(); + self.request_payments(puzzle, payments) + } + + pub fn request_cat_payments( + self, + asset_id: Bytes32, + payments: Vec, + ) -> Result { + let puzzle_hash = cat_puzzle_hash(asset_id.into(), SETTLEMENT_PAYMENTS_PUZZLE_HASH); + + let puzzle = if let Some(puzzle) = self.ctx.get_puzzle(&puzzle_hash) { + puzzle + } else { + let cat_puzzle = self.ctx.cat_puzzle(); + let settlement_payments_puzzle = self.ctx.settlement_payments_puzzle(); + let puzzle = self.ctx.alloc(CurriedProgram { + program: cat_puzzle, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: asset_id, + inner_puzzle: settlement_payments_puzzle, + }, + })?; + self.ctx.preload(puzzle_hash, puzzle); + puzzle + }; + + self.request_payments(puzzle, payments) + } + + pub fn request_payments( + mut self, + puzzle: NodePtr, + payments: Vec, + ) -> Result { + let (coin_spend, announcement_id) = + request_offer_payments(self.ctx, self.nonce, puzzle, payments)?; + + self.coin_spends.push(coin_spend); + self.assertions + .push(AssertPuzzleAnnouncement { announcement_id }); + + Ok(self) + } + + pub fn finish(self) -> OfferRequests { + OfferRequests { + coin_spends: self.coin_spends, + assertions: self.assertions, + } + } +} + +pub fn calculate_nonce(offered_coin_ids: Vec) -> Bytes32 { + let mut coin_ids = offered_coin_ids; + coin_ids.sort(); + + let mut tree_hash = tree_hash_atom(&[]); + + for coin_id in coin_ids.into_iter().rev() { + let item_hash = tree_hash_atom(&coin_id); + tree_hash = tree_hash_pair(item_hash, tree_hash); + } + + tree_hash.into() +} + +pub fn request_offer_payments( + ctx: &mut SpendContext, + nonce: Bytes32, + puzzle: NodePtr, + payments: Vec, +) -> Result<(CoinSpend, Bytes32), SpendError> { + let puzzle_reveal = ctx.serialize(puzzle)?; + let puzzle_hash = ctx.tree_hash(puzzle); + + let notarized_payment = NotarizedPayment { nonce, payments }; + + let settlement_solution = ctx.serialize(SettlementPaymentsSolution { + notarized_payments: vec![notarized_payment.clone()], + })?; + + let coin_spend = CoinSpend::new( + Coin::new(Bytes32::default(), puzzle_hash, 0), + puzzle_reveal, + settlement_solution, + ); + + let notarized_payment_ptr = ctx.alloc(notarized_payment)?; + let notarized_payment_hash = ctx.tree_hash(notarized_payment_ptr); + + let mut hasher = Sha256::new(); + hasher.update(puzzle_hash); + hasher.update(notarized_payment_hash); + let puzzle_announcement_id = Bytes32::new(hasher.finalize_fixed().into()); + + Ok((coin_spend, puzzle_announcement_id)) +} diff --git a/src/spends/puzzles/offer/settlement_payments.rs b/src/spends/puzzles/offer/settlement_payments.rs index a185aec3..4b608c07 100644 --- a/src/spends/puzzles/offer/settlement_payments.rs +++ b/src/spends/puzzles/offer/settlement_payments.rs @@ -1,9 +1,5 @@ -use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend}; +use chia_protocol::{Bytes, Bytes32}; use clvm_traits::{FromClvm, ToClvm}; -use clvmr::NodePtr; -use sha2::{digest::FixedOutput, Digest, Sha256}; - -use crate::{SpendContext, SpendError}; #[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)] #[clvm(tuple)] @@ -39,55 +35,3 @@ pub struct PaymentWithMemos { pub amount: u64, pub memos: Vec, } - -pub fn calculate_nonce( - ctx: &mut SpendContext, - mut offered_coin_ids: Vec, -) -> Result { - offered_coin_ids.sort(); - let offered_coin_ids = ctx.alloc(offered_coin_ids)?; - Ok(ctx.tree_hash(offered_coin_ids)) -} - -pub struct OfferRequest { - pub coin_spend: CoinSpend, - pub puzzle_announcement_id: Bytes32, -} - -pub fn request_offer_payments( - ctx: &mut SpendContext, - nonce: Bytes32, - requested_puzzle: NodePtr, - requested_payments: Vec, -) -> Result { - let puzzle_reveal = ctx.serialize(requested_puzzle)?; - let puzzle_hash = ctx.tree_hash(requested_puzzle); - - let notarized_payment = NotarizedPayment { - nonce, - payments: requested_payments, - }; - - let settlement_solution = ctx.serialize(SettlementPaymentsSolution { - notarized_payments: vec![notarized_payment.clone()], - })?; - - let coin_spend = CoinSpend::new( - Coin::new(Bytes32::default(), puzzle_hash, 0), - puzzle_reveal, - settlement_solution, - ); - - let notarized_payment_ptr = ctx.alloc(notarized_payment)?; - let notarized_payment_hash = ctx.tree_hash(notarized_payment_ptr); - - let mut hasher = Sha256::new(); - hasher.update(puzzle_hash); - hasher.update(notarized_payment_hash); - let puzzle_announcement_id = Bytes32::new(hasher.finalize_fixed().into()); - - Ok(OfferRequest { - coin_spend, - puzzle_announcement_id, - }) -} diff --git a/src/spends/spend_builder.rs b/src/spends/spend_builder.rs index 31a86f20..7514b046 100644 --- a/src/spends/spend_builder.rs +++ b/src/spends/spend_builder.rs @@ -1,17 +1,53 @@ use chia_protocol::CoinSpend; +use chia_wallet::offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH; use clvm_traits::ToClvm; use clvmr::NodePtr; -use crate::SpendError; +use crate::{CreateCoinWithMemos, CreateCoinWithoutMemos, SpendError}; pub trait BaseSpend { fn chain(self, chained_spend: ChainedSpend) -> Self; fn condition(self, condition: impl ToClvm) -> Result where Self: Sized; + + fn conditions( + mut self, + conditions: impl IntoIterator>, + ) -> Result + where + Self: Sized, + { + for condition in conditions { + self = self.condition(condition)?; + } + Ok(self) + } + + fn unhinted_settlement_coin(self, amount: u64) -> Result + where + Self: Sized, + { + self.condition(CreateCoinWithoutMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount, + }) + } + + fn settlement_coin(self, amount: u64) -> Result + where + Self: Sized, + { + self.condition(CreateCoinWithMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount, + memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], + }) + } } #[must_use = "The contents of a chained spend must be used."] +#[derive(Debug, Clone)] pub struct ChainedSpend { pub coin_spends: Vec, pub parent_conditions: Vec, diff --git a/src/spends/spend_context.rs b/src/spends/spend_context.rs index 0493ed3f..d75b19d9 100644 --- a/src/spends/spend_context.rs +++ b/src/spends/spend_context.rs @@ -159,6 +159,11 @@ impl<'a> SpendContext<'a> { self.puzzles.insert(puzzle_hash, ptr); } + /// Checks whether a puzzle is in the cache. + pub fn get_puzzle(&self, puzzle_hash: &[u8; 32]) -> Option { + self.puzzles.get(puzzle_hash).copied() + } + /// Get a puzzle from the cache or allocate a new one. pub fn puzzle(&mut self, puzzle_hash: &[u8; 32], puzzle_bytes: &[u8]) -> NodePtr { if let Some(puzzle) = self.puzzles.get(puzzle_bytes) { From 7d3639c78983d69a4c16466b3a4b871084e3ffd4 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 3 May 2024 19:37:02 -0500 Subject: [PATCH 08/25] Next steps --- src/spends/puzzles/cat.rs | 392 ++++++++++++++++----------------- src/spends/puzzles/did.rs | 2 +- src/spends/puzzles/standard.rs | 106 +++++---- src/spends/spend_builder.rs | 59 ++--- 4 files changed, 262 insertions(+), 297 deletions(-) diff --git a/src/spends/puzzles/cat.rs b/src/spends/puzzles/cat.rs index b52f6be1..81770d4b 100644 --- a/src/spends/puzzles/cat.rs +++ b/src/spends/puzzles/cat.rs @@ -5,20 +5,120 @@ use chia_wallet::{ CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, CAT_PUZZLE_HASH, EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, }, - standard::{StandardArgs, StandardSolution}, LineageProof, }; use clvm_traits::{clvm_quote, destructure_tuple, match_tuple, MatchByte, ToClvm, ToNodePtr}; use clvm_utils::{tree_hash, CurriedProgram}; use clvmr::{serde::node_from_bytes, Allocator, NodePtr}; -use crate::{BaseSpend, ChainedSpend, CreateCoinWithMemos, RunTail, SpendContext, SpendError}; +use crate::{ChainedSpend, CreateCoinWithMemos, InnerSpend, RunTail, SpendContext, SpendError}; -pub struct IssueCat<'a, 'b> { - ctx: &'a mut SpendContext<'b>, +struct CatSpendItem { + coin: Coin, + inner_spend: InnerSpend, + lineage_proof: LineageProof, + extra_delta: i64, +} + +pub struct CatSpend { + asset_id: Bytes32, + cat_spends: Vec, +} + +impl CatSpend { + pub 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 mut coin_spends = Vec::new(); + let mut total_delta = 0; + + let len = self.cat_spends.len(); + + 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::, NodePtr, u64, NodePtr)>(ptr) + .ok() + }); + + let delta = create_coins.fold( + coin.amount as i64 - *extra_delta, + |delta, destructure_tuple!(_, _, amount, _)| delta - amount as i64, + ); + + 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(), + tail_program_hash: self.asset_id, + inner_puzzle: inner_spend.puzzle(), + }, + })?; + + let solution = ctx.serialize(CatSolution { + inner_puzzle_solution: inner_spend.solution(), + lineage_proof: Some(lineage_proof.clone()), + prev_coin_id: prev_cat.coin.coin_id(), + this_coin_info: coin.clone(), + next_coin_proof: CoinProof { + parent_coin_info: next_cat.coin.parent_coin_info, + inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()), + amount: next_cat.coin.amount, + }, + prev_subtotal, + extra_delta: *extra_delta, + })?; + + coin_spends.push(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); + } + + Ok(coin_spends) + } +} + +pub struct IssueCat { parent_coin_id: Bytes32, conditions: Vec, - coin_spends: Vec, } pub struct IssuanceInfo { @@ -27,22 +127,35 @@ pub struct IssuanceInfo { pub eve_inner_puzzle_hash: Bytes32, } -impl<'a, 'b> IssueCat<'a, 'b> { - pub fn new(ctx: &'a mut SpendContext<'b>, parent_coin_id: Bytes32) -> Self { +impl IssueCat { + pub fn new(parent_coin_id: Bytes32) -> Self { Self { - ctx, parent_coin_id, conditions: Vec::new(), - coin_spends: Vec::with_capacity(1), } } + pub fn condition(mut self, condition: NodePtr) -> Self { + self.conditions.push(condition); + self + } + + pub fn conditions(mut self, conditions: impl IntoIterator) -> Self + where + Self: Sized, + { + for condition in conditions { + self = self.condition(condition); + } + self + } + pub fn multi_issuance( self, + ctx: &mut SpendContext, public_key: PublicKey, amount: u64, ) -> Result<(ChainedSpend, IssuanceInfo), SpendError> { - let ctx = self.ctx; let tail_puzzle_ptr = ctx.everything_with_signature_tail_puzzle(); let tail = ctx.alloc(CurriedProgram { @@ -67,110 +180,6 @@ impl<'a, 'b> IssueCat<'a, 'b> { } } -impl<'a, 'b> BaseSpend for IssueCat<'a, 'b> { - fn chain(mut self, chained_spend: ChainedSpend) -> Self { - self.conditions.extend(chained_spend.parent_conditions); - self.coin_spends.extend(chained_spend.coin_spends); - self - } - - fn condition(mut self, condition: impl ToClvm) -> Result { - self.conditions.push(self.ctx.alloc(condition)?); - Ok(self) - } -} - -/// The information required to spend a CAT coin. -/// This assumes that the inner puzzle is a standard transaction. -pub struct CatSpend { - /// The CAT coin that is being spent. - pub coin: Coin, - /// The public key used for the inner puzzle. - pub synthetic_key: PublicKey, - /// The desired output conditions for the coin spend. - pub conditions: NodePtr, - /// The extra delta produced as part of this spend. - pub extra_delta: i64, - /// The inner puzzle hash. - pub p2_puzzle_hash: Bytes32, - /// The lineage proof of the CAT. - pub lineage_proof: LineageProof, -} - -/// Creates a set of CAT coin spends for a given asset id. -pub fn spend_cat_coins( - ctx: &mut SpendContext, - asset_id: Bytes32, - cat_spends: &[CatSpend], -) -> Result, SpendError> { - let cat_puzzle_ptr = ctx.cat_puzzle(); - let standard_puzzle_ptr = ctx.standard_puzzle(); - - let mut coin_spends = Vec::new(); - let mut total_delta = 0; - let len = cat_spends.len(); - - for (index, cat_spend) in cat_spends.iter().enumerate() { - // Calculate the delta and add it to the subtotal. - let conditions: Vec = ctx.extract(cat_spend.conditions)?; - let create_coins = conditions.into_iter().filter_map(|ptr| { - ctx.extract::, NodePtr, u64, NodePtr)>(ptr) - .ok() - }); - let delta = create_coins.fold( - cat_spend.coin.amount as i64 - cat_spend.extra_delta, - |delta, destructure_tuple!(_, _, amount, _)| delta - amount as i64, - ); - - let prev_subtotal = total_delta; - total_delta += delta; - - // Find information of neighboring coins on the ring. - let prev_cat = &cat_spends[if index == 0 { len - 1 } else { index - 1 }]; - let next_cat = &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(), - tail_program_hash: asset_id, - inner_puzzle: CurriedProgram { - program: standard_puzzle_ptr, - args: StandardArgs { - synthetic_key: cat_spend.synthetic_key.clone(), - }, - }, - }, - })?; - - let solution = ctx.serialize(CatSolution { - inner_puzzle_solution: StandardSolution { - original_public_key: None, - delegated_puzzle: clvm_quote!(&cat_spend.conditions), - solution: (), - }, - lineage_proof: Some(cat_spend.lineage_proof.clone()), - prev_coin_id: prev_cat.coin.coin_id(), - this_coin_info: cat_spend.coin.clone(), - next_coin_proof: CoinProof { - parent_coin_info: next_cat.coin.parent_coin_info, - inner_puzzle_hash: next_cat.p2_puzzle_hash, - amount: next_cat.coin.amount, - }, - prev_subtotal, - extra_delta: cat_spend.extra_delta, - })?; - - coin_spends.push(CoinSpend::new( - cat_spend.coin.clone(), - puzzle_reveal, - solution, - )); - } - - Ok(coin_spends) -} - pub fn multi_issuance_asset_id(public_key: PublicKey) -> Bytes32 { let a = &mut Allocator::new(); let tail_mod = node_from_bytes(a, &EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE).unwrap(); @@ -265,14 +274,14 @@ mod tests { use hex_literal::hex; use crate::{ - testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, RequiredSignature, - StandardSpend, WalletSimulator, + testing::SECRET_KEY, BaseSpend, CreateCoinWithMemos, CreateCoinWithoutMemos, + RequiredSignature, StandardSpend, WalletSimulator, }; use super::*; #[tokio::test] - async fn test_cat_issuance() { + async fn test_cat_issuance() -> anyhow::Result<()> { let sim = WalletSimulator::new().await; let peer = sim.peer().await; @@ -281,32 +290,26 @@ mod tests { let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); - let xch_coin = sim.generate_coin(puzzle_hash, 1).await.coin; - let (issue_cat, _cat_info) = IssueCat::new(&mut ctx, xch_coin.coin_id()) - .condition(CreateCoinWithMemos { + let (issue_cat, _cat_info) = IssueCat::new(xch_coin.coin_id()) + .condition(ctx.alloc(CreateCoinWithMemos { puzzle_hash, amount: 1, memos: vec![puzzle_hash.to_vec().into()], - }) - .unwrap() - .multi_issuance(pk.clone(), 1) - .unwrap(); + })?) + .multi_issuance(&mut ctx, pk.clone(), 1)?; - let coin_spends = StandardSpend::new(&mut ctx, xch_coin) + let coin_spends = StandardSpend::default() .chain(issue_cat) - .finish(pk) - .unwrap(); + .finish(&mut ctx, xch_coin, pk)?; let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, &coin_spends, WalletSimulator::AGG_SIG_ME.into(), - ) - .unwrap(); + )?; let mut aggregated_signature = Signature::default(); @@ -316,13 +319,15 @@ mod tests { let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); - let ack = peer.send_transaction(spend_bundle).await.unwrap(); + let ack = peer.send_transaction(spend_bundle).await?; assert_eq!(ack.error, None); assert_eq!(ack.status, 1); + + Ok(()) } #[test] - fn test_cat_spend() { + fn test_cat_spend() -> anyhow::Result<()> { let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); @@ -341,38 +346,29 @@ mod tests { 42, ); - let conditions = ctx - .alloc([CreateCoinWithoutMemos { + let (inner_spend, _) = StandardSpend::default() + .condition(ctx.alloc(CreateCoinWithoutMemos { puzzle_hash: coin.puzzle_hash, amount: coin.amount, - }]) - .unwrap(); + })?) + .inner_spend(&mut ctx, synthetic_key)?; - let coin_spend = spend_cat_coins( - &mut ctx, - asset_id, - &[CatSpend { - coin, - synthetic_key, - conditions, - extra_delta: 0, - lineage_proof: LineageProof { - parent_coin_info: parent_coin.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin.amount, - }, - p2_puzzle_hash, - }], - ) - .unwrap() - .remove(0); + let lineage_proof = LineageProof { + parent_coin_info: parent_coin.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin.amount, + }; + + let coin_spend = CatSpend::new(asset_id) + .spend(coin, inner_spend, lineage_proof, 0) + .finish(&mut ctx)? + .remove(0); let output_ptr = coin_spend .puzzle_reveal - .run(&mut allocator, 0, u64::MAX, &coin_spend.solution) - .unwrap() + .run(&mut allocator, 0, u64::MAX, &coin_spend.solution)? .1; - let actual = node_to_bytes(&allocator, output_ptr).unwrap(); + let actual = node_to_bytes(&allocator, output_ptr)?; let expected = hex!( " @@ -387,10 +383,12 @@ mod tests { " ); assert_eq!(hex::encode(actual), hex::encode(expected)); + + Ok(()) } #[test] - fn test_cat_spend_multi() { + fn test_cat_spend_multi() -> anyhow::Result<()> { let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); @@ -423,56 +421,38 @@ mod tests { 69, ); - let conditions = ctx - .alloc([CreateCoinWithoutMemos { + let lineage_1 = LineageProof { + parent_coin_info: parent_coin_1.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin_1.amount, + }; + + let lineage_2 = LineageProof { + parent_coin_info: parent_coin_2.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin_2.amount, + }; + + let lineage_3 = LineageProof { + parent_coin_info: parent_coin_3.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin_3.amount, + }; + + let (inner_spend, _) = StandardSpend::default() + .condition(ctx.alloc(CreateCoinWithoutMemos { puzzle_hash: coin_1.puzzle_hash, amount: coin_1.amount + coin_2.amount + coin_3.amount, - }]) - .unwrap(); + })?) + .inner_spend(&mut ctx, synthetic_key.clone())?; - let coin_spends = spend_cat_coins( - &mut ctx, - asset_id, - &[ - CatSpend { - coin: coin_1, - synthetic_key: synthetic_key.clone(), - conditions, - extra_delta: 0, - lineage_proof: LineageProof { - parent_coin_info: parent_coin_1.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_1.amount, - }, - p2_puzzle_hash, - }, - CatSpend { - coin: coin_2, - synthetic_key: synthetic_key.clone(), - conditions: NodePtr::NIL, - extra_delta: 0, - lineage_proof: LineageProof { - parent_coin_info: parent_coin_2.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_2.amount, - }, - p2_puzzle_hash, - }, - CatSpend { - coin: coin_3, - synthetic_key, - conditions: NodePtr::NIL, - extra_delta: 0, - lineage_proof: LineageProof { - parent_coin_info: parent_coin_3.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_3.amount, - }, - p2_puzzle_hash, - }, - ], - ) - .unwrap(); + let (empty_spend, _) = StandardSpend::default().inner_spend(&mut ctx, synthetic_key)?; + + let coin_spends = CatSpend::new(asset_id) + .spend(coin_1, inner_spend, lineage_1, 0) + .spend(coin_2, empty_spend, lineage_2, 0) + .spend(coin_3, empty_spend, lineage_3, 0) + .finish(&mut ctx)?; let spend_vec = coin_spends .clone() @@ -554,5 +534,7 @@ mod tests { " ); assert_eq!(hex::encode(actual), hex::encode(expected)); + + Ok(()) } } diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index 298bb8cb..e0b54fe9 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -179,7 +179,7 @@ mod tests { }; use clvmr::Allocator; - use crate::{spend_standard_coin, testing::SECRET_KEY, RequiredSignature, WalletSimulator}; + use crate::{testing::SECRET_KEY, RequiredSignature, WalletSimulator}; use super::*; diff --git a/src/spends/puzzles/standard.rs b/src/spends/puzzles/standard.rs index 056d1b04..3a2e2169 100644 --- a/src/spends/puzzles/standard.rs +++ b/src/spends/puzzles/standard.rs @@ -1,46 +1,62 @@ use chia_bls::PublicKey; use chia_protocol::{Coin, CoinSpend}; use chia_wallet::standard::{StandardArgs, StandardSolution}; -use clvm_traits::{clvm_quote, ToClvm}; +use clvm_traits::clvm_quote; use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use crate::{BaseSpend, ChainedSpend, SpendContext, SpendError}; +use crate::{BaseSpend, ChainedSpend, InnerSpend, SpendContext, SpendError}; -pub struct StandardSpend<'a, 'b> { - ctx: &'a mut SpendContext<'b>, - coin: Coin, +#[derive(Default)] +pub struct StandardSpend { coin_spends: Vec, conditions: Vec, } -impl<'a, 'b> StandardSpend<'a, 'b> { - pub fn new(ctx: &'a mut SpendContext<'b>, coin: Coin) -> Self { - Self { - ctx, - coin, - coin_spends: Vec::with_capacity(1), - conditions: Vec::new(), - } +impl StandardSpend { + pub fn inner_spend( + self, + ctx: &mut SpendContext, + synthetic_key: PublicKey, + ) -> Result<(InnerSpend, Vec), SpendError> { + let standard_puzzle = ctx.standard_puzzle(); + + let puzzle = ctx.alloc(CurriedProgram { + program: standard_puzzle, + args: StandardArgs { synthetic_key }, + })?; + + let solution = ctx.alloc(standard_solution(self.conditions))?; + + Ok((InnerSpend::new(puzzle, solution), self.coin_spends)) } - pub fn finish(mut self, synthetic_key: PublicKey) -> Result, SpendError> { - let coin_spend = spend_standard_coin(self.ctx, self.coin, synthetic_key, self.conditions)?; - self.coin_spends.push(coin_spend); - Ok(self.coin_spends) + pub fn finish( + self, + ctx: &mut SpendContext, + coin: Coin, + synthetic_key: PublicKey, + ) -> Result, SpendError> { + let (inner_spend, mut coin_spends) = self.inner_spend(ctx, synthetic_key)?; + + let puzzle_reveal = ctx.serialize(inner_spend.puzzle())?; + let solution = ctx.serialize(inner_spend.solution())?; + coin_spends.push(CoinSpend::new(coin, puzzle_reveal, solution)); + + Ok(coin_spends) } } -impl<'a, 'b> BaseSpend for StandardSpend<'a, 'b> { +impl BaseSpend for StandardSpend { fn chain(mut self, chained_spend: ChainedSpend) -> Self { self.conditions.extend(chained_spend.parent_conditions); self.coin_spends.extend(chained_spend.coin_spends); self } - fn condition(mut self, condition: impl ToClvm) -> Result { - self.conditions.push(self.ctx.alloc(condition)?); - Ok(self) + fn condition(mut self, condition: NodePtr) -> Self { + self.conditions.push(condition); + self } } @@ -54,28 +70,6 @@ pub fn standard_solution(conditions: T) -> StandardSolution<(u8, T), ()> { } } -/// Creates a new coin spend for a given standard transaction coin. -pub fn spend_standard_coin( - ctx: &mut SpendContext, - coin: Coin, - synthetic_key: PublicKey, - conditions: T, -) -> Result -where - T: ToClvm, -{ - let standard_puzzle = ctx.standard_puzzle(); - - let puzzle_reveal = ctx.serialize(CurriedProgram { - program: standard_puzzle, - args: StandardArgs { synthetic_key }, - })?; - let solution = ctx.alloc(standard_solution(conditions))?; - let serialized_solution = ctx.serialize(solution)?; - - Ok(CoinSpend::new(coin, puzzle_reveal, serialized_solution)) -} - #[cfg(test)] mod tests { use chia_bls::derive_keys::master_to_wallet_unhardened; @@ -97,19 +91,19 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let coin = Coin::new(Bytes32::from([0; 32]), Bytes32::from([1; 32]), 42); - let puzzle_hash = coin.puzzle_hash; - let amount = coin.amount; - - let coin_spend = spend_standard_coin( - &mut ctx, - coin, - synthetic_key, - [CreateCoinWithoutMemos { - puzzle_hash, - amount, - }], - ) - .unwrap(); + + let coin_spend = StandardSpend::default() + .condition( + ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: coin.puzzle_hash, + amount: coin.amount, + }) + .unwrap(), + ) + .finish(&mut ctx, coin, synthetic_key) + .unwrap() + .remove(0); + let output_ptr = coin_spend .puzzle_reveal .run(&mut a, 0, u64::MAX, &coin_spend.solution) diff --git a/src/spends/spend_builder.rs b/src/spends/spend_builder.rs index 7514b046..9a423648 100644 --- a/src/spends/spend_builder.rs +++ b/src/spends/spend_builder.rs @@ -1,54 +1,43 @@ use chia_protocol::CoinSpend; -use chia_wallet::offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH; -use clvm_traits::ToClvm; use clvmr::NodePtr; -use crate::{CreateCoinWithMemos, CreateCoinWithoutMemos, SpendError}; - pub trait BaseSpend { fn chain(self, chained_spend: ChainedSpend) -> Self; - fn condition(self, condition: impl ToClvm) -> Result - where - Self: Sized; + fn condition(self, condition: NodePtr) -> Self; - fn conditions( - mut self, - conditions: impl IntoIterator>, - ) -> Result + fn conditions(mut self, conditions: impl IntoIterator) -> Self where Self: Sized, { for condition in conditions { - self = self.condition(condition)?; + self = self.condition(condition); } - Ok(self) - } - - fn unhinted_settlement_coin(self, amount: u64) -> Result - where - Self: Sized, - { - self.condition(CreateCoinWithoutMemos { - puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), - amount, - }) - } - - fn settlement_coin(self, amount: u64) -> Result - where - Self: Sized, - { - self.condition(CreateCoinWithMemos { - puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), - amount, - memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], - }) + self } } -#[must_use = "The contents of a chained spend must be used."] #[derive(Debug, Clone)] pub struct ChainedSpend { pub coin_spends: Vec, pub parent_conditions: Vec, } + +#[derive(Debug, Clone, Copy)] +pub struct InnerSpend { + puzzle: NodePtr, + solution: NodePtr, +} + +impl InnerSpend { + 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 + } +} From fb21af174f6e4f8315ff0591d2cbcedd1c50f635 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 3 May 2024 22:59:38 -0500 Subject: [PATCH 09/25] Get everything else working again I think --- src/condition.rs | 16 + src/lib.rs | 10 +- src/spends/puzzles/cat.rs | 543 +----------------- src/spends/puzzles/cat/cat_spend.rs | 345 +++++++++++ src/spends/puzzles/cat/issue_cat.rs | 187 ++++++ src/spends/puzzles/did.rs | 225 ++------ src/spends/puzzles/did/create_did.rs | 158 +++++ src/spends/puzzles/did/did_info.rs | 12 + src/spends/puzzles/did/did_spend.rs | 48 ++ src/spends/puzzles/offer.rs | 4 +- src/spends/puzzles/singleton.rs | 39 +- .../puzzles/singleton/launch_singleton.rs | 145 +++++ .../puzzles/singleton/singleton_spend.rs | 41 ++ src/spends/puzzles/standard.rs | 32 +- src/spends/spend_builder.rs | 15 - src/wallet/required_signature.rs | 7 +- 16 files changed, 1026 insertions(+), 801 deletions(-) create mode 100644 src/spends/puzzles/cat/cat_spend.rs create mode 100644 src/spends/puzzles/cat/issue_cat.rs create mode 100644 src/spends/puzzles/did/create_did.rs create mode 100644 src/spends/puzzles/did/did_info.rs create mode 100644 src/spends/puzzles/did/did_spend.rs create mode 100644 src/spends/puzzles/singleton/launch_singleton.rs create mode 100644 src/spends/puzzles/singleton/singleton_spend.rs diff --git a/src/condition.rs b/src/condition.rs index f8bdda7f..ee421443 100644 --- a/src/condition.rs +++ b/src/condition.rs @@ -104,6 +104,22 @@ pub enum CreateCoin { WithMemos(CreateCoinWithMemos), } +impl CreateCoin { + pub fn puzzle_hash(&self) -> Bytes32 { + match self { + Self::WithoutMemos(inner) => inner.puzzle_hash, + Self::WithMemos(inner) => inner.puzzle_hash, + } + } + + pub fn amount(&self) -> u64 { + match self { + Self::WithoutMemos(inner) => inner.amount, + Self::WithMemos(inner) => inner.amount, + } + } +} + condition!(Remark, 1, {}); condition!(AggSigParent, 43, { public_key: PublicKey, message: Bytes }); condition!(AggSigPuzzle, 44, { public_key: PublicKey, message: Bytes }); diff --git a/src/lib.rs b/src/lib.rs index 042603c6..f960591a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,8 +33,14 @@ pub fn trim_leading_zeros(mut slice: &[u8]) -> &[u8] { } /// Converts a `usize` to an atom in CLVM format, with leading zeros trimmed. -pub fn usize_to_bytes(amount: usize) -> Vec { - let bytes: Vec = amount.to_be_bytes().into(); +pub fn usize_to_bytes(num: usize) -> Vec { + let bytes: Vec = num.to_be_bytes().into(); + trim_leading_zeros(bytes.as_slice()).to_vec() +} + +/// Converts a `u64` to an atom in CLVM format, with leading zeros trimmed. +pub fn u64_to_bytes(num: u64) -> Vec { + let bytes: Vec = num.to_be_bytes().into(); trim_leading_zeros(bytes.as_slice()).to_vec() } diff --git a/src/spends/puzzles/cat.rs b/src/spends/puzzles/cat.rs index 81770d4b..0b9cb6ff 100644 --- a/src/spends/puzzles/cat.rs +++ b/src/spends/puzzles/cat.rs @@ -1,540 +1,5 @@ -use chia_bls::PublicKey; -use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::{ - cat::{ - CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, CAT_PUZZLE_HASH, - EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, - }, - LineageProof, -}; -use clvm_traits::{clvm_quote, destructure_tuple, match_tuple, MatchByte, ToClvm, ToNodePtr}; -use clvm_utils::{tree_hash, CurriedProgram}; -use clvmr::{serde::node_from_bytes, Allocator, NodePtr}; +mod cat_spend; +mod issue_cat; -use crate::{ChainedSpend, CreateCoinWithMemos, InnerSpend, RunTail, SpendContext, SpendError}; - -struct CatSpendItem { - coin: Coin, - inner_spend: InnerSpend, - lineage_proof: LineageProof, - extra_delta: i64, -} - -pub struct CatSpend { - asset_id: Bytes32, - cat_spends: Vec, -} - -impl CatSpend { - pub 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 mut coin_spends = Vec::new(); - let mut total_delta = 0; - - let len = self.cat_spends.len(); - - 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::, NodePtr, u64, NodePtr)>(ptr) - .ok() - }); - - let delta = create_coins.fold( - coin.amount as i64 - *extra_delta, - |delta, destructure_tuple!(_, _, amount, _)| delta - amount as i64, - ); - - 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(), - tail_program_hash: self.asset_id, - inner_puzzle: inner_spend.puzzle(), - }, - })?; - - let solution = ctx.serialize(CatSolution { - inner_puzzle_solution: inner_spend.solution(), - lineage_proof: Some(lineage_proof.clone()), - prev_coin_id: prev_cat.coin.coin_id(), - this_coin_info: coin.clone(), - next_coin_proof: CoinProof { - parent_coin_info: next_cat.coin.parent_coin_info, - inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()), - amount: next_cat.coin.amount, - }, - prev_subtotal, - extra_delta: *extra_delta, - })?; - - coin_spends.push(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); - } - - Ok(coin_spends) - } -} - -pub struct IssueCat { - parent_coin_id: Bytes32, - conditions: Vec, -} - -pub struct IssuanceInfo { - pub asset_id: Bytes32, - pub eve_coin: Coin, - pub eve_inner_puzzle_hash: Bytes32, -} - -impl IssueCat { - pub fn new(parent_coin_id: Bytes32) -> Self { - Self { - parent_coin_id, - conditions: Vec::new(), - } - } - - pub fn condition(mut self, condition: NodePtr) -> Self { - self.conditions.push(condition); - self - } - - pub fn conditions(mut self, conditions: impl IntoIterator) -> Self - where - Self: Sized, - { - for condition in conditions { - self = self.condition(condition); - } - self - } - - pub fn multi_issuance( - self, - ctx: &mut SpendContext, - public_key: PublicKey, - amount: u64, - ) -> Result<(ChainedSpend, IssuanceInfo), 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); - - create_and_spend_eve_cat( - ctx, - self.parent_coin_id, - asset_id, - amount, - ( - RunTail { - program: tail, - solution: NodePtr::NIL, - }, - self.conditions, - ), - ) - } -} - -pub fn multi_issuance_asset_id(public_key: PublicKey) -> Bytes32 { - let a = &mut Allocator::new(); - let tail_mod = node_from_bytes(a, &EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE).unwrap(); - - let ptr = CurriedProgram { - program: tail_mod, - args: EverythingWithSignatureTailArgs { public_key }, - } - .to_node_ptr(a) - .unwrap(); - - Bytes32::new(tree_hash(a, ptr)) -} - -/// Creates an eve CAT coin and spends it. -pub fn create_and_spend_eve_cat( - ctx: &mut SpendContext, - parent_coin_id: Bytes32, - asset_id: Bytes32, - amount: u64, - conditions: T, -) -> Result<(ChainedSpend, IssuanceInfo), SpendError> -where - T: ToClvm, -{ - let cat_puzzle_ptr = ctx.cat_puzzle(); - - let inner_puzzle = ctx.alloc(clvm_quote!(conditions))?; - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); - - let puzzle = ctx.alloc(CurriedProgram { - program: cat_puzzle_ptr, - args: CatArgs { - mod_hash: CAT_PUZZLE_HASH.into(), - tail_program_hash: asset_id, - inner_puzzle, - }, - })?; - - let puzzle_hash = ctx.tree_hash(puzzle); - let coin = Coin::new(parent_coin_id, puzzle_hash, amount); - - let solution = ctx.serialize(CatSolution { - inner_puzzle_solution: (), - lineage_proof: None, - prev_coin_id: coin.coin_id(), - this_coin_info: coin.clone(), - 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)?; - let coin_spend = CoinSpend::new(coin.clone(), puzzle_reveal, solution); - - let chained_spend = ChainedSpend { - coin_spends: vec![coin_spend], - parent_conditions: vec![ctx.alloc(CreateCoinWithMemos { - puzzle_hash, - amount, - memos: vec![puzzle_hash.to_vec().into()], - })?], - }; - - let issuance_info = IssuanceInfo { - asset_id, - eve_coin: coin, - eve_inner_puzzle_hash: inner_puzzle_hash, - }; - - Ok((chained_spend, issuance_info)) -} - -#[cfg(test)] -mod tests { - use chia_bls::{derive_keys::master_to_wallet_unhardened, sign, Signature}; - use chia_consensus::gen::{ - conditions::EmptyVisitor, run_block_generator::run_block_generator, - solution_generator::solution_generator, - }; - use chia_protocol::{Bytes32, Program, SpendBundle}; - use chia_wallet::{ - cat::cat_puzzle_hash, - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, - DeriveSynthetic, - }; - use clvmr::{serde::node_to_bytes, Allocator}; - use hex_literal::hex; - - use crate::{ - testing::SECRET_KEY, BaseSpend, CreateCoinWithMemos, CreateCoinWithoutMemos, - RequiredSignature, StandardSpend, WalletSimulator, - }; - - use super::*; - - #[tokio::test] - async fn test_cat_issuance() -> anyhow::Result<()> { - let sim = WalletSimulator::new().await; - let peer = sim.peer().await; - - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); - let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); - let xch_coin = sim.generate_coin(puzzle_hash, 1).await.coin; - - let (issue_cat, _cat_info) = IssueCat::new(xch_coin.coin_id()) - .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash, - amount: 1, - memos: vec![puzzle_hash.to_vec().into()], - })?) - .multi_issuance(&mut ctx, pk.clone(), 1)?; - - let coin_spends = StandardSpend::default() - .chain(issue_cat) - .finish(&mut ctx, xch_coin, pk)?; - - let required_signatures = RequiredSignature::from_coin_spends( - &mut allocator, - &coin_spends, - WalletSimulator::AGG_SIG_ME.into(), - )?; - - let mut aggregated_signature = Signature::default(); - - for required in required_signatures { - aggregated_signature += &sign(&sk, required.final_message()); - } - - let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); - - let ack = peer.send_transaction(spend_bundle).await?; - assert_eq!(ack.error, None); - assert_eq!(ack.status, 1); - - Ok(()) - } - - #[test] - fn test_cat_spend() -> anyhow::Result<()> { - let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); - - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let asset_id = Bytes32::new([42; 32]); - - let p2_puzzle_hash = Bytes32::new(standard_puzzle_hash(&synthetic_key)); - let cat_puzzle_hash = cat_puzzle_hash(asset_id.to_bytes(), p2_puzzle_hash.to_bytes()); - - let parent_coin = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin = Coin::new( - Bytes32::from(parent_coin.coin_id()), - Bytes32::new(cat_puzzle_hash), - 42, - ); - - let (inner_spend, _) = StandardSpend::default() - .condition(ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash: coin.puzzle_hash, - amount: coin.amount, - })?) - .inner_spend(&mut ctx, synthetic_key)?; - - let lineage_proof = LineageProof { - parent_coin_info: parent_coin.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin.amount, - }; - - let coin_spend = CatSpend::new(asset_id) - .spend(coin, inner_spend, lineage_proof, 0) - .finish(&mut ctx)? - .remove(0); - - let output_ptr = coin_spend - .puzzle_reveal - .run(&mut allocator, 0, u64::MAX, &coin_spend.solution)? - .1; - let actual = node_to_bytes(&allocator, output_ptr)?; - - let expected = hex!( - " - ffff46ffa06438c882c2db9f5c2a8b4cbda9258c40a6583b2d7c6becc1678607 - 4d558c834980ffff3cffa1cb9c4d253a0e1a091d620a55616e104f3329f58ee8 - 6e708d0527b1cc58a73b649e80ffff3dffa0c3bb7f0a7e1bd2cae332bbd0d1a7 - e275c1e6c643b2659e22c24f513886d3874e80ffff32ffb08584adae5630842a - 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc - c9183fe61e48d8bfffa0e5924c23faf33c9a1bf18c70d40cb09e4b194f521b9f - 6fceb2685c0612ac34a980ffff33ffa0f9f2d59294f2aae8f9833db876d1bf43 - 95d46af18c17312041c6f4a4d73fa041ff2a8080 - " - ); - assert_eq!(hex::encode(actual), hex::encode(expected)); - - Ok(()) - } - - #[test] - fn test_cat_spend_multi() -> anyhow::Result<()> { - let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); - - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let asset_id = Bytes32::new([42; 32]); - - let p2_puzzle_hash = Bytes32::new(standard_puzzle_hash(&synthetic_key)); - let cat_puzzle_hash = cat_puzzle_hash(asset_id.to_bytes(), p2_puzzle_hash.to_bytes()); - - let parent_coin_1 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin_1 = Coin::new( - Bytes32::from(parent_coin_1.coin_id()), - Bytes32::new(cat_puzzle_hash), - 42, - ); - - let parent_coin_2 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin_2 = Coin::new( - Bytes32::from(parent_coin_2.coin_id()), - Bytes32::new(cat_puzzle_hash), - 34, - ); - - let parent_coin_3 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin_3 = Coin::new( - Bytes32::from(parent_coin_3.coin_id()), - Bytes32::new(cat_puzzle_hash), - 69, - ); - - let lineage_1 = LineageProof { - parent_coin_info: parent_coin_1.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_1.amount, - }; - - let lineage_2 = LineageProof { - parent_coin_info: parent_coin_2.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_2.amount, - }; - - let lineage_3 = LineageProof { - parent_coin_info: parent_coin_3.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_3.amount, - }; - - let (inner_spend, _) = StandardSpend::default() - .condition(ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash: coin_1.puzzle_hash, - amount: coin_1.amount + coin_2.amount + coin_3.amount, - })?) - .inner_spend(&mut ctx, synthetic_key.clone())?; - - let (empty_spend, _) = StandardSpend::default().inner_spend(&mut ctx, synthetic_key)?; - - let coin_spends = CatSpend::new(asset_id) - .spend(coin_1, inner_spend, lineage_1, 0) - .spend(coin_2, empty_spend, lineage_2, 0) - .spend(coin_3, empty_spend, lineage_3, 0) - .finish(&mut ctx)?; - - let spend_vec = coin_spends - .clone() - .into_iter() - .map(|coin_spend| { - ( - coin_spend.coin, - coin_spend.puzzle_reveal, - coin_spend.solution, - ) - }) - .collect::>(); - let gen = solution_generator(spend_vec).unwrap(); - let block = - run_block_generator::(&mut allocator, &gen, &[], u64::MAX, 0) - .unwrap(); - - assert_eq!(block.cost, 101289468); - - assert_eq!(coin_spends.len(), 3); - - let output_ptr_1 = coin_spends[0] - .puzzle_reveal - .run(&mut allocator, 0, u64::MAX, &coin_spends[0].solution) - .unwrap() - .1; - let actual = node_to_bytes(&allocator, output_ptr_1).unwrap(); - - let expected = hex!( - " - ffff46ffa06438c882c2db9f5c2a8b4cbda9258c40a6583b2d7c6becc1678607 - 4d558c834980ffff3cffa1cb1cb6597fe61e67a6cbbcd4e8f0bda5e9fc56cd84 - c9e9502772b410dc8a03207680ffff3dffa0742ddb368882193072ea013bde24 - 4a5c9d40ab4454c09666e84777a79307e17a80ffff32ffb08584adae5630842a - 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc - c9183fe61e48d8bfffa004c476adfcffeacfef7c979bdd03b4641f1870d3f81b - 20636eefbcf879bb64ec80ffff33ffa0f9f2d59294f2aae8f9833db876d1bf43 - 95d46af18c17312041c6f4a4d73fa041ff8200918080 - " - ); - assert_eq!(hex::encode(actual), hex::encode(expected)); - - let output_ptr_2 = coin_spends[1] - .puzzle_reveal - .run(&mut allocator, 0, u64::MAX, &coin_spends[1].solution) - .unwrap() - .1; - let actual = node_to_bytes(&allocator, output_ptr_2).unwrap(); - - let expected = hex!( - " - ffff46ffa0ae60b8db0664959078a1c6e51ca6a8fc55207c63a8ac74d026f1d9 - 15c406bac480ffff3cffa1cb9a41843ab318a8336f61a6bf9e8b0b1d555b9f07 - cd19582e0bc52a961c65dc9e80ffff3dffa0294cda8d35164e01c4e3b7c07c36 - a5bb2f38a23e93ef49c882ee74349a0df8bd80ffff32ffb08584adae5630842a - 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc - c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 - 3b490ab1215dd33d8d468080 - " - ); - assert_eq!(hex::encode(actual), hex::encode(expected)); - - let output_ptr_3 = coin_spends[2] - .puzzle_reveal - .run(&mut allocator, 0, u64::MAX, &coin_spends[2].solution) - .unwrap() - .1; - let actual = node_to_bytes(&allocator, output_ptr_3).unwrap(); - - let expected = hex!( - " - ffff46ffa0f8eacbef2bad0c7b27b638a90a37244e75013e977f250230856d05 - a2784e1d0980ffff3cffa1cb17c47c5fa8d795efa0d9227d2066cde36dd4e845 - 7e8f4e507d2015a1c7f3d94b80ffff3dffa0629abc502829339c7880ee003c4e - 68a8181d71206e50e7b36c29301ef60128f580ffff32ffb08584adae5630842a - 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc - c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 - 3b490ab1215dd33d8d468080 - " - ); - assert_eq!(hex::encode(actual), hex::encode(expected)); - - Ok(()) - } -} +pub use cat_spend::*; +pub use issue_cat::*; diff --git a/src/spends/puzzles/cat/cat_spend.rs b/src/spends/puzzles/cat/cat_spend.rs new file mode 100644 index 00000000..afd1e3b9 --- /dev/null +++ b/src/spends/puzzles/cat/cat_spend.rs @@ -0,0 +1,345 @@ +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_wallet::{ + cat::{CatArgs, CatSolution, CoinProof, CAT_PUZZLE_HASH}, + LineageProof, +}; +use clvm_utils::CurriedProgram; +use clvmr::NodePtr; + +use crate::{CreateCoin, InnerSpend, SpendContext, SpendError}; + +#[derive(Default)] +pub struct CatSpend { + asset_id: Bytes32, + cat_spends: Vec, +} + +struct CatSpendItem { + coin: Coin, + inner_spend: InnerSpend, + lineage_proof: LineageProof, + extra_delta: i64, +} + +impl CatSpend { + pub 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 mut coin_spends = Vec::new(); + let mut total_delta = 0; + + let len = self.cat_spends.len(); + + 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(coin.amount as i64 - *extra_delta, |delta, create_coin| { + delta - create_coin.amount() as i64 + }); + + 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(), + tail_program_hash: self.asset_id, + inner_puzzle: inner_spend.puzzle(), + }, + })?; + + let solution = ctx.serialize(CatSolution { + inner_puzzle_solution: inner_spend.solution(), + lineage_proof: Some(lineage_proof.clone()), + prev_coin_id: prev_cat.coin.coin_id(), + this_coin_info: coin.clone(), + next_coin_proof: CoinProof { + parent_coin_info: next_cat.coin.parent_coin_info, + inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()), + amount: next_cat.coin.amount, + }, + prev_subtotal, + extra_delta: *extra_delta, + })?; + + coin_spends.push(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); + } + + Ok(coin_spends) + } +} + +#[cfg(test)] +mod tests { + use chia_bls::derive_keys::master_to_wallet_unhardened; + use chia_consensus::gen::{ + conditions::EmptyVisitor, run_block_generator::run_block_generator, + solution_generator::solution_generator, + }; + use chia_protocol::{Bytes32, Program}; + use chia_wallet::{ + cat::cat_puzzle_hash, + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, + }; + use clvmr::{serde::node_to_bytes, Allocator}; + use hex_literal::hex; + + use crate::{testing::SECRET_KEY, CreateCoinWithoutMemos, StandardSpend}; + + use super::*; + + #[test] + fn test_cat_spend() -> anyhow::Result<()> { + let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) + .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let asset_id = Bytes32::new([42; 32]); + + let p2_puzzle_hash = Bytes32::new(standard_puzzle_hash(&synthetic_key)); + let cat_puzzle_hash = cat_puzzle_hash(asset_id.to_bytes(), p2_puzzle_hash.to_bytes()); + + let parent_coin = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin = Coin::new( + Bytes32::from(parent_coin.coin_id()), + Bytes32::new(cat_puzzle_hash), + 42, + ); + + let (inner_spend, _) = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: coin.puzzle_hash, + amount: coin.amount, + })?) + .inner_spend(&mut ctx, synthetic_key)?; + + let lineage_proof = LineageProof { + parent_coin_info: parent_coin.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin.amount, + }; + + let coin_spend = CatSpend::new(asset_id) + .spend(coin, inner_spend, lineage_proof, 0) + .finish(&mut ctx)? + .remove(0); + + let output_ptr = coin_spend + .puzzle_reveal + .run(&mut allocator, 0, u64::MAX, &coin_spend.solution)? + .1; + let actual = node_to_bytes(&allocator, output_ptr)?; + + let expected = hex!( + " + ffff46ffa06438c882c2db9f5c2a8b4cbda9258c40a6583b2d7c6becc1678607 + 4d558c834980ffff3cffa1cb9c4d253a0e1a091d620a55616e104f3329f58ee8 + 6e708d0527b1cc58a73b649e80ffff3dffa0c3bb7f0a7e1bd2cae332bbd0d1a7 + e275c1e6c643b2659e22c24f513886d3874e80ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa0e5924c23faf33c9a1bf18c70d40cb09e4b194f521b9f + 6fceb2685c0612ac34a980ffff33ffa0f9f2d59294f2aae8f9833db876d1bf43 + 95d46af18c17312041c6f4a4d73fa041ff2a8080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + Ok(()) + } + + #[test] + fn test_cat_spend_multi() -> anyhow::Result<()> { + let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) + .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let asset_id = Bytes32::new([42; 32]); + + let p2_puzzle_hash = Bytes32::new(standard_puzzle_hash(&synthetic_key)); + let cat_puzzle_hash = cat_puzzle_hash(asset_id.to_bytes(), p2_puzzle_hash.to_bytes()); + + let parent_coin_1 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_1 = Coin::new( + Bytes32::from(parent_coin_1.coin_id()), + Bytes32::new(cat_puzzle_hash), + 42, + ); + + let parent_coin_2 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_2 = Coin::new( + Bytes32::from(parent_coin_2.coin_id()), + Bytes32::new(cat_puzzle_hash), + 34, + ); + + let parent_coin_3 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); + let coin_3 = Coin::new( + Bytes32::from(parent_coin_3.coin_id()), + Bytes32::new(cat_puzzle_hash), + 69, + ); + + let lineage_1 = LineageProof { + parent_coin_info: parent_coin_1.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin_1.amount, + }; + + let lineage_2 = LineageProof { + parent_coin_info: parent_coin_2.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin_2.amount, + }; + + let lineage_3 = LineageProof { + parent_coin_info: parent_coin_3.parent_coin_info, + inner_puzzle_hash: p2_puzzle_hash, + amount: parent_coin_3.amount, + }; + + let (inner_spend, _) = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: coin_1.puzzle_hash, + amount: coin_1.amount + coin_2.amount + coin_3.amount, + })?) + .inner_spend(&mut ctx, synthetic_key.clone())?; + + let (empty_spend, _) = StandardSpend::new().inner_spend(&mut ctx, synthetic_key)?; + + let coin_spends = CatSpend::new(asset_id) + .spend(coin_1, inner_spend, lineage_1, 0) + .spend(coin_2, empty_spend, lineage_2, 0) + .spend(coin_3, empty_spend, lineage_3, 0) + .finish(&mut ctx)?; + + let spend_vec = coin_spends + .clone() + .into_iter() + .map(|coin_spend| { + ( + coin_spend.coin, + coin_spend.puzzle_reveal, + coin_spend.solution, + ) + }) + .collect::>(); + let gen = solution_generator(spend_vec).unwrap(); + let block = + run_block_generator::(&mut allocator, &gen, &[], u64::MAX, 0) + .unwrap(); + + assert_eq!(block.cost, 101289468); + + assert_eq!(coin_spends.len(), 3); + + let output_ptr_1 = coin_spends[0] + .puzzle_reveal + .run(&mut allocator, 0, u64::MAX, &coin_spends[0].solution) + .unwrap() + .1; + let actual = node_to_bytes(&allocator, output_ptr_1).unwrap(); + + let expected = hex!( + " + ffff46ffa06438c882c2db9f5c2a8b4cbda9258c40a6583b2d7c6becc1678607 + 4d558c834980ffff3cffa1cb1cb6597fe61e67a6cbbcd4e8f0bda5e9fc56cd84 + c9e9502772b410dc8a03207680ffff3dffa0742ddb368882193072ea013bde24 + 4a5c9d40ab4454c09666e84777a79307e17a80ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa004c476adfcffeacfef7c979bdd03b4641f1870d3f81b + 20636eefbcf879bb64ec80ffff33ffa0f9f2d59294f2aae8f9833db876d1bf43 + 95d46af18c17312041c6f4a4d73fa041ff8200918080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + let output_ptr_2 = coin_spends[1] + .puzzle_reveal + .run(&mut allocator, 0, u64::MAX, &coin_spends[1].solution) + .unwrap() + .1; + let actual = node_to_bytes(&allocator, output_ptr_2).unwrap(); + + let expected = hex!( + " + ffff46ffa0ae60b8db0664959078a1c6e51ca6a8fc55207c63a8ac74d026f1d9 + 15c406bac480ffff3cffa1cb9a41843ab318a8336f61a6bf9e8b0b1d555b9f07 + cd19582e0bc52a961c65dc9e80ffff3dffa0294cda8d35164e01c4e3b7c07c36 + a5bb2f38a23e93ef49c882ee74349a0df8bd80ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 + 3b490ab1215dd33d8d468080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + let output_ptr_3 = coin_spends[2] + .puzzle_reveal + .run(&mut allocator, 0, u64::MAX, &coin_spends[2].solution) + .unwrap() + .1; + let actual = node_to_bytes(&allocator, output_ptr_3).unwrap(); + + let expected = hex!( + " + ffff46ffa0f8eacbef2bad0c7b27b638a90a37244e75013e977f250230856d05 + a2784e1d0980ffff3cffa1cb17c47c5fa8d795efa0d9227d2066cde36dd4e845 + 7e8f4e507d2015a1c7f3d94b80ffff3dffa0629abc502829339c7880ee003c4e + 68a8181d71206e50e7b36c29301ef60128f580ffff32ffb08584adae5630842a + 1766bc444d2b872dd3080f4e5daaecf6f762a4be7dc148f37868149d4217f3dc + c9183fe61e48d8bfffa0ba4484b961b7a2369d948d06c55b64bdbfaffb326bc1 + 3b490ab1215dd33d8d468080 + " + ); + assert_eq!(hex::encode(actual), hex::encode(expected)); + + Ok(()) + } +} diff --git a/src/spends/puzzles/cat/issue_cat.rs b/src/spends/puzzles/cat/issue_cat.rs new file mode 100644 index 00000000..234f7f20 --- /dev/null +++ b/src/spends/puzzles/cat/issue_cat.rs @@ -0,0 +1,187 @@ +use chia_bls::PublicKey; +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_wallet::cat::{ + CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, CAT_PUZZLE_HASH, +}; +use clvm_traits::clvm_quote; +use clvm_utils::{curry_tree_hash, tree_hash_atom, CurriedProgram}; +use clvmr::NodePtr; +use hex_literal::hex; + +use crate::{ChainedSpend, CreateCoinWithMemos, RunTail, SpendContext, SpendError}; + +pub struct IssueCat { + parent_coin_id: Bytes32, + conditions: Vec, +} + +pub struct CatIssuanceInfo { + pub asset_id: Bytes32, + pub eve_coin: Coin, + pub eve_inner_puzzle_hash: Bytes32, +} + +impl IssueCat { + pub fn new(parent_coin_id: Bytes32) -> Self { + Self { + parent_coin_id, + conditions: Vec::new(), + } + } + + pub fn condition(mut self, condition: NodePtr) -> Self { + self.conditions.push(condition); + self + } + + pub fn multi_issuance( + self, + ctx: &mut SpendContext, + public_key: PublicKey, + amount: u64, + ) -> Result<(ChainedSpend, 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); + + self.condition(ctx.alloc(RunTail { + program: tail, + solution: NodePtr::NIL, + })?) + .finish(ctx, asset_id, amount) + } + + pub fn finish( + self, + ctx: &mut SpendContext, + asset_id: Bytes32, + amount: u64, + ) -> Result<(ChainedSpend, 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); + + let puzzle = ctx.alloc(CurriedProgram { + program: cat_puzzle_ptr, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: asset_id, + inner_puzzle, + }, + })?; + + let puzzle_hash = ctx.tree_hash(puzzle); + let coin = Coin::new(self.parent_coin_id, puzzle_hash, amount); + + let solution = ctx.serialize(CatSolution { + inner_puzzle_solution: (), + lineage_proof: None, + prev_coin_id: coin.coin_id(), + this_coin_info: coin.clone(), + 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)?; + let coin_spend = CoinSpend::new(coin.clone(), puzzle_reveal, solution); + + let chained_spend = ChainedSpend { + coin_spends: vec![coin_spend], + parent_conditions: vec![ctx.alloc(CreateCoinWithMemos { + puzzle_hash, + amount, + memos: vec![puzzle_hash.to_vec().into()], + })?], + }; + + let issuance_info = CatIssuanceInfo { + asset_id, + eve_coin: coin, + eve_inner_puzzle_hash: inner_puzzle_hash, + }; + + Ok((chained_spend, issuance_info)) + } +} + +pub fn multi_issuance_asset_id(public_key: &PublicKey) -> Bytes32 { + let public_key_hash = tree_hash_atom(&public_key.to_bytes()); + curry_tree_hash( + hex!("1720d13250a7c16988eaf530331cefa9dd57a76b2c82236bec8bbbff91499b89"), + &[public_key_hash], + ) + .into() +} + +#[cfg(test)] +mod tests { + use chia_bls::{sign, Signature}; + use chia_protocol::SpendBundle; + use chia_wallet::{ + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, + }; + use clvmr::Allocator; + + use crate::{ + testing::SECRET_KEY, CreateCoinWithMemos, RequiredSignature, StandardSpend, WalletSimulator, + }; + + use super::*; + + #[tokio::test] + async fn test_cat_issuance() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + let xch_coin = sim.generate_coin(puzzle_hash, 1).await.coin; + + let (issue_cat, _cat_info) = IssueCat::new(xch_coin.coin_id()) + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash, + amount: 1, + memos: vec![puzzle_hash.to_vec().into()], + })?) + .multi_issuance(&mut ctx, pk.clone(), 1)?; + + let coin_spends = StandardSpend::new() + .chain(issue_cat) + .finish(&mut ctx, xch_coin, pk)?; + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?; + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); + + let ack = peer.send_transaction(spend_bundle).await?; + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + Ok(()) + } +} diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index e0b54fe9..be5f579f 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -1,173 +1,10 @@ -use chia_bls::PublicKey; -use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend, Program}; -use chia_wallet::{ - did::{DidArgs, DidSolution}, - singleton::{ - LauncherSolution, SingletonArgs, SingletonSolution, SingletonStruct, - SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, - }, - standard::StandardArgs, - EveProof, Proof, -}; -use clvm_traits::{clvm_list, ToClvm}; -use clvm_utils::CurriedProgram; -use clvmr::NodePtr; -use sha2::{digest::FixedOutput, Digest, Sha256}; - -use crate::{ - create_launcher, standard_solution, AssertCoinAnnouncement, CreateCoinWithMemos, SpendContext, - SpendError, -}; - -/// The output of a DID mint. -pub struct DidCreation { - /// The conditions that must be output from the parent to make this DID creation valid. - pub parent_conditions: Vec, - /// The coin spends required to fulfill the DID creation. - pub coin_spends: Vec, - /// The launcher id of the newly created DID. - pub did_id: Bytes32, - /// The inner puzzle hash of the DID. - pub did_inner_puzzle_hash: Bytes32, - /// The DID coin. - pub coin: Coin, - /// The DID puzzle reveal. - pub puzzle_reveal: Program, -} - -/// Creates a new DID singleton. -pub fn create_did( - ctx: &mut SpendContext, - parent_coin_id: Bytes32, - synthetic_key: PublicKey, - owner_puzzle_hash: Bytes32, -) -> Result { - let standard_puzzle = ctx.standard_puzzle(); - let launcher_puzzle = ctx.singleton_launcher(); - let singleton_puzzle = ctx.singleton_top_layer(); - let did_puzzle = ctx.did_inner_puzzle(); - - let mut coin_spends = Vec::new(); - - let launcher = create_launcher(ctx, parent_coin_id)?; - let launcher_id = launcher.coin.coin_id(); - let mut parent_conditions = launcher.parent_conditions; - - let singleton_struct = SingletonStruct { - mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), - launcher_id, - launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - }; +mod create_did; +mod did_info; +mod did_spend; - let p2 = CurriedProgram { - program: standard_puzzle, - args: StandardArgs { synthetic_key }, - }; - - let did = ctx.alloc(CurriedProgram { - program: did_puzzle, - args: DidArgs { - inner_puzzle: p2, - recovery_did_list_hash: ctx.tree_hash(NodePtr::NIL), - num_verifications_required: 1, - singleton_struct: singleton_struct.clone(), - metadata: (), - }, - })?; - - let did_inner_puzzle_hash = ctx.tree_hash(did); - - let singleton = ctx.alloc(CurriedProgram { - program: singleton_puzzle, - args: SingletonArgs { - singleton_struct, - inner_puzzle: did, - }, - })?; - - let eve_puzzle_hash = ctx.tree_hash(singleton); - - let eve_message = ctx.alloc(clvm_list!(eve_puzzle_hash, 1, ()))?; - let eve_message_hash = ctx.tree_hash(eve_message); - - let mut announcement_id = Sha256::new(); - announcement_id.update(launcher_id); - announcement_id.update(eve_message_hash); - - parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize_fixed().into()), - })?); - - // Spend the launcher coin. - let launcher_puzzle_reveal = ctx.serialize(launcher_puzzle)?; - let launcher_solution = ctx.serialize(LauncherSolution { - singleton_puzzle_hash: eve_puzzle_hash, - amount: 1, - key_value_list: (), - })?; - - coin_spends.push(CoinSpend::new( - launcher.coin, - launcher_puzzle_reveal, - launcher_solution, - )); - - // Spend the eve coin. - let eve_coin = Coin::new(launcher_id, eve_puzzle_hash, 1); - - let eve_proof = Proof::Eve(EveProof { - parent_coin_info: parent_coin_id, - amount: 1, - }); - - let eve_puzzle_reveal = ctx.serialize(singleton)?; - - let eve_coin_spend = spend_did( - ctx, - eve_coin.clone(), - eve_puzzle_reveal.clone(), - eve_proof, - clvm_list!(CreateCoinWithMemos { - puzzle_hash: did_inner_puzzle_hash, - amount: 1, - memos: vec![Bytes::new(owner_puzzle_hash.to_vec())], - },), - )?; - - coin_spends.push(eve_coin_spend); - - Ok(DidCreation { - parent_conditions, - coin_spends, - did_id: launcher_id, - did_inner_puzzle_hash, - coin: Coin::new(eve_coin.coin_id(), eve_puzzle_hash, 1), - puzzle_reveal: eve_puzzle_reveal, - }) -} - -/// Spend a standard DID coin (a DID singleton with the standard transaction inner puzzle). -pub fn spend_did( - ctx: &mut SpendContext, - coin: Coin, - puzzle_reveal: Program, - proof: Proof, - conditions: T, -) -> Result -where - T: ToClvm, -{ - let p2_solution = standard_solution(conditions); - let did_solution = DidSolution::InnerSpend(p2_solution); - - let solution = ctx.serialize(SingletonSolution { - proof, - amount: coin.amount, - inner_solution: did_solution, - })?; - - Ok(CoinSpend::new(coin, puzzle_reveal, solution)) -} +pub use create_did::*; +pub use did_info::*; +pub use did_spend::*; #[cfg(test)] mod tests { @@ -177,41 +14,53 @@ mod tests { standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, DeriveSynthetic, }; - use clvmr::Allocator; + use clvmr::{Allocator, NodePtr}; - use crate::{testing::SECRET_KEY, RequiredSignature, WalletSimulator}; + use crate::{ + testing::SECRET_KEY, CreateCoinWithMemos, LaunchSingleton, RequiredSignature, SpendContext, + StandardSpend, WalletSimulator, + }; use super::*; #[tokio::test] - async fn test_create_did() { + async fn test_create_did() -> anyhow::Result<()> { let sim = WalletSimulator::new().await; let peer = sim.peer().await; let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk); + let puzzle_hash = standard_puzzle_hash(&pk).into(); - let parent = sim.generate_coin(puzzle_hash.into(), 1).await; + let parent = sim.generate_coin(puzzle_hash, 1).await.coin; let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let did_creation = create_did( - &mut ctx, - parent.coin.coin_id(), - pk.clone(), - puzzle_hash.into(), + let recovery_did_list_hash = ctx.tree_hash(NodePtr::NIL); + let (launch_singleton, eve_inner_puzzle_hash, eve_did_info) = LaunchSingleton::new( + parent.coin_id(), + 1, ) - .unwrap(); - - let mut coin_spends = did_creation.coin_spends; - - coin_spends.push( - spend_standard_coin(&mut ctx, parent.coin, pk, did_creation.parent_conditions).unwrap(), + .launch_did(&mut ctx, puzzle_hash, recovery_did_list_hash, 1, ())?; + + let (inner_spend, _) = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash: eve_inner_puzzle_hash, + amount: eve_did_info.coin.amount, + memos: vec![puzzle_hash.to_vec().into()], + })?) + .inner_spend(&mut ctx, pk.clone())?; + + let mut coin_spends = vec![spend_did(&mut ctx, eve_did_info, inner_spend)?]; + + coin_spends.extend( + StandardSpend::new() + .chain(launch_singleton) + .finish(&mut ctx, parent, pk)?, ); - let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); + let mut spend_bundle = dbg!(SpendBundle::new(coin_spends, Signature::default())); let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, @@ -230,9 +79,11 @@ mod tests { // Make sure the DID was created. let found_coins = peer - .register_for_ph_updates(vec![puzzle_hash.into()], 0) + .register_for_ph_updates(vec![puzzle_hash], 0) .await .unwrap(); assert_eq!(found_coins.len(), 2); + + Ok(()) } } diff --git a/src/spends/puzzles/did/create_did.rs b/src/spends/puzzles/did/create_did.rs new file mode 100644 index 00000000..2b7fce37 --- /dev/null +++ b/src/spends/puzzles/did/create_did.rs @@ -0,0 +1,158 @@ +use chia_protocol::Bytes32; +use chia_wallet::{ + did::DID_INNER_PUZZLE_HASH, + singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, + EveProof, Proof, +}; +use clvm_traits::ToClvm; +use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; +use clvmr::NodePtr; + +use crate::{u64_to_bytes, ChainedSpend, DidInfo, LaunchSingleton, SpendContext, SpendError}; + +pub trait CreateDid { + fn launch_did( + self, + ctx: &mut SpendContext, + inner_puzzle_hash: Bytes32, + recovery_did_list_hash: Bytes32, + num_verifications_required: u64, + metadata: T, + ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> + where + T: ToClvm; +} + +impl CreateDid for LaunchSingleton { + fn launch_did( + self, + ctx: &mut SpendContext, + inner_puzzle_hash: Bytes32, + recovery_did_list_hash: Bytes32, + num_verifications_required: u64, + metadata: T, + ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> + where + T: ToClvm, + { + let metadata_ptr = ctx.alloc(&metadata)?; + let metadata_hash = ctx.tree_hash(metadata_ptr); + + let did_inner_puzzle_hash = did_inner_puzzle_hash( + inner_puzzle_hash, + recovery_did_list_hash, + num_verifications_required, + self.coin().coin_id(), + metadata_hash, + ); + + let launcher_coin = self.coin().clone(); + let (chained_spend, eve_coin) = self.finish(ctx, did_inner_puzzle_hash, ())?; + + let proof = Proof::Eve(EveProof { + parent_coin_info: launcher_coin.parent_coin_info, + amount: launcher_coin.amount, + }); + + let did_info = DidInfo { + launcher_id: launcher_coin.coin_id(), + coin: eve_coin, + proof, + recovery_did_list_hash, + num_verifications_required, + metadata, + }; + + Ok((chained_spend, did_inner_puzzle_hash, did_info)) + } +} + +pub fn did_inner_puzzle_hash( + inner_puzzle_hash: Bytes32, + recovery_did_list_hash: Bytes32, + num_verifications_required: u64, + launcher_id: Bytes32, + metadata_hash: Bytes32, +) -> Bytes32 { + let recovery_hash = tree_hash_atom(&recovery_did_list_hash); + let num_verifications_hash = tree_hash_atom(&u64_to_bytes(num_verifications_required)); + + let singleton_hash = tree_hash_atom(&SINGLETON_TOP_LAYER_PUZZLE_HASH); + let launcher_id_hash = tree_hash_atom(&launcher_id); + let launcher_puzzle_hash = tree_hash_atom(&SINGLETON_LAUNCHER_PUZZLE_HASH); + + let pair = tree_hash_pair(launcher_id_hash, launcher_puzzle_hash); + let singleton_struct_hash = tree_hash_pair(singleton_hash, pair); + + curry_tree_hash( + DID_INNER_PUZZLE_HASH, + &[ + inner_puzzle_hash.into(), + recovery_hash, + num_verifications_hash, + singleton_struct_hash, + metadata_hash.into(), + ], + ) + .into() +} + +#[cfg(test)] +mod tests { + use chia_wallet::{ + did::DidArgs, + singleton::{ + SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, + }, + }; + use clvm_utils::CurriedProgram; + use clvmr::Allocator; + + use super::*; + + #[test] + fn test_puzzle_hash() { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); + + let metadata = ctx.alloc([4, 5, 6]).unwrap(); + let metadata_hash = ctx.tree_hash(metadata); + + let launcher_id = Bytes32::new([34; 32]); + let recovery_did_list_hash = Bytes32::new([42; 32]); + let num_verifications_required = 2; + + let did_inner_puzzle = ctx.did_inner_puzzle(); + + let puzzle = ctx + .alloc(CurriedProgram { + program: did_inner_puzzle, + args: DidArgs { + inner_puzzle, + recovery_did_list_hash, + num_verifications_required, + singleton_struct: SingletonStruct { + mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), + launcher_id, + launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + }, + metadata, + }, + }) + .unwrap(); + let allocated_puzzle_hash = ctx.tree_hash(puzzle); + + let puzzle_hash = did_inner_puzzle_hash( + inner_puzzle_hash, + recovery_did_list_hash, + num_verifications_required, + launcher_id, + metadata_hash, + ); + + assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); + } +} diff --git a/src/spends/puzzles/did/did_info.rs b/src/spends/puzzles/did/did_info.rs new file mode 100644 index 00000000..32b0eb7c --- /dev/null +++ b/src/spends/puzzles/did/did_info.rs @@ -0,0 +1,12 @@ +use chia_protocol::{Bytes32, Coin}; +use chia_wallet::Proof; + +#[derive(Debug, Clone)] +pub struct DidInfo { + pub launcher_id: Bytes32, + pub coin: Coin, + pub proof: Proof, + pub recovery_did_list_hash: Bytes32, + pub num_verifications_required: u64, + pub metadata: T, +} diff --git a/src/spends/puzzles/did/did_spend.rs b/src/spends/puzzles/did/did_spend.rs new file mode 100644 index 00000000..4bab90c4 --- /dev/null +++ b/src/spends/puzzles/did/did_spend.rs @@ -0,0 +1,48 @@ +use chia_protocol::CoinSpend; +use chia_wallet::{ + did::{DidArgs, DidSolution}, + singleton::{SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, +}; +use clvm_traits::ToClvm; +use clvm_utils::CurriedProgram; +use clvmr::NodePtr; + +use crate::{spend_singleton, DidInfo, InnerSpend, SpendContext, SpendError}; + +pub fn spend_did( + ctx: &mut SpendContext, + did_info: DidInfo, + inner_spend: InnerSpend, +) -> Result +where + T: ToClvm, +{ + let did_inner_puzzle = ctx.did_inner_puzzle(); + + let puzzle = ctx.alloc(CurriedProgram { + program: did_inner_puzzle, + args: DidArgs { + 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(), + }, + 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, + ) +} diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index 7702791c..b5a29a45 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -66,8 +66,8 @@ mod tests { use hex_literal::hex; use crate::{ - spend_cat_coins, testing::SECRET_KEY, BaseSpend, CatSpend, CreateCoinWithMemos, IssueCat, - RequiredSignature, SpendContext, StandardSpend, WalletSimulator, + testing::SECRET_KEY, CatSpend, CreateCoinWithMemos, IssueCat, RequiredSignature, + SpendContext, StandardSpend, WalletSimulator, }; fn sk1() -> SecretKey { diff --git a/src/spends/puzzles/singleton.rs b/src/spends/puzzles/singleton.rs index 4fd079eb..70cd8547 100644 --- a/src/spends/puzzles/singleton.rs +++ b/src/spends/puzzles/singleton.rs @@ -1,36 +1,5 @@ -use chia_protocol::{Bytes32, Coin}; -use chia_wallet::singleton::SINGLETON_LAUNCHER_PUZZLE_HASH; -use clvmr::NodePtr; +mod launch_singleton; +mod singleton_spend; -use crate::{CreateCoinWithoutMemos, CreatePuzzleAnnouncement, SpendContext, SpendError}; - -/// The information required to create a new singleton launcher. -pub struct Launcher { - /// The conditions that must be output from the parent to make this singleton launcher valid. - pub parent_conditions: Vec, - /// The singleton launcher coin. - pub coin: Coin, -} - -/// Creates a new singleton launcher coin. -pub fn create_launcher( - ctx: &mut SpendContext, - parent_coin_id: Bytes32, -) -> Result { - let mut parent_conditions = vec![ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - amount: 1, - })?]; - - let launcher_coin = Coin::new(parent_coin_id, SINGLETON_LAUNCHER_PUZZLE_HASH.into(), 1); - let launcher_id = launcher_coin.coin_id(); - - parent_conditions.push(ctx.alloc(CreatePuzzleAnnouncement { - message: launcher_id.to_vec().into(), - })?); - - Ok(Launcher { - parent_conditions, - coin: launcher_coin, - }) -} +pub use launch_singleton::*; +pub use singleton_spend::*; diff --git a/src/spends/puzzles/singleton/launch_singleton.rs b/src/spends/puzzles/singleton/launch_singleton.rs new file mode 100644 index 00000000..416940a3 --- /dev/null +++ b/src/spends/puzzles/singleton/launch_singleton.rs @@ -0,0 +1,145 @@ +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_wallet::singleton::{ + LauncherSolution, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, +}; +use clvm_traits::{clvm_list, ToClvm}; +use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; +use clvmr::NodePtr; +use sha2::{digest::FixedOutput, Digest, Sha256}; + +use crate::{ + AssertCoinAnnouncement, ChainedSpend, CreateCoinWithoutMemos, SpendContext, SpendError, +}; + +pub struct LaunchSingleton { + coin: Coin, +} + +impl LaunchSingleton { + pub fn new(parent_coin_id: Bytes32, amount: u64) -> Self { + Self { + coin: Coin::new( + parent_coin_id, + SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + amount, + ), + } + } + + pub fn coin(&self) -> &Coin { + &self.coin + } + + pub fn finish( + self, + ctx: &mut SpendContext, + singleton_inner_puzzle_hash: Bytes32, + key_value_list: T, + ) -> Result<(ChainedSpend, Coin), SpendError> + where + T: ToClvm, + { + let create_launcher = ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + amount: self.coin.amount, + })?; + + let singleton_puzzle_hash = + singleton_puzzle_hash(self.coin.coin_id(), singleton_inner_puzzle_hash); + + let eve_message = ctx.alloc(clvm_list!( + singleton_puzzle_hash, + self.coin.amount, + &key_value_list + ))?; + let eve_message_hash = ctx.tree_hash(eve_message); + + let mut announcement_id = Sha256::new(); + announcement_id.update(self.coin.coin_id()); + announcement_id.update(eve_message_hash); + + let assert_announcement = ctx.alloc(AssertCoinAnnouncement { + announcement_id: Bytes32::new(announcement_id.finalize_fixed().into()), + })?; + + let launcher = ctx.singleton_launcher(); + let puzzle_reveal = ctx.serialize(launcher)?; + + let solution = ctx.serialize(LauncherSolution { + singleton_puzzle_hash, + amount: self.coin.amount, + key_value_list, + })?; + + let spend_launcher = CoinSpend::new(self.coin.clone(), puzzle_reveal, solution); + + let chained_spend = ChainedSpend { + coin_spends: vec![spend_launcher], + parent_conditions: vec![create_launcher, assert_announcement], + }; + + let singleton_coin = + Coin::new(self.coin.coin_id(), singleton_puzzle_hash, self.coin.amount); + + Ok((chained_spend, singleton_coin)) + } +} + +pub fn singleton_puzzle_hash(launcher_id: Bytes32, inner_puzzle_hash: Bytes32) -> Bytes32 { + let singleton_hash = tree_hash_atom(&SINGLETON_TOP_LAYER_PUZZLE_HASH); + let launcher_id_hash = tree_hash_atom(&launcher_id); + let launcher_puzzle_hash = tree_hash_atom(&SINGLETON_LAUNCHER_PUZZLE_HASH); + + let pair = tree_hash_pair(launcher_id_hash, launcher_puzzle_hash); + let singleton_struct_hash = tree_hash_pair(singleton_hash, pair); + + curry_tree_hash( + SINGLETON_TOP_LAYER_PUZZLE_HASH, + &[singleton_struct_hash, inner_puzzle_hash.into()], + ) + .into() +} + +#[cfg(test)] +mod tests { + use chia_wallet::singleton::{ + SingletonArgs, SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, + SINGLETON_TOP_LAYER_PUZZLE_HASH, + }; + use clvm_utils::CurriedProgram; + use clvmr::Allocator; + + use super::*; + + #[test] + fn test_puzzle_hash() { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); + + let launcher_id = Bytes32::new([34; 32]); + + let singleton_puzzle = ctx.singleton_top_layer(); + + let puzzle = ctx + .alloc(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, + }, + }) + .unwrap(); + let allocated_puzzle_hash = ctx.tree_hash(puzzle); + + let puzzle_hash = singleton_puzzle_hash(launcher_id, inner_puzzle_hash); + + assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); + } +} diff --git a/src/spends/puzzles/singleton/singleton_spend.rs b/src/spends/puzzles/singleton/singleton_spend.rs new file mode 100644 index 00000000..acd61fac --- /dev/null +++ b/src/spends/puzzles/singleton/singleton_spend.rs @@ -0,0 +1,41 @@ +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_wallet::{ + singleton::{ + SingletonArgs, SingletonSolution, SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, + SINGLETON_TOP_LAYER_PUZZLE_HASH, + }, + Proof, +}; +use clvm_utils::CurriedProgram; + +use crate::{InnerSpend, SpendContext, SpendError}; + +pub fn spend_singleton( + ctx: &mut SpendContext, + coin: Coin, + launcher_id: Bytes32, + proof: Proof, + inner_spend: InnerSpend, +) -> 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(), + }, + })?; + + let solution = ctx.serialize(SingletonSolution { + proof, + amount: coin.amount, + inner_solution: inner_spend.solution(), + })?; + + Ok(CoinSpend::new(coin, puzzle_reveal, solution)) +} diff --git a/src/spends/puzzles/standard.rs b/src/spends/puzzles/standard.rs index 3a2e2169..cf20fb0a 100644 --- a/src/spends/puzzles/standard.rs +++ b/src/spends/puzzles/standard.rs @@ -5,7 +5,7 @@ use clvm_traits::clvm_quote; use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use crate::{BaseSpend, ChainedSpend, InnerSpend, SpendContext, SpendError}; +use crate::{ChainedSpend, InnerSpend, SpendContext, SpendError}; #[derive(Default)] pub struct StandardSpend { @@ -14,6 +14,10 @@ pub struct StandardSpend { } impl StandardSpend { + pub fn new() -> Self { + Self::default() + } + pub fn inner_spend( self, ctx: &mut SpendContext, @@ -31,6 +35,17 @@ impl StandardSpend { Ok((InnerSpend::new(puzzle, solution), self.coin_spends)) } + pub fn chain(mut self, chained_spend: ChainedSpend) -> Self { + self.coin_spends.extend(chained_spend.coin_spends); + self.conditions.extend(chained_spend.parent_conditions); + self + } + + pub fn condition(mut self, condition: NodePtr) -> Self { + self.conditions.push(condition); + self + } + pub fn finish( self, ctx: &mut SpendContext, @@ -47,19 +62,6 @@ impl StandardSpend { } } -impl BaseSpend for StandardSpend { - fn chain(mut self, chained_spend: ChainedSpend) -> Self { - self.conditions.extend(chained_spend.parent_conditions); - self.coin_spends.extend(chained_spend.coin_spends); - self - } - - fn condition(mut self, condition: NodePtr) -> Self { - self.conditions.push(condition); - self - } -} - /// Constructs a solution for the standard puzzle, given a list of condition. /// This assumes no hidden puzzle is being used in this spend. pub fn standard_solution(conditions: T) -> StandardSolution<(u8, T), ()> { @@ -92,7 +94,7 @@ mod tests { let coin = Coin::new(Bytes32::from([0; 32]), Bytes32::from([1; 32]), 42); - let coin_spend = StandardSpend::default() + let coin_spend = StandardSpend::new() .condition( ctx.alloc(CreateCoinWithoutMemos { puzzle_hash: coin.puzzle_hash, diff --git a/src/spends/spend_builder.rs b/src/spends/spend_builder.rs index 9a423648..587d07b1 100644 --- a/src/spends/spend_builder.rs +++ b/src/spends/spend_builder.rs @@ -1,21 +1,6 @@ use chia_protocol::CoinSpend; use clvmr::NodePtr; -pub trait BaseSpend { - fn chain(self, chained_spend: ChainedSpend) -> Self; - fn condition(self, condition: NodePtr) -> Self; - - fn conditions(mut self, conditions: impl IntoIterator) -> Self - where - Self: Sized, - { - for condition in conditions { - self = self.condition(condition); - } - self - } -} - #[derive(Debug, Clone)] pub struct ChainedSpend { pub coin_spends: Vec, diff --git a/src/wallet/required_signature.rs b/src/wallet/required_signature.rs index 7787feda..ebe35a70 100644 --- a/src/wallet/required_signature.rs +++ b/src/wallet/required_signature.rs @@ -5,7 +5,7 @@ use clvmr::{allocator::NodePtr, reduction::EvalErr, Allocator}; use sha2::{digest::FixedOutput, Digest, Sha256}; use thiserror::Error; -use crate::{trim_leading_zeros, AggSig, AggSigKind}; +use crate::{u64_to_bytes, AggSig, AggSigKind}; /// An error that occurs while trying to sign a coin spend. #[derive(Debug, Error)] @@ -165,11 +165,6 @@ impl RequiredSignature { } } -fn u64_to_bytes(amount: u64) -> Vec { - let bytes: Vec = amount.to_be_bytes().into(); - trim_leading_zeros(bytes.as_slice()).to_vec() -} - #[cfg(test)] mod tests { use crate::testing::SECRET_KEY; From 88c9154050b8c558c700d8463b56d6efef48f08c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 3 May 2024 23:09:52 -0500 Subject: [PATCH 10/25] Next steps toward offers --- src/spends/puzzles/cat/issue_cat.rs | 5 ++ src/spends/puzzles/did.rs | 2 +- src/spends/puzzles/offer.rs | 86 ++++++++++------------- src/spends/puzzles/offer/offer_builder.rs | 61 ++++++++-------- src/spends/puzzles/standard.rs | 5 ++ 5 files changed, 80 insertions(+), 79 deletions(-) diff --git a/src/spends/puzzles/cat/issue_cat.rs b/src/spends/puzzles/cat/issue_cat.rs index 234f7f20..b6f31a23 100644 --- a/src/spends/puzzles/cat/issue_cat.rs +++ b/src/spends/puzzles/cat/issue_cat.rs @@ -34,6 +34,11 @@ impl IssueCat { self } + pub fn conditions(mut self, conditions: impl IntoIterator) -> Self { + self.conditions.extend(conditions); + self + } + pub fn multi_issuance( self, ctx: &mut SpendContext, diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index be5f579f..37286ef4 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -60,7 +60,7 @@ mod tests { .finish(&mut ctx, parent, pk)?, ); - let mut spend_bundle = dbg!(SpendBundle::new(coin_spends, Signature::default())); + let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index b5a29a45..c18ec398 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -106,15 +106,6 @@ mod tests { aggregated_signature } - #[tokio::test] - async fn test_chia_offer() { - let offer = "offer1qqr83wcuu2rykcmqvpsxvgqqemhmlaekcenaz02ma6hs5w600dhjlvfjn477nkwz369h88kll73h37fefnwk3qqnz8s0lle07zdk52smt8k62xtc53n9hxt4gpn4p0qp27dka6wa40d7djd0mrn86p4gvngckfmt877hr0h3uzekcus20afa0xwx54l3vhmtttsajt047dh5dnv4llk7c5nl84aczcgwktrmuezkmqfv2du9x5h4l9v8g6lx2ynpn068d07an2fl4r308aexve0nrj0h7gxv9g3dk78h4l7nzhlu0sk9vm8h6m7xzetn44em6ml6gwgkctnsancnkwwqyfctf7fkmf7pkmd735md735mdl5wj6d5l9rtlesmhpc97c97aaeu9w7kgm3k6uer5vnwg85fv4wp7cp7wakkphxwczhd6ddjtf24qe0t56jj7h8dcwsmjcq6wlte6p5aeytj8n870eftrwezkzjrjuv0frexu6z4s9wvzr0824uxpqeh9jl0y2w09nsxdp4a0fzwelftr7wz6xqlvnnmewgucrtmrsmrglz20wnt46a0evatzt0084jtt369w2kvszx7pqsvdzqrfk079usdf7hmmuxx5wgs2crn948xl7py7cgt0d4wvdg6g5kanvwh0j048ay4chdnw8fsh8av3u247kpzpd3a5qc447llpv9xea270hg55yyvdlsz3w6e946dkl40jkr0acntya6880nwl2j9uks8f88079evuxkyeeg4h5jl9amvmncskjevzu7nk5mll7nue34kunaffmuhmg7shl30mryt2dmjxmlm8l7wjcmlwjwlvdx48zugm26arer2urmpj7e7yltv3xvpnh0lqluphgm07r8psudp2f6l9runwl4enqf2xfnd7ds607fh46jfvfumdeyq2q3dy02ve74lspgyxzm2ryc5dmfmenur2h8ultcanyuw6464483m0m3vdzu5r8mzrhmx4s24qwtqktur34pg2qupqz5y5hehvtpfwq5hfu4gzw0l6dh0ehkled97xtdu0lquhdhhuhg7w3q62y7clzen99tzmsmuyuk8esaan5w4xdam33r0amjxhkf3hlj4mcm3ga93jdekkwez8k3hu70cl2tnn443n4nd95004jlf7tyltwl7s26u9v79gfnznt75a06lmxvkptwnanuh6axqucwmhx6zfn8d7y8ldx26l33ytl6m4kxwgp0dsr4yml4hxr7l5qzdlmlgjr5hmx9shrq4pghtq4pnnluke3cjmr0wc4h2ctc79l5tgu2hlz5q4fk07tac4xaw7ewdl2ug8dgvllvhatt642kzhym5hjy78rx00nn5wg3t60km4sxpxhzhjcn2sffxd2ljuw449cfndeah8kluh2wmg00yxzhandnuhh4044ekwanejnaujwhtll4egdx6wqa8za4mwejkmxn0h46mzand7qjxvts5vrah4x94tk0dkg7hfglawvd9d02lla24v5neegdzlldk3ll7v8wdq5q8kgq4fcnu54uz"; - let offer_data = decode_offer(offer).unwrap(); - let spend_bundle = decompress_offer(&offer_data).unwrap(); - - panic!("{:#?}", &spend_bundle); - } - #[tokio::test] async fn test_offer() -> anyhow::Result<()> { let sim = WalletSimulator::new().await; @@ -132,17 +123,19 @@ mod tests { let xch1 = sim.generate_coin(ph1, 1000).await; // Issue CAT. - let (issue_cat, cat_info) = IssueCat::new(&mut ctx, xch1.coin.coin_id()) - .condition(CreateCoinWithMemos { + let (issue_cat, cat_info) = IssueCat::new(xch1.coin.coin_id()) + .condition(ctx.alloc(CreateCoinWithMemos { puzzle_hash: ph1, amount: 1000, memos: vec![ph1.to_vec().into()], - })? - .multi_issuance(pk1.clone(), 1000)?; + })?) + .multi_issuance(&mut ctx, pk1.clone(), 1000)?; - let coin_spends = StandardSpend::new(&mut ctx, xch1.coin.clone()) - .chain(issue_cat) - .finish(pk1.clone())?; + let coin_spends = StandardSpend::new().chain(issue_cat).finish( + &mut ctx, + xch1.coin.clone(), + pk1.clone(), + )?; let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); @@ -167,8 +160,9 @@ mod tests { let xch2 = sim.generate_coin(ph2, 1000).await.coin; - let requests = OfferBuilder::new(&mut ctx, vec![cat1.coin_id(), xch2.coin_id()]) + let requests = OfferBuilder::new(vec![cat1.coin_id(), xch2.coin_id()]) .request_cat_payments( + &mut ctx, cat_info.asset_id, vec![Payment::WithMemos(PaymentWithMemos { puzzle_hash: ph2, @@ -180,10 +174,14 @@ mod tests { let mut coin_spends = requests.coin_spends; - let xch_spends = StandardSpend::new(&mut ctx, xch2) - .settlement_coin(1000)? - .conditions(requests.assertions)? - .finish(pk2)?; + let xch_spends = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount: 1000, + memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], + })?) + .conditions(requests.parent_conditions) + .finish(&mut ctx, xch2, pk2)?; let aggregated_signature = sign_tx(RequiredSignature::from_coin_spends( &mut allocator, @@ -206,37 +204,31 @@ mod tests { let mut spend_bundle = offer.offered_spend_bundle; - let conditions = ctx.alloc(vec![CreateCoinWithMemos { - puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + let lineage_proof = LineageProof { + parent_coin_info: xch1.coin.coin_id(), + inner_puzzle_hash: cat_info.eve_inner_puzzle_hash, amount: 1000, - memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], - }])?; + }; - let cat_spend = spend_cat_coins( - &mut ctx, - cat_info.asset_id, - &[CatSpend { - coin: cat1, - synthetic_key: pk1, - conditions, - p2_puzzle_hash: ph1, - extra_delta: 0, - lineage_proof: LineageProof { - parent_coin_info: xch1.coin.coin_id(), - inner_puzzle_hash: cat_info.eve_inner_puzzle_hash, - amount: 1000, - }, - }], - )? - .remove(0); + let (inner_spend, _) = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount: 1000, + memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], + })?) + .inner_spend(&mut ctx, pk1)?; + + let cat_spends = CatSpend::new(cat_info.asset_id) + .spend(cat1, inner_spend, lineage_proof, 0) + .finish(&mut ctx)?; - spend_bundle.aggregated_signature += &sign_tx(RequiredSignature::from_coin_spend( + spend_bundle.aggregated_signature += &sign_tx(RequiredSignature::from_coin_spends( &mut allocator, - &cat_spend, + &cat_spends, WalletSimulator::AGG_SIG_ME.into(), )?); - spend_bundle.coin_spends.push(cat_spend); + spend_bundle.coin_spends.extend(cat_spends); let ack = peer.send_transaction(spend_bundle).await?; assert_eq!(ack.error, None); @@ -245,9 +237,9 @@ mod tests { Ok(()) } - pub const EXAMPLE_OFFER_DATA: [u8; 535] = hex!( + pub const EXAMPLE_OFFER_DATA: [u8; 536] = hex!( " - 000678bb1ce2864b63606060622000726f47eb64fdea99caadb02b31f402df8f25f7274d2b562a6948cc62fff2cf7ddb5f983a783b75b4f61fadfd476bffd1da7fb4f61fadfdff0f54ed0f2edc1798effb9cf0aeb5d178dbdcc8a809b9476219d70798cf5d1b18b733b06b778dac5615541913a35ffc9c6b79778a6a3bea8e75083c0f7c30f34513ebfadb7c2aa5b367b2666442ab8ad1160511dd7f4688cc02b7736e8f8b26afbc77f7c16e9be7278c6b264f78aa9ec179f0eca3ecab768cec8cb7804afa2aebba6ef2c85f0c9c7f24382974f3652bc11b5ddf7e3afdec79dd9f35df5ba8ef7f13f30b2294812cfca0127770d376c987dd1384d9edc2ad7ace7a8779fabdb8ad9dfd30e45bca1d3f1e4266409a3040eb901b31c379a11ed8e00d4d87387be4baff3c61be5d2b7e6fd6ef33212f4e9877764cb40e2bb8b7dda3f0ef31813b077856a4f4cfcfb635d173b2132d026b6d80a4f6ff0bceefbff736c667d13d5beeaf0a3b4f7b6f2fb1329d7de9d9c28e8b739edfffa378f93934fe082903b9c6feff0213d765d75a5c6bf8f4fda66cb799c45174c24ff6ade792bdcea68b2326fbdcf27e0f0e9f29af827f9a54bc9f9dd411f939abe88e5de7b13501129a0106ae9eb78d97d65bd43d916f543fa1abad2b76ec958e7c91813ef7f4c0b4876bee6ed9b4e4e86cd5cd2bacd61f7bb6a2a13e67dff2e902ae0b3acf773818f84cde55fb6fc7f6b2905dd676df5f0300bd5673c3 + 000678bb1ce2864b63606060622000726f47eb64fdea99caadb02b31f402df8f25f7274d2b562a6948cc62fff2cf7ddb5f983a783b75b4f61fadfd476bffd1da7fb4f61fadfdff0f54ed0f2edc1798effb9cf0aeb5d178dbdcc8a809b9476219d70798cf5d1b18b733b06b778dac5615541913a35ffc9c6b79778a6a3bea8e75083c0f7c30f34513ebfadb7c2aa5b367b2666442ab8ad1160511dd7f4688cc824572f653769c7cfbc2ce6ddbe2a95f0c549f2d7ec23d25e3e6139d83d3a7fe605704a6c3057d95755d3779e42f06ce3f129c14baf9b295e08dae6f3f9d7ef6bceecf9aef2dd4f7bf89f90511ca40167e50893bb869bbe4c3ee09c2ec76e1563d67bdc33cfd5edcd6ce7e18f22de58e1f0f2133204d18a075c88d98e1bc500f6cf086a6439c3d72dd7f9e30dfae15bf37ebf799901727cc3b3b265a8715dcdbee51f8f798c09d033c2b52fae767db9ae839d9891681b5364052fbff05e7f7df7b1be3b3e89e2df757859da7bdb7975899cebef46c61c7c539cfefff51bcfc1c1a7f8494815c63ff7f41f992f999193b4259b6987a1fffcb5550ef18bc29965f34f2f4c260fec87ae339cce0f069f9d3b2309a85f9f1c1236e7797fa3f6d64ffb539e2db7b6f8df36e69a71ffecbfe7ec27ea6d6c3d58edb3a8e941fc8793d375a70c2b28bba6fe30eefebf3c85c5f992bc8efc371b27f6e9cd9c2c0f2becb0db25b9eb6ac77bf67d63a6fd9a7d9c94f7f56a60100f2b976f4 " ); } diff --git a/src/spends/puzzles/offer/offer_builder.rs b/src/spends/puzzles/offer/offer_builder.rs index bf6610be..67a88264 100644 --- a/src/spends/puzzles/offer/offer_builder.rs +++ b/src/spends/puzzles/offer/offer_builder.rs @@ -8,55 +8,53 @@ use clvmr::NodePtr; use sha2::{digest::FixedOutput, Digest, Sha256}; use crate::{ - AssertPuzzleAnnouncement, NotarizedPayment, Payment, SettlementPaymentsSolution, SpendContext, - SpendError, + AssertPuzzleAnnouncement, ChainedSpend, NotarizedPayment, Payment, SettlementPaymentsSolution, + SpendContext, SpendError, }; -pub struct OfferRequests { - pub coin_spends: Vec, - pub assertions: Vec, -} - -pub struct OfferBuilder<'a, 'b> { - ctx: &'a mut SpendContext<'b>, +pub struct OfferBuilder { nonce: Bytes32, coin_spends: Vec, - assertions: Vec, + parent_conditions: Vec, } -impl<'a, 'b> OfferBuilder<'a, 'b> { - pub fn new(ctx: &'a mut SpendContext<'b>, offered_coin_ids: Vec) -> Self { +impl OfferBuilder { + pub fn new(offered_coin_ids: Vec) -> Self { let nonce = calculate_nonce(offered_coin_ids); - Self::from_nonce(ctx, nonce) + Self::from_nonce(nonce) } - pub fn from_nonce(ctx: &'a mut SpendContext<'b>, nonce: Bytes32) -> Self { + pub fn from_nonce(nonce: Bytes32) -> Self { Self { - ctx, nonce, coin_spends: Vec::new(), - assertions: Vec::new(), + parent_conditions: Vec::new(), } } - pub fn request_xch_payments(self, payments: Vec) -> Result { - let puzzle = self.ctx.standard_puzzle(); - self.request_payments(puzzle, payments) + pub fn request_xch_payments( + self, + ctx: &mut SpendContext, + payments: Vec, + ) -> Result { + let puzzle = ctx.standard_puzzle(); + self.request_payments(ctx, puzzle, payments) } pub fn request_cat_payments( self, + ctx: &mut SpendContext, asset_id: Bytes32, payments: Vec, ) -> Result { let puzzle_hash = cat_puzzle_hash(asset_id.into(), SETTLEMENT_PAYMENTS_PUZZLE_HASH); - let puzzle = if let Some(puzzle) = self.ctx.get_puzzle(&puzzle_hash) { + let puzzle = if let Some(puzzle) = ctx.get_puzzle(&puzzle_hash) { puzzle } else { - let cat_puzzle = self.ctx.cat_puzzle(); - let settlement_payments_puzzle = self.ctx.settlement_payments_puzzle(); - let puzzle = self.ctx.alloc(CurriedProgram { + let cat_puzzle = ctx.cat_puzzle(); + let settlement_payments_puzzle = ctx.settlement_payments_puzzle(); + let puzzle = ctx.alloc(CurriedProgram { program: cat_puzzle, args: CatArgs { mod_hash: CAT_PUZZLE_HASH.into(), @@ -64,32 +62,33 @@ impl<'a, 'b> OfferBuilder<'a, 'b> { inner_puzzle: settlement_payments_puzzle, }, })?; - self.ctx.preload(puzzle_hash, puzzle); + ctx.preload(puzzle_hash, puzzle); puzzle }; - self.request_payments(puzzle, payments) + self.request_payments(ctx, puzzle, payments) } pub fn request_payments( mut self, + ctx: &mut SpendContext, puzzle: NodePtr, payments: Vec, ) -> Result { let (coin_spend, announcement_id) = - request_offer_payments(self.ctx, self.nonce, puzzle, payments)?; + request_offer_payments(ctx, self.nonce, puzzle, payments)?; self.coin_spends.push(coin_spend); - self.assertions - .push(AssertPuzzleAnnouncement { announcement_id }); + self.parent_conditions + .push(ctx.alloc(AssertPuzzleAnnouncement { announcement_id })?); Ok(self) } - pub fn finish(self) -> OfferRequests { - OfferRequests { + pub fn finish(self) -> ChainedSpend { + ChainedSpend { coin_spends: self.coin_spends, - assertions: self.assertions, + parent_conditions: self.parent_conditions, } } } diff --git a/src/spends/puzzles/standard.rs b/src/spends/puzzles/standard.rs index cf20fb0a..f602087f 100644 --- a/src/spends/puzzles/standard.rs +++ b/src/spends/puzzles/standard.rs @@ -46,6 +46,11 @@ impl StandardSpend { self } + pub fn conditions(mut self, conditions: impl IntoIterator) -> Self { + self.conditions.extend(conditions); + self + } + pub fn finish( self, ctx: &mut SpendContext, From cb212b6963841270d9a51441b413363cc3c88100 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sat, 4 May 2024 08:52:50 -0500 Subject: [PATCH 11/25] Simple offer bundle test --- src/spends/puzzles/offer.rs | 199 +++++++++++++--------- src/spends/puzzles/offer/offer_builder.rs | 14 ++ src/spends/spend_context.rs | 10 ++ 3 files changed, 145 insertions(+), 78 deletions(-) diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index c18ec398..a2a5513a 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -57,17 +57,18 @@ mod tests { use chia_bls::{sign, DerivableKey, SecretKey, Signature}; use chia_protocol::{Bytes32, Coin, SpendBundle}; use chia_wallet::{ - cat::cat_puzzle_hash, + cat::{cat_puzzle_hash, CatArgs, CAT_PUZZLE_HASH}, offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH, standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, DeriveSynthetic, LineageProof, }; + use clvm_utils::CurriedProgram; use clvmr::Allocator; - use hex_literal::hex; use crate::{ - testing::SECRET_KEY, CatSpend, CreateCoinWithMemos, IssueCat, RequiredSignature, - SpendContext, StandardSpend, WalletSimulator, + testing::SECRET_KEY, AssertPuzzleAnnouncement, CatSpend, CreateCoinWithMemos, + CreateCoinWithoutMemos, InnerSpend, IssueCat, RequiredSignature, SpendContext, + StandardSpend, WalletSimulator, }; fn sk1() -> SecretKey { @@ -107,40 +108,37 @@ mod tests { } #[tokio::test] - async fn test_offer() -> anyhow::Result<()> { + async fn test_offer_bundle() -> anyhow::Result<()> { let sim = WalletSimulator::new().await; let peer = sim.peer().await; let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let pk1 = sk1().public_key(); - let pk2 = sk2().public_key(); + let sk = sk1(); + let pk = sk.public_key(); - let ph1 = Bytes32::new(standard_puzzle_hash(&pk1)); - let ph2 = Bytes32::new(standard_puzzle_hash(&pk2)); + let puzzle_hash = Bytes32::new(standard_puzzle_hash(&pk)); - let xch1 = sim.generate_coin(ph1, 1000).await; + let parent = sim.generate_coin(puzzle_hash, 1000).await.coin; - // Issue CAT. - let (issue_cat, cat_info) = IssueCat::new(xch1.coin.coin_id()) + let (issue_cat, cat_info) = IssueCat::new(parent.coin_id()) .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash: ph1, + puzzle_hash, amount: 1000, - memos: vec![ph1.to_vec().into()], + memos: vec![puzzle_hash.to_vec().into()], })?) - .multi_issuance(&mut ctx, pk1.clone(), 1000)?; + .multi_issuance(&mut ctx, pk.clone(), 1000)?; - let coin_spends = StandardSpend::new().chain(issue_cat).finish( - &mut ctx, - xch1.coin.clone(), - pk1.clone(), - )?; + let coin_spends = + StandardSpend::new() + .chain(issue_cat) + .finish(&mut ctx, parent.clone(), pk.clone())?; let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); let required_signatures = RequiredSignature::from_coin_spends( - &mut allocator, + ctx.allocator_mut(), &spend_bundle.coin_spends, WalletSimulator::AGG_SIG_ME.into(), )?; @@ -151,61 +149,58 @@ mod tests { assert_eq!(ack.error, None); assert_eq!(ack.status, 1); - // Create offer for CAT coin. - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let cat1_ph = cat_puzzle_hash(cat_info.asset_id.into(), ph1.into()).into(); - let cat1 = Coin::new(cat_info.eve_coin.coin_id(), cat1_ph, 1000); - - let xch2 = sim.generate_coin(ph2, 1000).await.coin; + // Prepare offer contents. + let cat = Coin::new( + cat_info.eve_coin.coin_id(), + cat_puzzle_hash(cat_info.asset_id.into(), puzzle_hash.into()).into(), + 1000, + ); - let requests = OfferBuilder::new(vec![cat1.coin_id(), xch2.coin_id()]) - .request_cat_payments( - &mut ctx, - cat_info.asset_id, - vec![Payment::WithMemos(PaymentWithMemos { - puzzle_hash: ph2, - amount: 1000, - memos: vec![ph2.to_vec().into()], - })], - )? - .finish(); + let xch = sim.generate_coin(puzzle_hash, 1000).await.coin; - let mut coin_spends = requests.coin_spends; - - let xch_spends = StandardSpend::new() - .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + let xch_payment = NotarizedPayment { + nonce: calculate_nonce(vec![cat.coin_id()]), + payments: vec![Payment::WithoutMemos(PaymentWithoutMemos { + puzzle_hash, amount: 1000, - memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], - })?) - .conditions(requests.parent_conditions) - .finish(&mut ctx, xch2, pk2)?; + })], + }; - let aggregated_signature = sign_tx(RequiredSignature::from_coin_spends( - &mut allocator, - &xch_spends, - WalletSimulator::AGG_SIG_ME.into(), - )?); + let cat_payment = NotarizedPayment { + nonce: calculate_nonce(vec![xch.coin_id()]), + payments: vec![Payment::WithoutMemos(PaymentWithoutMemos { + puzzle_hash, + amount: 1000, + })], + }; - coin_spends.extend(xch_spends); + let cat_puzzle = ctx.cat_puzzle(); + let settlement_payments_puzzle = ctx.settlement_payments_puzzle(); - let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); - let offer_data = compress_offer(spend_bundle)?; + let cat_settlements = ctx.alloc(CurriedProgram { + program: cat_puzzle, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: cat_info.asset_id, + inner_puzzle: settlement_payments_puzzle, + }, + })?; - assert_eq!(hex::encode(&offer_data), hex::encode(EXAMPLE_OFFER_DATA)); + let cat_settlements_hash = ctx.tree_hash(cat_settlements); - // Decompress offer and accept it (we know the only requested payment is the CAT). - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); + let assert_xch = offer_announcement_id( + &mut ctx, + SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + xch_payment.clone(), + )?; - let offer = Offer::from(decompress_offer(&offer_data)?); + let assert_cat = + offer_announcement_id(&mut ctx, cat_settlements_hash, cat_payment.clone())?; - let mut spend_bundle = offer.offered_spend_bundle; + let mut coin_spends = Vec::new(); let lineage_proof = LineageProof { - parent_coin_info: xch1.coin.coin_id(), + parent_coin_info: parent.coin_id(), inner_puzzle_hash: cat_info.eve_inner_puzzle_hash, amount: 1000, }; @@ -216,19 +211,73 @@ mod tests { amount: 1000, memos: vec![SETTLEMENT_PAYMENTS_PUZZLE_HASH.to_vec().into()], })?) - .inner_spend(&mut ctx, pk1)?; + .condition(ctx.alloc(AssertPuzzleAnnouncement { + announcement_id: assert_xch, + })?) + .inner_spend(&mut ctx, pk.clone())?; + + let cat_to_settlement = CatSpend::new(cat_info.asset_id) + .spend(cat.clone(), inner_spend, lineage_proof, 0) + .finish(&mut ctx)?; + + coin_spends.extend(cat_to_settlement); + + let cat_settlement_coin = Coin::new( + cat.coin_id(), + cat_puzzle_hash(cat_info.asset_id.into(), SETTLEMENT_PAYMENTS_PUZZLE_HASH).into(), + 1000, + ); + + let xch_to_settlement = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), + amount: 1000, + })?) + .condition(ctx.alloc(AssertPuzzleAnnouncement { + announcement_id: assert_cat, + })?) + .finish(&mut ctx, xch.clone(), pk)?; + + coin_spends.extend(xch_to_settlement); + + let xch_settlement_coin = + Coin::new(xch.coin_id(), SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), 1000); + + let lineage_proof = LineageProof { + parent_coin_info: cat_info.eve_coin.coin_id(), + inner_puzzle_hash: puzzle_hash, + amount: 1000, + }; - let cat_spends = CatSpend::new(cat_info.asset_id) - .spend(cat1, inner_spend, lineage_proof, 0) + let solution = ctx.alloc(SettlementPaymentsSolution { + notarized_payments: vec![cat_payment], + })?; + let inner_spend = InnerSpend::new(settlement_payments_puzzle, solution); + + let settlement_to_cat = CatSpend::new(cat_info.asset_id) + .spend(cat_settlement_coin, inner_spend, lineage_proof, 0) .finish(&mut ctx)?; - spend_bundle.aggregated_signature += &sign_tx(RequiredSignature::from_coin_spends( - &mut allocator, - &cat_spends, + coin_spends.extend(settlement_to_cat); + + let puzzle_reveal = ctx.serialize(settlement_payments_puzzle)?; + let solution = ctx.serialize(SettlementPaymentsSolution { + notarized_payments: vec![xch_payment], + })?; + + let settlement_to_xch = CoinSpend::new(xch_settlement_coin, puzzle_reveal, solution); + + coin_spends.push(settlement_to_xch); + + let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); + + let required_signatures = RequiredSignature::from_coin_spends( + ctx.allocator_mut(), + &spend_bundle.coin_spends, WalletSimulator::AGG_SIG_ME.into(), - )?); + )?; - spend_bundle.coin_spends.extend(cat_spends); + spend_bundle.aggregated_signature = sign_tx(required_signatures); let ack = peer.send_transaction(spend_bundle).await?; assert_eq!(ack.error, None); @@ -236,10 +285,4 @@ mod tests { Ok(()) } - - pub const EXAMPLE_OFFER_DATA: [u8; 536] = hex!( - " - 000678bb1ce2864b63606060622000726f47eb64fdea99caadb02b31f402df8f25f7274d2b562a6948cc62fff2cf7ddb5f983a783b75b4f61fadfd476bffd1da7fb4f61fadfdff0f54ed0f2edc1798effb9cf0aeb5d178dbdcc8a809b9476219d70798cf5d1b18b733b06b778dac5615541913a35ffc9c6b79778a6a3bea8e75083c0f7c30f34513ebfadb7c2aa5b367b2666442ab8ad1160511dd7f4688cc824572f653769c7cfbc2ce6ddbe2a95f0c549f2d7ec23d25e3e6139d83d3a7fe605704a6c3057d95755d3779e42f06ce3f129c14baf9b295e08dae6f3f9d7ef6bceecf9aef2dd4f7bf89f90511ca40167e50893bb869bbe4c3ee09c2ec76e1563d67bdc33cfd5edcd6ce7e18f22de58e1f0f2133204d18a075c88d98e1bc500f6cf086a6439c3d72dd7f9e30dfae15bf37ebf799901727cc3b3b265a8715dcdbee51f8f798c09d033c2b52fae767db9ae839d9891681b5364052fbff05e7f7df7b1be3b3e89e2df757859da7bdb7975899cebef46c61c7c539cfefff51bcfc1c1a7f8494815c63ff7f41f992f999193b4259b6987a1fffcb5550ef18bc29965f34f2f4c260fec87ae339cce0f069f9d3b2309a85f9f1c1236e7797fa3f6d64ffb539e2db7b6f8df36e69a71ffecbfe7ec27ea6d6c3d58edb3a8e941fc8793d375a70c2b28bba6fe30eefebf3c85c5f992bc8efc371b27f6e9cd9c2c0f2becb0db25b9eb6ac77bf67d63a6fd9a7d9c94f7f56a60100f2b976f4 - " - ); } diff --git a/src/spends/puzzles/offer/offer_builder.rs b/src/spends/puzzles/offer/offer_builder.rs index 67a88264..4bca8f9c 100644 --- a/src/spends/puzzles/offer/offer_builder.rs +++ b/src/spends/puzzles/offer/offer_builder.rs @@ -138,3 +138,17 @@ pub fn request_offer_payments( Ok((coin_spend, puzzle_announcement_id)) } + +pub fn offer_announcement_id( + ctx: &mut SpendContext, + puzzle_hash: Bytes32, + notarized_payment: NotarizedPayment, +) -> Result { + let notarized_payment = ctx.alloc(notarized_payment)?; + let notarized_payment_hash = ctx.tree_hash(notarized_payment); + + let mut hasher = Sha256::new(); + hasher.update(puzzle_hash); + hasher.update(notarized_payment_hash); + Ok(Bytes32::new(hasher.finalize_fixed().into())) +} diff --git a/src/spends/spend_context.rs b/src/spends/spend_context.rs index d75b19d9..72745671 100644 --- a/src/spends/spend_context.rs +++ b/src/spends/spend_context.rs @@ -38,6 +38,16 @@ impl<'a> SpendContext<'a> { } } + /// Get a reference to the `Allocator`. + pub fn allocator(&self) -> &Allocator { + self.allocator + } + + /// Get a mutable reference to the `Allocator`. + pub fn allocator_mut(&mut self) -> &mut Allocator { + self.allocator + } + /// Allocate a new node and return its pointer. pub fn alloc(&mut self, value: T) -> Result where From e244fbdf16e25f2c4c7b2709097ea004c36407b0 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sat, 4 May 2024 12:16:37 -0500 Subject: [PATCH 12/25] Getting close to NFT minting working again --- src/lib.rs | 6 + src/spends/puzzles/did.rs | 34 +- src/spends/puzzles/did/create_did.rs | 91 +++- src/spends/puzzles/did/did_info.rs | 5 +- src/spends/puzzles/did/did_spend.rs | 12 +- src/spends/puzzles/nft.rs | 330 +------------ src/spends/puzzles/nft/mint_nft.rs | 441 ++++++++++++++++++ src/spends/puzzles/nft/nft_info.rs | 14 + src/spends/puzzles/nft/nft_spend.rs | 112 +++++ src/spends/puzzles/singleton.rs | 2 + .../singleton/intermediate_launcher.rs | 65 +++ 11 files changed, 742 insertions(+), 370 deletions(-) create mode 100644 src/spends/puzzles/nft/mint_nft.rs create mode 100644 src/spends/puzzles/nft/nft_info.rs create mode 100644 src/spends/puzzles/nft/nft_spend.rs create mode 100644 src/spends/puzzles/singleton/intermediate_launcher.rs diff --git a/src/lib.rs b/src/lib.rs index f960591a..4fdfae86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,12 @@ pub fn u64_to_bytes(num: u64) -> Vec { trim_leading_zeros(bytes.as_slice()).to_vec() } +/// Converts a `u16` to an atom in CLVM format, with leading zeros trimmed. +pub fn u16_to_bytes(num: u16) -> Vec { + let bytes: Vec = num.to_be_bytes().into(); + trim_leading_zeros(bytes.as_slice()).to_vec() +} + #[cfg(test)] mod testing { use std::str::FromStr; diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index 37286ef4..7434dc12 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -14,11 +14,11 @@ mod tests { standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, DeriveSynthetic, }; - use clvmr::{Allocator, NodePtr}; + use clvmr::Allocator; use crate::{ - testing::SECRET_KEY, CreateCoinWithMemos, LaunchSingleton, RequiredSignature, SpendContext, - StandardSpend, WalletSimulator, + testing::SECRET_KEY, LaunchSingleton, RequiredSignature, SpendContext, StandardSpend, + WalletSimulator, }; use super::*; @@ -37,28 +37,12 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let recovery_did_list_hash = ctx.tree_hash(NodePtr::NIL); - let (launch_singleton, eve_inner_puzzle_hash, eve_did_info) = LaunchSingleton::new( - parent.coin_id(), - 1, - ) - .launch_did(&mut ctx, puzzle_hash, recovery_did_list_hash, 1, ())?; - - let (inner_spend, _) = StandardSpend::new() - .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash: eve_inner_puzzle_hash, - amount: eve_did_info.coin.amount, - memos: vec![puzzle_hash.to_vec().into()], - })?) - .inner_spend(&mut ctx, pk.clone())?; - - let mut coin_spends = vec![spend_did(&mut ctx, eve_did_info, inner_spend)?]; - - coin_spends.extend( - StandardSpend::new() - .chain(launch_singleton) - .finish(&mut ctx, parent, pk)?, - ); + let (launch_singleton, _did_info) = + LaunchSingleton::new(parent.coin_id(), 1).create_standard_did(&mut ctx, pk.clone())?; + + let coin_spends = StandardSpend::new() + .chain(launch_singleton) + .finish(&mut ctx, parent, pk)?; let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); diff --git a/src/spends/puzzles/did/create_did.rs b/src/spends/puzzles/did/create_did.rs index 2b7fce37..4339da11 100644 --- a/src/spends/puzzles/did/create_did.rs +++ b/src/spends/puzzles/did/create_did.rs @@ -1,39 +1,103 @@ -use chia_protocol::Bytes32; +use chia_bls::PublicKey; +use chia_protocol::{Bytes32, Coin}; use chia_wallet::{ did::DID_INNER_PUZZLE_HASH, singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, - EveProof, Proof, + standard::standard_puzzle_hash, + EveProof, LineageProof, Proof, }; use clvm_traits::ToClvm; use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; use clvmr::NodePtr; -use crate::{u64_to_bytes, ChainedSpend, DidInfo, LaunchSingleton, SpendContext, SpendError}; +use crate::{ + spend_did, u64_to_bytes, ChainedSpend, CreateCoinWithMemos, DidInfo, LaunchSingleton, + SpendContext, SpendError, StandardSpend, +}; pub trait CreateDid { - fn launch_did( + fn create_eve_did( self, ctx: &mut SpendContext, inner_puzzle_hash: Bytes32, recovery_did_list_hash: Bytes32, num_verifications_required: u64, - metadata: T, - ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> + metadata: M, + ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> + where + M: ToClvm; + + fn create_custom_standard_did( + self, + ctx: &mut SpendContext, + recovery_did_list_hash: Bytes32, + num_verifications_required: u64, + metadata: M, + synthetic_key: PublicKey, + ) -> Result<(ChainedSpend, DidInfo), SpendError> + where + M: ToClvm, + Self: Sized, + { + let inner_puzzle_hash = standard_puzzle_hash(&synthetic_key).into(); + + let (mut create_did, did_inner_puzzle_hash, mut did_info) = self.create_eve_did( + ctx, + inner_puzzle_hash, + recovery_did_list_hash, + num_verifications_required, + metadata, + )?; + + let (inner_spend, _) = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash: did_inner_puzzle_hash, + amount: did_info.coin.amount, + memos: vec![inner_puzzle_hash.to_vec().into()], + })?) + .inner_spend(ctx, synthetic_key)?; + + let spend = spend_did(ctx, &did_info, inner_spend)?; + create_did.coin_spends.push(spend); + + did_info.proof = Proof::Lineage(LineageProof { + parent_coin_info: did_info.launcher_id, + inner_puzzle_hash: did_inner_puzzle_hash, + 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((create_did, did_info)) + } + + fn create_standard_did( + self, + ctx: &mut SpendContext, + synthetic_key: PublicKey, + ) -> Result<(ChainedSpend, DidInfo<()>), SpendError> where - T: ToClvm; + Self: Sized, + { + self.create_custom_standard_did(ctx, tree_hash_atom(&[]).into(), 1, (), synthetic_key) + } } impl CreateDid for LaunchSingleton { - fn launch_did( + fn create_eve_did( self, ctx: &mut SpendContext, inner_puzzle_hash: Bytes32, recovery_did_list_hash: Bytes32, num_verifications_required: u64, - metadata: T, - ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> + metadata: M, + ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> where - T: ToClvm, + M: ToClvm, { let metadata_ptr = ctx.alloc(&metadata)?; let metadata_hash = ctx.tree_hash(metadata_ptr); @@ -57,6 +121,7 @@ impl CreateDid for LaunchSingleton { let did_info = DidInfo { launcher_id: launcher_coin.coin_id(), coin: eve_coin, + did_inner_puzzle_hash, proof, recovery_did_list_hash, num_verifications_required, @@ -99,6 +164,8 @@ pub fn did_inner_puzzle_hash( #[cfg(test)] mod tests { + use super::*; + use chia_wallet::{ did::DidArgs, singleton::{ @@ -108,8 +175,6 @@ mod tests { use clvm_utils::CurriedProgram; use clvmr::Allocator; - use super::*; - #[test] fn test_puzzle_hash() { let mut allocator = Allocator::new(); diff --git a/src/spends/puzzles/did/did_info.rs b/src/spends/puzzles/did/did_info.rs index 32b0eb7c..1fabcf54 100644 --- a/src/spends/puzzles/did/did_info.rs +++ b/src/spends/puzzles/did/did_info.rs @@ -2,11 +2,12 @@ use chia_protocol::{Bytes32, Coin}; use chia_wallet::Proof; #[derive(Debug, Clone)] -pub struct DidInfo { +pub struct DidInfo { pub launcher_id: Bytes32, pub coin: Coin, + pub did_inner_puzzle_hash: Bytes32, pub proof: Proof, pub recovery_did_list_hash: Bytes32, pub num_verifications_required: u64, - pub metadata: T, + pub metadata: M, } diff --git a/src/spends/puzzles/did/did_spend.rs b/src/spends/puzzles/did/did_spend.rs index 4bab90c4..357faa8a 100644 --- a/src/spends/puzzles/did/did_spend.rs +++ b/src/spends/puzzles/did/did_spend.rs @@ -9,13 +9,13 @@ use clvmr::NodePtr; use crate::{spend_singleton, DidInfo, InnerSpend, SpendContext, SpendError}; -pub fn spend_did( +pub fn spend_did( ctx: &mut SpendContext, - did_info: DidInfo, + did_info: &DidInfo, inner_spend: InnerSpend, ) -> Result where - T: ToClvm, + M: ToClvm, { let did_inner_puzzle = ctx.did_inner_puzzle(); @@ -30,7 +30,7 @@ where launcher_id: did_info.launcher_id, launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), }, - metadata: did_info.metadata, + metadata: &did_info.metadata, }, })?; @@ -40,9 +40,9 @@ where spend_singleton( ctx, - did_info.coin, + did_info.coin.clone(), did_info.launcher_id, - did_info.proof, + did_info.proof.clone(), did_spend, ) } diff --git a/src/spends/puzzles/nft.rs b/src/spends/puzzles/nft.rs index a72f824e..06503bf9 100644 --- a/src/spends/puzzles/nft.rs +++ b/src/spends/puzzles/nft.rs @@ -1,325 +1,7 @@ -use chia_bls::PublicKey; -use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend, Program}; -use chia_wallet::{ - nft::{ - NftIntermediateLauncherArgs, NftOwnershipLayerArgs, NftOwnershipLayerSolution, - NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs, NftStateLayerSolution, - NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - NFT_STATE_LAYER_PUZZLE_HASH, - }, - singleton::{ - LauncherSolution, SingletonArgs, SingletonSolution, SingletonStruct, - SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, - }, - standard::{StandardArgs, StandardSolution}, - EveProof, Proof, -}; -use clvm_traits::{clvm_list, clvm_quote, ToClvm}; -use clvm_utils::CurriedProgram; -use clvmr::{ - sha2::{Digest, Sha256}, - NodePtr, -}; +mod mint_nft; +mod nft_info; +mod nft_spend; -use crate::{ - usize_to_bytes, AssertCoinAnnouncement, AssertPuzzleAnnouncement, CreateCoinWithMemos, - CreateCoinWithoutMemos, CreatePuzzleAnnouncement, NewNftOwner, SpendContext, SpendError, -}; - -/// Spend an NFT. -pub fn spend_nft( - ctx: &mut SpendContext, - coin: Coin, - puzzle_reveal: Program, - proof: Proof, - conditions: T, -) -> Result -where - T: ToClvm, -{ - // Construct the p2 solution. - let p2_solution = StandardSolution { - original_public_key: None, - delegated_puzzle: clvm_quote!(conditions), - solution: (), - }; - - // Construct the ownership layer solution. - let ownership_layer_solution = NftOwnershipLayerSolution { - inner_solution: p2_solution, - }; - - // Construct the state layer solution. - let state_layer_solution = NftStateLayerSolution { - inner_solution: ownership_layer_solution, - }; - - // Construct the singleton solution. - let solution = ctx.serialize(SingletonSolution { - proof, - amount: coin.amount, - inner_solution: state_layer_solution, - })?; - - // Construct the coin spend. - let coin_spend = CoinSpend::new(coin, puzzle_reveal, solution); - - Ok(coin_spend) -} - -/// The information required to mint an NFT. -pub struct MintInput { - /// The owner puzzle hash of the newly minted NFT. - pub owner_puzzle_hash: Bytes32, - /// The puzzle hash to send royalties to when trading the NFT. - pub royalty_puzzle_hash: Bytes32, - /// The percentage royalty to send to the royalty puzzle hash. - pub royalty_percentage: u16, - /// The NFT metadata. - pub metadata: M, - /// The amount of the launcher coin and subsequent NFT coin. - pub amount: u64, -} - -/// The information required to create and spend an NFT bulk mint. -pub struct BulkMint { - /// The coin spends for the NFT bulk mint. - pub coin_spends: Vec, - /// The new NFT launcher ids. - pub launcher_ids: Vec, - /// The conditions that must be output from the parent to make this mint valid. - pub parent_conditions: Vec, -} - -/// Bulk mints a set of NFTs. -#[allow(clippy::too_many_arguments)] -pub fn mint_nfts( - ctx: &mut SpendContext, - inputs: Vec>, - parent_coin_id: Bytes32, - synthetic_key: PublicKey, - did_id: Bytes32, - did_inner_puzzle_hash: Bytes32, - mint_start_index: usize, - mint_total: usize, -) -> Result -where - M: ToClvm, -{ - let standard_puzzle = ctx.standard_puzzle(); - let royalty_transfer_puzzle = ctx.nft_royalty_transfer(); - let ownership_puzzle = ctx.nft_ownership_layer(); - let state_puzzle = ctx.nft_state_layer(); - let singleton_puzzle = ctx.singleton_top_layer(); - let launcher_puzzle = ctx.singleton_launcher(); - - let p2 = ctx.alloc(CurriedProgram { - program: standard_puzzle, - args: StandardArgs { synthetic_key }, - })?; - - let mut coin_spends = Vec::new(); - let mut launcher_ids = Vec::new(); - let mut parent_conditions = Vec::new(); - - for (i, input) in inputs.into_iter().enumerate() { - // Create the intermediate launcher. - let intermediate_launcher = - create_intermediate_launcher(ctx, parent_coin_id, mint_start_index + i, mint_total)?; - - let intermediate_id = intermediate_launcher.coin_spend.coin.coin_id(); - let launcher_id = intermediate_launcher.launcher_coin.coin_id(); - - parent_conditions.extend(intermediate_launcher.parent_conditions); - coin_spends.push(intermediate_launcher.coin_spend); - - // Construct the eve NFT. - parent_conditions.push(ctx.alloc(CreatePuzzleAnnouncement { - message: launcher_id.to_vec().into(), - })?); - - let singleton_struct = SingletonStruct { - mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), - launcher_id, - launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - }; - - let royalty_transfer = CurriedProgram { - program: royalty_transfer_puzzle, - args: NftRoyaltyTransferPuzzleArgs { - singleton_struct: singleton_struct.clone(), - royalty_puzzle_hash: input.royalty_puzzle_hash, - trade_price_percentage: input.royalty_percentage, - }, - }; - - let ownership_layer = CurriedProgram { - program: ownership_puzzle, - args: NftOwnershipLayerArgs { - mod_hash: NFT_OWNERSHIP_LAYER_PUZZLE_HASH.into(), - current_owner: None, - transfer_program: royalty_transfer, - inner_puzzle: p2, - }, - }; - - let state_layer = CurriedProgram { - program: state_puzzle, - args: NftStateLayerArgs { - mod_hash: NFT_STATE_LAYER_PUZZLE_HASH.into(), - metadata: input.metadata, - metadata_updater_puzzle_hash: NFT_METADATA_UPDATER_PUZZLE_HASH.into(), - inner_puzzle: ownership_layer, - }, - }; - - let singleton = ctx.alloc(CurriedProgram { - program: singleton_puzzle, - args: SingletonArgs { - singleton_struct, - inner_puzzle: state_layer, - }, - })?; - - let eve_puzzle_hash = ctx.tree_hash(singleton); - - let eve_message = ctx.alloc(clvm_list!(eve_puzzle_hash, input.amount, ()))?; - let eve_message_hash = ctx.tree_hash(eve_message); - - let mut announcement_id = Sha256::new(); - announcement_id.update(launcher_id); - announcement_id.update(eve_message_hash); - - parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize().into()), - })?); - - // Spend the launcher coin. - let launcher_puzzle_reveal = ctx.serialize(launcher_puzzle)?; - let launcher_solution = ctx.serialize(LauncherSolution { - singleton_puzzle_hash: eve_puzzle_hash, - amount: input.amount, - key_value_list: (), - })?; - - coin_spends.push(CoinSpend::new( - intermediate_launcher.launcher_coin, - launcher_puzzle_reveal, - launcher_solution, - )); - - // Spend the eve coin. - let eve_coin = Coin::new(launcher_id, eve_puzzle_hash, input.amount); - - let eve_proof = Proof::Eve(EveProof { - parent_coin_info: intermediate_id, - amount: input.amount, - }); - - let eve_puzzle_reveal = ctx.serialize(singleton)?; - - let eve_coin_spend = spend_nft( - ctx, - eve_coin, - eve_puzzle_reveal, - eve_proof, - clvm_list!( - CreateCoinWithMemos { - puzzle_hash: input.owner_puzzle_hash, - amount: input.amount, - memos: vec![Bytes::new(input.owner_puzzle_hash.to_vec())], - }, - NewNftOwner { - new_owner: Some(did_id), - trade_prices_list: Vec::new(), - new_did_inner_hash: Some(did_inner_puzzle_hash) - } - ), - )?; - let new_nft_owner_args = ctx.alloc(clvm_list!(did_id, (), did_inner_puzzle_hash))?; - - coin_spends.push(eve_coin_spend); - - let mut announcement_id = Sha256::new(); - announcement_id.update(eve_puzzle_hash); - announcement_id.update([0xad, 0x4c]); - announcement_id.update(ctx.tree_hash(new_nft_owner_args)); - - parent_conditions.push(ctx.alloc(AssertPuzzleAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize().into()), - })?); - - // Finalize the output. - launcher_ids.push(launcher_id); - } - - Ok(BulkMint { - coin_spends, - launcher_ids, - parent_conditions, - }) -} - -/// Information required to create and spend a new intermediate launcher. -pub struct IntermediateLauncher { - /// The coin spend for the new intermediate launcher. - pub coin_spend: CoinSpend, - /// The conditions that must be output from the parent to make this intermediate launcher valid. - pub parent_conditions: Vec, - /// The final launcher coin. - pub launcher_coin: Coin, -} - -/// Creates and spends a new intermediate launcher coin. -pub fn create_intermediate_launcher( - ctx: &mut SpendContext, - parent_coin_id: Bytes32, - index: usize, - total: usize, -) -> Result { - let intermediate_puzzle = ctx.nft_intermediate_launcher(); - - let puzzle = ctx.alloc(CurriedProgram { - program: intermediate_puzzle, - args: NftIntermediateLauncherArgs { - launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - mint_number: index, - mint_total: total, - }, - })?; - let puzzle_reveal = ctx.serialize(puzzle)?; - let solution = ctx.serialize(())?; - - let puzzle_hash = ctx.tree_hash(puzzle); - - let intermediate_spend = CoinSpend::new( - Coin::new(parent_coin_id, puzzle_hash, 0), - puzzle_reveal, - solution, - ); - - let intermediate_id = intermediate_spend.coin.coin_id(); - - let mut parent_conditions = vec![ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash: intermediate_spend.coin.puzzle_hash, - amount: intermediate_spend.coin.amount, - })?]; - - let mut index_message = Sha256::new(); - index_message.update(usize_to_bytes(index)); - index_message.update(usize_to_bytes(total)); - - let mut announcement_id = Sha256::new(); - announcement_id.update(intermediate_id); - announcement_id.update(index_message.finalize()); - - parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize().into()), - })?); - - Ok(IntermediateLauncher { - coin_spend: intermediate_spend, - parent_conditions, - launcher_coin: Coin::new(intermediate_id, SINGLETON_LAUNCHER_PUZZLE_HASH.into(), 1), - }) -} +pub use mint_nft::*; +pub use nft_info::*; +pub use nft_spend::*; diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs new file mode 100644 index 00000000..2756a2d4 --- /dev/null +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -0,0 +1,441 @@ +use chia_bls::PublicKey; +use chia_protocol::{Bytes32, Coin}; +use chia_wallet::{ + nft::{ + NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, + NFT_ROYALTY_TRANSFER_PUZZLE_HASH, NFT_STATE_LAYER_PUZZLE_HASH, + }, + singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, + standard::standard_puzzle_hash, + EveProof, LineageProof, Proof, +}; +use clvm_traits::{clvm_list, ToClvm}; +use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; +use clvmr::NodePtr; +use sha2::{Digest, Sha256}; + +use crate::{ + spend_nft, u16_to_bytes, AssertPuzzleAnnouncement, ChainedSpend, CreateCoinWithMemos, + LaunchSingleton, NewNftOwner, NftInfo, SpendContext, SpendError, StandardSpend, +}; + +pub trait MintNft { + fn mint_eve_nft( + self, + ctx: &mut SpendContext, + inner_puzzle_hash: Bytes32, + metadata: M, + metadata_updater_hash: Bytes32, + royalty_puzzle_hash: Bytes32, + royalty_percentage: u16, + ) -> Result<(ChainedSpend, Bytes32, NftInfo), SpendError> + where + M: ToClvm; + + fn mint_custom_standard_nft( + self, + ctx: &mut SpendContext, + metadata: M, + metadata_updater_hash: Bytes32, + royalty_puzzle_hash: Bytes32, + royalty_percentage: u16, + synthetic_key: PublicKey, + did_id: Bytes32, + did_inner_puzzle_hash: Bytes32, + ) -> Result<(ChainedSpend, NftInfo), SpendError> + where + M: ToClvm, + Self: Sized, + { + let inner_puzzle_hash = standard_puzzle_hash(&synthetic_key).into(); + + let (mut mint_nft, nft_inner_puzzle_hash, mut nft_info) = self.mint_eve_nft( + ctx, + inner_puzzle_hash, + metadata, + metadata_updater_hash, + royalty_puzzle_hash, + royalty_percentage, + )?; + + let (inner_spend, _) = StandardSpend::new() + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash: inner_puzzle_hash, + amount: nft_info.coin.amount, + memos: vec![inner_puzzle_hash.to_vec().into()], + })?) + .condition(ctx.alloc(NewNftOwner { + new_owner: Some(did_id), + trade_prices_list: Vec::new(), + new_did_inner_hash: Some(did_inner_puzzle_hash), + })?) + .inner_spend(ctx, synthetic_key)?; + + let new_nft_owner_args = ctx.alloc(clvm_list!(did_id, (), did_inner_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)); + + mint_nft + .parent_conditions + .push(ctx.alloc(AssertPuzzleAnnouncement { + announcement_id: Bytes32::new(announcement_id.finalize().into()), + })?); + + let spend = spend_nft(ctx, &nft_info, inner_spend)?; + mint_nft.coin_spends.push(spend); + + nft_info.proof = Proof::Lineage(LineageProof { + parent_coin_info: nft_info.launcher_id, + inner_puzzle_hash: nft_inner_puzzle_hash, + amount: nft_info.coin.amount, + }); + + nft_info.coin = Coin::new( + nft_info.coin.coin_id(), + nft_info.coin.puzzle_hash, + nft_info.coin.amount, + ); + + Ok((mint_nft, nft_info)) + } + + fn mint_standard_nft( + self, + ctx: &mut SpendContext, + metadata: M, + royalty_percentage: u16, + synthetic_key: PublicKey, + did_id: Bytes32, + did_inner_puzzle_hash: Bytes32, + ) -> Result<(ChainedSpend, NftInfo), SpendError> + where + M: ToClvm, + Self: Sized, + { + let royalty_puzzle_hash = standard_puzzle_hash(&synthetic_key); + + self.mint_custom_standard_nft( + ctx, + metadata, + NFT_METADATA_UPDATER_PUZZLE_HASH.into(), + royalty_puzzle_hash.into(), + royalty_percentage, + synthetic_key, + did_id, + did_inner_puzzle_hash, + ) + } +} + +impl MintNft for LaunchSingleton { + fn mint_eve_nft( + self, + ctx: &mut SpendContext, + inner_puzzle_hash: Bytes32, + metadata: M, + metadata_updater_hash: Bytes32, + royalty_puzzle_hash: Bytes32, + royalty_percentage: u16, + ) -> Result<(ChainedSpend, Bytes32, NftInfo), SpendError> + where + M: ToClvm, + { + let metadata_ptr = ctx.alloc(&metadata)?; + let metadata_hash = ctx.tree_hash(metadata_ptr); + + let ownership_layer_hash = nft_ownership_layer_hash( + None, + nft_royalty_transfer_hash( + self.coin().coin_id(), + royalty_puzzle_hash, + royalty_percentage, + ), + inner_puzzle_hash, + ); + let nft_inner_puzzle_hash = + nft_state_layer_hash(metadata_hash, metadata_updater_hash, ownership_layer_hash); + + let launcher_coin = self.coin().clone(); + let (chained_spend, eve_coin) = self.finish(ctx, nft_inner_puzzle_hash, ())?; + + let proof = Proof::Eve(EveProof { + parent_coin_info: launcher_coin.parent_coin_info, + amount: launcher_coin.amount, + }); + + let nft_info = NftInfo { + launcher_id: launcher_coin.coin_id(), + coin: eve_coin, + proof, + metadata, + metadata_updater_hash, + current_owner: None, + royalty_puzzle_hash, + royalty_percentage, + }; + + Ok((chained_spend, nft_inner_puzzle_hash, nft_info)) + } +} + +pub fn nft_state_layer_hash( + metadata_hash: Bytes32, + metadata_updater_hash: Bytes32, + inner_puzzle_hash: Bytes32, +) -> Bytes32 { + let mod_hash = tree_hash_atom(&NFT_STATE_LAYER_PUZZLE_HASH); + let metadata_updater_hash = tree_hash_atom(&metadata_updater_hash); + + curry_tree_hash( + NFT_STATE_LAYER_PUZZLE_HASH, + &[ + mod_hash, + metadata_hash.into(), + metadata_updater_hash, + inner_puzzle_hash.into(), + ], + ) + .into() +} + +pub fn nft_ownership_layer_hash( + current_owner: Option, + transfer_program_hash: Bytes32, + inner_puzzle_hash: Bytes32, +) -> Bytes32 { + let mod_hash = tree_hash_atom(&NFT_OWNERSHIP_LAYER_PUZZLE_HASH); + let current_owner_hash = match current_owner { + Some(did_id) => tree_hash_atom(&did_id), + None => tree_hash_atom(&[]), + }; + + curry_tree_hash( + NFT_OWNERSHIP_LAYER_PUZZLE_HASH, + &[ + mod_hash, + current_owner_hash, + transfer_program_hash.into(), + inner_puzzle_hash.into(), + ], + ) + .into() +} + +pub fn nft_royalty_transfer_hash( + launcher_id: Bytes32, + royalty_puzzle_hash: Bytes32, + royalty_percentage: u16, +) -> Bytes32 { + let royalty_puzzle_hash = tree_hash_atom(&royalty_puzzle_hash); + let royalty_percentage_hash = tree_hash_atom(&u16_to_bytes(royalty_percentage)); + + let singleton_hash = tree_hash_atom(&SINGLETON_TOP_LAYER_PUZZLE_HASH); + let launcher_id_hash = tree_hash_atom(&launcher_id); + let launcher_puzzle_hash = tree_hash_atom(&SINGLETON_LAUNCHER_PUZZLE_HASH); + + let pair = tree_hash_pair(launcher_id_hash, launcher_puzzle_hash); + let singleton_struct_hash = tree_hash_pair(singleton_hash, pair); + + curry_tree_hash( + NFT_ROYALTY_TRANSFER_PUZZLE_HASH, + &[ + singleton_struct_hash, + royalty_puzzle_hash, + royalty_percentage_hash, + ], + ) + .into() +} + +#[cfg(test)] +mod tests { + use chia_bls::{sign, Signature}; + use chia_protocol::SpendBundle; + use chia_wallet::{ + nft::{ + NftOwnershipLayerArgs, NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs, + NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, + NFT_STATE_LAYER_PUZZLE_HASH, + }, + singleton::{ + SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, + }, + standard::DEFAULT_HIDDEN_PUZZLE_HASH, + DeriveSynthetic, + }; + use clvm_utils::CurriedProgram; + use clvmr::Allocator; + + use crate::{ + intermediate_launcher, spend_did, testing::SECRET_KEY, CreateDid, RequiredSignature, + WalletSimulator, + }; + + use super::*; + + #[tokio::test] + async fn test_bulk_mint() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + + let puzzle_hash = Bytes32::new(standard_puzzle_hash(&pk)); + + let parent = sim.generate_coin(puzzle_hash, 3).await.coin; + + let (create_did, did_info) = + LaunchSingleton::new(parent.coin_id(), 1).create_standard_did(&mut ctx, pk.clone())?; + + let mut coin_spends = + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk.clone())?; + + let (intermediate, launcher) = + intermediate_launcher(&mut ctx, did_info.coin.coin_id(), 0, 1)?; + + let (nft_mint, _nft_info) = launcher.mint_standard_nft( + &mut ctx, + (), + 100, + pk.clone(), + did_info.launcher_id, + did_info.did_inner_puzzle_hash, + )?; + + let (inner_spend, did_spends) = StandardSpend::new() + .chain(intermediate) + .chain(nft_mint) + .inner_spend(&mut ctx, pk)?; + + coin_spends.extend(did_spends); + coin_spends.push(spend_did(&mut ctx, &did_info, inner_spend)?); + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?; + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature); + let ack = peer.send_transaction(spend_bundle).await?; + + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + Ok(()) + } + + #[test] + fn test_state_layer_hash() { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); + + let metadata = ctx.alloc([4, 5, 6]).unwrap(); + let metadata_hash = ctx.tree_hash(metadata); + + let nft_state_layer = ctx.nft_state_layer(); + + let puzzle = ctx + .alloc(CurriedProgram { + program: nft_state_layer, + args: NftStateLayerArgs { + mod_hash: NFT_STATE_LAYER_PUZZLE_HASH.into(), + metadata, + metadata_updater_puzzle_hash: NFT_METADATA_UPDATER_PUZZLE_HASH.into(), + inner_puzzle, + }, + }) + .unwrap(); + let allocated_puzzle_hash = ctx.tree_hash(puzzle); + + let puzzle_hash = nft_state_layer_hash( + metadata_hash, + NFT_METADATA_UPDATER_PUZZLE_HASH.into(), + inner_puzzle_hash, + ); + + assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); + } + + #[test] + fn test_ownership_layer_hash() { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); + + let launcher_id = Bytes32::new([69; 32]); + + let royalty_puzzle_hash = Bytes32::new([34; 32]); + let royalty_percentage = 100; + + let current_owner = Some(Bytes32::new([42; 32])); + + let nft_ownership_layer = ctx.nft_ownership_layer(); + let nft_royalty_transfer = ctx.nft_royalty_transfer(); + + let transfer_program = ctx + .alloc(CurriedProgram { + program: nft_royalty_transfer, + args: NftRoyaltyTransferPuzzleArgs { + singleton_struct: SingletonStruct { + mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), + launcher_id, + launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + }, + royalty_puzzle_hash, + trade_price_percentage: royalty_percentage, + }, + }) + .unwrap(); + let allocated_transfer_program_hash = ctx.tree_hash(transfer_program); + + let puzzle = ctx + .alloc(CurriedProgram { + program: nft_ownership_layer, + args: NftOwnershipLayerArgs { + mod_hash: NFT_OWNERSHIP_LAYER_PUZZLE_HASH.into(), + current_owner, + transfer_program, + inner_puzzle, + }, + }) + .unwrap(); + let allocated_puzzle_hash = ctx.tree_hash(puzzle); + + let puzzle_hash = nft_ownership_layer_hash( + current_owner, + allocated_transfer_program_hash, + inner_puzzle_hash, + ); + + let transfer_program_hash = + nft_royalty_transfer_hash(launcher_id, royalty_puzzle_hash, royalty_percentage); + + assert_eq!( + hex::encode(allocated_transfer_program_hash), + hex::encode(transfer_program_hash) + ); + + assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); + } +} diff --git a/src/spends/puzzles/nft/nft_info.rs b/src/spends/puzzles/nft/nft_info.rs new file mode 100644 index 00000000..be559be7 --- /dev/null +++ b/src/spends/puzzles/nft/nft_info.rs @@ -0,0 +1,14 @@ +use chia_protocol::{Bytes32, Coin}; +use chia_wallet::Proof; + +#[derive(Debug, Clone)] +pub struct NftInfo { + pub launcher_id: Bytes32, + pub coin: Coin, + pub proof: Proof, + pub metadata: M, + pub metadata_updater_hash: Bytes32, + pub current_owner: Option, + pub royalty_puzzle_hash: Bytes32, + pub royalty_percentage: u16, +} diff --git a/src/spends/puzzles/nft/nft_spend.rs b/src/spends/puzzles/nft/nft_spend.rs new file mode 100644 index 00000000..40162dd2 --- /dev/null +++ b/src/spends/puzzles/nft/nft_spend.rs @@ -0,0 +1,112 @@ +use chia_protocol::{Bytes32, CoinSpend}; +use chia_wallet::{ + nft::{ + NftOwnershipLayerArgs, NftOwnershipLayerSolution, NftRoyaltyTransferPuzzleArgs, + NftStateLayerArgs, NftStateLayerSolution, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, + NFT_STATE_LAYER_PUZZLE_HASH, + }, + singleton::{SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, +}; +use clvm_traits::ToClvm; +use clvm_utils::CurriedProgram; +use clvmr::NodePtr; + +use crate::{spend_singleton, InnerSpend, NftInfo, SpendContext, SpendError}; + +pub fn spend_nft( + ctx: &mut SpendContext, + nft_info: &NftInfo, + inner_spend: InnerSpend, +) -> Result +where + M: ToClvm, +{ + let transfer_program_puzzle = ctx.nft_royalty_transfer(); + + let transfer_program = CurriedProgram { + program: transfer_program_puzzle, + args: NftRoyaltyTransferPuzzleArgs { + singleton_struct: SingletonStruct { + mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), + launcher_id: nft_info.launcher_id, + launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + }, + royalty_puzzle_hash: nft_info.royalty_puzzle_hash, + trade_price_percentage: nft_info.royalty_percentage, + }, + }; + + let ownership_layer_spend = + spend_nft_ownership_layer(ctx, nft_info.current_owner, transfer_program, inner_spend)?; + + let state_layer_spend = spend_nft_state_layer( + ctx, + &nft_info.metadata, + nft_info.metadata_updater_hash, + ownership_layer_spend, + )?; + + spend_singleton( + ctx, + nft_info.coin.clone(), + nft_info.launcher_id, + nft_info.proof.clone(), + state_layer_spend, + ) +} + +pub fn spend_nft_state_layer( + ctx: &mut SpendContext, + metadata: M, + metadata_updater_puzzle_hash: Bytes32, + inner_spend: InnerSpend, +) -> Result +where + M: ToClvm, +{ + let nft_state_layer = ctx.nft_state_layer(); + + let puzzle = ctx.alloc(CurriedProgram { + program: nft_state_layer, + args: NftStateLayerArgs { + mod_hash: NFT_STATE_LAYER_PUZZLE_HASH.into(), + metadata, + metadata_updater_puzzle_hash, + inner_puzzle: inner_spend.puzzle(), + }, + })?; + + let solution = ctx.alloc(NftStateLayerSolution { + inner_solution: inner_spend.solution(), + })?; + + Ok(InnerSpend::new(puzzle, solution)) +} + +pub fn spend_nft_ownership_layer

( + ctx: &mut SpendContext, + current_owner: Option, + transfer_program: P, + inner_spend: InnerSpend, +) -> Result +where + P: ToClvm, +{ + let nft_ownership_layer = ctx.nft_ownership_layer(); + + let puzzle = ctx.alloc(CurriedProgram { + program: nft_ownership_layer, + args: NftOwnershipLayerArgs { + mod_hash: NFT_OWNERSHIP_LAYER_PUZZLE_HASH.into(), + current_owner, + transfer_program, + inner_puzzle: inner_spend.puzzle(), + }, + })?; + + let solution = ctx.alloc(NftOwnershipLayerSolution { + inner_solution: inner_spend.solution(), + })?; + + Ok(InnerSpend::new(puzzle, solution)) +} diff --git a/src/spends/puzzles/singleton.rs b/src/spends/puzzles/singleton.rs index 70cd8547..ae7e70ce 100644 --- a/src/spends/puzzles/singleton.rs +++ b/src/spends/puzzles/singleton.rs @@ -1,5 +1,7 @@ +mod intermediate_launcher; mod launch_singleton; mod singleton_spend; +pub use intermediate_launcher::*; pub use launch_singleton::*; pub use singleton_spend::*; diff --git a/src/spends/puzzles/singleton/intermediate_launcher.rs b/src/spends/puzzles/singleton/intermediate_launcher.rs new file mode 100644 index 00000000..9e185cbe --- /dev/null +++ b/src/spends/puzzles/singleton/intermediate_launcher.rs @@ -0,0 +1,65 @@ +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_wallet::{nft::NftIntermediateLauncherArgs, singleton::SINGLETON_LAUNCHER_PUZZLE_HASH}; +use clvm_utils::CurriedProgram; +use sha2::{Digest, Sha256}; + +use crate::{ + usize_to_bytes, AssertCoinAnnouncement, ChainedSpend, CreateCoinWithoutMemos, LaunchSingleton, + SpendContext, SpendError, +}; + +pub fn intermediate_launcher( + ctx: &mut SpendContext, + parent_coin_id: Bytes32, + index: usize, + total: usize, +) -> Result<(ChainedSpend, LaunchSingleton), SpendError> { + let mut coin_spends = Vec::new(); + let mut parent_conditions = Vec::new(); + + let intermediate_puzzle = ctx.nft_intermediate_launcher(); + + let puzzle = ctx.alloc(CurriedProgram { + program: intermediate_puzzle, + args: NftIntermediateLauncherArgs { + launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + mint_number: index, + mint_total: total, + }, + })?; + + let puzzle_hash = ctx.tree_hash(puzzle); + + parent_conditions.push(ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash, + amount: 0, + })?); + + let puzzle_reveal = ctx.serialize(puzzle)?; + let solution = ctx.serialize(())?; + + let intermediate_coin = Coin::new(parent_coin_id, puzzle_hash, 0); + let intermediate_id = intermediate_coin.coin_id(); + + coin_spends.push(CoinSpend::new(intermediate_coin, puzzle_reveal, solution)); + + let mut index_message = Sha256::new(); + index_message.update(usize_to_bytes(index)); + index_message.update(usize_to_bytes(total)); + + let mut announcement_id = Sha256::new(); + announcement_id.update(intermediate_id); + announcement_id.update(index_message.finalize()); + + parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { + announcement_id: Bytes32::new(announcement_id.finalize().into()), + })?); + + let chained_spend = ChainedSpend { + coin_spends, + parent_conditions, + }; + let launcher = LaunchSingleton::new(intermediate_id, 1); + + Ok((chained_spend, launcher)) +} From 3701e377f57a10f5e7a65c9cd01b491458ba2271 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Mon, 6 May 2024 17:22:38 -0400 Subject: [PATCH 13/25] Cleanup and improve DID spend logic --- src/spends/puzzles/cat/cat_spend.rs | 2 +- src/spends/puzzles/cat/issue_cat.rs | 3 +- src/spends/puzzles/did.rs | 11 +- src/spends/puzzles/did/create_did.rs | 51 ++--- src/spends/puzzles/did/did_info.rs | 1 + src/spends/puzzles/did/did_spend.rs | 175 +++++++++++++++++- src/spends/puzzles/nft/mint_nft.rs | 91 +++++---- src/spends/puzzles/offer.rs | 2 +- src/spends/puzzles/singleton.rs | 6 +- .../singleton/intermediate_launcher.rs | 171 ++++++++++++----- .../{launch_singleton.rs => launcher.rs} | 84 ++------- .../puzzles/singleton/spendable_launcher.rs | 76 ++++++++ src/spends/puzzles/standard.rs | 31 ++-- src/spends/spend_builder.rs | 15 ++ 14 files changed, 510 insertions(+), 209 deletions(-) rename src/spends/puzzles/singleton/{launch_singleton.rs => launcher.rs} (51%) create mode 100644 src/spends/puzzles/singleton/spendable_launcher.rs diff --git a/src/spends/puzzles/cat/cat_spend.rs b/src/spends/puzzles/cat/cat_spend.rs index afd1e3b9..4b478a3a 100644 --- a/src/spends/puzzles/cat/cat_spend.rs +++ b/src/spends/puzzles/cat/cat_spend.rs @@ -127,7 +127,7 @@ mod tests { use clvmr::{serde::node_to_bytes, Allocator}; use hex_literal::hex; - use crate::{testing::SECRET_KEY, CreateCoinWithoutMemos, StandardSpend}; + use crate::{testing::SECRET_KEY, Chainable, CreateCoinWithoutMemos, StandardSpend}; use super::*; diff --git a/src/spends/puzzles/cat/issue_cat.rs b/src/spends/puzzles/cat/issue_cat.rs index b6f31a23..adc3dda5 100644 --- a/src/spends/puzzles/cat/issue_cat.rs +++ b/src/spends/puzzles/cat/issue_cat.rs @@ -139,7 +139,8 @@ mod tests { use clvmr::Allocator; use crate::{ - testing::SECRET_KEY, CreateCoinWithMemos, RequiredSignature, StandardSpend, WalletSimulator, + testing::SECRET_KEY, Chainable, CreateCoinWithMemos, RequiredSignature, StandardSpend, + WalletSimulator, }; use super::*; diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index 7434dc12..165d7bce 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -8,6 +8,8 @@ pub use did_spend::*; #[cfg(test)] mod tests { + use super::*; + use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; use chia_wallet::{ @@ -17,12 +19,10 @@ mod tests { use clvmr::Allocator; use crate::{ - testing::SECRET_KEY, LaunchSingleton, RequiredSignature, SpendContext, StandardSpend, + testing::SECRET_KEY, Chainable, Launcher, RequiredSignature, SpendContext, StandardSpend, WalletSimulator, }; - use super::*; - #[tokio::test] async fn test_create_did() -> anyhow::Result<()> { let sim = WalletSimulator::new().await; @@ -37,8 +37,9 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let (launch_singleton, _did_info) = - LaunchSingleton::new(parent.coin_id(), 1).create_standard_did(&mut ctx, pk.clone())?; + let (launch_singleton, _did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk.clone())?; let coin_spends = StandardSpend::new() .chain(launch_singleton) diff --git a/src/spends/puzzles/did/create_did.rs b/src/spends/puzzles/did/create_did.rs index 4339da11..c65a2d63 100644 --- a/src/spends/puzzles/did/create_did.rs +++ b/src/spends/puzzles/did/create_did.rs @@ -1,18 +1,18 @@ use chia_bls::PublicKey; -use chia_protocol::{Bytes32, Coin}; +use chia_protocol::Bytes32; use chia_wallet::{ did::DID_INNER_PUZZLE_HASH, singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, standard::standard_puzzle_hash, - EveProof, LineageProof, Proof, + EveProof, Proof, }; use clvm_traits::ToClvm; use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; use clvmr::NodePtr; use crate::{ - spend_did, u64_to_bytes, ChainedSpend, CreateCoinWithMemos, DidInfo, LaunchSingleton, - SpendContext, SpendError, StandardSpend, + u64_to_bytes, ChainedSpend, DidInfo, SpendContext, SpendError, SpendableLauncher, + StandardDidSpend, }; pub trait CreateDid { @@ -23,7 +23,7 @@ pub trait CreateDid { recovery_did_list_hash: Bytes32, num_verifications_required: u64, metadata: M, - ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> + ) -> Result<(ChainedSpend, DidInfo), SpendError> where M: ToClvm; @@ -41,7 +41,7 @@ pub trait CreateDid { { let inner_puzzle_hash = standard_puzzle_hash(&synthetic_key).into(); - let (mut create_did, did_inner_puzzle_hash, mut did_info) = self.create_eve_did( + let (mut create_did, did_info) = self.create_eve_did( ctx, inner_puzzle_hash, recovery_did_list_hash, @@ -49,28 +49,12 @@ pub trait CreateDid { metadata, )?; - let (inner_spend, _) = StandardSpend::new() - .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash: did_inner_puzzle_hash, - amount: did_info.coin.amount, - memos: vec![inner_puzzle_hash.to_vec().into()], - })?) - .inner_spend(ctx, synthetic_key)?; - - let spend = spend_did(ctx, &did_info, inner_spend)?; - create_did.coin_spends.push(spend); - - did_info.proof = Proof::Lineage(LineageProof { - parent_coin_info: did_info.launcher_id, - inner_puzzle_hash: did_inner_puzzle_hash, - amount: did_info.coin.amount, - }); + let (coin_spends, did_info) = + StandardDidSpend::new() + .recreate() + .finish(ctx, synthetic_key, did_info)?; - did_info.coin = Coin::new( - did_info.coin.coin_id(), - did_info.coin.puzzle_hash, - did_info.coin.amount, - ); + create_did.coin_spends.extend(coin_spends); Ok((create_did, did_info)) } @@ -87,15 +71,15 @@ pub trait CreateDid { } } -impl CreateDid for LaunchSingleton { +impl CreateDid for SpendableLauncher { fn create_eve_did( self, ctx: &mut SpendContext, - inner_puzzle_hash: Bytes32, + owner_puzzle_hash: Bytes32, recovery_did_list_hash: Bytes32, num_verifications_required: u64, metadata: M, - ) -> Result<(ChainedSpend, Bytes32, DidInfo), SpendError> + ) -> Result<(ChainedSpend, DidInfo), SpendError> where M: ToClvm, { @@ -103,7 +87,7 @@ impl CreateDid for LaunchSingleton { let metadata_hash = ctx.tree_hash(metadata_ptr); let did_inner_puzzle_hash = did_inner_puzzle_hash( - inner_puzzle_hash, + owner_puzzle_hash, recovery_did_list_hash, num_verifications_required, self.coin().coin_id(), @@ -111,7 +95,7 @@ impl CreateDid for LaunchSingleton { ); let launcher_coin = self.coin().clone(); - let (chained_spend, eve_coin) = self.finish(ctx, did_inner_puzzle_hash, ())?; + let (chained_spend, eve_coin) = self.spend(ctx, did_inner_puzzle_hash, ())?; let proof = Proof::Eve(EveProof { parent_coin_info: launcher_coin.parent_coin_info, @@ -122,13 +106,14 @@ impl CreateDid for LaunchSingleton { launcher_id: launcher_coin.coin_id(), coin: eve_coin, did_inner_puzzle_hash, + owner_puzzle_hash, proof, recovery_did_list_hash, num_verifications_required, metadata, }; - Ok((chained_spend, did_inner_puzzle_hash, did_info)) + Ok((chained_spend, did_info)) } } diff --git a/src/spends/puzzles/did/did_info.rs b/src/spends/puzzles/did/did_info.rs index 1fabcf54..98563afc 100644 --- a/src/spends/puzzles/did/did_info.rs +++ b/src/spends/puzzles/did/did_info.rs @@ -6,6 +6,7 @@ pub struct DidInfo { pub launcher_id: Bytes32, pub coin: Coin, pub did_inner_puzzle_hash: Bytes32, + pub owner_puzzle_hash: Bytes32, pub proof: Proof, pub recovery_did_list_hash: Bytes32, pub num_verifications_required: u64, diff --git a/src/spends/puzzles/did/did_spend.rs b/src/spends/puzzles/did/did_spend.rs index 357faa8a..8c375fdc 100644 --- a/src/spends/puzzles/did/did_spend.rs +++ b/src/spends/puzzles/did/did_spend.rs @@ -1,15 +1,110 @@ -use chia_protocol::CoinSpend; +use chia_bls::PublicKey; +use chia_protocol::{Coin, CoinSpend}; use chia_wallet::{ did::{DidArgs, DidSolution}, singleton::{SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, + LineageProof, Proof, }; use clvm_traits::ToClvm; use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use crate::{spend_singleton, DidInfo, InnerSpend, SpendContext, SpendError}; +use crate::{ + spend_singleton, Chainable, ChainedSpend, CreateCoinWithMemos, DidInfo, InnerSpend, + SpendContext, SpendError, StandardSpend, +}; + +pub struct NoOutput; + +pub enum DidOutput { + Recreate, +} + +pub struct StandardDidSpend { + standard_spend: StandardSpend, + output: T, +} + +impl Default for StandardDidSpend { + fn default() -> Self { + Self { + output: NoOutput, + standard_spend: StandardSpend::new(), + } + } +} + +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<(Vec, DidInfo), SpendError> + where + M: ToClvm, + { + let create_coin = match self.output { + DidOutput::Recreate => CreateCoinWithMemos { + puzzle_hash: did_info.did_inner_puzzle_hash, + amount: did_info.coin.amount, + memos: vec![did_info.owner_puzzle_hash.to_vec().into()], + }, + }; + + let (inner_spend, mut coin_spends) = self + .standard_spend + .condition(ctx.alloc(create_coin)?) + .inner_spend(ctx, synthetic_key)?; + + coin_spends.push(raw_did_spend(ctx, &did_info, inner_spend)?); + + match self.output { + DidOutput::Recreate => { + did_info.proof = Proof::Lineage(LineageProof { + parent_coin_info: did_info.coin.parent_coin_info, + inner_puzzle_hash: did_info.did_inner_puzzle_hash, + 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((coin_spends, did_info)) + } +} + +impl Chainable for StandardDidSpend { + fn chain(mut self, chained_spend: ChainedSpend) -> Self { + self.standard_spend = self.standard_spend.chain(chained_spend); + self + } -pub fn spend_did( + fn condition(mut self, condition: NodePtr) -> Self { + self.standard_spend = self.standard_spend.condition(condition); + self + } +} + +pub fn raw_did_spend( ctx: &mut SpendContext, did_info: &DidInfo, inner_spend: InnerSpend, @@ -46,3 +141,77 @@ where did_spend, ) } + +#[cfg(test)] +mod tests { + use chia_bls::{sign, Signature}; + use chia_protocol::SpendBundle; + use chia_wallet::{ + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, + }; + use clvmr::Allocator; + + use crate::{testing::SECRET_KEY, CreateDid, Launcher, RequiredSignature, WalletSimulator}; + + use super::*; + + #[tokio::test] + async fn test_did_recreation() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let parent = sim.generate_coin(puzzle_hash, 1).await.coin; + + let (create_did, mut did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk.clone())?; + + let mut coin_spends = + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk.clone())?; + + for _ in 0..10 { + let (did_spends, new_did_info) = + StandardDidSpend::new() + .recreate() + .finish(&mut ctx, pk.clone(), did_info)?; + did_info = new_did_info; + coin_spends.extend(did_spends); + } + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?; + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let ack = peer + .send_transaction(SpendBundle::new(coin_spends, aggregated_signature)) + .await?; + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + let coin_state = peer + .register_for_coin_updates(vec![did_info.coin.coin_id()], 0) + .await? + .remove(0); + assert_eq!(coin_state.coin, did_info.coin); + + Ok(()) + } +} diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index 2756a2d4..5a0ee34b 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -15,8 +15,9 @@ use clvmr::NodePtr; use sha2::{Digest, Sha256}; use crate::{ - spend_nft, u16_to_bytes, AssertPuzzleAnnouncement, ChainedSpend, CreateCoinWithMemos, - LaunchSingleton, NewNftOwner, NftInfo, SpendContext, SpendError, StandardSpend, + spend_nft, u16_to_bytes, AssertPuzzleAnnouncement, Chainable, ChainedSpend, + CreateCoinWithMemos, CreatePuzzleAnnouncement, NewNftOwner, NftInfo, SpendContext, SpendError, + SpendableLauncher, StandardSpend, }; pub trait MintNft { @@ -25,18 +26,17 @@ pub trait MintNft { ctx: &mut SpendContext, inner_puzzle_hash: Bytes32, metadata: M, - metadata_updater_hash: Bytes32, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, ) -> Result<(ChainedSpend, Bytes32, NftInfo), SpendError> where M: ToClvm; + #[allow(clippy::too_many_arguments)] fn mint_custom_standard_nft( self, ctx: &mut SpendContext, metadata: M, - metadata_updater_hash: Bytes32, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, synthetic_key: PublicKey, @@ -53,7 +53,6 @@ pub trait MintNft { ctx, inner_puzzle_hash, metadata, - metadata_updater_hash, royalty_puzzle_hash, royalty_percentage, )?; @@ -120,7 +119,6 @@ pub trait MintNft { self.mint_custom_standard_nft( ctx, metadata, - NFT_METADATA_UPDATER_PUZZLE_HASH.into(), royalty_puzzle_hash.into(), royalty_percentage, synthetic_key, @@ -130,13 +128,12 @@ pub trait MintNft { } } -impl MintNft for LaunchSingleton { +impl MintNft for SpendableLauncher { fn mint_eve_nft( self, ctx: &mut SpendContext, inner_puzzle_hash: Bytes32, metadata: M, - metadata_updater_hash: Bytes32, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, ) -> Result<(ChainedSpend, Bytes32, NftInfo), SpendError> @@ -155,11 +152,20 @@ impl MintNft for LaunchSingleton { ), inner_puzzle_hash, ); - let nft_inner_puzzle_hash = - nft_state_layer_hash(metadata_hash, metadata_updater_hash, ownership_layer_hash); + let nft_inner_puzzle_hash = nft_state_layer_hash( + metadata_hash, + NFT_METADATA_UPDATER_PUZZLE_HASH.into(), + ownership_layer_hash, + ); let launcher_coin = self.coin().clone(); - let (chained_spend, eve_coin) = self.finish(ctx, nft_inner_puzzle_hash, ())?; + let (mut chained_spend, eve_coin) = self.spend(ctx, nft_inner_puzzle_hash, ())?; + + chained_spend + .parent_conditions + .push(ctx.alloc(CreatePuzzleAnnouncement { + message: launcher_coin.coin_id().to_vec().into(), + })?); let proof = Proof::Eve(EveProof { parent_coin_info: launcher_coin.parent_coin_info, @@ -171,7 +177,7 @@ impl MintNft for LaunchSingleton { coin: eve_coin, proof, metadata, - metadata_updater_hash, + metadata_updater_hash: NFT_METADATA_UPDATER_PUZZLE_HASH.into(), current_owner: None, royalty_puzzle_hash, royalty_percentage, @@ -270,8 +276,8 @@ mod tests { use clvmr::Allocator; use crate::{ - intermediate_launcher, spend_did, testing::SECRET_KEY, CreateDid, RequiredSignature, - WalletSimulator, + testing::SECRET_KEY, Chainable, CreateDid, IntermediateLauncher, Launcher, + RequiredSignature, StandardDidSpend, WalletSimulator, }; use super::*; @@ -291,37 +297,50 @@ mod tests { let parent = sim.generate_coin(puzzle_hash, 3).await.coin; - let (create_did, did_info) = - LaunchSingleton::new(parent.coin_id(), 1).create_standard_did(&mut ctx, pk.clone())?; + let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk.clone())?; let mut coin_spends = StandardSpend::new() .chain(create_did) .finish(&mut ctx, parent, pk.clone())?; - let (intermediate, launcher) = - intermediate_launcher(&mut ctx, did_info.coin.coin_id(), 0, 1)?; - - let (nft_mint, _nft_info) = launcher.mint_standard_nft( - &mut ctx, - (), - 100, - pk.clone(), - did_info.launcher_id, - did_info.did_inner_puzzle_hash, - )?; - - let (inner_spend, did_spends) = StandardSpend::new() - .chain(intermediate) - .chain(nft_mint) - .inner_spend(&mut ctx, pk)?; - - coin_spends.extend(did_spends); - coin_spends.push(spend_did(&mut ctx, &did_info, inner_spend)?); + let (did_coin_spends, did_info) = StandardDidSpend::new() + .chain( + IntermediateLauncher::new(did_info.coin.coin_id(), 0, 2) + .create(&mut ctx)? + .mint_standard_nft( + &mut ctx, + (), + 100, + pk.clone(), + did_info.launcher_id, + did_info.did_inner_puzzle_hash, + )? + .0, + ) + .chain( + IntermediateLauncher::new(did_info.coin.coin_id(), 1, 2) + .create(&mut ctx)? + .mint_standard_nft( + &mut ctx, + (), + 300, + pk.clone(), + did_info.launcher_id, + did_info.did_inner_puzzle_hash, + )? + .0, + ) + .recreate() + .finish(&mut ctx, pk, did_info)?; + + coin_spends.extend(did_coin_spends); let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, - &coin_spends, + dbg!(&coin_spends), WalletSimulator::AGG_SIG_ME.into(), )?; diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index a2a5513a..da2133b5 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -66,7 +66,7 @@ mod tests { use clvmr::Allocator; use crate::{ - testing::SECRET_KEY, AssertPuzzleAnnouncement, CatSpend, CreateCoinWithMemos, + testing::SECRET_KEY, AssertPuzzleAnnouncement, CatSpend, Chainable, CreateCoinWithMemos, CreateCoinWithoutMemos, InnerSpend, IssueCat, RequiredSignature, SpendContext, StandardSpend, WalletSimulator, }; diff --git a/src/spends/puzzles/singleton.rs b/src/spends/puzzles/singleton.rs index ae7e70ce..0c6b4e1d 100644 --- a/src/spends/puzzles/singleton.rs +++ b/src/spends/puzzles/singleton.rs @@ -1,7 +1,9 @@ mod intermediate_launcher; -mod launch_singleton; +mod launcher; mod singleton_spend; +mod spendable_launcher; pub use intermediate_launcher::*; -pub use launch_singleton::*; +pub use launcher::*; pub use singleton_spend::*; +pub use spendable_launcher::*; diff --git a/src/spends/puzzles/singleton/intermediate_launcher.rs b/src/spends/puzzles/singleton/intermediate_launcher.rs index 9e185cbe..f674dc36 100644 --- a/src/spends/puzzles/singleton/intermediate_launcher.rs +++ b/src/spends/puzzles/singleton/intermediate_launcher.rs @@ -1,65 +1,144 @@ use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::{nft::NftIntermediateLauncherArgs, singleton::SINGLETON_LAUNCHER_PUZZLE_HASH}; -use clvm_utils::CurriedProgram; +use chia_wallet::{ + nft::{NftIntermediateLauncherArgs, NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH}, + singleton::SINGLETON_LAUNCHER_PUZZLE_HASH, +}; +use clvm_utils::{curry_tree_hash, tree_hash_atom, CurriedProgram}; use sha2::{Digest, Sha256}; use crate::{ - usize_to_bytes, AssertCoinAnnouncement, ChainedSpend, CreateCoinWithoutMemos, LaunchSingleton, - SpendContext, SpendError, + usize_to_bytes, AssertCoinAnnouncement, ChainedSpend, CreateCoinWithoutMemos, SpendContext, + SpendError, SpendableLauncher, }; -pub fn intermediate_launcher( - ctx: &mut SpendContext, - parent_coin_id: Bytes32, - index: usize, - total: usize, -) -> Result<(ChainedSpend, LaunchSingleton), SpendError> { - let mut coin_spends = Vec::new(); - let mut parent_conditions = Vec::new(); +pub struct IntermediateLauncher { + mint_number: usize, + mint_total: usize, + intermediate_coin: Coin, + launcher_coin: Coin, +} + +impl IntermediateLauncher { + pub fn new(parent_coin_id: Bytes32, mint_number: usize, mint_total: usize) -> Self { + let puzzle_hash = intermediate_launcher_puzzle_hash(mint_number, mint_total); + let intermediate_coin = Coin::new(parent_coin_id, puzzle_hash, 0); + let launcher_coin = Coin::new( + intermediate_coin.coin_id(), + SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + 1, + ); + Self { + mint_number, + mint_total, + intermediate_coin, + launcher_coin, + } + } + + pub fn intermediate_coin(&self) -> &Coin { + &self.intermediate_coin + } + + pub fn launcher_coin(&self) -> &Coin { + &self.launcher_coin + } + + pub fn create(self, ctx: &mut SpendContext) -> Result { + let mut coin_spends = Vec::new(); + let mut parent_conditions = Vec::new(); + + let intermediate_puzzle = ctx.nft_intermediate_launcher(); + + let puzzle = ctx.alloc(CurriedProgram { + program: intermediate_puzzle, + args: NftIntermediateLauncherArgs { + launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + mint_number: self.mint_number, + mint_total: self.mint_total, + }, + })?; + + let puzzle_hash = ctx.tree_hash(puzzle); - let intermediate_puzzle = ctx.nft_intermediate_launcher(); + parent_conditions.push(ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash, + amount: 0, + })?); - let puzzle = ctx.alloc(CurriedProgram { - program: intermediate_puzzle, - args: NftIntermediateLauncherArgs { - launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - mint_number: index, - mint_total: total, - }, - })?; + let puzzle_reveal = ctx.serialize(puzzle)?; + let solution = ctx.serialize(())?; - let puzzle_hash = ctx.tree_hash(puzzle); + let intermediate_id = self.intermediate_coin.coin_id(); - parent_conditions.push(ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash, - amount: 0, - })?); + coin_spends.push(CoinSpend::new( + self.intermediate_coin, + puzzle_reveal, + solution, + )); + + let mut index_message = Sha256::new(); + index_message.update(usize_to_bytes(self.mint_number)); + index_message.update(usize_to_bytes(self.mint_total)); + + let mut announcement_id = Sha256::new(); + announcement_id.update(intermediate_id); + announcement_id.update(index_message.finalize()); + + parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { + announcement_id: Bytes32::new(announcement_id.finalize().into()), + })?); + + let chained_spend = ChainedSpend { + coin_spends, + parent_conditions, + }; + + Ok(SpendableLauncher::new(self.launcher_coin, chained_spend)) + } +} + +fn intermediate_launcher_puzzle_hash(mint_number: usize, mint_total: usize) -> Bytes32 { + let launcher_hash = tree_hash_atom(&SINGLETON_LAUNCHER_PUZZLE_HASH); + let mint_number_hash = tree_hash_atom(&usize_to_bytes(mint_number)); + let mint_total_hash = tree_hash_atom(&usize_to_bytes(mint_total)); + + curry_tree_hash( + NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, + &[launcher_hash, mint_number_hash, mint_total_hash], + ) + .into() +} - let puzzle_reveal = ctx.serialize(puzzle)?; - let solution = ctx.serialize(())?; +#[cfg(test)] +mod tests { + use clvmr::Allocator; - let intermediate_coin = Coin::new(parent_coin_id, puzzle_hash, 0); - let intermediate_id = intermediate_coin.coin_id(); + use super::*; - coin_spends.push(CoinSpend::new(intermediate_coin, puzzle_reveal, solution)); + #[test] + fn test_intermediate_hash() { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); - let mut index_message = Sha256::new(); - index_message.update(usize_to_bytes(index)); - index_message.update(usize_to_bytes(total)); + let mint_number = 3; + let mint_total = 4; - let mut announcement_id = Sha256::new(); - announcement_id.update(intermediate_id); - announcement_id.update(index_message.finalize()); + let intermediate_launcher_puzzle = ctx.nft_intermediate_launcher(); - parent_conditions.push(ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize().into()), - })?); + let puzzle = ctx + .alloc(CurriedProgram { + program: intermediate_launcher_puzzle, + args: NftIntermediateLauncherArgs { + launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + mint_number, + mint_total, + }, + }) + .unwrap(); + let allocated_puzzle_hash = ctx.tree_hash(puzzle); - let chained_spend = ChainedSpend { - coin_spends, - parent_conditions, - }; - let launcher = LaunchSingleton::new(intermediate_id, 1); + let puzzle_hash = intermediate_launcher_puzzle_hash(mint_number, mint_total); - Ok((chained_spend, launcher)) + assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); + } } diff --git a/src/spends/puzzles/singleton/launch_singleton.rs b/src/spends/puzzles/singleton/launcher.rs similarity index 51% rename from src/spends/puzzles/singleton/launch_singleton.rs rename to src/spends/puzzles/singleton/launcher.rs index 416940a3..9d132cde 100644 --- a/src/spends/puzzles/singleton/launch_singleton.rs +++ b/src/spends/puzzles/singleton/launcher.rs @@ -1,21 +1,14 @@ -use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::singleton::{ - LauncherSolution, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, -}; -use clvm_traits::{clvm_list, ToClvm}; +use chia_protocol::{Bytes32, Coin}; +use chia_wallet::singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}; use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; -use clvmr::NodePtr; -use sha2::{digest::FixedOutput, Digest, Sha256}; -use crate::{ - AssertCoinAnnouncement, ChainedSpend, CreateCoinWithoutMemos, SpendContext, SpendError, -}; +use crate::{ChainedSpend, CreateCoinWithoutMemos, SpendContext, SpendError, SpendableLauncher}; -pub struct LaunchSingleton { +pub struct Launcher { coin: Coin, } -impl LaunchSingleton { +impl Launcher { pub fn new(parent_coin_id: Bytes32, amount: u64) -> Self { Self { coin: Coin::new( @@ -30,58 +23,19 @@ impl LaunchSingleton { &self.coin } - pub fn finish( - self, - ctx: &mut SpendContext, - singleton_inner_puzzle_hash: Bytes32, - key_value_list: T, - ) -> Result<(ChainedSpend, Coin), SpendError> - where - T: ToClvm, - { - let create_launcher = ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - amount: self.coin.amount, - })?; - - let singleton_puzzle_hash = - singleton_puzzle_hash(self.coin.coin_id(), singleton_inner_puzzle_hash); - - let eve_message = ctx.alloc(clvm_list!( - singleton_puzzle_hash, - self.coin.amount, - &key_value_list - ))?; - let eve_message_hash = ctx.tree_hash(eve_message); - - let mut announcement_id = Sha256::new(); - announcement_id.update(self.coin.coin_id()); - announcement_id.update(eve_message_hash); - - let assert_announcement = ctx.alloc(AssertCoinAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize_fixed().into()), - })?; - - let launcher = ctx.singleton_launcher(); - let puzzle_reveal = ctx.serialize(launcher)?; - - let solution = ctx.serialize(LauncherSolution { - singleton_puzzle_hash, - amount: self.coin.amount, - key_value_list, - })?; - - let spend_launcher = CoinSpend::new(self.coin.clone(), puzzle_reveal, solution); - - let chained_spend = ChainedSpend { - coin_spends: vec![spend_launcher], - parent_conditions: vec![create_launcher, assert_announcement], - }; - - let singleton_coin = - Coin::new(self.coin.coin_id(), singleton_puzzle_hash, self.coin.amount); - - Ok((chained_spend, singleton_coin)) + pub fn create(self, ctx: &mut SpendContext) -> Result { + let amount = self.coin.amount; + + Ok(SpendableLauncher::new( + self.coin, + ChainedSpend { + parent_conditions: vec![ctx.alloc(CreateCoinWithoutMemos { + puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), + amount, + })?], + coin_spends: Vec::new(), + }, + )) } } @@ -109,6 +63,8 @@ mod tests { use clvm_utils::CurriedProgram; use clvmr::Allocator; + use crate::SpendContext; + use super::*; #[test] diff --git a/src/spends/puzzles/singleton/spendable_launcher.rs b/src/spends/puzzles/singleton/spendable_launcher.rs new file mode 100644 index 00000000..fef8636e --- /dev/null +++ b/src/spends/puzzles/singleton/spendable_launcher.rs @@ -0,0 +1,76 @@ +use chia_protocol::{Bytes32, Coin, CoinSpend}; +use chia_wallet::singleton::LauncherSolution; +use clvm_traits::{clvm_list, ToClvm}; +use clvmr::NodePtr; +use sha2::{digest::FixedOutput, Digest, Sha256}; + +use crate::{ + singleton_puzzle_hash, AssertCoinAnnouncement, ChainedSpend, SpendContext, SpendError, +}; + +#[must_use = "Launcher coins must be spent in order to create the singleton output."] +pub struct SpendableLauncher { + coin: Coin, + chained_spend: ChainedSpend, +} + +impl SpendableLauncher { + pub(crate) fn new(coin: Coin, chained_spend: ChainedSpend) -> Self { + Self { + coin, + chained_spend, + } + } + + pub fn coin(&self) -> &Coin { + &self.coin + } + + pub fn spend( + self, + ctx: &mut SpendContext, + singleton_inner_puzzle_hash: Bytes32, + key_value_list: T, + ) -> Result<(ChainedSpend, Coin), SpendError> + where + T: ToClvm, + { + let singleton_puzzle_hash = + singleton_puzzle_hash(self.coin.coin_id(), singleton_inner_puzzle_hash); + + let eve_message = ctx.alloc(clvm_list!( + singleton_puzzle_hash, + self.coin.amount, + &key_value_list + ))?; + let eve_message_hash = ctx.tree_hash(eve_message); + + let mut announcement_id = Sha256::new(); + announcement_id.update(self.coin.coin_id()); + announcement_id.update(eve_message_hash); + + let assert_announcement = ctx.alloc(AssertCoinAnnouncement { + announcement_id: Bytes32::new(announcement_id.finalize_fixed().into()), + })?; + + let launcher = ctx.singleton_launcher(); + let puzzle_reveal = ctx.serialize(launcher)?; + + let solution = ctx.serialize(LauncherSolution { + singleton_puzzle_hash, + amount: self.coin.amount, + key_value_list, + })?; + + let spend_launcher = CoinSpend::new(self.coin.clone(), puzzle_reveal, solution); + + let mut chained_spend = self.chained_spend; + chained_spend.coin_spends.push(spend_launcher); + chained_spend.parent_conditions.push(assert_announcement); + + let singleton_coin = + Coin::new(self.coin.coin_id(), singleton_puzzle_hash, self.coin.amount); + + Ok((chained_spend, singleton_coin)) + } +} diff --git a/src/spends/puzzles/standard.rs b/src/spends/puzzles/standard.rs index f602087f..e9661402 100644 --- a/src/spends/puzzles/standard.rs +++ b/src/spends/puzzles/standard.rs @@ -5,7 +5,7 @@ use clvm_traits::clvm_quote; use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use crate::{ChainedSpend, InnerSpend, SpendContext, SpendError}; +use crate::{Chainable, ChainedSpend, InnerSpend, SpendContext, SpendError}; #[derive(Default)] pub struct StandardSpend { @@ -35,22 +35,6 @@ impl StandardSpend { Ok((InnerSpend::new(puzzle, solution), self.coin_spends)) } - pub fn chain(mut self, chained_spend: ChainedSpend) -> Self { - self.coin_spends.extend(chained_spend.coin_spends); - self.conditions.extend(chained_spend.parent_conditions); - self - } - - pub fn condition(mut self, condition: NodePtr) -> Self { - self.conditions.push(condition); - self - } - - pub fn conditions(mut self, conditions: impl IntoIterator) -> Self { - self.conditions.extend(conditions); - self - } - pub fn finish( self, ctx: &mut SpendContext, @@ -67,6 +51,19 @@ impl StandardSpend { } } +impl Chainable for StandardSpend { + fn chain(mut self, chained_spend: ChainedSpend) -> Self { + self.coin_spends.extend(chained_spend.coin_spends); + self.conditions.extend(chained_spend.parent_conditions); + self + } + + fn condition(mut self, condition: NodePtr) -> Self { + self.conditions.push(condition); + self + } +} + /// Constructs a solution for the standard puzzle, given a list of condition. /// This assumes no hidden puzzle is being used in this spend. pub fn standard_solution(conditions: T) -> StandardSolution<(u8, T), ()> { diff --git a/src/spends/spend_builder.rs b/src/spends/spend_builder.rs index 587d07b1..abbd36c4 100644 --- a/src/spends/spend_builder.rs +++ b/src/spends/spend_builder.rs @@ -1,6 +1,21 @@ use chia_protocol::CoinSpend; use clvmr::NodePtr; +pub trait Chainable { + fn condition(self, condition: NodePtr) -> Self; + fn chain(self, other: ChainedSpend) -> Self; + + fn conditions(mut self, conditions: impl IntoIterator) -> Self + where + Self: Sized, + { + for condition in conditions { + self = self.condition(condition); + } + self + } +} + #[derive(Debug, Clone)] pub struct ChainedSpend { pub coin_spends: Vec, From ccde7cb17574f2961c96e258fb5fc75e298948a3 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Mon, 6 May 2024 17:22:50 -0400 Subject: [PATCH 14/25] Remove warning --- src/spends/puzzles/nft/mint_nft.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index 5a0ee34b..c947177e 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -306,7 +306,7 @@ mod tests { .chain(create_did) .finish(&mut ctx, parent, pk.clone())?; - let (did_coin_spends, did_info) = StandardDidSpend::new() + let (did_coin_spends, _did_info) = StandardDidSpend::new() .chain( IntermediateLauncher::new(did_info.coin.coin_id(), 0, 2) .create(&mut ctx)? From 91108e38848ec7f29a0c1320620bf66c1a8788c6 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 7 May 2024 19:50:01 -0400 Subject: [PATCH 15/25] NFT spend overhaul --- src/spends/puzzles/did/did_spend.rs | 8 +- src/spends/puzzles/nft/mint_nft.rs | 151 ++++-------- src/spends/puzzles/nft/nft_info.rs | 2 + src/spends/puzzles/nft/nft_spend.rs | 294 ++++++++++++++++++++++- src/spends/puzzles/singleton/launcher.rs | 11 +- src/spends/spend_builder.rs | 13 +- 6 files changed, 362 insertions(+), 117 deletions(-) diff --git a/src/spends/puzzles/did/did_spend.rs b/src/spends/puzzles/did/did_spend.rs index 8c375fdc..9134f9d8 100644 --- a/src/spends/puzzles/did/did_spend.rs +++ b/src/spends/puzzles/did/did_spend.rs @@ -14,7 +14,7 @@ use crate::{ SpendContext, SpendError, StandardSpend, }; -pub struct NoOutput; +pub struct NoDidOutput; pub enum DidOutput { Recreate, @@ -25,16 +25,16 @@ pub struct StandardDidSpend { output: T, } -impl Default for StandardDidSpend { +impl Default for StandardDidSpend { fn default() -> Self { Self { - output: NoOutput, + output: NoDidOutput, standard_spend: StandardSpend::new(), } } } -impl StandardDidSpend { +impl StandardDidSpend { pub fn new() -> Self { Self::default() } diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index c947177e..b814acd5 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -1,5 +1,5 @@ use chia_bls::PublicKey; -use chia_protocol::{Bytes32, Coin}; +use chia_protocol::Bytes32; use chia_wallet::{ nft::{ NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, @@ -7,19 +7,28 @@ use chia_wallet::{ }, singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, standard::standard_puzzle_hash, - EveProof, LineageProof, Proof, + EveProof, Proof, }; -use clvm_traits::{clvm_list, ToClvm}; +use clvm_traits::ToClvm; use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; use clvmr::NodePtr; -use sha2::{Digest, Sha256}; use crate::{ - spend_nft, u16_to_bytes, AssertPuzzleAnnouncement, Chainable, ChainedSpend, - CreateCoinWithMemos, CreatePuzzleAnnouncement, NewNftOwner, NftInfo, SpendContext, SpendError, - SpendableLauncher, StandardSpend, + u16_to_bytes, ChainedSpend, CreatePuzzleAnnouncement, NftInfo, SpendContext, SpendError, + SpendableLauncher, StandardNftSpend, }; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StandardMint { + pub metadata: M, + pub royalty_puzzle_hash: Bytes32, + pub royalty_percentage: u16, + pub synthetic_key: PublicKey, + pub owner_puzzle_hash: Bytes32, + pub did_id: Bytes32, + pub did_inner_puzzle_hash: Bytes32, +} + pub trait MintNft { fn mint_eve_nft( self, @@ -28,104 +37,38 @@ pub trait MintNft { metadata: M, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, - ) -> Result<(ChainedSpend, Bytes32, NftInfo), SpendError> + ) -> Result<(ChainedSpend, NftInfo), SpendError> where M: ToClvm; - #[allow(clippy::too_many_arguments)] - fn mint_custom_standard_nft( + fn mint_standard_nft( self, ctx: &mut SpendContext, - metadata: M, - royalty_puzzle_hash: Bytes32, - royalty_percentage: u16, - synthetic_key: PublicKey, - did_id: Bytes32, - did_inner_puzzle_hash: Bytes32, + mint: StandardMint, ) -> Result<(ChainedSpend, NftInfo), SpendError> where M: ToClvm, Self: Sized, { - let inner_puzzle_hash = standard_puzzle_hash(&synthetic_key).into(); + let inner_puzzle_hash = standard_puzzle_hash(&mint.synthetic_key).into(); - let (mut mint_nft, nft_inner_puzzle_hash, mut nft_info) = self.mint_eve_nft( + let (mut mint_nft, nft_info) = self.mint_eve_nft( ctx, inner_puzzle_hash, - metadata, - royalty_puzzle_hash, - royalty_percentage, + mint.metadata, + mint.royalty_puzzle_hash, + mint.royalty_percentage, )?; - let (inner_spend, _) = StandardSpend::new() - .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash: inner_puzzle_hash, - amount: nft_info.coin.amount, - memos: vec![inner_puzzle_hash.to_vec().into()], - })?) - .condition(ctx.alloc(NewNftOwner { - new_owner: Some(did_id), - trade_prices_list: Vec::new(), - new_did_inner_hash: Some(did_inner_puzzle_hash), - })?) - .inner_spend(ctx, synthetic_key)?; - - let new_nft_owner_args = ctx.alloc(clvm_list!(did_id, (), did_inner_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)); - - mint_nft - .parent_conditions - .push(ctx.alloc(AssertPuzzleAnnouncement { - announcement_id: Bytes32::new(announcement_id.finalize().into()), - })?); + let (nft_spend, nft_info) = StandardNftSpend::new() + .new_owner(mint.did_id, mint.did_inner_puzzle_hash) + .transfer(mint.owner_puzzle_hash) + .finish(ctx, mint.synthetic_key, nft_info)?; - let spend = spend_nft(ctx, &nft_info, inner_spend)?; - mint_nft.coin_spends.push(spend); - - nft_info.proof = Proof::Lineage(LineageProof { - parent_coin_info: nft_info.launcher_id, - inner_puzzle_hash: nft_inner_puzzle_hash, - amount: nft_info.coin.amount, - }); - - nft_info.coin = Coin::new( - nft_info.coin.coin_id(), - nft_info.coin.puzzle_hash, - nft_info.coin.amount, - ); + mint_nft.extend(nft_spend); Ok((mint_nft, nft_info)) } - - fn mint_standard_nft( - self, - ctx: &mut SpendContext, - metadata: M, - royalty_percentage: u16, - synthetic_key: PublicKey, - did_id: Bytes32, - did_inner_puzzle_hash: Bytes32, - ) -> Result<(ChainedSpend, NftInfo), SpendError> - where - M: ToClvm, - Self: Sized, - { - let royalty_puzzle_hash = standard_puzzle_hash(&synthetic_key); - - self.mint_custom_standard_nft( - ctx, - metadata, - royalty_puzzle_hash.into(), - royalty_percentage, - synthetic_key, - did_id, - did_inner_puzzle_hash, - ) - } } impl MintNft for SpendableLauncher { @@ -136,7 +79,7 @@ impl MintNft for SpendableLauncher { metadata: M, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, - ) -> Result<(ChainedSpend, Bytes32, NftInfo), SpendError> + ) -> Result<(ChainedSpend, NftInfo), SpendError> where M: ToClvm, { @@ -175,6 +118,8 @@ impl MintNft for SpendableLauncher { let nft_info = NftInfo { launcher_id: launcher_coin.coin_id(), coin: eve_coin, + nft_inner_puzzle_hash, + owner_puzzle_hash: inner_puzzle_hash, proof, metadata, metadata_updater_hash: NFT_METADATA_UPDATER_PUZZLE_HASH.into(), @@ -183,7 +128,7 @@ impl MintNft for SpendableLauncher { royalty_percentage, }; - Ok((chained_spend, nft_inner_puzzle_hash, nft_info)) + Ok((chained_spend, nft_info)) } } @@ -277,7 +222,7 @@ mod tests { use crate::{ testing::SECRET_KEY, Chainable, CreateDid, IntermediateLauncher, Launcher, - RequiredSignature, StandardDidSpend, WalletSimulator, + RequiredSignature, StandardDidSpend, StandardSpend, WalletSimulator, }; use super::*; @@ -306,31 +251,27 @@ mod tests { .chain(create_did) .finish(&mut ctx, parent, pk.clone())?; + let mint = StandardMint { + metadata: (), + royalty_puzzle_hash: puzzle_hash, + royalty_percentage: 100, + owner_puzzle_hash: puzzle_hash, + synthetic_key: pk.clone(), + did_id: did_info.launcher_id, + did_inner_puzzle_hash: did_info.did_inner_puzzle_hash, + }; + let (did_coin_spends, _did_info) = StandardDidSpend::new() .chain( IntermediateLauncher::new(did_info.coin.coin_id(), 0, 2) .create(&mut ctx)? - .mint_standard_nft( - &mut ctx, - (), - 100, - pk.clone(), - did_info.launcher_id, - did_info.did_inner_puzzle_hash, - )? + .mint_standard_nft(&mut ctx, mint.clone())? .0, ) .chain( IntermediateLauncher::new(did_info.coin.coin_id(), 1, 2) .create(&mut ctx)? - .mint_standard_nft( - &mut ctx, - (), - 300, - pk.clone(), - did_info.launcher_id, - did_info.did_inner_puzzle_hash, - )? + .mint_standard_nft(&mut ctx, mint.clone())? .0, ) .recreate() diff --git a/src/spends/puzzles/nft/nft_info.rs b/src/spends/puzzles/nft/nft_info.rs index be559be7..43647e83 100644 --- a/src/spends/puzzles/nft/nft_info.rs +++ b/src/spends/puzzles/nft/nft_info.rs @@ -5,6 +5,8 @@ use chia_wallet::Proof; pub struct NftInfo { pub launcher_id: Bytes32, pub coin: Coin, + pub nft_inner_puzzle_hash: Bytes32, + pub owner_puzzle_hash: Bytes32, pub proof: Proof, pub metadata: M, pub metadata_updater_hash: Bytes32, diff --git a/src/spends/puzzles/nft/nft_spend.rs b/src/spends/puzzles/nft/nft_spend.rs index 40162dd2..decc6524 100644 --- a/src/spends/puzzles/nft/nft_spend.rs +++ b/src/spends/puzzles/nft/nft_spend.rs @@ -1,4 +1,5 @@ -use chia_protocol::{Bytes32, CoinSpend}; +use chia_bls::PublicKey; +use chia_protocol::{Bytes32, Coin, CoinSpend}; use chia_wallet::{ nft::{ NftOwnershipLayerArgs, NftOwnershipLayerSolution, NftRoyaltyTransferPuzzleArgs, @@ -6,14 +7,184 @@ use chia_wallet::{ NFT_STATE_LAYER_PUZZLE_HASH, }, singleton::{SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, + LineageProof, Proof, }; -use clvm_traits::ToClvm; +use clvm_traits::{clvm_list, ToClvm}; use clvm_utils::CurriedProgram; use clvmr::NodePtr; +use sha2::{Digest, Sha256}; -use crate::{spend_singleton, InnerSpend, NftInfo, SpendContext, SpendError}; +use crate::{ + nft_ownership_layer_hash, nft_royalty_transfer_hash, nft_state_layer_hash, + singleton_puzzle_hash, spend_singleton, AssertPuzzleAnnouncement, Chainable, ChainedSpend, + CreateCoinWithMemos, InnerSpend, NewNftOwner, NftInfo, SpendContext, SpendError, StandardSpend, +}; + +pub struct NoNftOutput; + +pub enum NftOutput { + SamePuzzleHash, + NewPuzzleHash { puzzle_hash: Bytes32 }, +} + +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, + } + } +} + +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_inner_hash: Some(did_inner_puzzle_hash), + }); + self + } +} + +impl StandardNftSpend { + pub fn finish( + mut self, + ctx: &mut SpendContext, + synthetic_key: PublicKey, + mut nft_info: NftInfo, + ) -> Result<(ChainedSpend, NftInfo), SpendError> + where + M: ToClvm, + { + let mut chained_spend = ChainedSpend { + parent_conditions: Vec::new(), + coin_spends: Vec::new(), + }; + + let owner_puzzle_hash = match self.output { + NftOutput::SamePuzzleHash => nft_info.owner_puzzle_hash, + NftOutput::NewPuzzleHash { puzzle_hash } => puzzle_hash, + }; + + if let Some(new_owner) = &self.new_owner { + self.standard_spend = self.standard_spend.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_inner_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)); + + chained_spend + .parent_conditions + .push(ctx.alloc(AssertPuzzleAnnouncement { + announcement_id: Bytes32::new(announcement_id.finalize().into()), + })?); + } + + let (inner_spend, coin_spends) = self + .standard_spend + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash: owner_puzzle_hash, + amount: nft_info.coin.amount, + memos: vec![owner_puzzle_hash.to_vec().into()], + })?) + .inner_spend(ctx, synthetic_key)?; + + chained_spend.coin_spends.extend(coin_spends); + chained_spend + .coin_spends + .push(raw_nft_spend(ctx, &nft_info, inner_spend)?); + + nft_info.current_owner = self + .new_owner + .map(|value| value.new_owner) + .unwrap_or(nft_info.current_owner); + + let metadata_ptr = ctx.alloc(&nft_info.metadata)?; + + let new_inner_puzzle_hash = nft_state_layer_hash( + ctx.tree_hash(metadata_ptr), + nft_info.metadata_updater_hash, + nft_ownership_layer_hash( + nft_info.current_owner, + nft_royalty_transfer_hash( + nft_info.launcher_id, + nft_info.royalty_puzzle_hash, + nft_info.royalty_percentage, + ), + owner_puzzle_hash, + ), + ); + + let new_puzzle_hash = singleton_puzzle_hash(nft_info.launcher_id, new_inner_puzzle_hash); + + nft_info.proof = Proof::Lineage(LineageProof { + parent_coin_info: nft_info.coin.parent_coin_info, + inner_puzzle_hash: nft_info.nft_inner_puzzle_hash, + amount: nft_info.coin.amount, + }); + + nft_info.coin = Coin::new( + nft_info.coin.coin_id(), + new_puzzle_hash, + nft_info.coin.amount, + ); + + nft_info.nft_inner_puzzle_hash = new_inner_puzzle_hash; + + Ok((chained_spend, nft_info)) + } +} -pub fn spend_nft( +impl Chainable for StandardNftSpend { + fn chain(mut self, chained_spend: ChainedSpend) -> Self { + self.standard_spend = self.standard_spend.chain(chained_spend); + self + } + + fn condition(mut self, condition: NodePtr) -> Self { + self.standard_spend = self.standard_spend.condition(condition); + self + } +} + +pub fn raw_nft_spend( ctx: &mut SpendContext, nft_info: &NftInfo, inner_spend: InnerSpend, @@ -110,3 +281,118 @@ where Ok(InnerSpend::new(puzzle, solution)) } + +#[cfg(test)] +mod tests { + use super::*; + + use chia_bls::{sign, Signature}; + use chia_protocol::SpendBundle; + use chia_wallet::{ + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, + }; + use clvmr::Allocator; + + use crate::{ + testing::SECRET_KEY, CreateDid, IntermediateLauncher, Launcher, MintNft, RequiredSignature, + StandardDidSpend, StandardMint, WalletSimulator, + }; + + #[tokio::test] + async fn test_nft_lineage() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let parent = sim.generate_coin(puzzle_hash, 2).await.coin; + + let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk.clone())?; + + let mut coin_spends = + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk.clone())?; + + let (mint_nft, mut nft_info) = IntermediateLauncher::new(did_info.coin.coin_id(), 0, 1) + .create(&mut ctx)? + .mint_standard_nft( + &mut ctx, + StandardMint { + metadata: (), + royalty_puzzle_hash: puzzle_hash, + royalty_percentage: 300, + synthetic_key: pk.clone(), + owner_puzzle_hash: puzzle_hash, + did_id: did_info.launcher_id, + did_inner_puzzle_hash: did_info.did_inner_puzzle_hash, + }, + )?; + + let (did_spends, mut did_info) = StandardDidSpend::new() + .chain(mint_nft) + .recreate() + .finish(&mut ctx, pk.clone(), did_info)?; + + coin_spends.extend(did_spends); + + 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(&mut ctx, pk.clone(), nft_info)?; + + nft_info = new_nft_info; + + let (did_spends, new_did_info) = StandardDidSpend::new() + .chain(nft_spend) + .recreate() + .finish(&mut ctx, pk.clone(), did_info)?; + did_info = new_did_info; + coin_spends.extend(did_spends); + } + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?; + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let ack = peer + .send_transaction(SpendBundle::new(coin_spends, aggregated_signature)) + .await?; + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + let coin_state = peer + .register_for_coin_updates(vec![did_info.coin.coin_id()], 0) + .await? + .remove(0); + assert_eq!(coin_state.coin, did_info.coin); + + let coin_state = peer + .register_for_coin_updates(vec![nft_info.coin.coin_id()], 0) + .await? + .remove(0); + assert_eq!(coin_state.coin, nft_info.coin); + + Ok(()) + } +} diff --git a/src/spends/puzzles/singleton/launcher.rs b/src/spends/puzzles/singleton/launcher.rs index 9d132cde..9c0faac8 100644 --- a/src/spends/puzzles/singleton/launcher.rs +++ b/src/spends/puzzles/singleton/launcher.rs @@ -39,17 +39,22 @@ impl Launcher { } } -pub fn singleton_puzzle_hash(launcher_id: Bytes32, inner_puzzle_hash: Bytes32) -> Bytes32 { +pub fn singleton_struct_hash(launcher_id: Bytes32) -> Bytes32 { let singleton_hash = tree_hash_atom(&SINGLETON_TOP_LAYER_PUZZLE_HASH); let launcher_id_hash = tree_hash_atom(&launcher_id); let launcher_puzzle_hash = tree_hash_atom(&SINGLETON_LAUNCHER_PUZZLE_HASH); let pair = tree_hash_pair(launcher_id_hash, launcher_puzzle_hash); - let singleton_struct_hash = tree_hash_pair(singleton_hash, pair); + tree_hash_pair(singleton_hash, pair).into() +} +pub fn singleton_puzzle_hash(launcher_id: Bytes32, inner_puzzle_hash: Bytes32) -> Bytes32 { curry_tree_hash( SINGLETON_TOP_LAYER_PUZZLE_HASH, - &[singleton_struct_hash, inner_puzzle_hash.into()], + &[ + singleton_struct_hash(launcher_id).into(), + inner_puzzle_hash.into(), + ], ) .into() } diff --git a/src/spends/spend_builder.rs b/src/spends/spend_builder.rs index abbd36c4..47b65014 100644 --- a/src/spends/spend_builder.rs +++ b/src/spends/spend_builder.rs @@ -16,12 +16,23 @@ pub trait Chainable { } } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct ChainedSpend { pub coin_spends: Vec, pub parent_conditions: Vec, } +impl ChainedSpend { + pub fn new() -> Self { + Self::default() + } + + pub fn extend(&mut self, other: ChainedSpend) { + self.coin_spends.extend(other.coin_spends); + self.parent_conditions.extend(other.parent_conditions); + } +} + #[derive(Debug, Clone, Copy)] pub struct InnerSpend { puzzle: NodePtr, From b8a608e310891e1b6a77d60e0e1457788fc2fa32 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 7 May 2024 21:51:06 -0400 Subject: [PATCH 16/25] CAT parser --- src/lib.rs | 2 + src/puzzle_parser.rs | 180 +++++++++++++++++++++++++++ src/spends/puzzles/cat.rs | 2 + src/spends/puzzles/cat/cat_info.rs | 10 ++ src/spends/puzzles/did/create_did.rs | 6 +- src/spends/puzzles/did/did_info.rs | 4 +- src/spends/puzzles/did/did_spend.rs | 2 +- src/spends/puzzles/nft/mint_nft.rs | 6 +- src/spends/puzzles/nft/nft_info.rs | 4 +- src/spends/puzzles/nft/nft_spend.rs | 10 +- 10 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 src/puzzle_parser.rs create mode 100644 src/spends/puzzles/cat/cat_info.rs diff --git a/src/lib.rs b/src/lib.rs index 4fdfae86..e24b2463 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ mod address; mod condition; +mod puzzle_parser; mod spends; mod ssl; @@ -17,6 +18,7 @@ pub mod wallet; pub use address::*; pub use condition::*; +pub use puzzle_parser::*; pub use spends::*; pub use ssl::*; pub use wallet::*; diff --git a/src/puzzle_parser.rs b/src/puzzle_parser.rs new file mode 100644 index 00000000..f2406faf --- /dev/null +++ b/src/puzzle_parser.rs @@ -0,0 +1,180 @@ +use chia_protocol::{Bytes32, Coin}; +use chia_wallet::{ + cat::{cat_puzzle_hash, CatArgs, CatSolution, CAT_PUZZLE_HASH}, + LineageProof, +}; +use clvm_traits::{FromClvm, FromClvmError}; +use clvm_utils::{tree_hash, CurriedProgram}; +use clvmr::{ + reduction::{EvalErr, Reduction}, + run_program, Allocator, ChiaDialect, NodePtr, +}; +use thiserror::Error; + +use crate::{CatInfo, CreateCoin, DidInfo, NftInfo}; + +#[derive(Debug, Error)] +pub enum ParserError { + #[error("eval error: {0}")] + Eval(#[from] EvalErr), + + #[error("clvm error: {0}")] + FromClvm(#[from] FromClvmError), + + #[error("invalid puzzle")] + InvalidPuzzle, + + #[error("incorrect hint")] + IncorrectHint, +} + +pub enum Puzzle { + Cat(CatInfo), + Did(DidInfo), + Nft(NftInfo), +} + +impl Puzzle { + pub fn parse( + allocator: &mut Allocator, + parent_puzzle: NodePtr, + parent_solution: NodePtr, + parent_coin: Coin, + coin: Coin, + max_cost: u64, + ) -> Result { + let CurriedProgram { program, args } = + CurriedProgram::::from_clvm(allocator, parent_puzzle)?; + + match tree_hash(allocator, program) { + CAT_PUZZLE_HASH => { + let cat_args = CatArgs::::from_clvm(allocator, args)?; + let cat_solution = CatSolution::::from_clvm(allocator, parent_solution)?; + + let Reduction(_cost, output) = run_program( + allocator, + &ChiaDialect::new(0), + cat_args.inner_puzzle, + cat_solution.inner_puzzle_solution, + max_cost, + )?; + + let conditions = Vec::::from_clvm(allocator, output)?; + let mut p2_puzzle_hash = None; + + for condition in conditions { + let Ok(create_coin) = CreateCoin::from_clvm(allocator, condition) else { + continue; + }; + + let cat_puzzle_hash = Bytes32::new(cat_puzzle_hash( + cat_args.tail_program_hash.into(), + create_coin.puzzle_hash().into(), + )); + + if cat_puzzle_hash == coin.puzzle_hash && create_coin.amount() == coin.amount { + p2_puzzle_hash = Some(create_coin.puzzle_hash()); + break; + } + } + + let Some(p2_puzzle_hash) = p2_puzzle_hash else { + return Err(ParserError::IncorrectHint); + }; + + Ok(Puzzle::Cat(CatInfo { + asset_id: cat_args.tail_program_hash, + p2_puzzle_hash, + coin, + lineage_proof: LineageProof { + parent_coin_info: parent_coin.parent_coin_info, + inner_puzzle_hash: tree_hash(allocator, cat_args.inner_puzzle).into(), + amount: parent_coin.amount, + }, + })) + } + _ => Err(ParserError::InvalidPuzzle), + } + } +} + +#[cfg(test)] +mod tests { + use chia_wallet::{ + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, + }; + use clvm_traits::ToNodePtr; + + use crate::{ + testing::SECRET_KEY, Chainable, CreateCoinWithMemos, IssueCat, SpendContext, StandardSpend, + WalletSimulator, + }; + + use super::*; + + #[tokio::test] + async fn test_parse_cat() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let parent = sim.generate_coin(puzzle_hash, 1).await.coin; + + let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash, + amount: 1, + memos: vec![puzzle_hash.to_vec().into()], + })?) + .multi_issuance(&mut ctx, pk.clone(), 1)?; + + 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(issuance_info.asset_id.into(), puzzle_hash.into()).into(), + 1, + ), + lineage_proof: LineageProof { + parent_coin_info: issuance_info.eve_coin.parent_coin_info, + inner_puzzle_hash: issuance_info.eve_inner_puzzle_hash, + amount: 1, + }, + }; + + let standard_spend = StandardSpend::new() + .chain(issue_cat) + .finish(&mut ctx, parent, pk)?; + + let coin_spend = standard_spend + .into_iter() + .find(|cs| cs.coin.coin_id() == issuance_info.eve_coin.coin_id()) + .unwrap(); + + let puzzle = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; + let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; + + let parse = Puzzle::parse( + &mut allocator, + puzzle, + solution, + issuance_info.eve_coin, + cat_info.coin.clone(), + u64::MAX, + )?; + + match parse { + Puzzle::Cat(parsed_cat_info) => assert_eq!(parsed_cat_info, cat_info), + _ => panic!("unexpected puzzle"), + } + + Ok(()) + } +} diff --git a/src/spends/puzzles/cat.rs b/src/spends/puzzles/cat.rs index 0b9cb6ff..f3c65c5a 100644 --- a/src/spends/puzzles/cat.rs +++ b/src/spends/puzzles/cat.rs @@ -1,5 +1,7 @@ +mod cat_info; mod cat_spend; mod issue_cat; +pub use cat_info::*; pub use cat_spend::*; pub use issue_cat::*; diff --git a/src/spends/puzzles/cat/cat_info.rs b/src/spends/puzzles/cat/cat_info.rs new file mode 100644 index 00000000..10cf786b --- /dev/null +++ b/src/spends/puzzles/cat/cat_info.rs @@ -0,0 +1,10 @@ +use chia_protocol::{Bytes32, Coin}; +use chia_wallet::LineageProof; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CatInfo { + pub asset_id: Bytes32, + pub p2_puzzle_hash: Bytes32, + pub coin: Coin, + pub lineage_proof: LineageProof, +} diff --git a/src/spends/puzzles/did/create_did.rs b/src/spends/puzzles/did/create_did.rs index c65a2d63..9142263a 100644 --- a/src/spends/puzzles/did/create_did.rs +++ b/src/spends/puzzles/did/create_did.rs @@ -75,7 +75,7 @@ impl CreateDid for SpendableLauncher { fn create_eve_did( self, ctx: &mut SpendContext, - owner_puzzle_hash: Bytes32, + p2_puzzle_hash: Bytes32, recovery_did_list_hash: Bytes32, num_verifications_required: u64, metadata: M, @@ -87,7 +87,7 @@ impl CreateDid for SpendableLauncher { let metadata_hash = ctx.tree_hash(metadata_ptr); let did_inner_puzzle_hash = did_inner_puzzle_hash( - owner_puzzle_hash, + p2_puzzle_hash, recovery_did_list_hash, num_verifications_required, self.coin().coin_id(), @@ -106,7 +106,7 @@ impl CreateDid for SpendableLauncher { launcher_id: launcher_coin.coin_id(), coin: eve_coin, did_inner_puzzle_hash, - owner_puzzle_hash, + p2_puzzle_hash, proof, recovery_did_list_hash, num_verifications_required, diff --git a/src/spends/puzzles/did/did_info.rs b/src/spends/puzzles/did/did_info.rs index 98563afc..06143282 100644 --- a/src/spends/puzzles/did/did_info.rs +++ b/src/spends/puzzles/did/did_info.rs @@ -1,12 +1,12 @@ use chia_protocol::{Bytes32, Coin}; use chia_wallet::Proof; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct DidInfo { pub launcher_id: Bytes32, pub coin: Coin, pub did_inner_puzzle_hash: Bytes32, - pub owner_puzzle_hash: Bytes32, + pub p2_puzzle_hash: Bytes32, pub proof: Proof, pub recovery_did_list_hash: Bytes32, pub num_verifications_required: u64, diff --git a/src/spends/puzzles/did/did_spend.rs b/src/spends/puzzles/did/did_spend.rs index 9134f9d8..f4c5df5c 100644 --- a/src/spends/puzzles/did/did_spend.rs +++ b/src/spends/puzzles/did/did_spend.rs @@ -61,7 +61,7 @@ impl StandardDidSpend { DidOutput::Recreate => CreateCoinWithMemos { puzzle_hash: did_info.did_inner_puzzle_hash, amount: did_info.coin.amount, - memos: vec![did_info.owner_puzzle_hash.to_vec().into()], + memos: vec![did_info.p2_puzzle_hash.to_vec().into()], }, }; diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index b814acd5..0b700c3e 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -75,7 +75,7 @@ impl MintNft for SpendableLauncher { fn mint_eve_nft( self, ctx: &mut SpendContext, - inner_puzzle_hash: Bytes32, + p2_puzzle_hash: Bytes32, metadata: M, royalty_puzzle_hash: Bytes32, royalty_percentage: u16, @@ -93,7 +93,7 @@ impl MintNft for SpendableLauncher { royalty_puzzle_hash, royalty_percentage, ), - inner_puzzle_hash, + p2_puzzle_hash, ); let nft_inner_puzzle_hash = nft_state_layer_hash( metadata_hash, @@ -119,7 +119,7 @@ impl MintNft for SpendableLauncher { launcher_id: launcher_coin.coin_id(), coin: eve_coin, nft_inner_puzzle_hash, - owner_puzzle_hash: inner_puzzle_hash, + p2_puzzle_hash, proof, metadata, metadata_updater_hash: NFT_METADATA_UPDATER_PUZZLE_HASH.into(), diff --git a/src/spends/puzzles/nft/nft_info.rs b/src/spends/puzzles/nft/nft_info.rs index 43647e83..0f43bb7e 100644 --- a/src/spends/puzzles/nft/nft_info.rs +++ b/src/spends/puzzles/nft/nft_info.rs @@ -1,12 +1,12 @@ use chia_protocol::{Bytes32, Coin}; use chia_wallet::Proof; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct NftInfo { pub launcher_id: Bytes32, pub coin: Coin, pub nft_inner_puzzle_hash: Bytes32, - pub owner_puzzle_hash: Bytes32, + pub p2_puzzle_hash: Bytes32, pub proof: Proof, pub metadata: M, pub metadata_updater_hash: Bytes32, diff --git a/src/spends/puzzles/nft/nft_spend.rs b/src/spends/puzzles/nft/nft_spend.rs index decc6524..a699d3c5 100644 --- a/src/spends/puzzles/nft/nft_spend.rs +++ b/src/spends/puzzles/nft/nft_spend.rs @@ -91,8 +91,8 @@ impl StandardNftSpend { coin_spends: Vec::new(), }; - let owner_puzzle_hash = match self.output { - NftOutput::SamePuzzleHash => nft_info.owner_puzzle_hash, + let p2_puzzle_hash = match self.output { + NftOutput::SamePuzzleHash => nft_info.p2_puzzle_hash, NftOutput::NewPuzzleHash { puzzle_hash } => puzzle_hash, }; @@ -120,9 +120,9 @@ impl StandardNftSpend { let (inner_spend, coin_spends) = self .standard_spend .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash: owner_puzzle_hash, + puzzle_hash: p2_puzzle_hash, amount: nft_info.coin.amount, - memos: vec![owner_puzzle_hash.to_vec().into()], + memos: vec![p2_puzzle_hash.to_vec().into()], })?) .inner_spend(ctx, synthetic_key)?; @@ -148,7 +148,7 @@ impl StandardNftSpend { nft_info.royalty_puzzle_hash, nft_info.royalty_percentage, ), - owner_puzzle_hash, + p2_puzzle_hash, ), ); From 818263c292a230ddf3c1247c9d02d16f8098408a Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 7 May 2024 23:25:34 -0400 Subject: [PATCH 17/25] Add DID parsing --- src/puzzle_parser.rs | 210 +++++++++++++++++++++++++---- src/spends/puzzles/did/did_info.rs | 15 +++ src/spends/puzzles/nft/mint_nft.rs | 2 +- src/spends/puzzles/nft/nft_info.rs | 17 +++ 4 files changed, 217 insertions(+), 27 deletions(-) diff --git a/src/puzzle_parser.rs b/src/puzzle_parser.rs index f2406faf..358f4844 100644 --- a/src/puzzle_parser.rs +++ b/src/puzzle_parser.rs @@ -1,7 +1,12 @@ use chia_protocol::{Bytes32, Coin}; use chia_wallet::{ cat::{cat_puzzle_hash, CatArgs, CatSolution, CAT_PUZZLE_HASH}, - LineageProof, + did::{DidArgs, DidSolution, DID_INNER_PUZZLE_HASH}, + singleton::{ + SingletonArgs, SingletonSolution, SINGLETON_LAUNCHER_PUZZLE_HASH, + SINGLETON_TOP_LAYER_PUZZLE_HASH, + }, + LineageProof, Proof, }; use clvm_traits::{FromClvm, FromClvmError}; use clvm_utils::{tree_hash, CurriedProgram}; @@ -11,21 +16,33 @@ use clvmr::{ }; use thiserror::Error; -use crate::{CatInfo, CreateCoin, DidInfo, NftInfo}; +use crate::{ + did_inner_puzzle_hash, singleton_puzzle_hash, CatInfo, CreateCoin, CreateCoinWithMemos, + DidInfo, NftInfo, +}; #[derive(Debug, Error)] -pub enum ParserError { - #[error("eval error: {0}")] +pub enum PuzzleError { + #[error("Eval error: {0}")] Eval(#[from] EvalErr), - #[error("clvm error: {0}")] + #[error("CLVM error: {0}")] FromClvm(#[from] FromClvmError), - #[error("invalid puzzle")] + #[error("Invalid puzzle")] InvalidPuzzle, - #[error("incorrect hint")] - IncorrectHint, + #[error("Incorrect hint")] + MissingCreateCoin, + + #[error("DID singleton struct mismatch")] + DidSingletonStructMismatch, + + #[error("Invalid singleton struct")] + InvalidSingletonStruct, + + #[error("Unknown DID output")] + UnknownDidOutput, } pub enum Puzzle { @@ -42,7 +59,7 @@ impl Puzzle { parent_coin: Coin, coin: Coin, max_cost: u64, - ) -> Result { + ) -> Result { let CurriedProgram { program, args } = CurriedProgram::::from_clvm(allocator, parent_puzzle)?; @@ -79,7 +96,7 @@ impl Puzzle { } let Some(p2_puzzle_hash) = p2_puzzle_hash else { - return Err(ParserError::IncorrectHint); + return Err(PuzzleError::MissingCreateCoin); }; Ok(Puzzle::Cat(CatInfo { @@ -93,38 +110,137 @@ impl Puzzle { }, })) } - _ => Err(ParserError::InvalidPuzzle), + SINGLETON_TOP_LAYER_PUZZLE_HASH => { + let singleton_args = SingletonArgs::::from_clvm(allocator, args)?; + let singleton_solution = + SingletonSolution::::from_clvm(allocator, parent_solution)?; + let CurriedProgram { program, args } = + CurriedProgram::::from_clvm( + allocator, + singleton_args.inner_puzzle, + )?; + + let singleton_mod_hash = singleton_args.singleton_struct.mod_hash.as_ref(); + let launcher_puzzle_hash = singleton_args + .singleton_struct + .launcher_puzzle_hash + .as_ref(); + + if singleton_mod_hash != SINGLETON_TOP_LAYER_PUZZLE_HASH + || launcher_puzzle_hash != SINGLETON_LAUNCHER_PUZZLE_HASH + { + return Err(PuzzleError::InvalidSingletonStruct); + } + + match tree_hash(allocator, program) { + DID_INNER_PUZZLE_HASH => { + let did_args = DidArgs::::from_clvm(allocator, args)?; + let DidSolution::InnerSpend(p2_solution) = + DidSolution::::from_clvm( + allocator, + singleton_solution.inner_solution, + )?; + + if did_args.singleton_struct != singleton_args.singleton_struct { + return Err(PuzzleError::DidSingletonStructMismatch); + } + + let Reduction(_cost, output) = run_program( + allocator, + &ChiaDialect::new(0), + did_args.inner_puzzle, + p2_solution, + max_cost, + )?; + + let conditions = Vec::::from_clvm(allocator, output)?; + let mut p2_puzzle_hash = None; + + for condition in conditions { + let Ok(create_coin) = + CreateCoinWithMemos::from_clvm(allocator, condition) + else { + continue; + }; + + if create_coin.amount % 2 == 0 { + continue; + } + + p2_puzzle_hash = create_coin.memos.first().and_then(|memo| { + Some(Bytes32::new(memo.as_ref().try_into().ok()?)) + }); + break; + } + + let Some(p2_puzzle_hash) = p2_puzzle_hash else { + return Err(PuzzleError::MissingCreateCoin); + }; + + let did_inner_puzzle_hash = did_inner_puzzle_hash( + p2_puzzle_hash, + did_args.recovery_did_list_hash, + did_args.num_verifications_required, + did_args.singleton_struct.launcher_id, + tree_hash(allocator, did_args.metadata).into(), + ); + + let singleton_puzzle_hash = singleton_puzzle_hash( + singleton_args.singleton_struct.launcher_id, + did_inner_puzzle_hash, + ); + + if singleton_puzzle_hash != coin.puzzle_hash { + return Err(PuzzleError::UnknownDidOutput); + } + + Ok(Puzzle::Did(DidInfo { + launcher_id: singleton_args.singleton_struct.launcher_id, + coin, + p2_puzzle_hash, + did_inner_puzzle_hash, + recovery_did_list_hash: did_args.recovery_did_list_hash, + num_verifications_required: did_args.num_verifications_required, + metadata: did_args.metadata, + proof: Proof::Lineage(LineageProof { + parent_coin_info: parent_coin.parent_coin_info, + inner_puzzle_hash: tree_hash( + allocator, + singleton_args.inner_puzzle, + ) + .into(), + amount: parent_coin.amount, + }), + })) + } + _ => Err(PuzzleError::InvalidPuzzle), + } + } + _ => Err(PuzzleError::InvalidPuzzle), } } } #[cfg(test)] mod tests { - use chia_wallet::{ - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, - DeriveSynthetic, - }; + use chia_bls::PublicKey; + use chia_wallet::standard::standard_puzzle_hash; use clvm_traits::ToNodePtr; use crate::{ - testing::SECRET_KEY, Chainable, CreateCoinWithMemos, IssueCat, SpendContext, StandardSpend, - WalletSimulator, + Chainable, CreateCoinWithMemos, CreateDid, IssueCat, Launcher, SpendContext, StandardSpend, }; use super::*; - #[tokio::test] - async fn test_parse_cat() -> anyhow::Result<()> { - let sim = WalletSimulator::new().await; - + #[test] + fn test_parse_cat() -> anyhow::Result<()> { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); - let pk = sk.public_key(); + let pk = PublicKey::default(); let puzzle_hash = standard_puzzle_hash(&pk).into(); - - let parent = sim.generate_coin(puzzle_hash, 1).await.coin; + let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) .condition(ctx.alloc(CreateCoinWithMemos { @@ -165,7 +281,7 @@ mod tests { &mut allocator, puzzle, solution, - issuance_info.eve_coin, + coin_spend.coin, cat_info.coin.clone(), u64::MAX, )?; @@ -177,4 +293,46 @@ mod tests { Ok(()) } + + #[test] + fn test_parse_did() -> anyhow::Result<()> { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let pk = PublicKey::default(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); + + let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk.clone())?; + + let standard_spend = StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk)?; + + let coin_spend = standard_spend + .into_iter() + .find(|cs| cs.coin.coin_id() == did_info.coin.parent_coin_info) + .unwrap(); + + let puzzle = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; + let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; + + let parse = Puzzle::parse( + &mut allocator, + puzzle, + solution, + coin_spend.coin, + did_info.coin.clone(), + u64::MAX, + )?; + + match parse { + Puzzle::Did(parsed_did_info) => assert_eq!(parsed_did_info.with_metadata(()), did_info), + _ => panic!("unexpected puzzle"), + } + + Ok(()) + } } diff --git a/src/spends/puzzles/did/did_info.rs b/src/spends/puzzles/did/did_info.rs index 06143282..87c49798 100644 --- a/src/spends/puzzles/did/did_info.rs +++ b/src/spends/puzzles/did/did_info.rs @@ -12,3 +12,18 @@ pub struct DidInfo { pub num_verifications_required: u64, pub metadata: M, } + +impl DidInfo { + pub fn with_metadata(self, metadata: N) -> DidInfo { + DidInfo { + launcher_id: self.launcher_id, + coin: self.coin, + did_inner_puzzle_hash: self.did_inner_puzzle_hash, + p2_puzzle_hash: self.p2_puzzle_hash, + proof: self.proof, + recovery_did_list_hash: self.recovery_did_list_hash, + num_verifications_required: self.num_verifications_required, + metadata, + } + } +} diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index 0b700c3e..46748fed 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -281,7 +281,7 @@ mod tests { let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, - dbg!(&coin_spends), + &coin_spends, WalletSimulator::AGG_SIG_ME.into(), )?; diff --git a/src/spends/puzzles/nft/nft_info.rs b/src/spends/puzzles/nft/nft_info.rs index 0f43bb7e..54d37400 100644 --- a/src/spends/puzzles/nft/nft_info.rs +++ b/src/spends/puzzles/nft/nft_info.rs @@ -14,3 +14,20 @@ pub struct NftInfo { pub royalty_puzzle_hash: Bytes32, pub royalty_percentage: u16, } + +impl NftInfo { + pub fn with_metadata(self, metadata: N) -> NftInfo { + NftInfo { + launcher_id: self.launcher_id, + coin: self.coin, + nft_inner_puzzle_hash: self.nft_inner_puzzle_hash, + p2_puzzle_hash: self.p2_puzzle_hash, + proof: self.proof, + metadata, + metadata_updater_hash: self.metadata_updater_hash, + current_owner: self.current_owner, + royalty_puzzle_hash: self.royalty_puzzle_hash, + royalty_percentage: self.royalty_percentage, + } + } +} From b1033fdc2f88296ba0754854328c83c35b929935 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 7 May 2024 23:47:20 -0400 Subject: [PATCH 18/25] Don't support NFTs yet for puzzle parser --- src/puzzle_parser.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/puzzle_parser.rs b/src/puzzle_parser.rs index 358f4844..f115dbc1 100644 --- a/src/puzzle_parser.rs +++ b/src/puzzle_parser.rs @@ -17,8 +17,7 @@ use clvmr::{ use thiserror::Error; use crate::{ - did_inner_puzzle_hash, singleton_puzzle_hash, CatInfo, CreateCoin, CreateCoinWithMemos, - DidInfo, NftInfo, + did_inner_puzzle_hash, singleton_puzzle_hash, CatInfo, CreateCoin, CreateCoinWithMemos, DidInfo, }; #[derive(Debug, Error)] @@ -48,7 +47,6 @@ pub enum PuzzleError { pub enum Puzzle { Cat(CatInfo), Did(DidInfo), - Nft(NftInfo), } impl Puzzle { From b54a1c8514e10a0e8e388bbaa8374dcc54b31cfe Mon Sep 17 00:00:00 2001 From: Rigidity Date: Wed, 8 May 2024 22:08:16 -0400 Subject: [PATCH 19/25] Refactor a bit --- src/puzzle_parser.rs | 12 ++- src/spends/puzzles/cat/cat_spend.rs | 27 ++--- src/spends/puzzles/cat/issue_cat.rs | 7 +- src/spends/puzzles/did.rs | 4 +- src/spends/puzzles/did/create_did.rs | 11 +- src/spends/puzzles/did/did_spend.rs | 27 +++-- src/spends/puzzles/nft/mint_nft.rs | 11 +- src/spends/puzzles/nft/nft_spend.rs | 41 ++++--- src/spends/puzzles/offer.rs | 30 ++---- src/spends/puzzles/offer/offer_builder.rs | 7 +- .../singleton/intermediate_launcher.rs | 8 +- src/spends/puzzles/singleton/launcher.rs | 1 - .../puzzles/singleton/spendable_launcher.rs | 3 +- src/spends/puzzles/standard.rs | 101 ++++++++++-------- src/spends/spend_builder.rs | 3 - src/spends/spend_context.rs | 23 +++- 16 files changed, 163 insertions(+), 153 deletions(-) diff --git a/src/puzzle_parser.rs b/src/puzzle_parser.rs index f115dbc1..c4009150 100644 --- a/src/puzzle_parser.rs +++ b/src/puzzle_parser.rs @@ -263,11 +263,13 @@ mod tests { }, }; - let standard_spend = StandardSpend::new() + StandardSpend::new() .chain(issue_cat) .finish(&mut ctx, parent, pk)?; - let coin_spend = standard_spend + 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(); @@ -305,11 +307,13 @@ mod tests { .create(&mut ctx)? .create_standard_did(&mut ctx, pk.clone())?; - let standard_spend = StandardSpend::new() + StandardSpend::new() .chain(create_did) .finish(&mut ctx, parent, pk)?; - let coin_spend = standard_spend + let coin_spends = ctx.take_spends(); + + let coin_spend = coin_spends .into_iter() .find(|cs| cs.coin.coin_id() == did_info.coin.parent_coin_info) .unwrap(); diff --git a/src/spends/puzzles/cat/cat_spend.rs b/src/spends/puzzles/cat/cat_spend.rs index 4b478a3a..5aa195a7 100644 --- a/src/spends/puzzles/cat/cat_spend.rs +++ b/src/spends/puzzles/cat/cat_spend.rs @@ -45,14 +45,12 @@ impl CatSpend { self } - pub fn finish(self, ctx: &mut SpendContext) -> Result, SpendError> { + pub fn finish(self, ctx: &mut SpendContext) -> Result<(), SpendError> { let cat_puzzle_ptr = ctx.cat_puzzle(); + let len = self.cat_spends.len(); - let mut coin_spends = Vec::new(); let mut total_delta = 0; - let len = self.cat_spends.len(); - for (index, item) in self.cat_spends.iter().enumerate() { let CatSpendItem { coin, @@ -104,10 +102,10 @@ impl CatSpend { extra_delta: *extra_delta, })?; - coin_spends.push(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); + ctx.spend(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); } - Ok(coin_spends) + Ok(()) } } @@ -151,7 +149,7 @@ mod tests { 42, ); - let (inner_spend, _) = StandardSpend::new() + let inner_spend = StandardSpend::new() .condition(ctx.alloc(CreateCoinWithoutMemos { puzzle_hash: coin.puzzle_hash, amount: coin.amount, @@ -164,10 +162,11 @@ mod tests { amount: parent_coin.amount, }; - let coin_spend = CatSpend::new(asset_id) + CatSpend::new(asset_id) .spend(coin, inner_spend, lineage_proof, 0) - .finish(&mut ctx)? - .remove(0); + .finish(&mut ctx)?; + + let coin_spend = ctx.take_spends().remove(0); let output_ptr = coin_spend .puzzle_reveal @@ -244,21 +243,23 @@ mod tests { amount: parent_coin_3.amount, }; - let (inner_spend, _) = StandardSpend::new() + let inner_spend = StandardSpend::new() .condition(ctx.alloc(CreateCoinWithoutMemos { puzzle_hash: coin_1.puzzle_hash, amount: coin_1.amount + coin_2.amount + coin_3.amount, })?) .inner_spend(&mut ctx, synthetic_key.clone())?; - let (empty_spend, _) = StandardSpend::new().inner_spend(&mut ctx, synthetic_key)?; + let empty_spend = StandardSpend::new().inner_spend(&mut ctx, synthetic_key)?; - let coin_spends = CatSpend::new(asset_id) + CatSpend::new(asset_id) .spend(coin_1, inner_spend, lineage_1, 0) .spend(coin_2, empty_spend, lineage_2, 0) .spend(coin_3, empty_spend, lineage_3, 0) .finish(&mut ctx)?; + let coin_spends = ctx.take_spends(); + let spend_vec = coin_spends .clone() .into_iter() diff --git a/src/spends/puzzles/cat/issue_cat.rs b/src/spends/puzzles/cat/issue_cat.rs index adc3dda5..89743c6d 100644 --- a/src/spends/puzzles/cat/issue_cat.rs +++ b/src/spends/puzzles/cat/issue_cat.rs @@ -98,10 +98,9 @@ impl IssueCat { })?; let puzzle_reveal = ctx.serialize(puzzle)?; - let coin_spend = CoinSpend::new(coin.clone(), puzzle_reveal, solution); + ctx.spend(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); let chained_spend = ChainedSpend { - coin_spends: vec![coin_spend], parent_conditions: vec![ctx.alloc(CreateCoinWithMemos { puzzle_hash, amount, @@ -166,10 +165,12 @@ mod tests { })?) .multi_issuance(&mut ctx, pk.clone(), 1)?; - let coin_spends = StandardSpend::new() + StandardSpend::new() .chain(issue_cat) .finish(&mut ctx, xch_coin, pk)?; + let coin_spends = ctx.take_spends(); + let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, &coin_spends, diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index 165d7bce..e0eb0e94 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -41,10 +41,12 @@ mod tests { .create(&mut ctx)? .create_standard_did(&mut ctx, pk.clone())?; - let coin_spends = StandardSpend::new() + StandardSpend::new() .chain(launch_singleton) .finish(&mut ctx, parent, pk)?; + let coin_spends = ctx.take_spends(); + let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); let required_signatures = RequiredSignature::from_coin_spends( diff --git a/src/spends/puzzles/did/create_did.rs b/src/spends/puzzles/did/create_did.rs index 9142263a..cb8a0c9a 100644 --- a/src/spends/puzzles/did/create_did.rs +++ b/src/spends/puzzles/did/create_did.rs @@ -41,7 +41,7 @@ pub trait CreateDid { { let inner_puzzle_hash = standard_puzzle_hash(&synthetic_key).into(); - let (mut create_did, did_info) = self.create_eve_did( + let (create_did, did_info) = self.create_eve_did( ctx, inner_puzzle_hash, recovery_did_list_hash, @@ -49,12 +49,9 @@ pub trait CreateDid { metadata, )?; - let (coin_spends, did_info) = - StandardDidSpend::new() - .recreate() - .finish(ctx, synthetic_key, did_info)?; - - create_did.coin_spends.extend(coin_spends); + let did_info = StandardDidSpend::new() + .recreate() + .finish(ctx, synthetic_key, did_info)?; Ok((create_did, did_info)) } diff --git a/src/spends/puzzles/did/did_spend.rs b/src/spends/puzzles/did/did_spend.rs index f4c5df5c..37c6c459 100644 --- a/src/spends/puzzles/did/did_spend.rs +++ b/src/spends/puzzles/did/did_spend.rs @@ -53,7 +53,7 @@ impl StandardDidSpend { ctx: &mut SpendContext, synthetic_key: PublicKey, mut did_info: DidInfo, - ) -> Result<(Vec, DidInfo), SpendError> + ) -> Result, SpendError> where M: ToClvm, { @@ -65,12 +65,13 @@ impl StandardDidSpend { }, }; - let (inner_spend, mut coin_spends) = self + let inner_spend = self .standard_spend .condition(ctx.alloc(create_coin)?) .inner_spend(ctx, synthetic_key)?; - coin_spends.push(raw_did_spend(ctx, &did_info, inner_spend)?); + let did_spend = raw_did_spend(ctx, &did_info, inner_spend)?; + ctx.spend(did_spend); match self.output { DidOutput::Recreate => { @@ -88,7 +89,7 @@ impl StandardDidSpend { } } - Ok((coin_spends, did_info)) + Ok(did_info) } } @@ -174,20 +175,18 @@ mod tests { .create(&mut ctx)? .create_standard_did(&mut ctx, pk.clone())?; - let mut coin_spends = - StandardSpend::new() - .chain(create_did) - .finish(&mut ctx, parent, pk.clone())?; + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk.clone())?; for _ in 0..10 { - let (did_spends, new_did_info) = - StandardDidSpend::new() - .recreate() - .finish(&mut ctx, pk.clone(), did_info)?; - did_info = new_did_info; - coin_spends.extend(did_spends); + did_info = StandardDidSpend::new() + .recreate() + .finish(&mut ctx, pk.clone(), did_info)?; } + let coin_spends = ctx.take_spends(); + let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, &coin_spends, diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index 46748fed..d7743439 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -246,10 +246,9 @@ mod tests { .create(&mut ctx)? .create_standard_did(&mut ctx, pk.clone())?; - let mut coin_spends = - StandardSpend::new() - .chain(create_did) - .finish(&mut ctx, parent, pk.clone())?; + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk.clone())?; let mint = StandardMint { metadata: (), @@ -261,7 +260,7 @@ mod tests { did_inner_puzzle_hash: did_info.did_inner_puzzle_hash, }; - let (did_coin_spends, _did_info) = StandardDidSpend::new() + let _did_info = StandardDidSpend::new() .chain( IntermediateLauncher::new(did_info.coin.coin_id(), 0, 2) .create(&mut ctx)? @@ -277,7 +276,7 @@ mod tests { .recreate() .finish(&mut ctx, pk, did_info)?; - coin_spends.extend(did_coin_spends); + let coin_spends = ctx.take_spends(); let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, diff --git a/src/spends/puzzles/nft/nft_spend.rs b/src/spends/puzzles/nft/nft_spend.rs index a699d3c5..e4603ee6 100644 --- a/src/spends/puzzles/nft/nft_spend.rs +++ b/src/spends/puzzles/nft/nft_spend.rs @@ -88,7 +88,6 @@ impl StandardNftSpend { { let mut chained_spend = ChainedSpend { parent_conditions: Vec::new(), - coin_spends: Vec::new(), }; let p2_puzzle_hash = match self.output { @@ -117,7 +116,7 @@ impl StandardNftSpend { })?); } - let (inner_spend, coin_spends) = self + let inner_spend = self .standard_spend .condition(ctx.alloc(CreateCoinWithMemos { puzzle_hash: p2_puzzle_hash, @@ -126,10 +125,8 @@ impl StandardNftSpend { })?) .inner_spend(ctx, synthetic_key)?; - chained_spend.coin_spends.extend(coin_spends); - chained_spend - .coin_spends - .push(raw_nft_spend(ctx, &nft_info, inner_spend)?); + let nft_spend = raw_nft_spend(ctx, &nft_info, inner_spend)?; + ctx.spend(nft_spend); nft_info.current_owner = self .new_owner @@ -317,10 +314,9 @@ mod tests { .create(&mut ctx)? .create_standard_did(&mut ctx, pk.clone())?; - let mut coin_spends = - StandardSpend::new() - .chain(create_did) - .finish(&mut ctx, parent, pk.clone())?; + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk.clone())?; let (mint_nft, mut nft_info) = IntermediateLauncher::new(did_info.coin.coin_id(), 0, 1) .create(&mut ctx)? @@ -337,12 +333,11 @@ mod tests { }, )?; - let (did_spends, mut did_info) = StandardDidSpend::new() - .chain(mint_nft) - .recreate() - .finish(&mut ctx, pk.clone(), did_info)?; - - coin_spends.extend(did_spends); + let mut did_info = StandardDidSpend::new().chain(mint_nft).recreate().finish( + &mut ctx, + pk.clone(), + did_info, + )?; for i in 0..5 { let mut spend = StandardNftSpend::new().update(); @@ -352,17 +347,17 @@ mod tests { } let (nft_spend, new_nft_info) = spend.finish(&mut ctx, pk.clone(), nft_info)?; - nft_info = new_nft_info; - let (did_spends, new_did_info) = StandardDidSpend::new() - .chain(nft_spend) - .recreate() - .finish(&mut ctx, pk.clone(), did_info)?; - did_info = new_did_info; - coin_spends.extend(did_spends); + did_info = StandardDidSpend::new().chain(nft_spend).recreate().finish( + &mut ctx, + pk.clone(), + did_info, + )?; } + let coin_spends = ctx.take_spends(); + let required_signatures = RequiredSignature::from_coin_spends( &mut allocator, &coin_spends, diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index da2133b5..4e2b3128 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -130,10 +130,11 @@ mod tests { })?) .multi_issuance(&mut ctx, pk.clone(), 1000)?; - let coin_spends = - StandardSpend::new() - .chain(issue_cat) - .finish(&mut ctx, parent.clone(), pk.clone())?; + StandardSpend::new() + .chain(issue_cat) + .finish(&mut ctx, parent.clone(), pk.clone())?; + + let coin_spends = ctx.take_spends(); let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); @@ -197,15 +198,13 @@ mod tests { let assert_cat = offer_announcement_id(&mut ctx, cat_settlements_hash, cat_payment.clone())?; - let mut coin_spends = Vec::new(); - let lineage_proof = LineageProof { parent_coin_info: parent.coin_id(), inner_puzzle_hash: cat_info.eve_inner_puzzle_hash, amount: 1000, }; - let (inner_spend, _) = StandardSpend::new() + let inner_spend = StandardSpend::new() .condition(ctx.alloc(CreateCoinWithMemos { puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), amount: 1000, @@ -216,19 +215,17 @@ mod tests { })?) .inner_spend(&mut ctx, pk.clone())?; - let cat_to_settlement = CatSpend::new(cat_info.asset_id) + CatSpend::new(cat_info.asset_id) .spend(cat.clone(), inner_spend, lineage_proof, 0) .finish(&mut ctx)?; - coin_spends.extend(cat_to_settlement); - let cat_settlement_coin = Coin::new( cat.coin_id(), cat_puzzle_hash(cat_info.asset_id.into(), SETTLEMENT_PAYMENTS_PUZZLE_HASH).into(), 1000, ); - let xch_to_settlement = StandardSpend::new() + StandardSpend::new() .condition(ctx.alloc(CreateCoinWithoutMemos { puzzle_hash: SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), amount: 1000, @@ -238,8 +235,6 @@ mod tests { })?) .finish(&mut ctx, xch.clone(), pk)?; - coin_spends.extend(xch_to_settlement); - let xch_settlement_coin = Coin::new(xch.coin_id(), SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), 1000); @@ -254,21 +249,18 @@ mod tests { })?; let inner_spend = InnerSpend::new(settlement_payments_puzzle, solution); - let settlement_to_cat = CatSpend::new(cat_info.asset_id) + CatSpend::new(cat_info.asset_id) .spend(cat_settlement_coin, inner_spend, lineage_proof, 0) .finish(&mut ctx)?; - coin_spends.extend(settlement_to_cat); - let puzzle_reveal = ctx.serialize(settlement_payments_puzzle)?; let solution = ctx.serialize(SettlementPaymentsSolution { notarized_payments: vec![xch_payment], })?; - let settlement_to_xch = CoinSpend::new(xch_settlement_coin, puzzle_reveal, solution); - - coin_spends.push(settlement_to_xch); + ctx.spend(CoinSpend::new(xch_settlement_coin, puzzle_reveal, solution)); + let coin_spends = ctx.take_spends(); let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default()); let required_signatures = RequiredSignature::from_coin_spends( diff --git a/src/spends/puzzles/offer/offer_builder.rs b/src/spends/puzzles/offer/offer_builder.rs index 4bca8f9c..76078851 100644 --- a/src/spends/puzzles/offer/offer_builder.rs +++ b/src/spends/puzzles/offer/offer_builder.rs @@ -85,9 +85,12 @@ impl OfferBuilder { Ok(self) } - pub fn finish(self) -> ChainedSpend { + pub fn finish(self, ctx: &mut SpendContext) -> ChainedSpend { + for coin_spend in self.coin_spends { + ctx.spend(coin_spend); + } + ChainedSpend { - coin_spends: self.coin_spends, parent_conditions: self.parent_conditions, } } diff --git a/src/spends/puzzles/singleton/intermediate_launcher.rs b/src/spends/puzzles/singleton/intermediate_launcher.rs index f674dc36..6e5cd7bb 100644 --- a/src/spends/puzzles/singleton/intermediate_launcher.rs +++ b/src/spends/puzzles/singleton/intermediate_launcher.rs @@ -44,7 +44,6 @@ impl IntermediateLauncher { } pub fn create(self, ctx: &mut SpendContext) -> Result { - let mut coin_spends = Vec::new(); let mut parent_conditions = Vec::new(); let intermediate_puzzle = ctx.nft_intermediate_launcher(); @@ -70,7 +69,7 @@ impl IntermediateLauncher { let intermediate_id = self.intermediate_coin.coin_id(); - coin_spends.push(CoinSpend::new( + ctx.spend(CoinSpend::new( self.intermediate_coin, puzzle_reveal, solution, @@ -88,10 +87,7 @@ impl IntermediateLauncher { announcement_id: Bytes32::new(announcement_id.finalize().into()), })?); - let chained_spend = ChainedSpend { - coin_spends, - parent_conditions, - }; + let chained_spend = ChainedSpend { parent_conditions }; Ok(SpendableLauncher::new(self.launcher_coin, chained_spend)) } diff --git a/src/spends/puzzles/singleton/launcher.rs b/src/spends/puzzles/singleton/launcher.rs index 9c0faac8..929a6997 100644 --- a/src/spends/puzzles/singleton/launcher.rs +++ b/src/spends/puzzles/singleton/launcher.rs @@ -33,7 +33,6 @@ impl Launcher { puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), amount, })?], - coin_spends: Vec::new(), }, )) } diff --git a/src/spends/puzzles/singleton/spendable_launcher.rs b/src/spends/puzzles/singleton/spendable_launcher.rs index fef8636e..00df9e41 100644 --- a/src/spends/puzzles/singleton/spendable_launcher.rs +++ b/src/spends/puzzles/singleton/spendable_launcher.rs @@ -62,10 +62,9 @@ impl SpendableLauncher { key_value_list, })?; - let spend_launcher = CoinSpend::new(self.coin.clone(), puzzle_reveal, solution); + ctx.spend(CoinSpend::new(self.coin.clone(), puzzle_reveal, solution)); let mut chained_spend = self.chained_spend; - chained_spend.coin_spends.push(spend_launcher); chained_spend.parent_conditions.push(assert_announcement); let singleton_coin = diff --git a/src/spends/puzzles/standard.rs b/src/spends/puzzles/standard.rs index e9661402..783053df 100644 --- a/src/spends/puzzles/standard.rs +++ b/src/spends/puzzles/standard.rs @@ -22,7 +22,11 @@ impl StandardSpend { self, ctx: &mut SpendContext, synthetic_key: PublicKey, - ) -> Result<(InnerSpend, Vec), SpendError> { + ) -> Result { + for coin_spend in self.coin_spends { + ctx.spend(coin_spend); + } + let standard_puzzle = ctx.standard_puzzle(); let puzzle = ctx.alloc(CurriedProgram { @@ -32,7 +36,7 @@ impl StandardSpend { let solution = ctx.alloc(standard_solution(self.conditions))?; - Ok((InnerSpend::new(puzzle, solution), self.coin_spends)) + Ok(InnerSpend::new(puzzle, solution)) } pub fn finish( @@ -40,20 +44,17 @@ impl StandardSpend { ctx: &mut SpendContext, coin: Coin, synthetic_key: PublicKey, - ) -> Result, SpendError> { - let (inner_spend, mut coin_spends) = self.inner_spend(ctx, synthetic_key)?; - + ) -> 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())?; - coin_spends.push(CoinSpend::new(coin, puzzle_reveal, solution)); - - Ok(coin_spends) + ctx.spend(CoinSpend::new(coin, puzzle_reveal, solution)); + Ok(()) } } impl Chainable for StandardSpend { fn chain(mut self, chained_spend: ChainedSpend) -> Self { - self.coin_spends.extend(chained_spend.coin_spends); self.conditions.extend(chained_spend.parent_conditions); self } @@ -76,54 +77,62 @@ pub fn standard_solution(conditions: T) -> StandardSolution<(u8, T), ()> { #[cfg(test)] mod tests { - use chia_bls::derive_keys::master_to_wallet_unhardened; - use chia_protocol::Bytes32; - use chia_wallet::{standard::DEFAULT_HIDDEN_PUZZLE_HASH, DeriveSynthetic}; - use clvmr::{serde::node_to_bytes, Allocator}; - use hex_literal::hex; + use chia_bls::{sign, Signature}; + use chia_protocol::SpendBundle; + use chia_wallet::{ + standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + DeriveSynthetic, + }; + use clvmr::Allocator; - use crate::{testing::SECRET_KEY, CreateCoinWithoutMemos}; + use crate::{testing::SECRET_KEY, CreateCoinWithoutMemos, RequiredSignature, WalletSimulator}; use super::*; - #[test] - fn test_standard_spend() { - let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + #[tokio::test] + async fn test_standard_spend() -> anyhow::Result<()> { + let sim = WalletSimulator::new().await; + let peer = sim.peer().await; - let mut a = Allocator::new(); - let mut ctx = SpendContext::new(&mut a); + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); - let coin = Coin::new(Bytes32::from([0; 32]), Bytes32::from([1; 32]), 42); + let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let pk = sk.public_key(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); - let coin_spend = StandardSpend::new() + let parent = sim.generate_coin(puzzle_hash, 1).await.coin; + + StandardSpend::new() .condition( ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash: coin.puzzle_hash, - amount: coin.amount, + puzzle_hash, + amount: 1, }) .unwrap(), ) - .finish(&mut ctx, coin, synthetic_key) - .unwrap() - .remove(0); - - let output_ptr = coin_spend - .puzzle_reveal - .run(&mut a, 0, u64::MAX, &coin_spend.solution) - .unwrap() - .1; - let actual = node_to_bytes(&a, output_ptr).unwrap(); - - let expected = hex!( - " - ffff32ffb08584adae5630842a1766bc444d2b872dd3080f4e5daaecf6f762a4 - be7dc148f37868149d4217f3dcc9183fe61e48d8bfffa09744e53c76d9ce3c6b - eb75a3d414ebbec42e31e96621c66b7a832ca1feccceea80ffff33ffa0010101 - 0101010101010101010101010101010101010101010101010101010101ff2a80 - 80 - " - ); - assert_eq!(hex::encode(actual), hex::encode(expected)); + .finish(&mut ctx, parent, pk)?; + + let coin_spends = ctx.take_spends(); + + let required_signatures = RequiredSignature::from_coin_spends( + &mut allocator, + &coin_spends, + WalletSimulator::AGG_SIG_ME.into(), + )?; + + let mut aggregated_signature = Signature::default(); + + for required in required_signatures { + aggregated_signature += &sign(&sk, required.final_message()); + } + + let ack = peer + .send_transaction(SpendBundle::new(coin_spends, aggregated_signature)) + .await?; + assert_eq!(ack.error, None); + assert_eq!(ack.status, 1); + + Ok(()) } } diff --git a/src/spends/spend_builder.rs b/src/spends/spend_builder.rs index 47b65014..d7e9fbe0 100644 --- a/src/spends/spend_builder.rs +++ b/src/spends/spend_builder.rs @@ -1,4 +1,3 @@ -use chia_protocol::CoinSpend; use clvmr::NodePtr; pub trait Chainable { @@ -18,7 +17,6 @@ pub trait Chainable { #[derive(Debug, Default, Clone)] pub struct ChainedSpend { - pub coin_spends: Vec, pub parent_conditions: Vec, } @@ -28,7 +26,6 @@ impl ChainedSpend { } pub fn extend(&mut self, other: ChainedSpend) { - self.coin_spends.extend(other.coin_spends); self.parent_conditions.extend(other.parent_conditions); } } diff --git a/src/spends/spend_context.rs b/src/spends/spend_context.rs index 72745671..48eba495 100644 --- a/src/spends/spend_context.rs +++ b/src/spends/spend_context.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use chia_protocol::{Bytes32, Program}; +use chia_protocol::{Bytes32, CoinSpend, Program}; use chia_wallet::{ cat::{CAT_PUZZLE, CAT_PUZZLE_HASH, EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE}, did::{DID_INNER_PUZZLE, DID_INNER_PUZZLE_HASH}, @@ -27,6 +27,7 @@ use crate::SpendError; pub struct SpendContext<'a> { allocator: &'a mut Allocator, puzzles: HashMap<[u8; 32], NodePtr>, + coin_spends: Vec, } impl<'a> SpendContext<'a> { @@ -35,19 +36,35 @@ impl<'a> SpendContext<'a> { Self { allocator, puzzles: HashMap::new(), + coin_spends: Vec::new(), } } - /// Get a reference to the `Allocator`. + /// Get a reference to the [`Allocator`]. pub fn allocator(&self) -> &Allocator { self.allocator } - /// Get a mutable reference to the `Allocator`. + /// Get a mutable reference to the [`Allocator`]. pub fn allocator_mut(&mut self) -> &mut Allocator { self.allocator } + /// Get a reference to the list of coin spends. + pub fn spends(&self) -> &[CoinSpend] { + &self.coin_spends + } + + /// Take the coin spends out of the [`SpendContext`]. + pub fn take_spends(&mut self) -> Vec { + std::mem::take(&mut self.coin_spends) + } + + /// Add a [`CoinSpend`] to the list. + pub fn spend(&mut self, coin_spend: CoinSpend) { + self.coin_spends.push(coin_spend); + } + /// Allocate a new node and return its pointer. pub fn alloc(&mut self, value: T) -> Result where From b865ae70ce5dc0eb94a4bb274e68082589f13110 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sat, 11 May 2024 00:09:58 -0400 Subject: [PATCH 20/25] Refactor puzzle parser --- src/lib.rs | 4 +- src/parser.rs | 7 + src/parser/parse_context.rs | 54 +++++ src/parser/parse_error.rs | 27 +++ src/parser/puzzles.rs | 7 + src/parser/puzzles/cat.rs | 139 +++++++++++++ src/parser/puzzles/did.rs | 149 ++++++++++++++ src/parser/puzzles/singleton.rs | 77 ++++++++ src/puzzle_parser.rs | 340 -------------------------------- 9 files changed, 462 insertions(+), 342 deletions(-) create mode 100644 src/parser.rs create mode 100644 src/parser/parse_context.rs create mode 100644 src/parser/parse_error.rs create mode 100644 src/parser/puzzles.rs create mode 100644 src/parser/puzzles/cat.rs create mode 100644 src/parser/puzzles/did.rs create mode 100644 src/parser/puzzles/singleton.rs delete mode 100644 src/puzzle_parser.rs diff --git a/src/lib.rs b/src/lib.rs index e24b2463..e8acd00f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ mod address; mod condition; -mod puzzle_parser; +mod parser; mod spends; mod ssl; @@ -18,7 +18,7 @@ pub mod wallet; pub use address::*; pub use condition::*; -pub use puzzle_parser::*; +pub use parser::*; pub use spends::*; pub use ssl::*; pub use wallet::*; diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 00000000..40172dca --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,7 @@ +mod parse_context; +mod parse_error; +mod puzzles; + +pub use parse_context::*; +pub use parse_error::*; +pub use puzzles::*; diff --git a/src/parser/parse_context.rs b/src/parser/parse_context.rs new file mode 100644 index 00000000..71a5652f --- /dev/null +++ b/src/parser/parse_context.rs @@ -0,0 +1,54 @@ +use chia_protocol::{Bytes32, Coin}; +use clvm_traits::FromClvm; +use clvm_utils::{tree_hash, CurriedProgram}; +use clvmr::{Allocator, NodePtr}; + +use crate::ParseError; + +pub struct ParseContext { + mod_hash: Bytes32, + args: NodePtr, + solution: NodePtr, + parent_coin: Coin, + coin: Coin, +} + +impl ParseContext { + pub fn mod_hash(&self) -> Bytes32 { + self.mod_hash + } + + pub fn args(&self) -> NodePtr { + self.args + } + + pub fn solution(&self) -> NodePtr { + self.solution + } + + pub fn parent_coin(&self) -> &Coin { + &self.parent_coin + } + + pub fn coin(&self) -> &Coin { + &self.coin + } +} + +pub fn parse_puzzle( + allocator: &mut Allocator, + parent_puzzle: NodePtr, + parent_solution: NodePtr, + parent_coin: Coin, + coin: Coin, +) -> Result { + let CurriedProgram { program, args } = CurriedProgram::from_clvm(allocator, parent_puzzle)?; + + Ok(ParseContext { + mod_hash: tree_hash(allocator, program).into(), + args, + solution: parent_solution, + parent_coin, + coin, + }) +} diff --git a/src/parser/parse_error.rs b/src/parser/parse_error.rs new file mode 100644 index 00000000..cffa5e32 --- /dev/null +++ b/src/parser/parse_error.rs @@ -0,0 +1,27 @@ +use clvm_traits::FromClvmError; +use clvmr::reduction::EvalErr; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ParseError { + #[error("Eval error: {0}")] + Eval(#[from] EvalErr), + + #[error("CLVM error: {0}")] + FromClvm(#[from] FromClvmError), + + #[error("Invalid puzzle")] + InvalidPuzzle, + + #[error("Incorrect hint")] + MissingCreateCoin, + + #[error("DID singleton struct mismatch")] + DidSingletonStructMismatch, + + #[error("Invalid singleton struct")] + InvalidSingletonStruct, + + #[error("Unknown DID output")] + UnknownDidOutput, +} diff --git a/src/parser/puzzles.rs b/src/parser/puzzles.rs new file mode 100644 index 00000000..b6dd4911 --- /dev/null +++ b/src/parser/puzzles.rs @@ -0,0 +1,7 @@ +mod cat; +mod did; +mod singleton; + +pub use cat::*; +pub use did::*; +pub use singleton::*; diff --git a/src/parser/puzzles/cat.rs b/src/parser/puzzles/cat.rs new file mode 100644 index 00000000..1fde78c8 --- /dev/null +++ b/src/parser/puzzles/cat.rs @@ -0,0 +1,139 @@ +use chia_protocol::Bytes32; +use chia_wallet::{ + cat::{cat_puzzle_hash, CatArgs, CatSolution, CAT_PUZZLE_HASH}, + LineageProof, +}; +use clvm_traits::FromClvm; +use clvm_utils::tree_hash; +use clvmr::{reduction::Reduction, run_program, Allocator, ChiaDialect, NodePtr}; + +use crate::{CatInfo, CreateCoin, ParseContext, ParseError}; + +pub fn parse_cat( + allocator: &mut Allocator, + ctx: &ParseContext, + max_cost: u64, +) -> Result, ParseError> { + if ctx.mod_hash().to_bytes() != CAT_PUZZLE_HASH { + return Ok(None); + } + + let args = CatArgs::::from_clvm(allocator, ctx.args())?; + let solution = CatSolution::::from_clvm(allocator, ctx.solution())?; + + let Reduction(_cost, output) = run_program( + allocator, + &ChiaDialect::new(0), + args.inner_puzzle, + solution.inner_puzzle_solution, + max_cost, + )?; + + let conditions = Vec::::from_clvm(allocator, output)?; + let mut p2_puzzle_hash = None; + + for condition in conditions { + let Ok(create_coin) = CreateCoin::from_clvm(allocator, condition) else { + continue; + }; + + let cat_puzzle_hash = Bytes32::new(cat_puzzle_hash( + args.tail_program_hash.into(), + create_coin.puzzle_hash().into(), + )); + + if cat_puzzle_hash == ctx.coin().puzzle_hash && create_coin.amount() == ctx.coin().amount { + p2_puzzle_hash = Some(create_coin.puzzle_hash()); + break; + } + } + + let Some(p2_puzzle_hash) = p2_puzzle_hash else { + return Err(ParseError::MissingCreateCoin); + }; + + Ok(Some(CatInfo { + asset_id: args.tail_program_hash, + p2_puzzle_hash, + coin: ctx.coin().clone(), + lineage_proof: LineageProof { + parent_coin_info: ctx.parent_coin().parent_coin_info, + inner_puzzle_hash: tree_hash(allocator, args.inner_puzzle).into(), + amount: ctx.parent_coin().amount, + }, + })) +} + +#[cfg(test)] +mod tests { + use chia_bls::PublicKey; + use chia_protocol::Coin; + use chia_wallet::standard::standard_puzzle_hash; + use clvm_traits::ToNodePtr; + + use crate::{ + parse_puzzle, Chainable, CreateCoinWithMemos, IssueCat, SpendContext, StandardSpend, + }; + + use super::*; + + #[test] + fn test_parse_cat() -> anyhow::Result<()> { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let pk = PublicKey::default(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); + + let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) + .condition(ctx.alloc(CreateCoinWithMemos { + puzzle_hash, + amount: 1, + memos: vec![puzzle_hash.to_vec().into()], + })?) + .multi_issuance(&mut ctx, pk.clone(), 1)?; + + 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(issuance_info.asset_id.into(), puzzle_hash.into()).into(), + 1, + ), + lineage_proof: LineageProof { + parent_coin_info: issuance_info.eve_coin.parent_coin_info, + inner_puzzle_hash: issuance_info.eve_inner_puzzle_hash, + amount: 1, + }, + }; + + StandardSpend::new() + .chain(issue_cat) + .finish(&mut 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 = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; + let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; + + let parse_ctx = parse_puzzle( + &mut allocator, + puzzle, + solution, + coin_spend.coin, + cat_info.coin.clone(), + )?; + + let parse = parse_cat(&mut allocator, &parse_ctx, u64::MAX)?; + assert_eq!(parse, Some(cat_info)); + + Ok(()) + } +} diff --git a/src/parser/puzzles/did.rs b/src/parser/puzzles/did.rs new file mode 100644 index 00000000..dead2a3f --- /dev/null +++ b/src/parser/puzzles/did.rs @@ -0,0 +1,149 @@ +use chia_protocol::Bytes32; +use chia_wallet::{ + did::{DidArgs, DidSolution}, + LineageProof, Proof, +}; +use clvm_traits::FromClvm; +use clvm_utils::tree_hash; +use clvmr::{reduction::Reduction, run_program, Allocator, ChiaDialect, NodePtr}; + +use crate::{ + did_inner_puzzle_hash, singleton_puzzle_hash, CreateCoinWithMemos, DidInfo, ParseContext, + ParseError, ParseSingleton, +}; + +pub fn parse_did( + allocator: &mut Allocator, + ctx: &ParseContext, + singleton: &ParseSingleton, + max_cost: u64, +) -> Result>, ParseError> { + let args = DidArgs::::from_clvm(allocator, singleton.inner_args())?; + + let DidSolution::InnerSpend(p2_solution) = + DidSolution::::from_clvm(allocator, singleton.inner_solution())?; + + if args.singleton_struct != singleton.args().singleton_struct { + return Err(ParseError::DidSingletonStructMismatch); + } + + let Reduction(_cost, output) = run_program( + allocator, + &ChiaDialect::new(0), + args.inner_puzzle, + p2_solution, + max_cost, + )?; + + let conditions = Vec::::from_clvm(allocator, output)?; + let mut p2_puzzle_hash = None; + + for condition in conditions { + let Ok(create_coin) = CreateCoinWithMemos::from_clvm(allocator, condition) else { + continue; + }; + + if create_coin.amount % 2 == 0 { + continue; + } + + p2_puzzle_hash = create_coin + .memos + .first() + .and_then(|memo| Some(Bytes32::new(memo.as_ref().try_into().ok()?))); + break; + } + + let Some(p2_puzzle_hash) = p2_puzzle_hash else { + return Err(ParseError::MissingCreateCoin); + }; + + let did_inner_puzzle_hash = did_inner_puzzle_hash( + p2_puzzle_hash, + args.recovery_did_list_hash, + args.num_verifications_required, + args.singleton_struct.launcher_id, + tree_hash(allocator, args.metadata).into(), + ); + + let singleton_puzzle_hash = + singleton_puzzle_hash(args.singleton_struct.launcher_id, did_inner_puzzle_hash); + + if singleton_puzzle_hash != ctx.coin().puzzle_hash { + return Err(ParseError::UnknownDidOutput); + } + + Ok(Some(DidInfo { + launcher_id: args.singleton_struct.launcher_id, + coin: ctx.coin().clone(), + p2_puzzle_hash, + did_inner_puzzle_hash, + recovery_did_list_hash: args.recovery_did_list_hash, + num_verifications_required: args.num_verifications_required, + metadata: args.metadata, + proof: Proof::Lineage(LineageProof { + parent_coin_info: ctx.parent_coin().parent_coin_info, + inner_puzzle_hash: tree_hash(allocator, singleton.args().inner_puzzle).into(), + amount: ctx.parent_coin().amount, + }), + })) +} + +#[cfg(test)] +mod tests { + use chia_bls::PublicKey; + use chia_protocol::{Bytes32, Coin}; + use chia_wallet::standard::standard_puzzle_hash; + use clvm_traits::ToNodePtr; + use clvmr::Allocator; + + use crate::{ + parse_did, parse_puzzle, parse_singleton, Chainable, CreateDid, Launcher, SpendContext, + StandardSpend, + }; + + #[test] + fn test_parse_did() -> anyhow::Result<()> { + let mut allocator = Allocator::new(); + let mut ctx = SpendContext::new(&mut allocator); + + let pk = PublicKey::default(); + let puzzle_hash = standard_puzzle_hash(&pk).into(); + let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); + + let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) + .create(&mut ctx)? + .create_standard_did(&mut ctx, pk.clone())?; + + StandardSpend::new() + .chain(create_did) + .finish(&mut ctx, parent, pk)?; + + let coin_spends = ctx.take_spends(); + + let coin_spend = coin_spends + .into_iter() + .find(|cs| cs.coin.coin_id() == did_info.coin.parent_coin_info) + .unwrap(); + + let puzzle = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; + let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; + + let parse_ctx = parse_puzzle( + &mut allocator, + puzzle, + solution, + coin_spend.coin, + did_info.coin.clone(), + )?; + + let parse = parse_singleton(&mut allocator, &parse_ctx)?.unwrap(); + let parse = parse_did(&mut allocator, &parse_ctx, &parse, u64::MAX)?; + assert_eq!( + parse.map(|did_info| did_info.with_metadata(())), + Some(did_info) + ); + + Ok(()) + } +} diff --git a/src/parser/puzzles/singleton.rs b/src/parser/puzzles/singleton.rs new file mode 100644 index 00000000..ecccc7e0 --- /dev/null +++ b/src/parser/puzzles/singleton.rs @@ -0,0 +1,77 @@ +use chia_protocol::Bytes32; +use chia_wallet::singleton::{ + SingletonArgs, SingletonSolution, SINGLETON_LAUNCHER_PUZZLE_HASH, + SINGLETON_TOP_LAYER_PUZZLE_HASH, +}; +use clvm_traits::FromClvm; +use clvm_utils::{tree_hash, CurriedProgram}; +use clvmr::{Allocator, NodePtr}; + +use crate::{ParseContext, ParseError}; + +pub struct ParseSingleton { + args: SingletonArgs, + solution: SingletonSolution, + inner_mod_hash: Bytes32, + inner_args: NodePtr, + inner_solution: NodePtr, +} + +impl ParseSingleton { + pub fn args(&self) -> &SingletonArgs { + &self.args + } + + pub fn solution(&self) -> &SingletonSolution { + &self.solution + } + + pub fn inner_mod_hash(&self) -> Bytes32 { + self.inner_mod_hash + } + + pub fn inner_args(&self) -> NodePtr { + self.inner_args + } + + pub fn inner_solution(&self) -> NodePtr { + self.inner_solution + } +} + +pub fn parse_singleton( + allocator: &mut Allocator, + ctx: &ParseContext, +) -> Result, ParseError> { + if ctx.mod_hash().to_bytes() != SINGLETON_TOP_LAYER_PUZZLE_HASH { + return Ok(None); + } + + let singleton_args = SingletonArgs::::from_clvm(allocator, ctx.args())?; + let singleton_solution = SingletonSolution::::from_clvm(allocator, ctx.solution())?; + + let CurriedProgram { program, args } = + CurriedProgram::::from_clvm(allocator, singleton_args.inner_puzzle)?; + + let singleton_mod_hash = singleton_args.singleton_struct.mod_hash.as_ref(); + let launcher_puzzle_hash = singleton_args + .singleton_struct + .launcher_puzzle_hash + .as_ref(); + + if singleton_mod_hash != SINGLETON_TOP_LAYER_PUZZLE_HASH + || launcher_puzzle_hash != SINGLETON_LAUNCHER_PUZZLE_HASH + { + return Err(ParseError::InvalidSingletonStruct); + } + + let inner_solution = singleton_solution.inner_solution; + + Ok(Some(ParseSingleton { + args: singleton_args, + solution: singleton_solution, + inner_mod_hash: tree_hash(allocator, program).into(), + inner_args: args, + inner_solution, + })) +} diff --git a/src/puzzle_parser.rs b/src/puzzle_parser.rs deleted file mode 100644 index c4009150..00000000 --- a/src/puzzle_parser.rs +++ /dev/null @@ -1,340 +0,0 @@ -use chia_protocol::{Bytes32, Coin}; -use chia_wallet::{ - cat::{cat_puzzle_hash, CatArgs, CatSolution, CAT_PUZZLE_HASH}, - did::{DidArgs, DidSolution, DID_INNER_PUZZLE_HASH}, - singleton::{ - SingletonArgs, SingletonSolution, SINGLETON_LAUNCHER_PUZZLE_HASH, - SINGLETON_TOP_LAYER_PUZZLE_HASH, - }, - LineageProof, Proof, -}; -use clvm_traits::{FromClvm, FromClvmError}; -use clvm_utils::{tree_hash, CurriedProgram}; -use clvmr::{ - reduction::{EvalErr, Reduction}, - run_program, Allocator, ChiaDialect, NodePtr, -}; -use thiserror::Error; - -use crate::{ - did_inner_puzzle_hash, singleton_puzzle_hash, CatInfo, CreateCoin, CreateCoinWithMemos, DidInfo, -}; - -#[derive(Debug, Error)] -pub enum PuzzleError { - #[error("Eval error: {0}")] - Eval(#[from] EvalErr), - - #[error("CLVM error: {0}")] - FromClvm(#[from] FromClvmError), - - #[error("Invalid puzzle")] - InvalidPuzzle, - - #[error("Incorrect hint")] - MissingCreateCoin, - - #[error("DID singleton struct mismatch")] - DidSingletonStructMismatch, - - #[error("Invalid singleton struct")] - InvalidSingletonStruct, - - #[error("Unknown DID output")] - UnknownDidOutput, -} - -pub enum Puzzle { - Cat(CatInfo), - Did(DidInfo), -} - -impl Puzzle { - pub fn parse( - allocator: &mut Allocator, - parent_puzzle: NodePtr, - parent_solution: NodePtr, - parent_coin: Coin, - coin: Coin, - max_cost: u64, - ) -> Result { - let CurriedProgram { program, args } = - CurriedProgram::::from_clvm(allocator, parent_puzzle)?; - - match tree_hash(allocator, program) { - CAT_PUZZLE_HASH => { - let cat_args = CatArgs::::from_clvm(allocator, args)?; - let cat_solution = CatSolution::::from_clvm(allocator, parent_solution)?; - - let Reduction(_cost, output) = run_program( - allocator, - &ChiaDialect::new(0), - cat_args.inner_puzzle, - cat_solution.inner_puzzle_solution, - max_cost, - )?; - - let conditions = Vec::::from_clvm(allocator, output)?; - let mut p2_puzzle_hash = None; - - for condition in conditions { - let Ok(create_coin) = CreateCoin::from_clvm(allocator, condition) else { - continue; - }; - - let cat_puzzle_hash = Bytes32::new(cat_puzzle_hash( - cat_args.tail_program_hash.into(), - create_coin.puzzle_hash().into(), - )); - - if cat_puzzle_hash == coin.puzzle_hash && create_coin.amount() == coin.amount { - p2_puzzle_hash = Some(create_coin.puzzle_hash()); - break; - } - } - - let Some(p2_puzzle_hash) = p2_puzzle_hash else { - return Err(PuzzleError::MissingCreateCoin); - }; - - Ok(Puzzle::Cat(CatInfo { - asset_id: cat_args.tail_program_hash, - p2_puzzle_hash, - coin, - lineage_proof: LineageProof { - parent_coin_info: parent_coin.parent_coin_info, - inner_puzzle_hash: tree_hash(allocator, cat_args.inner_puzzle).into(), - amount: parent_coin.amount, - }, - })) - } - SINGLETON_TOP_LAYER_PUZZLE_HASH => { - let singleton_args = SingletonArgs::::from_clvm(allocator, args)?; - let singleton_solution = - SingletonSolution::::from_clvm(allocator, parent_solution)?; - let CurriedProgram { program, args } = - CurriedProgram::::from_clvm( - allocator, - singleton_args.inner_puzzle, - )?; - - let singleton_mod_hash = singleton_args.singleton_struct.mod_hash.as_ref(); - let launcher_puzzle_hash = singleton_args - .singleton_struct - .launcher_puzzle_hash - .as_ref(); - - if singleton_mod_hash != SINGLETON_TOP_LAYER_PUZZLE_HASH - || launcher_puzzle_hash != SINGLETON_LAUNCHER_PUZZLE_HASH - { - return Err(PuzzleError::InvalidSingletonStruct); - } - - match tree_hash(allocator, program) { - DID_INNER_PUZZLE_HASH => { - let did_args = DidArgs::::from_clvm(allocator, args)?; - let DidSolution::InnerSpend(p2_solution) = - DidSolution::::from_clvm( - allocator, - singleton_solution.inner_solution, - )?; - - if did_args.singleton_struct != singleton_args.singleton_struct { - return Err(PuzzleError::DidSingletonStructMismatch); - } - - let Reduction(_cost, output) = run_program( - allocator, - &ChiaDialect::new(0), - did_args.inner_puzzle, - p2_solution, - max_cost, - )?; - - let conditions = Vec::::from_clvm(allocator, output)?; - let mut p2_puzzle_hash = None; - - for condition in conditions { - let Ok(create_coin) = - CreateCoinWithMemos::from_clvm(allocator, condition) - else { - continue; - }; - - if create_coin.amount % 2 == 0 { - continue; - } - - p2_puzzle_hash = create_coin.memos.first().and_then(|memo| { - Some(Bytes32::new(memo.as_ref().try_into().ok()?)) - }); - break; - } - - let Some(p2_puzzle_hash) = p2_puzzle_hash else { - return Err(PuzzleError::MissingCreateCoin); - }; - - let did_inner_puzzle_hash = did_inner_puzzle_hash( - p2_puzzle_hash, - did_args.recovery_did_list_hash, - did_args.num_verifications_required, - did_args.singleton_struct.launcher_id, - tree_hash(allocator, did_args.metadata).into(), - ); - - let singleton_puzzle_hash = singleton_puzzle_hash( - singleton_args.singleton_struct.launcher_id, - did_inner_puzzle_hash, - ); - - if singleton_puzzle_hash != coin.puzzle_hash { - return Err(PuzzleError::UnknownDidOutput); - } - - Ok(Puzzle::Did(DidInfo { - launcher_id: singleton_args.singleton_struct.launcher_id, - coin, - p2_puzzle_hash, - did_inner_puzzle_hash, - recovery_did_list_hash: did_args.recovery_did_list_hash, - num_verifications_required: did_args.num_verifications_required, - metadata: did_args.metadata, - proof: Proof::Lineage(LineageProof { - parent_coin_info: parent_coin.parent_coin_info, - inner_puzzle_hash: tree_hash( - allocator, - singleton_args.inner_puzzle, - ) - .into(), - amount: parent_coin.amount, - }), - })) - } - _ => Err(PuzzleError::InvalidPuzzle), - } - } - _ => Err(PuzzleError::InvalidPuzzle), - } - } -} - -#[cfg(test)] -mod tests { - use chia_bls::PublicKey; - use chia_wallet::standard::standard_puzzle_hash; - use clvm_traits::ToNodePtr; - - use crate::{ - Chainable, CreateCoinWithMemos, CreateDid, IssueCat, Launcher, SpendContext, StandardSpend, - }; - - use super::*; - - #[test] - fn test_parse_cat() -> anyhow::Result<()> { - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let pk = PublicKey::default(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); - let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); - - let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) - .condition(ctx.alloc(CreateCoinWithMemos { - puzzle_hash, - amount: 1, - memos: vec![puzzle_hash.to_vec().into()], - })?) - .multi_issuance(&mut ctx, pk.clone(), 1)?; - - 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(issuance_info.asset_id.into(), puzzle_hash.into()).into(), - 1, - ), - lineage_proof: LineageProof { - parent_coin_info: issuance_info.eve_coin.parent_coin_info, - inner_puzzle_hash: issuance_info.eve_inner_puzzle_hash, - amount: 1, - }, - }; - - StandardSpend::new() - .chain(issue_cat) - .finish(&mut 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 = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; - let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; - - let parse = Puzzle::parse( - &mut allocator, - puzzle, - solution, - coin_spend.coin, - cat_info.coin.clone(), - u64::MAX, - )?; - - match parse { - Puzzle::Cat(parsed_cat_info) => assert_eq!(parsed_cat_info, cat_info), - _ => panic!("unexpected puzzle"), - } - - Ok(()) - } - - #[test] - fn test_parse_did() -> anyhow::Result<()> { - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let pk = PublicKey::default(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); - let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); - - let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) - .create(&mut ctx)? - .create_standard_did(&mut ctx, pk.clone())?; - - StandardSpend::new() - .chain(create_did) - .finish(&mut ctx, parent, pk)?; - - let coin_spends = ctx.take_spends(); - - let coin_spend = coin_spends - .into_iter() - .find(|cs| cs.coin.coin_id() == did_info.coin.parent_coin_info) - .unwrap(); - - let puzzle = coin_spend.puzzle_reveal.to_node_ptr(&mut allocator)?; - let solution = coin_spend.solution.to_node_ptr(&mut allocator)?; - - let parse = Puzzle::parse( - &mut allocator, - puzzle, - solution, - coin_spend.coin, - did_info.coin.clone(), - u64::MAX, - )?; - - match parse { - Puzzle::Did(parsed_did_info) => assert_eq!(parsed_did_info.with_metadata(()), did_info), - _ => panic!("unexpected puzzle"), - } - - Ok(()) - } -} From 59c7834c7f0b8bd4a3d0358a24060a961a64a3d6 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sat, 11 May 2024 00:18:34 -0400 Subject: [PATCH 21/25] Lower max cost for wallet sim --- src/wallet/wallet_simulator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet_simulator.rs b/src/wallet/wallet_simulator.rs index 7e7bbcde..38de4e7a 100644 --- a/src/wallet/wallet_simulator.rs +++ b/src/wallet/wallet_simulator.rs @@ -402,7 +402,7 @@ async fn process_spend_bundle( &mut allocator, &gen, &[], - 11_000_000_000, + 6_600_000_000, MEMPOOL_MODE, )?; From a8f022300efe2605ef6b67f57792b201ed93e02f Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 14 May 2024 18:14:07 -0400 Subject: [PATCH 22/25] Update to chia_rs 0.8.0 --- Cargo.lock | 124 +++++++++--------- Cargo.toml | 18 +-- examples/key_stores.rs | 10 +- src/parser/parse_context.rs | 8 +- src/parser/puzzles/cat.rs | 69 ++++++---- src/parser/puzzles/did.rs | 24 ++-- src/parser/puzzles/singleton.rs | 8 +- src/spends/puzzles/cat/cat_info.rs | 2 +- src/spends/puzzles/cat/cat_spend.rs | 117 +++++++++-------- src/spends/puzzles/cat/issue_cat.rs | 42 +++--- src/spends/puzzles/did.rs | 17 ++- src/spends/puzzles/did/create_did.rs | 23 ++-- src/spends/puzzles/did/did_info.rs | 2 +- src/spends/puzzles/did/did_spend.rs | 33 +++-- src/spends/puzzles/nft/mint_nft.rs | 46 ++++--- src/spends/puzzles/nft/nft_info.rs | 2 +- src/spends/puzzles/nft/nft_spend.rs | 55 ++++---- src/spends/puzzles/offer.rs | 87 +++++++----- src/spends/puzzles/offer/offer_builder.rs | 18 ++- src/spends/puzzles/offer/offer_compression.rs | 2 +- .../singleton/intermediate_launcher.rs | 12 +- src/spends/puzzles/singleton/launcher.rs | 10 +- .../puzzles/singleton/singleton_spend.rs | 6 +- .../puzzles/singleton/spendable_launcher.rs | 8 +- src/spends/puzzles/standard.rs | 18 ++- src/spends/spend_context.rs | 59 ++++----- src/sqlite/coin_store.rs | 6 +- src/sqlite/key_store.rs | 85 +++++------- src/sqlite/puzzle_store.rs | 16 ++- src/sqlite/transaction_store.rs | 4 +- src/wallet/coin_selection.rs | 10 +- src/wallet/required_signature.rs | 12 +- src/wallet/signer.rs | 15 +-- src/wallet/wallet_simulator.rs | 68 +++++----- 34 files changed, 551 insertions(+), 485 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15001372..8d6655fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,6 @@ name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] [[package]] name = "asn1-rs" @@ -267,16 +264,16 @@ dependencies = [ [[package]] name = "chia-bls" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82db17433a5830b55f678d4d143fec4f04668e5909c7be3468f2b3067673308c" +checksum = "a8df881bc212d42e043dd4e0c123e2c22f7627d4ed1ffb4d1b63f405b5d1acaa" dependencies = [ "anyhow", - "arbitrary", "blst", - "chia-traits 0.7.0", + "chia-traits 0.8.0", "hex", "hkdf", + "lru", "sha2 0.10.8", "thiserror", "tiny-bip39", @@ -284,12 +281,12 @@ dependencies = [ [[package]] name = "chia-client" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e63b55cf5f8dd9b4943541c96ed9355e6ae23d3c5cf064bc3a1f13417bd09f" +checksum = "34b6b73380e3fc49b8fdcb98ba4b55c82633b99d65811b9472a07eff19a7ae69" dependencies = [ "chia-protocol", - "chia-traits 0.7.0", + "chia-traits 0.8.0", "futures-util", "thiserror", "tokio", @@ -299,14 +296,15 @@ dependencies = [ [[package]] name = "chia-consensus" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "971cd78633bb54fa52ecf98aaa56d09027ce17189dd65a1f99b1057ddc148f7d" +checksum = "d3fa2616765266a85960196cde6abd537fcc8efe5e8e6c5655f225f7143cfa2f" dependencies = [ + "chia-bls 0.8.0", "chia-protocol", - "chia-traits 0.7.0", - "chia-wallet", - "chia_streamable_macro 0.6.0", + "chia-puzzles", + "chia-traits 0.8.0", + "chia_streamable_macro 0.8.0", "clvm-derive", "clvm-traits", "clvm-utils", @@ -319,14 +317,13 @@ dependencies = [ [[package]] name = "chia-protocol" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c97afc52ecb2f0f29bee3ba13d2af53d80309af4e6c8e858ef70131bc375dd2" +checksum = "a4024a0d4955781983962ec06040443985fb86a7b969d7287bd05c2041259b5f" dependencies = [ - "arbitrary", - "chia-bls 0.7.0", - "chia-traits 0.7.0", - "chia_streamable_macro 0.6.0", + "chia-bls 0.8.0", + "chia-traits 0.8.0", + "chia_streamable_macro 0.8.0", "clvm-traits", "clvm-utils", "clvmr", @@ -334,6 +331,22 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "chia-puzzles" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a6e1ac8bd718b60e0b95d80301b31bb3894dc688c6ca77f4996bfa542b3ac4b" +dependencies = [ + "chia-bls 0.8.0", + "chia-protocol", + "clvm-traits", + "clvm-utils", + "clvmr", + "hex-literal", + "num-bigint", + "sha2 0.10.8", +] + [[package]] name = "chia-ssl" version = "0.7.0" @@ -362,33 +375,15 @@ dependencies = [ [[package]] name = "chia-traits" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58c4b856bcd141893b3b21b921764c6d76d4c73f20924bc11bdc3d1df10945a" +checksum = "2bca924e54d99288562e7c7e2f7dc39e12a0a1ee772b44a07ed5248199e8ac3f" dependencies = [ - "chia_streamable_macro 0.6.0", - "hex", + "chia_streamable_macro 0.8.0", "sha2 0.10.8", "thiserror", ] -[[package]] -name = "chia-wallet" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e9c5d92b703e8c4457ccfd89529403f888d6d8d700af35244fda0e71c4de8f" -dependencies = [ - "arbitrary", - "chia-bls 0.7.0", - "chia-protocol", - "clvm-traits", - "clvm-utils", - "clvmr", - "hex-literal", - "num-bigint", - "sha2 0.10.8", -] - [[package]] name = "chia-wallet-sdk" version = "0.7.1" @@ -396,13 +391,13 @@ dependencies = [ "anyhow", "bech32", "bip39", - "chia-bls 0.7.0", + "chia-bls 0.8.0", "chia-client", "chia-consensus", "chia-protocol", + "chia-puzzles", "chia-ssl", - "chia-traits 0.7.0", - "chia-wallet", + "chia-traits 0.8.0", "clvm-traits", "clvm-utils", "clvmr", @@ -440,9 +435,9 @@ dependencies = [ [[package]] name = "chia_streamable_macro" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fa14fa30a2560f36afb0d9f88d33a5d630858e0f893dc5e7094f06063d9e86" +checksum = "a1e2025e62defea0c0bb0912e15091e632cdad892986563e179ce9ad3d2c7d0b" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -463,11 +458,11 @@ dependencies = [ [[package]] name = "clvm-traits" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6fad36c3bffe918b28a0f56b00ada0b898e8932a4b08206596ebb8a79ec8d15" +checksum = "25b0460979b59433e5163ce90c95e6ad7d4448b0b01bf8a186f280320bec770e" dependencies = [ - "chia-bls 0.7.0", + "chia-bls 0.8.0", "clvm-derive", "clvmr", "num-bigint", @@ -476,19 +471,20 @@ dependencies = [ [[package]] name = "clvm-utils" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf3ba043f6bc8101b4ad5767e3701decdc722a66cc16caefba8b8a44f09dece" +checksum = "59ba275ebb00b110b331fdf326ebf1c223ec092eb3468df08a8a3c16b6495746" dependencies = [ "clvm-traits", "clvmr", + "hex", ] [[package]] name = "clvmr" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb235bb5ce94e1e64e54cede97ca5a4766590d3584dea1b4285499d8e7f56f6" +checksum = "b4b05784a842671ccedbefb0e3dabab7d0eb3411395f5182a4c68722a5284591" dependencies = [ "chia-bls 0.4.0", "hex-literal", @@ -633,17 +629,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive_arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "digest" version = "0.9.0" @@ -1196,6 +1181,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown", +] + [[package]] name = "md-5" version = "0.10.6" diff --git a/Cargo.toml b/Cargo.toml index 75972f90..4fc1bca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,14 +15,16 @@ sqlite = ["dep:sqlx"] tokio = { version = "1.33.0", features = ["macros"] } sha2 = "0.9.9" thiserror = "1.0.50" -clvmr = "0.6.1" -chia-protocol = "0.7.0" -chia-wallet = "0.7.0" -chia-bls = "0.7.0" -chia-client = "0.7.0" -clvm-traits = "0.7.0" -clvm-utils = "0.7.0" +clvmr = "0.7.0" +chia-protocol = "0.8.0" +chia-puzzles = "0.8.0" +chia-bls = "0.8.0" +chia-client = "0.8.0" +clvm-traits = "0.8.0" +clvm-utils = "0.8.0" chia-ssl = "0.7.0" +chia-traits = "0.8.0" +chia-consensus = "0.8.0" native-tls = "0.2.11" tokio-tungstenite = { version = "0.21.0", features = ["native-tls"] } hex = "0.4.3" @@ -35,8 +37,6 @@ serde_json = "1.0.115" serde = { version = "1.0.197", features = ["derive"] } futures-util = "0.3.30" futures-channel = { version = "0.3.30", features = ["sink"] } -chia-traits = "0.7.0" -chia-consensus = "0.7.0" indexmap = "2.2.6" flate2 = { version = "1.0.29", features = ["zlib"] } diff --git a/examples/key_stores.rs b/examples/key_stores.rs index 2833c756..204af44e 100644 --- a/examples/key_stores.rs +++ b/examples/key_stores.rs @@ -7,7 +7,7 @@ use chia_bls::{ }, DerivableKey, PublicKey, SecretKey, }; -use chia_wallet::{standard::DEFAULT_HIDDEN_PUZZLE_HASH, DeriveSynthetic}; +use chia_puzzles::DeriveSynthetic; use chia_wallet_sdk::sqlite::{fetch_puzzle_hash, insert_keys, SQLITE_MIGRATOR}; use sqlx::SqlitePool; @@ -32,11 +32,7 @@ async fn main() -> Result<(), Box> { let int_sk = master_to_wallet_hardened_intermediate(&root_sk); let unhardened_pks: Vec = (0..100) - .map(|index| { - int_pk - .derive_unhardened(index) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) - }) + .map(|index| int_pk.derive_unhardened(index).derive_synthetic()) .collect(); insert_keys(&mut tx, 0, unhardened_pks.as_slice(), false).await?; @@ -45,7 +41,7 @@ async fn main() -> Result<(), Box> { int_sk .derive_hardened(index) .public_key() - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) + .derive_synthetic() }) .collect(); insert_keys(&mut tx, 0, hardened_pks.as_slice(), true).await?; diff --git a/src/parser/parse_context.rs b/src/parser/parse_context.rs index 71a5652f..b03fbc17 100644 --- a/src/parser/parse_context.rs +++ b/src/parser/parse_context.rs @@ -26,12 +26,12 @@ impl ParseContext { self.solution } - pub fn parent_coin(&self) -> &Coin { - &self.parent_coin + pub fn parent_coin(&self) -> Coin { + self.parent_coin } - pub fn coin(&self) -> &Coin { - &self.coin + pub fn coin(&self) -> Coin { + self.coin } } diff --git a/src/parser/puzzles/cat.rs b/src/parser/puzzles/cat.rs index 1fde78c8..5206ea51 100644 --- a/src/parser/puzzles/cat.rs +++ b/src/parser/puzzles/cat.rs @@ -1,10 +1,10 @@ use chia_protocol::Bytes32; -use chia_wallet::{ - cat::{cat_puzzle_hash, CatArgs, CatSolution, CAT_PUZZLE_HASH}, +use chia_puzzles::{ + cat::{CatArgs, CatSolution, CAT_PUZZLE_HASH}, LineageProof, }; use clvm_traits::FromClvm; -use clvm_utils::tree_hash; +use clvm_utils::{tree_hash, CurriedProgram, ToTreeHash, TreeHash}; use clvmr::{reduction::Reduction, run_program, Allocator, ChiaDialect, NodePtr}; use crate::{CatInfo, CreateCoin, ParseContext, ParseError}; @@ -14,7 +14,7 @@ pub fn parse_cat( ctx: &ParseContext, max_cost: u64, ) -> Result, ParseError> { - if ctx.mod_hash().to_bytes() != CAT_PUZZLE_HASH { + if ctx.mod_hash().to_bytes() != CAT_PUZZLE_HASH.to_bytes() { return Ok(None); } @@ -37,12 +37,19 @@ pub fn parse_cat( continue; }; - let cat_puzzle_hash = Bytes32::new(cat_puzzle_hash( - args.tail_program_hash.into(), - create_coin.puzzle_hash().into(), - )); + let cat_puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: args.tail_program_hash, + inner_puzzle: TreeHash::from(create_coin.puzzle_hash()), + }, + } + .tree_hash(); - if cat_puzzle_hash == ctx.coin().puzzle_hash && create_coin.amount() == ctx.coin().amount { + if Bytes32::from(cat_puzzle_hash) == ctx.coin().puzzle_hash + && create_coin.amount() == ctx.coin().amount + { p2_puzzle_hash = Some(create_coin.puzzle_hash()); break; } @@ -55,11 +62,11 @@ pub fn parse_cat( Ok(Some(CatInfo { asset_id: args.tail_program_hash, p2_puzzle_hash, - coin: ctx.coin().clone(), + coin: ctx.coin(), lineage_proof: LineageProof { - parent_coin_info: ctx.parent_coin().parent_coin_info, - inner_puzzle_hash: tree_hash(allocator, args.inner_puzzle).into(), - amount: ctx.parent_coin().amount, + parent_parent_coin_id: ctx.parent_coin().parent_coin_info, + parent_inner_puzzle_hash: tree_hash(allocator, args.inner_puzzle).into(), + parent_amount: ctx.parent_coin().amount, }, })) } @@ -68,8 +75,9 @@ pub fn parse_cat( mod tests { use chia_bls::PublicKey; use chia_protocol::Coin; - use chia_wallet::standard::standard_puzzle_hash; + use chia_puzzles::standard::{StandardArgs, STANDARD_PUZZLE_HASH}; use clvm_traits::ToNodePtr; + use clvm_utils::CurriedProgram; use crate::{ parse_puzzle, Chainable, CreateCoinWithMemos, IssueCat, SpendContext, StandardSpend, @@ -83,7 +91,12 @@ mod tests { let mut ctx = SpendContext::new(&mut allocator); let pk = PublicKey::default(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); let (issue_cat, issuance_info) = IssueCat::new(parent.coin_id()) @@ -92,20 +105,26 @@ mod tests { amount: 1, memos: vec![puzzle_hash.to_vec().into()], })?) - .multi_issuance(&mut ctx, pk.clone(), 1)?; + .multi_issuance(&mut ctx, pk, 1)?; + + let cat_puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: issuance_info.asset_id, + inner_puzzle: TreeHash::from(puzzle_hash), + }, + } + .tree_hash(); 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(issuance_info.asset_id.into(), puzzle_hash.into()).into(), - 1, - ), + coin: Coin::new(issuance_info.eve_coin.coin_id(), cat_puzzle_hash.into(), 1), lineage_proof: LineageProof { - parent_coin_info: issuance_info.eve_coin.parent_coin_info, - inner_puzzle_hash: issuance_info.eve_inner_puzzle_hash, - amount: 1, + parent_parent_coin_id: issuance_info.eve_coin.parent_coin_info, + parent_inner_puzzle_hash: issuance_info.eve_inner_puzzle_hash, + parent_amount: 1, }, }; @@ -128,7 +147,7 @@ mod tests { puzzle, solution, coin_spend.coin, - cat_info.coin.clone(), + cat_info.coin, )?; let parse = parse_cat(&mut allocator, &parse_ctx, u64::MAX)?; diff --git a/src/parser/puzzles/did.rs b/src/parser/puzzles/did.rs index dead2a3f..ce30a2f6 100644 --- a/src/parser/puzzles/did.rs +++ b/src/parser/puzzles/did.rs @@ -1,5 +1,5 @@ use chia_protocol::Bytes32; -use chia_wallet::{ +use chia_puzzles::{ did::{DidArgs, DidSolution}, LineageProof, Proof, }; @@ -75,16 +75,16 @@ pub fn parse_did( Ok(Some(DidInfo { launcher_id: args.singleton_struct.launcher_id, - coin: ctx.coin().clone(), + coin: ctx.coin(), p2_puzzle_hash, did_inner_puzzle_hash, recovery_did_list_hash: args.recovery_did_list_hash, num_verifications_required: args.num_verifications_required, metadata: args.metadata, proof: Proof::Lineage(LineageProof { - parent_coin_info: ctx.parent_coin().parent_coin_info, - inner_puzzle_hash: tree_hash(allocator, singleton.args().inner_puzzle).into(), - amount: ctx.parent_coin().amount, + parent_parent_coin_id: ctx.parent_coin().parent_coin_info, + parent_inner_puzzle_hash: tree_hash(allocator, singleton.args().inner_puzzle).into(), + parent_amount: ctx.parent_coin().amount, }), })) } @@ -93,8 +93,9 @@ pub fn parse_did( mod tests { use chia_bls::PublicKey; use chia_protocol::{Bytes32, Coin}; - use chia_wallet::standard::standard_puzzle_hash; + use chia_puzzles::standard::{StandardArgs, STANDARD_PUZZLE_HASH}; use clvm_traits::ToNodePtr; + use clvm_utils::{CurriedProgram, ToTreeHash}; use clvmr::Allocator; use crate::{ @@ -108,12 +109,17 @@ mod tests { let mut ctx = SpendContext::new(&mut allocator); let pk = PublicKey::default(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = Coin::new(Bytes32::default(), puzzle_hash, 1); let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) .create(&mut ctx)? - .create_standard_did(&mut ctx, pk.clone())?; + .create_standard_did(&mut ctx, pk)?; StandardSpend::new() .chain(create_did) @@ -134,7 +140,7 @@ mod tests { puzzle, solution, coin_spend.coin, - did_info.coin.clone(), + did_info.coin, )?; let parse = parse_singleton(&mut allocator, &parse_ctx)?.unwrap(); diff --git a/src/parser/puzzles/singleton.rs b/src/parser/puzzles/singleton.rs index ecccc7e0..2af8dd1a 100644 --- a/src/parser/puzzles/singleton.rs +++ b/src/parser/puzzles/singleton.rs @@ -1,5 +1,5 @@ use chia_protocol::Bytes32; -use chia_wallet::singleton::{ +use chia_puzzles::singleton::{ SingletonArgs, SingletonSolution, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, }; @@ -43,7 +43,7 @@ pub fn parse_singleton( allocator: &mut Allocator, ctx: &ParseContext, ) -> Result, ParseError> { - if ctx.mod_hash().to_bytes() != SINGLETON_TOP_LAYER_PUZZLE_HASH { + if ctx.mod_hash().to_bytes() != SINGLETON_TOP_LAYER_PUZZLE_HASH.to_bytes() { return Ok(None); } @@ -59,8 +59,8 @@ pub fn parse_singleton( .launcher_puzzle_hash .as_ref(); - if singleton_mod_hash != SINGLETON_TOP_LAYER_PUZZLE_HASH - || launcher_puzzle_hash != SINGLETON_LAUNCHER_PUZZLE_HASH + if singleton_mod_hash != SINGLETON_TOP_LAYER_PUZZLE_HASH.to_bytes() + || launcher_puzzle_hash != SINGLETON_LAUNCHER_PUZZLE_HASH.to_bytes() { return Err(ParseError::InvalidSingletonStruct); } diff --git a/src/spends/puzzles/cat/cat_info.rs b/src/spends/puzzles/cat/cat_info.rs index 10cf786b..fa23b9d4 100644 --- a/src/spends/puzzles/cat/cat_info.rs +++ b/src/spends/puzzles/cat/cat_info.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin}; -use chia_wallet::LineageProof; +use chia_puzzles::LineageProof; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CatInfo { diff --git a/src/spends/puzzles/cat/cat_spend.rs b/src/spends/puzzles/cat/cat_spend.rs index 5aa195a7..a3adb0d3 100644 --- a/src/spends/puzzles/cat/cat_spend.rs +++ b/src/spends/puzzles/cat/cat_spend.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::{ +use chia_puzzles::{ cat::{CatArgs, CatSolution, CoinProof, CAT_PUZZLE_HASH}, LineageProof, }; @@ -90,19 +90,19 @@ impl CatSpend { let solution = ctx.serialize(CatSolution { inner_puzzle_solution: inner_spend.solution(), - lineage_proof: Some(lineage_proof.clone()), + lineage_proof: Some(*lineage_proof), prev_coin_id: prev_cat.coin.coin_id(), - this_coin_info: coin.clone(), + 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()), + inner_puzzle_hash: ctx.tree_hash(inner_spend.puzzle()).into(), amount: next_cat.coin.amount, }, prev_subtotal, extra_delta: *extra_delta, })?; - ctx.spend(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); + ctx.spend(CoinSpend::new(*coin, puzzle_reveal, solution)); } Ok(()) @@ -117,11 +117,11 @@ mod tests { solution_generator::solution_generator, }; use chia_protocol::{Bytes32, Program}; - use chia_wallet::{ - cat::cat_puzzle_hash, - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + use chia_puzzles::{ + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, DeriveSynthetic, }; + use clvm_utils::ToTreeHash; use clvmr::{serde::node_to_bytes, Allocator}; use hex_literal::hex; @@ -131,23 +131,32 @@ mod tests { #[test] fn test_cat_spend() -> anyhow::Result<()> { - let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let synthetic_key = + master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0).derive_synthetic(); let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); let asset_id = Bytes32::new([42; 32]); - let p2_puzzle_hash = Bytes32::new(standard_puzzle_hash(&synthetic_key)); - let cat_puzzle_hash = cat_puzzle_hash(asset_id.to_bytes(), p2_puzzle_hash.to_bytes()); + let p2_puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key }, + } + .tree_hash(); + + let cat_puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: asset_id, + inner_puzzle: p2_puzzle_hash, + }, + } + .tree_hash(); - let parent_coin = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin = Coin::new( - Bytes32::from(parent_coin.coin_id()), - Bytes32::new(cat_puzzle_hash), - 42, - ); + let parent_coin = Coin::new(Bytes32::new([0; 32]), cat_puzzle_hash.into(), 69); + let coin = Coin::new(parent_coin.coin_id(), cat_puzzle_hash.into(), 42); let inner_spend = StandardSpend::new() .condition(ctx.alloc(CreateCoinWithoutMemos { @@ -157,9 +166,9 @@ mod tests { .inner_spend(&mut ctx, synthetic_key)?; let lineage_proof = LineageProof { - parent_coin_info: parent_coin.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin.amount, + parent_parent_coin_id: parent_coin.parent_coin_info, + parent_inner_puzzle_hash: p2_puzzle_hash.into(), + parent_amount: parent_coin.amount, }; CatSpend::new(asset_id) @@ -193,54 +202,56 @@ mod tests { #[test] fn test_cat_spend_multi() -> anyhow::Result<()> { - let synthetic_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let synthetic_key = + master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0).derive_synthetic(); let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); let asset_id = Bytes32::new([42; 32]); - let p2_puzzle_hash = Bytes32::new(standard_puzzle_hash(&synthetic_key)); - let cat_puzzle_hash = cat_puzzle_hash(asset_id.to_bytes(), p2_puzzle_hash.to_bytes()); + let p2_puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key }, + } + .tree_hash(); + + let cat_puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: asset_id, + inner_puzzle: p2_puzzle_hash, + }, + } + .tree_hash() + .into(); - let parent_coin_1 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin_1 = Coin::new( - Bytes32::from(parent_coin_1.coin_id()), - Bytes32::new(cat_puzzle_hash), - 42, - ); + let parent_coin_1 = Coin::new(Bytes32::new([0; 32]), cat_puzzle_hash, 69); + let coin_1 = Coin::new(parent_coin_1.coin_id(), cat_puzzle_hash, 42); - let parent_coin_2 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin_2 = Coin::new( - Bytes32::from(parent_coin_2.coin_id()), - Bytes32::new(cat_puzzle_hash), - 34, - ); + let parent_coin_2 = Coin::new(Bytes32::new([0; 32]), cat_puzzle_hash, 69); + let coin_2 = Coin::new(parent_coin_2.coin_id(), cat_puzzle_hash, 34); - let parent_coin_3 = Coin::new(Bytes32::new([0; 32]), Bytes32::new(cat_puzzle_hash), 69); - let coin_3 = Coin::new( - Bytes32::from(parent_coin_3.coin_id()), - Bytes32::new(cat_puzzle_hash), - 69, - ); + let parent_coin_3 = Coin::new(Bytes32::new([0; 32]), cat_puzzle_hash, 69); + let coin_3 = Coin::new(parent_coin_3.coin_id(), cat_puzzle_hash, 69); let lineage_1 = LineageProof { - parent_coin_info: parent_coin_1.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_1.amount, + parent_parent_coin_id: parent_coin_1.parent_coin_info, + parent_inner_puzzle_hash: p2_puzzle_hash.into(), + parent_amount: parent_coin_1.amount, }; let lineage_2 = LineageProof { - parent_coin_info: parent_coin_2.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_2.amount, + parent_parent_coin_id: parent_coin_2.parent_coin_info, + parent_inner_puzzle_hash: p2_puzzle_hash.into(), + parent_amount: parent_coin_2.amount, }; let lineage_3 = LineageProof { - parent_coin_info: parent_coin_3.parent_coin_info, - inner_puzzle_hash: p2_puzzle_hash, - amount: parent_coin_3.amount, + parent_parent_coin_id: parent_coin_3.parent_coin_info, + parent_inner_puzzle_hash: p2_puzzle_hash.into(), + parent_amount: parent_coin_3.amount, }; let inner_spend = StandardSpend::new() @@ -248,7 +259,7 @@ mod tests { puzzle_hash: coin_1.puzzle_hash, amount: coin_1.amount + coin_2.amount + coin_3.amount, })?) - .inner_spend(&mut ctx, synthetic_key.clone())?; + .inner_spend(&mut ctx, synthetic_key)?; let empty_spend = StandardSpend::new().inner_spend(&mut ctx, synthetic_key)?; diff --git a/src/spends/puzzles/cat/issue_cat.rs b/src/spends/puzzles/cat/issue_cat.rs index 89743c6d..812a4710 100644 --- a/src/spends/puzzles/cat/issue_cat.rs +++ b/src/spends/puzzles/cat/issue_cat.rs @@ -1,12 +1,11 @@ use chia_bls::PublicKey; use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::cat::{ +use chia_puzzles::cat::{ CatArgs, CatSolution, CoinProof, EverythingWithSignatureTailArgs, CAT_PUZZLE_HASH, }; use clvm_traits::clvm_quote; -use clvm_utils::{curry_tree_hash, tree_hash_atom, CurriedProgram}; +use clvm_utils::CurriedProgram; use clvmr::NodePtr; -use hex_literal::hex; use crate::{ChainedSpend, CreateCoinWithMemos, RunTail, SpendContext, SpendError}; @@ -51,7 +50,7 @@ impl IssueCat { program: tail_puzzle_ptr, args: EverythingWithSignatureTailArgs { public_key }, })?; - let asset_id = ctx.tree_hash(tail); + let asset_id = ctx.tree_hash(tail).into(); self.condition(ctx.alloc(RunTail { program: tail, @@ -69,7 +68,7 @@ impl IssueCat { 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); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); let puzzle = ctx.alloc(CurriedProgram { program: cat_puzzle_ptr, @@ -80,14 +79,14 @@ impl IssueCat { }, })?; - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let coin = Coin::new(self.parent_coin_id, puzzle_hash, amount); let solution = ctx.serialize(CatSolution { inner_puzzle_solution: (), lineage_proof: None, prev_coin_id: coin.coin_id(), - this_coin_info: coin.clone(), + this_coin_info: coin, next_coin_proof: CoinProof { parent_coin_info: self.parent_coin_id, inner_puzzle_hash, @@ -98,7 +97,7 @@ impl IssueCat { })?; let puzzle_reveal = ctx.serialize(puzzle)?; - ctx.spend(CoinSpend::new(coin.clone(), puzzle_reveal, solution)); + ctx.spend(CoinSpend::new(coin, puzzle_reveal, solution)); let chained_spend = ChainedSpend { parent_conditions: vec![ctx.alloc(CreateCoinWithMemos { @@ -118,23 +117,15 @@ impl IssueCat { } } -pub fn multi_issuance_asset_id(public_key: &PublicKey) -> Bytes32 { - let public_key_hash = tree_hash_atom(&public_key.to_bytes()); - curry_tree_hash( - hex!("1720d13250a7c16988eaf530331cefa9dd57a76b2c82236bec8bbbff91499b89"), - &[public_key_hash], - ) - .into() -} - #[cfg(test)] mod tests { use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; - use chia_wallet::{ - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + use chia_puzzles::{ + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, DeriveSynthetic, }; + use clvm_utils::ToTreeHash; use clvmr::Allocator; use crate::{ @@ -152,9 +143,16 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let sk = SECRET_KEY.derive_synthetic(); let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); + let xch_coin = sim.generate_coin(puzzle_hash, 1).await.coin; let (issue_cat, _cat_info) = IssueCat::new(xch_coin.coin_id()) @@ -163,7 +161,7 @@ mod tests { amount: 1, memos: vec![puzzle_hash.to_vec().into()], })?) - .multi_issuance(&mut ctx, pk.clone(), 1)?; + .multi_issuance(&mut ctx, pk, 1)?; StandardSpend::new() .chain(issue_cat) diff --git a/src/spends/puzzles/did.rs b/src/spends/puzzles/did.rs index e0eb0e94..469f2cb7 100644 --- a/src/spends/puzzles/did.rs +++ b/src/spends/puzzles/did.rs @@ -12,10 +12,11 @@ mod tests { use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; - use chia_wallet::{ - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + use chia_puzzles::{ + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, DeriveSynthetic, }; + use clvm_utils::{CurriedProgram, ToTreeHash}; use clvmr::Allocator; use crate::{ @@ -28,9 +29,15 @@ mod tests { let sim = WalletSimulator::new().await; let peer = sim.peer().await; - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let sk = SECRET_KEY.derive_synthetic(); let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = sim.generate_coin(puzzle_hash, 1).await.coin; @@ -39,7 +46,7 @@ mod tests { let (launch_singleton, _did_info) = Launcher::new(parent.coin_id(), 1) .create(&mut ctx)? - .create_standard_did(&mut ctx, pk.clone())?; + .create_standard_did(&mut ctx, pk)?; StandardSpend::new() .chain(launch_singleton) diff --git a/src/spends/puzzles/did/create_did.rs b/src/spends/puzzles/did/create_did.rs index cb8a0c9a..b2ceefe4 100644 --- a/src/spends/puzzles/did/create_did.rs +++ b/src/spends/puzzles/did/create_did.rs @@ -1,13 +1,13 @@ use chia_bls::PublicKey; use chia_protocol::Bytes32; -use chia_wallet::{ +use chia_puzzles::{ did::DID_INNER_PUZZLE_HASH, singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, - standard::standard_puzzle_hash, + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, EveProof, Proof, }; use clvm_traits::ToClvm; -use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; +use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair, CurriedProgram, ToTreeHash}; use clvmr::NodePtr; use crate::{ @@ -39,7 +39,12 @@ pub trait CreateDid { M: ToClvm, Self: Sized, { - let inner_puzzle_hash = standard_puzzle_hash(&synthetic_key).into(); + let inner_puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key }, + } + .tree_hash() + .into(); let (create_did, did_info) = self.create_eve_did( ctx, @@ -81,7 +86,7 @@ impl CreateDid for SpendableLauncher { M: ToClvm, { let metadata_ptr = ctx.alloc(&metadata)?; - let metadata_hash = ctx.tree_hash(metadata_ptr); + let metadata_hash = ctx.tree_hash(metadata_ptr).into(); let did_inner_puzzle_hash = did_inner_puzzle_hash( p2_puzzle_hash, @@ -91,7 +96,7 @@ impl CreateDid for SpendableLauncher { metadata_hash, ); - let launcher_coin = self.coin().clone(); + let launcher_coin = self.coin(); let (chained_spend, eve_coin) = self.spend(ctx, did_inner_puzzle_hash, ())?; let proof = Proof::Eve(EveProof { @@ -148,7 +153,7 @@ pub fn did_inner_puzzle_hash( mod tests { use super::*; - use chia_wallet::{ + use chia_puzzles::{ did::DidArgs, singleton::{ SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, @@ -163,10 +168,10 @@ mod tests { let mut ctx = SpendContext::new(&mut allocator); let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); let metadata = ctx.alloc([4, 5, 6]).unwrap(); - let metadata_hash = ctx.tree_hash(metadata); + let metadata_hash = ctx.tree_hash(metadata).into(); let launcher_id = Bytes32::new([34; 32]); let recovery_did_list_hash = Bytes32::new([42; 32]); diff --git a/src/spends/puzzles/did/did_info.rs b/src/spends/puzzles/did/did_info.rs index 87c49798..2ec279be 100644 --- a/src/spends/puzzles/did/did_info.rs +++ b/src/spends/puzzles/did/did_info.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin}; -use chia_wallet::Proof; +use chia_puzzles::Proof; #[derive(Debug, Clone, PartialEq, Eq)] pub struct DidInfo { diff --git a/src/spends/puzzles/did/did_spend.rs b/src/spends/puzzles/did/did_spend.rs index 37c6c459..22f1e14e 100644 --- a/src/spends/puzzles/did/did_spend.rs +++ b/src/spends/puzzles/did/did_spend.rs @@ -1,6 +1,6 @@ use chia_bls::PublicKey; use chia_protocol::{Coin, CoinSpend}; -use chia_wallet::{ +use chia_puzzles::{ did::{DidArgs, DidSolution}, singleton::{SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, LineageProof, Proof, @@ -76,9 +76,9 @@ impl StandardDidSpend { match self.output { DidOutput::Recreate => { did_info.proof = Proof::Lineage(LineageProof { - parent_coin_info: did_info.coin.parent_coin_info, - inner_puzzle_hash: did_info.did_inner_puzzle_hash, - amount: did_info.coin.amount, + 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( @@ -136,9 +136,9 @@ where spend_singleton( ctx, - did_info.coin.clone(), + did_info.coin, did_info.launcher_id, - did_info.proof.clone(), + did_info.proof, did_spend, ) } @@ -147,10 +147,11 @@ where mod tests { use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; - use chia_wallet::{ - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + use chia_puzzles::{ + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, DeriveSynthetic, }; + use clvm_utils::ToTreeHash; use clvmr::Allocator; use crate::{testing::SECRET_KEY, CreateDid, Launcher, RequiredSignature, WalletSimulator}; @@ -165,24 +166,30 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let sk = SECRET_KEY.derive_synthetic(); let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = sim.generate_coin(puzzle_hash, 1).await.coin; let (create_did, mut did_info) = Launcher::new(parent.coin_id(), 1) .create(&mut ctx)? - .create_standard_did(&mut ctx, pk.clone())?; + .create_standard_did(&mut ctx, pk)?; StandardSpend::new() .chain(create_did) - .finish(&mut ctx, parent, pk.clone())?; + .finish(&mut ctx, parent, pk)?; for _ in 0..10 { did_info = StandardDidSpend::new() .recreate() - .finish(&mut ctx, pk.clone(), did_info)?; + .finish(&mut ctx, pk, did_info)?; } let coin_spends = ctx.take_spends(); diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index d7743439..0689ca48 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -1,16 +1,16 @@ use chia_bls::PublicKey; use chia_protocol::Bytes32; -use chia_wallet::{ +use chia_puzzles::{ nft::{ NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, NFT_ROYALTY_TRANSFER_PUZZLE_HASH, NFT_STATE_LAYER_PUZZLE_HASH, }, singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, - standard::standard_puzzle_hash, + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, EveProof, Proof, }; use clvm_traits::ToClvm; -use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; +use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair, CurriedProgram, ToTreeHash}; use clvmr::NodePtr; use crate::{ @@ -50,7 +50,14 @@ pub trait MintNft { M: ToClvm, Self: Sized, { - let inner_puzzle_hash = standard_puzzle_hash(&mint.synthetic_key).into(); + 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( ctx, @@ -84,7 +91,7 @@ impl MintNft for SpendableLauncher { M: ToClvm, { let metadata_ptr = ctx.alloc(&metadata)?; - let metadata_hash = ctx.tree_hash(metadata_ptr); + let metadata_hash = ctx.tree_hash(metadata_ptr).into(); let ownership_layer_hash = nft_ownership_layer_hash( None, @@ -95,13 +102,14 @@ impl MintNft for SpendableLauncher { ), p2_puzzle_hash, ); + let nft_inner_puzzle_hash = nft_state_layer_hash( metadata_hash, NFT_METADATA_UPDATER_PUZZLE_HASH.into(), ownership_layer_hash, ); - let launcher_coin = self.coin().clone(); + let launcher_coin = self.coin(); let (mut chained_spend, eve_coin) = self.spend(ctx, nft_inner_puzzle_hash, ())?; chained_spend @@ -205,7 +213,7 @@ pub fn nft_royalty_transfer_hash( mod tests { use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; - use chia_wallet::{ + use chia_puzzles::{ nft::{ NftOwnershipLayerArgs, NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs, NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, @@ -214,7 +222,6 @@ mod tests { singleton::{ SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, }, - standard::DEFAULT_HIDDEN_PUZZLE_HASH, DeriveSynthetic, }; use clvm_utils::CurriedProgram; @@ -235,27 +242,32 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let sk = SECRET_KEY.derive_synthetic(); let pk = sk.public_key(); - let puzzle_hash = Bytes32::new(standard_puzzle_hash(&pk)); + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = sim.generate_coin(puzzle_hash, 3).await.coin; let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) .create(&mut ctx)? - .create_standard_did(&mut ctx, pk.clone())?; + .create_standard_did(&mut ctx, pk)?; StandardSpend::new() .chain(create_did) - .finish(&mut ctx, parent, pk.clone())?; + .finish(&mut ctx, parent, pk)?; let mint = StandardMint { metadata: (), royalty_puzzle_hash: puzzle_hash, royalty_percentage: 100, owner_puzzle_hash: puzzle_hash, - synthetic_key: pk.clone(), + synthetic_key: pk, did_id: did_info.launcher_id, did_inner_puzzle_hash: did_info.did_inner_puzzle_hash, }; @@ -305,10 +317,10 @@ mod tests { let mut ctx = SpendContext::new(&mut allocator); let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); let metadata = ctx.alloc([4, 5, 6]).unwrap(); - let metadata_hash = ctx.tree_hash(metadata); + let metadata_hash = ctx.tree_hash(metadata).into(); let nft_state_layer = ctx.nft_state_layer(); @@ -340,7 +352,7 @@ mod tests { let mut ctx = SpendContext::new(&mut allocator); let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle); + let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); let launcher_id = Bytes32::new([69; 32]); @@ -366,7 +378,7 @@ mod tests { }, }) .unwrap(); - let allocated_transfer_program_hash = ctx.tree_hash(transfer_program); + let allocated_transfer_program_hash = ctx.tree_hash(transfer_program).into(); let puzzle = ctx .alloc(CurriedProgram { diff --git a/src/spends/puzzles/nft/nft_info.rs b/src/spends/puzzles/nft/nft_info.rs index 54d37400..d6f73e33 100644 --- a/src/spends/puzzles/nft/nft_info.rs +++ b/src/spends/puzzles/nft/nft_info.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin}; -use chia_wallet::Proof; +use chia_puzzles::Proof; #[derive(Debug, Clone, PartialEq, Eq)] pub struct NftInfo { diff --git a/src/spends/puzzles/nft/nft_spend.rs b/src/spends/puzzles/nft/nft_spend.rs index e4603ee6..f64e06f6 100644 --- a/src/spends/puzzles/nft/nft_spend.rs +++ b/src/spends/puzzles/nft/nft_spend.rs @@ -1,6 +1,6 @@ use chia_bls::PublicKey; use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::{ +use chia_puzzles::{ nft::{ NftOwnershipLayerArgs, NftOwnershipLayerSolution, NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs, NftStateLayerSolution, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, @@ -136,7 +136,7 @@ impl StandardNftSpend { let metadata_ptr = ctx.alloc(&nft_info.metadata)?; let new_inner_puzzle_hash = nft_state_layer_hash( - ctx.tree_hash(metadata_ptr), + ctx.tree_hash(metadata_ptr).into(), nft_info.metadata_updater_hash, nft_ownership_layer_hash( nft_info.current_owner, @@ -152,9 +152,9 @@ impl StandardNftSpend { let new_puzzle_hash = singleton_puzzle_hash(nft_info.launcher_id, new_inner_puzzle_hash); nft_info.proof = Proof::Lineage(LineageProof { - parent_coin_info: nft_info.coin.parent_coin_info, - inner_puzzle_hash: nft_info.nft_inner_puzzle_hash, - amount: nft_info.coin.amount, + 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( @@ -216,9 +216,9 @@ where spend_singleton( ctx, - nft_info.coin.clone(), + nft_info.coin, nft_info.launcher_id, - nft_info.proof.clone(), + nft_info.proof, state_layer_spend, ) } @@ -285,10 +285,11 @@ mod tests { use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; - use chia_wallet::{ - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + use chia_puzzles::{ + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, DeriveSynthetic, }; + use clvm_utils::ToTreeHash; use clvmr::Allocator; use crate::{ @@ -304,19 +305,25 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let sk = SECRET_KEY.derive_synthetic(); let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = sim.generate_coin(puzzle_hash, 2).await.coin; let (create_did, did_info) = Launcher::new(parent.coin_id(), 1) .create(&mut ctx)? - .create_standard_did(&mut ctx, pk.clone())?; + .create_standard_did(&mut ctx, pk)?; StandardSpend::new() .chain(create_did) - .finish(&mut ctx, parent, pk.clone())?; + .finish(&mut ctx, parent, pk)?; let (mint_nft, mut nft_info) = IntermediateLauncher::new(did_info.coin.coin_id(), 0, 1) .create(&mut ctx)? @@ -326,18 +333,17 @@ mod tests { metadata: (), royalty_puzzle_hash: puzzle_hash, royalty_percentage: 300, - synthetic_key: pk.clone(), + synthetic_key: pk, owner_puzzle_hash: puzzle_hash, did_id: did_info.launcher_id, did_inner_puzzle_hash: did_info.did_inner_puzzle_hash, }, )?; - let mut did_info = StandardDidSpend::new().chain(mint_nft).recreate().finish( - &mut ctx, - pk.clone(), - did_info, - )?; + let mut did_info = StandardDidSpend::new() + .chain(mint_nft) + .recreate() + .finish(&mut ctx, pk, did_info)?; for i in 0..5 { let mut spend = StandardNftSpend::new().update(); @@ -346,14 +352,13 @@ mod tests { spend = spend.new_owner(did_info.launcher_id, did_info.did_inner_puzzle_hash); } - let (nft_spend, new_nft_info) = spend.finish(&mut ctx, pk.clone(), nft_info)?; + let (nft_spend, new_nft_info) = spend.finish(&mut ctx, pk, nft_info)?; nft_info = new_nft_info; - did_info = StandardDidSpend::new().chain(nft_spend).recreate().finish( - &mut ctx, - pk.clone(), - did_info, - )?; + did_info = StandardDidSpend::new() + .chain(nft_spend) + .recreate() + .finish(&mut ctx, pk, did_info)?; } let coin_spends = ctx.take_spends(); diff --git a/src/spends/puzzles/offer.rs b/src/spends/puzzles/offer.rs index 4e2b3128..c596eb31 100644 --- a/src/spends/puzzles/offer.rs +++ b/src/spends/puzzles/offer.rs @@ -55,14 +55,14 @@ mod tests { use super::*; use chia_bls::{sign, DerivableKey, SecretKey, Signature}; - use chia_protocol::{Bytes32, Coin, SpendBundle}; - use chia_wallet::{ - cat::{cat_puzzle_hash, CatArgs, CAT_PUZZLE_HASH}, + use chia_protocol::{Coin, SpendBundle}; + use chia_puzzles::{ + cat::{CatArgs, CAT_PUZZLE_HASH}, offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH, - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, + standard::{StandardArgs, STANDARD_PUZZLE_HASH}, DeriveSynthetic, LineageProof, }; - use clvm_utils::CurriedProgram; + use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use clvmr::Allocator; use crate::{ @@ -72,15 +72,11 @@ mod tests { }; fn sk1() -> SecretKey { - SECRET_KEY - .derive_unhardened(0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) + SECRET_KEY.derive_unhardened(0).derive_synthetic() } fn sk2() -> SecretKey { - SECRET_KEY - .derive_unhardened(1) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) + SECRET_KEY.derive_unhardened(1).derive_synthetic() } fn sign_tx(required_signatures: Vec) -> Signature { @@ -93,10 +89,10 @@ mod tests { let mut aggregated_signature = Signature::default(); for req in required_signatures { - if req.public_key() == &pk1 { + if req.public_key() == pk1 { let sig = sign(&sk1, &req.final_message()); aggregated_signature += &sig; - } else if req.public_key() == &pk2 { + } else if req.public_key() == pk2 { let sig = sign(&sk2, &req.final_message()); aggregated_signature += &sig; } else { @@ -118,7 +114,12 @@ mod tests { let sk = sk1(); let pk = sk.public_key(); - let puzzle_hash = Bytes32::new(standard_puzzle_hash(&pk)); + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = sim.generate_coin(puzzle_hash, 1000).await.coin; @@ -128,11 +129,11 @@ mod tests { amount: 1000, memos: vec![puzzle_hash.to_vec().into()], })?) - .multi_issuance(&mut ctx, pk.clone(), 1000)?; + .multi_issuance(&mut ctx, pk, 1000)?; StandardSpend::new() .chain(issue_cat) - .finish(&mut ctx, parent.clone(), pk.clone())?; + .finish(&mut ctx, parent, pk)?; let coin_spends = ctx.take_spends(); @@ -151,11 +152,18 @@ mod tests { assert_eq!(ack.status, 1); // Prepare offer contents. - let cat = Coin::new( - cat_info.eve_coin.coin_id(), - cat_puzzle_hash(cat_info.asset_id.into(), puzzle_hash.into()).into(), - 1000, - ); + let cat_puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: cat_info.asset_id, + inner_puzzle: TreeHash::from(puzzle_hash), + }, + } + .tree_hash() + .into(); + + let cat = Coin::new(cat_info.eve_coin.coin_id(), cat_puzzle_hash, 1000); let xch = sim.generate_coin(puzzle_hash, 1000).await.coin; @@ -196,12 +204,12 @@ mod tests { )?; let assert_cat = - offer_announcement_id(&mut ctx, cat_settlements_hash, cat_payment.clone())?; + offer_announcement_id(&mut ctx, cat_settlements_hash.into(), cat_payment.clone())?; let lineage_proof = LineageProof { - parent_coin_info: parent.coin_id(), - inner_puzzle_hash: cat_info.eve_inner_puzzle_hash, - amount: 1000, + parent_parent_coin_id: parent.coin_id(), + parent_inner_puzzle_hash: cat_info.eve_inner_puzzle_hash, + parent_amount: 1000, }; let inner_spend = StandardSpend::new() @@ -213,17 +221,24 @@ mod tests { .condition(ctx.alloc(AssertPuzzleAnnouncement { announcement_id: assert_xch, })?) - .inner_spend(&mut ctx, pk.clone())?; + .inner_spend(&mut ctx, pk)?; CatSpend::new(cat_info.asset_id) - .spend(cat.clone(), inner_spend, lineage_proof, 0) + .spend(cat, inner_spend, lineage_proof, 0) .finish(&mut ctx)?; - let cat_settlement_coin = Coin::new( - cat.coin_id(), - cat_puzzle_hash(cat_info.asset_id.into(), SETTLEMENT_PAYMENTS_PUZZLE_HASH).into(), - 1000, - ); + let cat_puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: cat_info.asset_id, + inner_puzzle: SETTLEMENT_PAYMENTS_PUZZLE_HASH, + }, + } + .tree_hash() + .into(); + + let cat_settlement_coin = Coin::new(cat.coin_id(), cat_puzzle_hash, 1000); StandardSpend::new() .condition(ctx.alloc(CreateCoinWithoutMemos { @@ -233,15 +248,15 @@ mod tests { .condition(ctx.alloc(AssertPuzzleAnnouncement { announcement_id: assert_cat, })?) - .finish(&mut ctx, xch.clone(), pk)?; + .finish(&mut ctx, xch, pk)?; let xch_settlement_coin = Coin::new(xch.coin_id(), SETTLEMENT_PAYMENTS_PUZZLE_HASH.into(), 1000); let lineage_proof = LineageProof { - parent_coin_info: cat_info.eve_coin.coin_id(), - inner_puzzle_hash: puzzle_hash, - amount: 1000, + parent_parent_coin_id: cat_info.eve_coin.coin_id(), + parent_inner_puzzle_hash: puzzle_hash, + parent_amount: 1000, }; let solution = ctx.alloc(SettlementPaymentsSolution { diff --git a/src/spends/puzzles/offer/offer_builder.rs b/src/spends/puzzles/offer/offer_builder.rs index 76078851..8dce57cf 100644 --- a/src/spends/puzzles/offer/offer_builder.rs +++ b/src/spends/puzzles/offer/offer_builder.rs @@ -1,9 +1,9 @@ use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::{ - cat::{cat_puzzle_hash, CatArgs, CAT_PUZZLE_HASH}, +use chia_puzzles::{ + cat::{CatArgs, CAT_PUZZLE_HASH}, offer::SETTLEMENT_PAYMENTS_PUZZLE_HASH, }; -use clvm_utils::{tree_hash_atom, tree_hash_pair, CurriedProgram}; +use clvm_utils::{tree_hash_atom, tree_hash_pair, CurriedProgram, ToTreeHash}; use clvmr::NodePtr; use sha2::{digest::FixedOutput, Digest, Sha256}; @@ -47,7 +47,15 @@ impl OfferBuilder { asset_id: Bytes32, payments: Vec, ) -> Result { - let puzzle_hash = cat_puzzle_hash(asset_id.into(), SETTLEMENT_PAYMENTS_PUZZLE_HASH); + let puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: asset_id, + inner_puzzle: SETTLEMENT_PAYMENTS_PUZZLE_HASH, + }, + } + .tree_hash(); let puzzle = if let Some(puzzle) = ctx.get_puzzle(&puzzle_hash) { puzzle @@ -117,7 +125,7 @@ pub fn request_offer_payments( payments: Vec, ) -> Result<(CoinSpend, Bytes32), SpendError> { let puzzle_reveal = ctx.serialize(puzzle)?; - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let notarized_payment = NotarizedPayment { nonce, payments }; diff --git a/src/spends/puzzles/offer/offer_compression.rs b/src/spends/puzzles/offer/offer_compression.rs index ca9f4021..12461e96 100644 --- a/src/spends/puzzles/offer/offer_compression.rs +++ b/src/spends/puzzles/offer/offer_compression.rs @@ -5,7 +5,7 @@ use std::{ use chia_protocol::SpendBundle; use chia_traits::Streamable; -use chia_wallet::{ +use chia_puzzles::{ cat::{CAT_PUZZLE, CAT_PUZZLE_V1}, nft::{ NFT_METADATA_UPDATER_PUZZLE, NFT_OWNERSHIP_LAYER_PUZZLE, NFT_ROYALTY_TRANSFER_PUZZLE, diff --git a/src/spends/puzzles/singleton/intermediate_launcher.rs b/src/spends/puzzles/singleton/intermediate_launcher.rs index 6e5cd7bb..0ee5804c 100644 --- a/src/spends/puzzles/singleton/intermediate_launcher.rs +++ b/src/spends/puzzles/singleton/intermediate_launcher.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::{ +use chia_puzzles::{ nft::{NftIntermediateLauncherArgs, NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH}, singleton::SINGLETON_LAUNCHER_PUZZLE_HASH, }; @@ -35,12 +35,12 @@ impl IntermediateLauncher { } } - pub fn intermediate_coin(&self) -> &Coin { - &self.intermediate_coin + pub fn intermediate_coin(&self) -> Coin { + self.intermediate_coin } - pub fn launcher_coin(&self) -> &Coin { - &self.launcher_coin + pub fn launcher_coin(&self) -> Coin { + self.launcher_coin } pub fn create(self, ctx: &mut SpendContext) -> Result { @@ -60,7 +60,7 @@ impl IntermediateLauncher { let puzzle_hash = ctx.tree_hash(puzzle); parent_conditions.push(ctx.alloc(CreateCoinWithoutMemos { - puzzle_hash, + puzzle_hash: puzzle_hash.into(), amount: 0, })?); diff --git a/src/spends/puzzles/singleton/launcher.rs b/src/spends/puzzles/singleton/launcher.rs index 929a6997..99d604cd 100644 --- a/src/spends/puzzles/singleton/launcher.rs +++ b/src/spends/puzzles/singleton/launcher.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin}; -use chia_wallet::singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}; +use chia_puzzles::singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}; use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair}; use crate::{ChainedSpend, CreateCoinWithoutMemos, SpendContext, SpendError, SpendableLauncher}; @@ -19,8 +19,8 @@ impl Launcher { } } - pub fn coin(&self) -> &Coin { - &self.coin + pub fn coin(&self) -> Coin { + self.coin } pub fn create(self, ctx: &mut SpendContext) -> Result { @@ -60,7 +60,7 @@ pub fn singleton_puzzle_hash(launcher_id: Bytes32, inner_puzzle_hash: Bytes32) - #[cfg(test)] mod tests { - use chia_wallet::singleton::{ + use chia_puzzles::singleton::{ SingletonArgs, SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, }; @@ -98,7 +98,7 @@ mod tests { .unwrap(); let allocated_puzzle_hash = ctx.tree_hash(puzzle); - let puzzle_hash = singleton_puzzle_hash(launcher_id, inner_puzzle_hash); + let puzzle_hash = singleton_puzzle_hash(launcher_id, inner_puzzle_hash.into()); assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); } diff --git a/src/spends/puzzles/singleton/singleton_spend.rs b/src/spends/puzzles/singleton/singleton_spend.rs index acd61fac..2c0ec314 100644 --- a/src/spends/puzzles/singleton/singleton_spend.rs +++ b/src/spends/puzzles/singleton/singleton_spend.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::{ +use chia_puzzles::{ singleton::{ SingletonArgs, SingletonSolution, SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, @@ -14,7 +14,7 @@ pub fn spend_singleton( ctx: &mut SpendContext, coin: Coin, launcher_id: Bytes32, - proof: Proof, + lineage_proof: Proof, inner_spend: InnerSpend, ) -> Result { let singleton_puzzle = ctx.singleton_top_layer(); @@ -32,7 +32,7 @@ pub fn spend_singleton( })?; let solution = ctx.serialize(SingletonSolution { - proof, + lineage_proof, amount: coin.amount, inner_solution: inner_spend.solution(), })?; diff --git a/src/spends/puzzles/singleton/spendable_launcher.rs b/src/spends/puzzles/singleton/spendable_launcher.rs index 00df9e41..0e166417 100644 --- a/src/spends/puzzles/singleton/spendable_launcher.rs +++ b/src/spends/puzzles/singleton/spendable_launcher.rs @@ -1,5 +1,5 @@ use chia_protocol::{Bytes32, Coin, CoinSpend}; -use chia_wallet::singleton::LauncherSolution; +use chia_puzzles::singleton::LauncherSolution; use clvm_traits::{clvm_list, ToClvm}; use clvmr::NodePtr; use sha2::{digest::FixedOutput, Digest, Sha256}; @@ -22,8 +22,8 @@ impl SpendableLauncher { } } - pub fn coin(&self) -> &Coin { - &self.coin + pub fn coin(&self) -> Coin { + self.coin } pub fn spend( @@ -62,7 +62,7 @@ impl SpendableLauncher { key_value_list, })?; - ctx.spend(CoinSpend::new(self.coin.clone(), puzzle_reveal, solution)); + ctx.spend(CoinSpend::new(self.coin, puzzle_reveal, solution)); let mut chained_spend = self.chained_spend; chained_spend.parent_conditions.push(assert_announcement); diff --git a/src/spends/puzzles/standard.rs b/src/spends/puzzles/standard.rs index 783053df..82e37e98 100644 --- a/src/spends/puzzles/standard.rs +++ b/src/spends/puzzles/standard.rs @@ -1,6 +1,6 @@ use chia_bls::PublicKey; use chia_protocol::{Coin, CoinSpend}; -use chia_wallet::standard::{StandardArgs, StandardSolution}; +use chia_puzzles::standard::{StandardArgs, StandardSolution}; use clvm_traits::clvm_quote; use clvm_utils::CurriedProgram; use clvmr::NodePtr; @@ -79,10 +79,8 @@ pub fn standard_solution(conditions: T) -> StandardSolution<(u8, T), ()> { mod tests { use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; - use chia_wallet::{ - standard::{standard_puzzle_hash, DEFAULT_HIDDEN_PUZZLE_HASH}, - DeriveSynthetic, - }; + use chia_puzzles::{standard::STANDARD_PUZZLE_HASH, DeriveSynthetic}; + use clvm_utils::ToTreeHash; use clvmr::Allocator; use crate::{testing::SECRET_KEY, CreateCoinWithoutMemos, RequiredSignature, WalletSimulator}; @@ -97,9 +95,15 @@ mod tests { let mut allocator = Allocator::new(); let mut ctx = SpendContext::new(&mut allocator); - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let sk = SECRET_KEY.derive_synthetic(); let pk = sk.public_key(); - let puzzle_hash = standard_puzzle_hash(&pk).into(); + + let puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash() + .into(); let parent = sim.generate_coin(puzzle_hash, 1).await.coin; diff --git a/src/spends/spend_context.rs b/src/spends/spend_context.rs index 48eba495..4b7a9245 100644 --- a/src/spends/spend_context.rs +++ b/src/spends/spend_context.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; -use chia_protocol::{Bytes32, CoinSpend, Program}; -use chia_wallet::{ - cat::{CAT_PUZZLE, CAT_PUZZLE_HASH, EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE}, +use chia_protocol::{CoinSpend, Program}; +use chia_puzzles::{ + cat::{ + CAT_PUZZLE, CAT_PUZZLE_HASH, EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, + EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE_HASH, + }, did::{DID_INNER_PUZZLE, DID_INNER_PUZZLE_HASH}, nft::{ NFT_INTERMEDIATE_LAUNCHER_PUZZLE, NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, @@ -17,16 +20,15 @@ use chia_wallet::{ standard::{STANDARD_PUZZLE, STANDARD_PUZZLE_HASH}, }; use clvm_traits::{FromNodePtr, ToNodePtr}; -use clvm_utils::tree_hash; +use clvm_utils::{tree_hash, TreeHash}; use clvmr::{run_program, serde::node_from_bytes, Allocator, ChiaDialect, NodePtr}; -use hex_literal::hex; use crate::SpendError; /// A wrapper around `Allocator` that caches puzzles and simplifies coin spending. pub struct SpendContext<'a> { allocator: &'a mut Allocator, - puzzles: HashMap<[u8; 32], NodePtr>, + puzzles: HashMap, coin_spends: Vec, } @@ -82,8 +84,8 @@ impl<'a> SpendContext<'a> { } /// Compute the tree hash of a node pointer. - pub fn tree_hash(&self, ptr: NodePtr) -> Bytes32 { - Bytes32::new(tree_hash(self.allocator, ptr)) + pub fn tree_hash(&self, ptr: NodePtr) -> TreeHash { + tree_hash(self.allocator, ptr) } /// Run a puzzle with a solution and return the result. @@ -109,23 +111,23 @@ impl<'a> SpendContext<'a> { /// Allocate the standard puzzle and return its pointer. pub fn standard_puzzle(&mut self) -> NodePtr { - self.puzzle(&STANDARD_PUZZLE_HASH, &STANDARD_PUZZLE) + self.puzzle(STANDARD_PUZZLE_HASH, &STANDARD_PUZZLE) } /// Allocate the CAT puzzle and return its pointer. pub fn cat_puzzle(&mut self) -> NodePtr { - self.puzzle(&CAT_PUZZLE_HASH, &CAT_PUZZLE) + self.puzzle(CAT_PUZZLE_HASH, &CAT_PUZZLE) } /// Allocate the DID inner puzzle and return its pointer. pub fn did_inner_puzzle(&mut self) -> NodePtr { - self.puzzle(&DID_INNER_PUZZLE_HASH, &DID_INNER_PUZZLE) + self.puzzle(DID_INNER_PUZZLE_HASH, &DID_INNER_PUZZLE) } /// Allocate the NFT intermediate launcher puzzle and return its pointer. pub fn nft_intermediate_launcher(&mut self) -> NodePtr { self.puzzle( - &NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, + NFT_INTERMEDIATE_LAUNCHER_PUZZLE_HASH, &NFT_INTERMEDIATE_LAUNCHER_PUZZLE, ) } @@ -133,71 +135,62 @@ impl<'a> SpendContext<'a> { /// Allocate the NFT royalty transfer puzzle and return its pointer. pub fn nft_royalty_transfer(&mut self) -> NodePtr { self.puzzle( - &NFT_ROYALTY_TRANSFER_PUZZLE_HASH, + NFT_ROYALTY_TRANSFER_PUZZLE_HASH, &NFT_ROYALTY_TRANSFER_PUZZLE, ) } /// Allocate the NFT ownership layer puzzle and return its pointer. pub fn nft_ownership_layer(&mut self) -> NodePtr { - self.puzzle( - &NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - &NFT_OWNERSHIP_LAYER_PUZZLE, - ) + self.puzzle(NFT_OWNERSHIP_LAYER_PUZZLE_HASH, &NFT_OWNERSHIP_LAYER_PUZZLE) } /// Allocate the NFT state layer puzzle and return its pointer. pub fn nft_state_layer(&mut self) -> NodePtr { - self.puzzle(&NFT_STATE_LAYER_PUZZLE_HASH, &NFT_STATE_LAYER_PUZZLE) + self.puzzle(NFT_STATE_LAYER_PUZZLE_HASH, &NFT_STATE_LAYER_PUZZLE) } /// Allocate the singleton top layer puzzle and return its pointer. pub fn singleton_top_layer(&mut self) -> NodePtr { - self.puzzle( - &SINGLETON_TOP_LAYER_PUZZLE_HASH, - &SINGLETON_TOP_LAYER_PUZZLE, - ) + self.puzzle(SINGLETON_TOP_LAYER_PUZZLE_HASH, &SINGLETON_TOP_LAYER_PUZZLE) } /// Allocate the singleton launcher puzzle and return its pointer. pub fn singleton_launcher(&mut self) -> NodePtr { - self.puzzle(&SINGLETON_LAUNCHER_PUZZLE_HASH, &SINGLETON_LAUNCHER_PUZZLE) + self.puzzle(SINGLETON_LAUNCHER_PUZZLE_HASH, &SINGLETON_LAUNCHER_PUZZLE) } /// Allocate the EverythingWithSignature TAIL puzzle and return its pointer. pub fn everything_with_signature_tail_puzzle(&mut self) -> NodePtr { // todo: add constant to chia_rs self.puzzle( - &hex!("1720d13250a7c16988eaf530331cefa9dd57a76b2c82236bec8bbbff91499b89"), + EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE_HASH, &EVERYTHING_WITH_SIGNATURE_TAIL_PUZZLE, ) } /// Allocate the settlement payments puzzle and return its pointer. pub fn settlement_payments_puzzle(&mut self) -> NodePtr { - self.puzzle( - &SETTLEMENT_PAYMENTS_PUZZLE_HASH, - &SETTLEMENT_PAYMENTS_PUZZLE, - ) + self.puzzle(SETTLEMENT_PAYMENTS_PUZZLE_HASH, &SETTLEMENT_PAYMENTS_PUZZLE) } /// Preload a puzzle into the cache. - pub fn preload(&mut self, puzzle_hash: [u8; 32], ptr: NodePtr) { + pub fn preload(&mut self, puzzle_hash: TreeHash, ptr: NodePtr) { self.puzzles.insert(puzzle_hash, ptr); } /// Checks whether a puzzle is in the cache. - pub fn get_puzzle(&self, puzzle_hash: &[u8; 32]) -> Option { + pub fn get_puzzle(&self, puzzle_hash: &TreeHash) -> Option { self.puzzles.get(puzzle_hash).copied() } /// Get a puzzle from the cache or allocate a new one. - pub fn puzzle(&mut self, puzzle_hash: &[u8; 32], puzzle_bytes: &[u8]) -> NodePtr { - if let Some(puzzle) = self.puzzles.get(puzzle_bytes) { + pub fn puzzle(&mut self, puzzle_hash: TreeHash, puzzle_bytes: &[u8]) -> NodePtr { + if let Some(puzzle) = self.puzzles.get(&puzzle_hash) { *puzzle } else { let puzzle = node_from_bytes(self.allocator, puzzle_bytes).unwrap(); - self.puzzles.insert(*puzzle_hash, puzzle); + self.puzzles.insert(puzzle_hash, puzzle); puzzle } } diff --git a/src/sqlite/coin_store.rs b/src/sqlite/coin_store.rs index 71731923..f1faa780 100644 --- a/src/sqlite/coin_store.rs +++ b/src/sqlite/coin_store.rs @@ -189,7 +189,7 @@ mod tests { spent_height: None, }; - upsert_coin_states(&mut conn, vec![coin_state.clone()], None) + upsert_coin_states(&mut conn, vec![coin_state], None) .await .unwrap(); @@ -216,7 +216,7 @@ mod tests { spent_height: None, }; - upsert_coin_states(&mut conn, vec![coin_state.clone()], None) + upsert_coin_states(&mut conn, vec![coin_state], None) .await .unwrap(); @@ -249,7 +249,7 @@ mod tests { spent_height: None, }; - upsert_coin_states(&mut conn, vec![coin_state.clone()], asset_id) + upsert_coin_states(&mut conn, vec![coin_state], asset_id) .await .unwrap(); diff --git a/src/sqlite/key_store.rs b/src/sqlite/key_store.rs index 84a5aa6b..46065a37 100644 --- a/src/sqlite/key_store.rs +++ b/src/sqlite/key_store.rs @@ -1,6 +1,7 @@ use chia_bls::PublicKey; use chia_protocol::Bytes32; -use chia_wallet::standard::standard_puzzle_hash; +use chia_puzzles::standard::{StandardArgs, STANDARD_PUZZLE_HASH}; +use clvm_utils::{CurriedProgram, ToTreeHash}; use sqlx::{Result, SqliteConnection}; /// Get the number of keys in the store. @@ -50,7 +51,15 @@ pub async fn insert_keys( for (i, public_key) in public_keys.iter().enumerate() { let index = index + i as u32; let public_key_bytes = public_key.to_bytes().to_vec(); - let p2_puzzle_hash = standard_puzzle_hash(public_key).to_vec(); + + let p2_puzzle_hash = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { + synthetic_key: *public_key, + }, + } + .tree_hash() + .to_vec(); sqlx::query!( " @@ -148,7 +157,7 @@ pub async fn puzzle_hash_index( /// Get the index of a public key. pub async fn public_key_index( conn: &mut SqliteConnection, - public_key: &PublicKey, + public_key: PublicKey, is_hardened: bool, ) -> Result> { let public_key_bytes = public_key.to_bytes().to_vec(); @@ -174,7 +183,7 @@ mod tests { }, DerivableKey, }; - use chia_wallet::{standard::DEFAULT_HIDDEN_PUZZLE_HASH, DeriveSynthetic}; + use chia_puzzles::DeriveSynthetic; use sqlx::SqlitePool; use crate::testing::SECRET_KEY; @@ -195,21 +204,13 @@ mod tests { // Insert the first batch. let pk_batch_1: Vec = (0..100) - .map(|i| { - intermediate_pk - .derive_unhardened(i) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) - }) + .map(|i| intermediate_pk.derive_unhardened(i).derive_synthetic()) .collect(); insert_keys(&mut conn, 0, &pk_batch_1, false).await.unwrap(); // Insert the second batch. let pk_batch_2: Vec = (100..200) - .map(|i| { - intermediate_pk - .derive_unhardened(i) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) - }) + .map(|i| intermediate_pk.derive_unhardened(i).derive_synthetic()) .collect(); insert_keys(&mut conn, 100, &pk_batch_2, false) .await @@ -241,17 +242,13 @@ mod tests { // Insert a batch of keys. let pk_batch: Vec = (0..100) - .map(|i| { - intermediate_pk - .derive_unhardened(i) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) - }) + .map(|i| intermediate_pk.derive_unhardened(i).derive_synthetic()) .collect(); insert_keys(&mut conn, 0, &pk_batch, false).await.unwrap(); for (i, pk) in pk_batch.into_iter().enumerate() { // Check the index of the key. - let index = public_key_index(&mut conn, &pk, false) + let index = public_key_index(&mut conn, pk, false) .await .unwrap() .unwrap(); @@ -265,7 +262,12 @@ mod tests { assert_eq!(actual, pk); // Ensure the puzzle hash at the index matches. - let ph = standard_puzzle_hash(&pk); + let ph = CurriedProgram { + program: STANDARD_PUZZLE_HASH, + args: StandardArgs { synthetic_key: pk }, + } + .tree_hash(); + let actual = fetch_puzzle_hash(&mut conn, index, false) .await .unwrap() @@ -289,13 +291,9 @@ mod tests { let hardened_sk = master_to_wallet_hardened_intermediate(&SECRET_KEY); // Insert a public key to unhardened and make sure it's not in hardened. - let pk = unhardened_pk - .derive_unhardened(0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); - insert_keys(&mut conn, 0, &[pk.clone()], false) - .await - .unwrap(); - assert!(public_key_index(&mut conn, &pk, true) + let pk = unhardened_pk.derive_unhardened(0).derive_synthetic(); + insert_keys(&mut conn, 0, &[pk], false).await.unwrap(); + assert!(public_key_index(&mut conn, pk, true) .await .unwrap() .is_none()); @@ -304,11 +302,9 @@ mod tests { let pk = hardened_sk .derive_hardened(0) .public_key() - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); - insert_keys(&mut conn, 0, &[pk.clone()], true) - .await - .unwrap(); - assert!(public_key_index(&mut conn, &pk, false) + .derive_synthetic(); + insert_keys(&mut conn, 0, &[pk], true).await.unwrap(); + assert!(public_key_index(&mut conn, pk, false) .await .unwrap() .is_none()); @@ -321,21 +317,13 @@ mod tests { // Insert a batch of keys. let pk_batch: Vec = (0..100) - .map(|i| { - intermediate_pk - .derive_unhardened(i) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) - }) + .map(|i| intermediate_pk.derive_unhardened(i).derive_synthetic()) .collect(); insert_keys(&mut conn, 0, &pk_batch, false).await.unwrap(); // Insert a batch of keys with overlap. let pk_batch: Vec = (50..150) - .map(|i| { - intermediate_pk - .derive_unhardened(i) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) - }) + .map(|i| intermediate_pk.derive_unhardened(i).derive_synthetic()) .collect(); insert_keys(&mut conn, 50, &pk_batch, false).await.unwrap(); @@ -348,12 +336,7 @@ mod tests { .await .unwrap() .expect("no public key"); - assert_eq!( - pk, - intermediate_pk - .derive_unhardened(0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) - ); + assert_eq!(pk, intermediate_pk.derive_unhardened(0).derive_synthetic()); // Check the last key. let pk = fetch_public_key(&mut conn, 149, false) @@ -362,9 +345,7 @@ mod tests { .expect("no public key"); assert_eq!( pk, - intermediate_pk - .derive_unhardened(149) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH) + intermediate_pk.derive_unhardened(149).derive_synthetic() ); } } diff --git a/src/sqlite/puzzle_store.rs b/src/sqlite/puzzle_store.rs index 5e83f518..e6a570df 100644 --- a/src/sqlite/puzzle_store.rs +++ b/src/sqlite/puzzle_store.rs @@ -1,5 +1,6 @@ use chia_protocol::Bytes32; -use chia_wallet::cat::cat_puzzle_hash; +use chia_puzzles::cat::{CatArgs, CAT_PUZZLE_HASH}; +use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use sqlx::{Result, SqliteConnection}; use super::fetch_derivation_index; @@ -71,8 +72,17 @@ pub async fn extend_cat_puzzle_hashes( .try_into() .unwrap(); - let puzzle_hash = cat_puzzle_hash(asset_id.to_bytes(), p2_puzzle_hash); - puzzle_hashes.push(Bytes32::new(puzzle_hash)); + let puzzle_hash = CurriedProgram { + program: CAT_PUZZLE_HASH, + args: CatArgs { + mod_hash: CAT_PUZZLE_HASH.into(), + tail_program_hash: asset_id, + inner_puzzle: TreeHash::new(p2_puzzle_hash), + }, + } + .tree_hash(); + + puzzle_hashes.push(Bytes32::from(puzzle_hash)); let puzzle_hash = puzzle_hash.to_vec(); let asset_id = asset_id.to_vec(); diff --git a/src/sqlite/transaction_store.rs b/src/sqlite/transaction_store.rs index 557da9f1..ef66cb44 100644 --- a/src/sqlite/transaction_store.rs +++ b/src/sqlite/transaction_store.rs @@ -223,7 +223,7 @@ mod tests { let solution = Program::default(); let coin_spend = CoinSpend { - coin: coin.clone(), + coin, puzzle_reveal, solution, }; @@ -245,7 +245,7 @@ mod tests { // Get the removals and compare. let removals = fetch_removals(&mut conn, transaction_id).await.unwrap(); - assert_eq!(removals, vec![coin.clone()]); + assert_eq!(removals, vec![coin]); // Get all spent coins and make sure the coin is there. let spent_coins = fetch_spent_coins(&mut conn).await.unwrap(); diff --git a/src/wallet/coin_selection.rs b/src/wallet/coin_selection.rs index b4da5c7c..7e59977d 100644 --- a/src/wallet/coin_selection.rs +++ b/src/wallet/coin_selection.rs @@ -49,7 +49,7 @@ pub fn select_coins( for coin in spendable_coins.iter() { if coin.amount as u128 == amount { let mut result = HashSet::new(); - result.insert(coin.clone()); + result.insert(*coin); return Ok(result); } } @@ -61,7 +61,7 @@ pub fn select_coins( let coin_amount = coin.amount as u128; if coin_amount < amount { - smaller_coins.insert(coin.clone()); + smaller_coins.insert(*coin); smaller_sum += coin_amount; } } @@ -117,7 +117,7 @@ fn sum_largest_coins(coins: &[Coin], amount: u128) -> HashSet { let mut selected_sum = 0; for coin in coins { selected_sum += coin.amount as u128; - selected_coins.insert(coin.clone()); + selected_coins.insert(*coin); if selected_sum >= amount { return selected_coins; @@ -132,7 +132,7 @@ fn smallest_coin_above(coins: &[Coin], amount: u128) -> Option { } for coin in coins.iter().rev() { if (coin.amount as u128) >= amount { - return Some(coin.clone()); + return Some(*coin); } } unreachable!(); @@ -172,7 +172,7 @@ pub fn knapsack_coin_algorithm( } selected_sum += coin.amount as u128; - selected_coins.insert(coin.clone()); + selected_coins.insert(*coin); if selected_sum == amount { return Some(selected_coins); diff --git a/src/wallet/required_signature.rs b/src/wallet/required_signature.rs index ebe35a70..e6b3f03c 100644 --- a/src/wallet/required_signature.rs +++ b/src/wallet/required_signature.rs @@ -135,8 +135,8 @@ impl RequiredSignature { } /// The public key required to verify the signature. - pub fn public_key(&self) -> &PublicKey { - &self.public_key + pub fn public_key(&self) -> PublicKey { + self.public_key } /// The message field of the condition, without anything appended. @@ -173,15 +173,15 @@ mod tests { use chia_bls::derive_keys::master_to_wallet_unhardened; use chia_protocol::Bytes32; - use chia_wallet::{standard::DEFAULT_HIDDEN_PUZZLE_HASH, DeriveSynthetic}; + use chia_puzzles::DeriveSynthetic; #[test] fn test_messages() { let coin = Coin::new(Bytes32::from([1; 32]), Bytes32::from([2; 32]), 3); let agg_sig_data = Bytes32::new([4u8; 32]); - let public_key = master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let public_key = + master_to_wallet_unhardened(&SECRET_KEY.public_key(), 0).derive_synthetic(); let message: Bytes = vec![1, 2, 3].into(); @@ -251,7 +251,7 @@ mod tests { for (condition, appended_info, domain_string) in cases { let required = RequiredSignature::from_condition(&coin, condition, agg_sig_data); - assert_eq!(required.public_key(), &public_key); + assert_eq!(required.public_key(), public_key); assert_eq!(required.raw_message(), message.as_ref()); assert_eq!(hex::encode(required.appended_info()), appended_info); assert_eq!(required.domain_string().map(hex::encode), domain_string); diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 4246a3be..a77324d7 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -1,5 +1,5 @@ use chia_bls::{sign, DerivableKey, SecretKey, Signature}; -use chia_wallet::DeriveSynthetic; +use chia_puzzles::DeriveSynthetic; /// Responsible for signing messages. pub trait Signer { @@ -28,7 +28,7 @@ impl Signer for HardenedMemorySigner { let sk = self .intermediate_sk .derive_hardened(index) - .derive_synthetic(&self.hidden_puzzle_hash); + .derive_synthetic_hidden(&self.hidden_puzzle_hash); sign(&sk, message) } } @@ -54,7 +54,7 @@ impl Signer for UnhardenedMemorySigner { let sk = self .intermediate_sk .derive_unhardened(index) - .derive_synthetic(&self.hidden_puzzle_hash); + .derive_synthetic_hidden(&self.hidden_puzzle_hash); sign(&sk, message) } } @@ -63,7 +63,7 @@ impl Signer for UnhardenedMemorySigner { mod tests { use chia_bls::derive_keys::master_to_wallet_unhardened_intermediate; use chia_protocol::{Bytes, Bytes32, Coin, CoinSpend, Program}; - use chia_wallet::standard::DEFAULT_HIDDEN_PUZZLE_HASH; + use chia_puzzles::standard::DEFAULT_HIDDEN_PUZZLE_HASH; use clvm_traits::{clvm_list, FromNodePtr, ToClvm}; use clvmr::{Allocator, NodePtr}; use hex_literal::hex; @@ -135,14 +135,13 @@ mod tests { let intermediate_pk = intermediate_sk.public_key(); let mut conn = pool.acquire().await.unwrap(); - let signer = UnhardenedMemorySigner::new(intermediate_sk, DEFAULT_HIDDEN_PUZZLE_HASH); + let signer = + UnhardenedMemorySigner::new(intermediate_sk, DEFAULT_HIDDEN_PUZZLE_HASH.into()); insert_keys( &mut conn, 0, - &[intermediate_pk - .derive_unhardened(0) - .derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH)], + &[intermediate_pk.derive_unhardened(0).derive_synthetic()], false, ) .await diff --git a/src/wallet/wallet_simulator.rs b/src/wallet/wallet_simulator.rs index 38de4e7a..3c87b1b2 100644 --- a/src/wallet/wallet_simulator.rs +++ b/src/wallet/wallet_simulator.rs @@ -103,7 +103,7 @@ impl WalletSimulator { let coin_id = coin.coin_id(); let coin_state = CoinState::new(coin, None, Some(data.block_height)); - data.coin_states.insert(coin_id, coin_state.clone()); + data.coin_states.insert(coin_id, coin_state); coin_state } @@ -200,8 +200,8 @@ async fn handle_connection( let mut data = data.lock().await; for coin_id in request.coin_ids.iter() { - if let Some(coin_state) = data.coin_states.get(coin_id) { - coin_states.push(coin_state.clone()); + if let Some(coin_state) = data.coin_states.get(coin_id).copied() { + coin_states.push(coin_state); } data.coin_subscriptions @@ -242,20 +242,20 @@ async fn handle_connection( for (coin_id, coin_state) in data.coin_states.iter() { if request.puzzle_hashes.contains(&coin_state.coin.puzzle_hash) { - coin_states.insert(*coin_id, data.coin_states[coin_id].clone()); + coin_states.insert(*coin_id, data.coin_states[coin_id]); } } for puzzle_hash in request.puzzle_hashes.iter() { - if let Some(coin_state) = data.coin_states.get(puzzle_hash) { - coin_states.insert(coin_state.coin.coin_id(), coin_state.clone()); + if let Some(coin_state) = data.coin_states.get(puzzle_hash).copied() { + coin_states.insert(coin_state.coin.coin_id(), coin_state); } if let Some(hinted_coins) = data.hinted_coins.get(&Bytes::new(puzzle_hash.to_vec())) { for coin_id in hinted_coins.iter() { - coin_states.insert(*coin_id, data.coin_states[coin_id].clone()); + coin_states.insert(*coin_id, data.coin_states[coin_id]); } } @@ -344,7 +344,7 @@ async fn handle_connection( .coin_states .iter() .filter(|(_, cs)| cs.coin.parent_coin_info == request.coin_name) - .map(|(_, cs)| cs.clone()) + .map(|(_, cs)| *cs) .collect(); let response = Message { @@ -406,7 +406,7 @@ async fn process_spend_bundle( MEMPOOL_MODE, )?; - let conds = OwnedSpendBundleConditions::from(&allocator, conds); + let conds = OwnedSpendBundleConditions::from(&allocator, conds).unwrap(); let cond_puzzle_hashes = conds .spends @@ -435,7 +435,7 @@ async fn process_spend_bundle( &spend_bundle.aggregated_signature, required_signatures .into_iter() - .map(|r| (r.public_key().clone(), r.final_message())) + .map(|r| (r.public_key(), r.final_message())) .collect::>(), ) { return Err(ValidationErr( @@ -552,21 +552,17 @@ async fn process_spend_bundle( let hint = Bytes32::new(hint); if puzzle_subscriptions.contains(&hint) { - peer_updates.extend( - coins - .iter() - .map(|coin_id| data.coin_states[coin_id].clone()), - ); + peer_updates.extend(coins.iter().map(|coin_id| data.coin_states[coin_id])); } } for coin_id in updates.keys() { if coin_subscriptions.contains(coin_id) { - peer_updates.insert(data.coin_states[coin_id].clone()); + peer_updates.insert(data.coin_states[coin_id]); } if puzzle_subscriptions.contains(&data.coin_states[coin_id].coin.puzzle_hash) { - peer_updates.insert(data.coin_states[coin_id].clone()); + peer_updates.insert(data.coin_states[coin_id]); } } @@ -614,7 +610,7 @@ mod tests { use chia_bls::{sign, Signature}; use chia_client::PeerEvent; use chia_protocol::{CoinSpend, SpendBundle}; - use chia_wallet::{standard::DEFAULT_HIDDEN_PUZZLE_HASH, DeriveSynthetic}; + use chia_puzzles::DeriveSynthetic; use crate::{ testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, RequiredSignature, @@ -624,7 +620,7 @@ mod tests { use super::*; fn sign_bundle(spend_bundle: &mut SpendBundle) { - let sk = SECRET_KEY.derive_synthetic(&DEFAULT_HIDDEN_PUZZLE_HASH); + let sk = SECRET_KEY.derive_synthetic(); let mut a = Allocator::new(); @@ -651,7 +647,7 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let puzzle = ctx.alloc(1).unwrap(); - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let puzzle_reveal = ctx.serialize(puzzle).unwrap(); let mut coin = sim.generate_coin(puzzle_hash, 1000).await.coin; @@ -664,7 +660,7 @@ mod tests { }]) .unwrap(); - let coin_spend = CoinSpend::new(coin.clone(), puzzle_reveal.clone(), solution); + let coin_spend = CoinSpend::new(coin, puzzle_reveal.clone(), solution); let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); sign_bundle(&mut spend_bundle); @@ -694,7 +690,7 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let puzzle = ctx.alloc(1).unwrap(); - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let puzzle_reveal = ctx.serialize(puzzle).unwrap(); let solution = ctx @@ -732,7 +728,7 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let puzzle = ctx.alloc(1).unwrap(); - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let puzzle_reveal = ctx.serialize(puzzle).unwrap(); let mut cs = sim.generate_coin(puzzle_hash, 1000).await; @@ -743,7 +739,7 @@ mod tests { .await .unwrap(); - assert_eq!(results, vec![cs.clone()]); + assert_eq!(results, vec![cs]); // The initial state should still be the same. let results = peer @@ -751,7 +747,7 @@ mod tests { .await .unwrap(); - assert_eq!(results, vec![cs.clone()]); + assert_eq!(results, vec![cs]); let mut receiver = peer.receiver().resubscribe(); @@ -765,7 +761,7 @@ mod tests { }]) .unwrap(); - let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal, solution); + let coin_spend = CoinSpend::new(cs.coin, puzzle_reveal, solution); let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); sign_bundle(&mut spend_bundle); @@ -799,7 +795,7 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let puzzle = ctx.alloc(1).unwrap(); - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let puzzle_reveal = ctx.serialize(puzzle).unwrap(); let mut cs = sim.generate_coin(puzzle_hash, 1000).await; @@ -810,7 +806,7 @@ mod tests { .await .unwrap(); - assert_eq!(results, vec![cs.clone()]); + assert_eq!(results, vec![cs]); // The initial state should still be the same. let results = peer @@ -818,7 +814,7 @@ mod tests { .await .unwrap(); - assert_eq!(results, vec![cs.clone()]); + assert_eq!(results, vec![cs]); let mut receiver = peer.receiver().resubscribe(); @@ -832,7 +828,7 @@ mod tests { }]) .unwrap(); - let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal, solution); + let coin_spend = CoinSpend::new(cs.coin, puzzle_reveal, solution); let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); sign_bundle(&mut spend_bundle); @@ -877,7 +873,7 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let puzzle = ctx.alloc(1).unwrap(); - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let puzzle_reveal = ctx.serialize(puzzle).unwrap(); let cs = sim.generate_coin(puzzle_hash, 1000).await; @@ -901,7 +897,7 @@ mod tests { }]) .unwrap(); - let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal, solution); + let coin_spend = CoinSpend::new(cs.coin, puzzle_reveal, solution); let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); sign_bundle(&mut spend_bundle); @@ -942,7 +938,7 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let puzzle = ctx.alloc(1).unwrap(); - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let puzzle_reveal = ctx.serialize(puzzle).unwrap(); let cs = sim.generate_coin(puzzle_hash, 1000).await; @@ -954,7 +950,7 @@ mod tests { }]) .unwrap(); - let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal.clone(), solution.clone()); + let coin_spend = CoinSpend::new(cs.coin, puzzle_reveal.clone(), solution.clone()); let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); sign_bundle(&mut spend_bundle); @@ -987,7 +983,7 @@ mod tests { let mut ctx = SpendContext::new(&mut a); let puzzle = ctx.alloc(1).unwrap(); - let puzzle_hash = ctx.tree_hash(puzzle); + let puzzle_hash = ctx.tree_hash(puzzle).into(); let puzzle_reveal = ctx.serialize(puzzle).unwrap(); let cs = sim.generate_coin(puzzle_hash, 1000).await; @@ -999,7 +995,7 @@ mod tests { }]) .unwrap(); - let coin_spend = CoinSpend::new(cs.coin.clone(), puzzle_reveal.clone(), solution.clone()); + let coin_spend = CoinSpend::new(cs.coin, puzzle_reveal.clone(), solution.clone()); let mut spend_bundle = SpendBundle::new(vec![coin_spend], Signature::default()); sign_bundle(&mut spend_bundle); From afd85d4aa3071da7be7ae087d0078e2c0a07af8a Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 14 May 2024 18:35:18 -0400 Subject: [PATCH 23/25] Remove NFT puzzle hash functions --- src/spends/puzzles/nft/mint_nft.rs | 232 +++++----------------------- src/spends/puzzles/nft/nft_spend.rs | 48 ++++-- 2 files changed, 67 insertions(+), 213 deletions(-) diff --git a/src/spends/puzzles/nft/mint_nft.rs b/src/spends/puzzles/nft/mint_nft.rs index 0689ca48..1ac1b1f1 100644 --- a/src/spends/puzzles/nft/mint_nft.rs +++ b/src/spends/puzzles/nft/mint_nft.rs @@ -2,20 +2,21 @@ 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::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, + singleton::SingletonStruct, standard::{StandardArgs, STANDARD_PUZZLE_HASH}, EveProof, Proof, }; use clvm_traits::ToClvm; -use clvm_utils::{curry_tree_hash, tree_hash_atom, tree_hash_pair, CurriedProgram, ToTreeHash}; +use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use clvmr::NodePtr; use crate::{ - u16_to_bytes, ChainedSpend, CreatePuzzleAnnouncement, NftInfo, SpendContext, SpendError, - SpendableLauncher, StandardNftSpend, + ChainedSpend, CreatePuzzleAnnouncement, NftInfo, SpendContext, SpendError, SpendableLauncher, + StandardNftSpend, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -91,23 +92,38 @@ impl MintNft for SpendableLauncher { M: ToClvm, { let metadata_ptr = ctx.alloc(&metadata)?; - let metadata_hash = ctx.tree_hash(metadata_ptr).into(); + let metadata_hash = ctx.tree_hash(metadata_ptr); - let ownership_layer_hash = nft_ownership_layer_hash( - None, - nft_royalty_transfer_hash( - self.coin().coin_id(), + let transfer_program = CurriedProgram { + program: NFT_ROYALTY_TRANSFER_PUZZLE_HASH, + args: NftRoyaltyTransferPuzzleArgs { + singleton_struct: SingletonStruct::new(self.coin().coin_id()), royalty_puzzle_hash, - royalty_percentage, - ), - p2_puzzle_hash, - ); + trade_price_percentage: royalty_percentage, + }, + }; - let nft_inner_puzzle_hash = nft_state_layer_hash( - metadata_hash, - NFT_METADATA_UPDATER_PUZZLE_HASH.into(), - ownership_layer_hash, - ); + 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 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 launcher_coin = self.coin(); let (mut chained_spend, eve_coin) = self.spend(ctx, nft_inner_puzzle_hash, ())?; @@ -140,90 +156,11 @@ impl MintNft for SpendableLauncher { } } -pub fn nft_state_layer_hash( - metadata_hash: Bytes32, - metadata_updater_hash: Bytes32, - inner_puzzle_hash: Bytes32, -) -> Bytes32 { - let mod_hash = tree_hash_atom(&NFT_STATE_LAYER_PUZZLE_HASH); - let metadata_updater_hash = tree_hash_atom(&metadata_updater_hash); - - curry_tree_hash( - NFT_STATE_LAYER_PUZZLE_HASH, - &[ - mod_hash, - metadata_hash.into(), - metadata_updater_hash, - inner_puzzle_hash.into(), - ], - ) - .into() -} - -pub fn nft_ownership_layer_hash( - current_owner: Option, - transfer_program_hash: Bytes32, - inner_puzzle_hash: Bytes32, -) -> Bytes32 { - let mod_hash = tree_hash_atom(&NFT_OWNERSHIP_LAYER_PUZZLE_HASH); - let current_owner_hash = match current_owner { - Some(did_id) => tree_hash_atom(&did_id), - None => tree_hash_atom(&[]), - }; - - curry_tree_hash( - NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - &[ - mod_hash, - current_owner_hash, - transfer_program_hash.into(), - inner_puzzle_hash.into(), - ], - ) - .into() -} - -pub fn nft_royalty_transfer_hash( - launcher_id: Bytes32, - royalty_puzzle_hash: Bytes32, - royalty_percentage: u16, -) -> Bytes32 { - let royalty_puzzle_hash = tree_hash_atom(&royalty_puzzle_hash); - let royalty_percentage_hash = tree_hash_atom(&u16_to_bytes(royalty_percentage)); - - let singleton_hash = tree_hash_atom(&SINGLETON_TOP_LAYER_PUZZLE_HASH); - let launcher_id_hash = tree_hash_atom(&launcher_id); - let launcher_puzzle_hash = tree_hash_atom(&SINGLETON_LAUNCHER_PUZZLE_HASH); - - let pair = tree_hash_pair(launcher_id_hash, launcher_puzzle_hash); - let singleton_struct_hash = tree_hash_pair(singleton_hash, pair); - - curry_tree_hash( - NFT_ROYALTY_TRANSFER_PUZZLE_HASH, - &[ - singleton_struct_hash, - royalty_puzzle_hash, - royalty_percentage_hash, - ], - ) - .into() -} - #[cfg(test)] mod tests { use chia_bls::{sign, Signature}; use chia_protocol::SpendBundle; - use chia_puzzles::{ - nft::{ - NftOwnershipLayerArgs, NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs, - NFT_METADATA_UPDATER_PUZZLE_HASH, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - NFT_STATE_LAYER_PUZZLE_HASH, - }, - singleton::{ - SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH, - }, - DeriveSynthetic, - }; + use chia_puzzles::DeriveSynthetic; use clvm_utils::CurriedProgram; use clvmr::Allocator; @@ -310,103 +247,4 @@ mod tests { Ok(()) } - - #[test] - fn test_state_layer_hash() { - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); - - let metadata = ctx.alloc([4, 5, 6]).unwrap(); - let metadata_hash = ctx.tree_hash(metadata).into(); - - let nft_state_layer = ctx.nft_state_layer(); - - let puzzle = ctx - .alloc(CurriedProgram { - program: nft_state_layer, - args: NftStateLayerArgs { - mod_hash: NFT_STATE_LAYER_PUZZLE_HASH.into(), - metadata, - metadata_updater_puzzle_hash: NFT_METADATA_UPDATER_PUZZLE_HASH.into(), - inner_puzzle, - }, - }) - .unwrap(); - let allocated_puzzle_hash = ctx.tree_hash(puzzle); - - let puzzle_hash = nft_state_layer_hash( - metadata_hash, - NFT_METADATA_UPDATER_PUZZLE_HASH.into(), - inner_puzzle_hash, - ); - - assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); - } - - #[test] - fn test_ownership_layer_hash() { - let mut allocator = Allocator::new(); - let mut ctx = SpendContext::new(&mut allocator); - - let inner_puzzle = ctx.alloc([1, 2, 3]).unwrap(); - let inner_puzzle_hash = ctx.tree_hash(inner_puzzle).into(); - - let launcher_id = Bytes32::new([69; 32]); - - let royalty_puzzle_hash = Bytes32::new([34; 32]); - let royalty_percentage = 100; - - let current_owner = Some(Bytes32::new([42; 32])); - - let nft_ownership_layer = ctx.nft_ownership_layer(); - let nft_royalty_transfer = ctx.nft_royalty_transfer(); - - let transfer_program = ctx - .alloc(CurriedProgram { - program: nft_royalty_transfer, - args: NftRoyaltyTransferPuzzleArgs { - singleton_struct: SingletonStruct { - mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(), - launcher_id, - launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(), - }, - royalty_puzzle_hash, - trade_price_percentage: royalty_percentage, - }, - }) - .unwrap(); - let allocated_transfer_program_hash = ctx.tree_hash(transfer_program).into(); - - let puzzle = ctx - .alloc(CurriedProgram { - program: nft_ownership_layer, - args: NftOwnershipLayerArgs { - mod_hash: NFT_OWNERSHIP_LAYER_PUZZLE_HASH.into(), - current_owner, - transfer_program, - inner_puzzle, - }, - }) - .unwrap(); - let allocated_puzzle_hash = ctx.tree_hash(puzzle); - - let puzzle_hash = nft_ownership_layer_hash( - current_owner, - allocated_transfer_program_hash, - inner_puzzle_hash, - ); - - let transfer_program_hash = - nft_royalty_transfer_hash(launcher_id, royalty_puzzle_hash, royalty_percentage); - - assert_eq!( - hex::encode(allocated_transfer_program_hash), - hex::encode(transfer_program_hash) - ); - - assert_eq!(hex::encode(allocated_puzzle_hash), hex::encode(puzzle_hash)); - } } diff --git a/src/spends/puzzles/nft/nft_spend.rs b/src/spends/puzzles/nft/nft_spend.rs index f64e06f6..d6c0b83b 100644 --- a/src/spends/puzzles/nft/nft_spend.rs +++ b/src/spends/puzzles/nft/nft_spend.rs @@ -4,18 +4,17 @@ use chia_puzzles::{ nft::{ NftOwnershipLayerArgs, NftOwnershipLayerSolution, NftRoyaltyTransferPuzzleArgs, NftStateLayerArgs, NftStateLayerSolution, NFT_OWNERSHIP_LAYER_PUZZLE_HASH, - NFT_STATE_LAYER_PUZZLE_HASH, + NFT_ROYALTY_TRANSFER_PUZZLE_HASH, NFT_STATE_LAYER_PUZZLE_HASH, }, singleton::{SingletonStruct, SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH}, LineageProof, Proof, }; use clvm_traits::{clvm_list, ToClvm}; -use clvm_utils::CurriedProgram; +use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash}; use clvmr::NodePtr; use sha2::{Digest, Sha256}; use crate::{ - nft_ownership_layer_hash, nft_royalty_transfer_hash, nft_state_layer_hash, singleton_puzzle_hash, spend_singleton, AssertPuzzleAnnouncement, Chainable, ChainedSpend, CreateCoinWithMemos, InnerSpend, NewNftOwner, NftInfo, SpendContext, SpendError, StandardSpend, }; @@ -135,19 +134,36 @@ impl StandardNftSpend { let metadata_ptr = ctx.alloc(&nft_info.metadata)?; - let new_inner_puzzle_hash = nft_state_layer_hash( - ctx.tree_hash(metadata_ptr).into(), - nft_info.metadata_updater_hash, - nft_ownership_layer_hash( - nft_info.current_owner, - nft_royalty_transfer_hash( - nft_info.launcher_id, - nft_info.royalty_puzzle_hash, - nft_info.royalty_percentage, - ), - p2_puzzle_hash, - ), - ); + let transfer_program = CurriedProgram { + program: NFT_ROYALTY_TRANSFER_PUZZLE_HASH, + args: NftRoyaltyTransferPuzzleArgs { + singleton_struct: SingletonStruct::new(nft_info.launcher_id), + royalty_puzzle_hash: nft_info.royalty_puzzle_hash, + trade_price_percentage: nft_info.royalty_percentage, + }, + }; + + let ownership_layer = CurriedProgram { + program: NFT_OWNERSHIP_LAYER_PUZZLE_HASH, + args: NftOwnershipLayerArgs { + mod_hash: NFT_OWNERSHIP_LAYER_PUZZLE_HASH.into(), + current_owner: nft_info.current_owner, + transfer_program, + inner_puzzle: TreeHash::from(p2_puzzle_hash), + }, + }; + + let new_inner_puzzle_hash = CurriedProgram { + program: NFT_STATE_LAYER_PUZZLE_HASH, + args: NftStateLayerArgs { + mod_hash: NFT_STATE_LAYER_PUZZLE_HASH.into(), + metadata: ctx.tree_hash(metadata_ptr), + metadata_updater_puzzle_hash: nft_info.metadata_updater_hash, + inner_puzzle: ownership_layer, + }, + } + .tree_hash() + .into(); let new_puzzle_hash = singleton_puzzle_hash(nft_info.launcher_id, new_inner_puzzle_hash); From 4a9e9666abee9dac959499e87d52d191b4a3282c Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 14 May 2024 19:08:24 -0400 Subject: [PATCH 24/25] Remove redundant import --- src/wallet/wallet_simulator.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/wallet/wallet_simulator.rs b/src/wallet/wallet_simulator.rs index 3c87b1b2..afae50eb 100644 --- a/src/wallet/wallet_simulator.rs +++ b/src/wallet/wallet_simulator.rs @@ -612,10 +612,7 @@ mod tests { use chia_protocol::{CoinSpend, SpendBundle}; use chia_puzzles::DeriveSynthetic; - use crate::{ - testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, RequiredSignature, - SpendContext, - }; + use crate::{testing::SECRET_KEY, CreateCoinWithMemos, CreateCoinWithoutMemos, SpendContext}; use super::*; From 22bd7fc15f3502fdb0ebc957494c8d9fd1c2ca1a Mon Sep 17 00:00:00 2001 From: Rigidity Date: Wed, 15 May 2024 09:18:41 -0400 Subject: [PATCH 25/25] Fix did parse --- src/parser/puzzles/did.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/parser/puzzles/did.rs b/src/parser/puzzles/did.rs index ce30a2f6..a2915c19 100644 --- a/src/parser/puzzles/did.rs +++ b/src/parser/puzzles/did.rs @@ -1,6 +1,6 @@ use chia_protocol::Bytes32; use chia_puzzles::{ - did::{DidArgs, DidSolution}, + did::{DidArgs, DidSolution, DID_INNER_PUZZLE_HASH}, LineageProof, Proof, }; use clvm_traits::FromClvm; @@ -18,6 +18,10 @@ pub fn parse_did( singleton: &ParseSingleton, max_cost: u64, ) -> Result>, ParseError> { + if singleton.inner_mod_hash().to_bytes() != DID_INNER_PUZZLE_HASH.to_bytes() { + return Ok(None); + } + let args = DidArgs::::from_clvm(allocator, singleton.inner_args())?; let DidSolution::InnerSpend(p2_solution) =