From a6b692b811a7cc2ec58b38f347cb2f23965e3f55 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Tue, 29 Oct 2024 13:10:11 -0400 Subject: [PATCH] Add transfer helper to DIDs --- crates/chia-sdk-driver/src/primitives/did.rs | 94 +++++++++++++++++-- .../src/primitives/did/did_info.rs | 10 ++ crates/chia-sdk-driver/src/primitives/nft.rs | 9 +- .../src/primitives/nft/nft_info.rs | 14 +++ crates/chia-sdk-test/src/simulator.rs | 14 ++- 5 files changed, 130 insertions(+), 11 deletions(-) diff --git a/crates/chia-sdk-driver/src/primitives/did.rs b/crates/chia-sdk-driver/src/primitives/did.rs index 338d44f0..b1125942 100644 --- a/crates/chia-sdk-driver/src/primitives/did.rs +++ b/crates/chia-sdk-driver/src/primitives/did.rs @@ -1,5 +1,9 @@ -use chia_protocol::Coin; -use chia_puzzles::{did::DidSolution, singleton::SingletonSolution, LineageProof, Proof}; +use chia_protocol::{Bytes32, Coin}; +use chia_puzzles::{ + did::DidSolution, + singleton::{SingletonArgs, SingletonSolution}, + LineageProof, Proof, +}; use chia_sdk_types::{run_puzzle, Condition, Conditions}; use clvm_traits::{FromClvm, ToClvm}; use clvm_utils::{tree_hash, ToTreeHash}; @@ -50,11 +54,27 @@ where } /// Creates a wrapped spendable DID for the child. - pub fn wrapped_child(self) -> Self { - Self { - coin: Coin::new(self.coin.coin_id(), self.coin.puzzle_hash, self.coin.amount), + pub fn wrapped_child(&self, p2_puzzle_hash: Bytes32, metadata: N) -> Did + where + M: Clone, + N: ToTreeHash, + { + let info = self + .info + .clone() + .with_p2_puzzle_hash(p2_puzzle_hash) + .with_metadata(metadata); + + let inner_puzzle_hash = info.inner_puzzle_hash(); + + Did { + coin: Coin::new( + self.coin.coin_id(), + SingletonArgs::curry_tree_hash(info.launcher_id, inner_puzzle_hash).into(), + self.coin.amount, + ), proof: Proof::Lineage(self.child_lineage_proof()), - info: self.info, + info, } } } @@ -96,6 +116,41 @@ where self.spend(ctx, inner_spend) } + /// Transfers this DID to a new p2 puzzle hash. + /// + /// Note: This does not update the metadata. You need to do an update spend to change the metadata. + pub fn transfer( + self, + ctx: &mut SpendContext, + inner: &I, + p2_puzzle_hash: Bytes32, + extra_conditions: Conditions, + ) -> Result, DriverError> + where + M: ToTreeHash, + I: SpendWithConditions, + { + let new_inner_puzzle_hash = self + .info + .clone() + .with_p2_puzzle_hash(p2_puzzle_hash) + .inner_puzzle_hash(); + + self.spend_with( + ctx, + inner, + extra_conditions.create_coin( + new_inner_puzzle_hash.into(), + self.coin.amount, + vec![p2_puzzle_hash.into()], + ), + )?; + + let metadata = self.info.metadata.clone(); + + Ok(self.wrapped_child(p2_puzzle_hash, metadata)) + } + /// Recreates this DID and outputs additional conditions via the inner puzzle. pub fn update_with_metadata( self, @@ -125,7 +180,7 @@ where ), )?; - Ok(self.wrapped_child().with_metadata(metadata)) + Ok(self.wrapped_child(self.info.p2_puzzle_hash, metadata)) } /// Creates a new DID coin with the given metadata. @@ -293,6 +348,31 @@ mod tests { Ok(()) } + #[test] + fn test_transfer_did() -> anyhow::Result<()> { + let mut sim = Simulator::new(); + let ctx = &mut SpendContext::new(); + let (sk, pk, bob_puzzle_hash, coin) = sim.child_p2(1, 0)?; + let bob = StandardLayer::new(pk); + + let (create_did, bob_did) = + Launcher::new(coin.coin_id(), 1).create_simple_did(ctx, &bob)?; + bob.spend(ctx, coin, create_did)?; + + let (sk2, pk2, alice_puzzle_hash, _) = sim.child_p2(0, 1)?; + let alice = StandardLayer::new(pk2); + + let alice_did = bob_did.transfer(ctx, &bob, alice_puzzle_hash, Conditions::new())?; + let did = alice_did.update(ctx, &alice, Conditions::new())?; + + assert_eq!(did.info.p2_puzzle_hash, alice_puzzle_hash); + assert_ne!(bob_puzzle_hash, alice_puzzle_hash); + + sim.spend_coins(ctx.take(), &[sk, sk2])?; + + Ok(()) + } + #[test] fn test_update_did_metadata() -> anyhow::Result<()> { let mut sim = Simulator::new(); diff --git a/crates/chia-sdk-driver/src/primitives/did/did_info.rs b/crates/chia-sdk-driver/src/primitives/did/did_info.rs index 8913bef3..95ba4ea7 100644 --- a/crates/chia-sdk-driver/src/primitives/did/did_info.rs +++ b/crates/chia-sdk-driver/src/primitives/did/did_info.rs @@ -89,6 +89,16 @@ impl DidInfo { } } + pub fn with_p2_puzzle_hash(self, p2_puzzle_hash: Bytes32) -> Self { + Self { + launcher_id: self.launcher_id, + recovery_list_hash: self.recovery_list_hash, + num_verifications_required: self.num_verifications_required, + metadata: self.metadata, + p2_puzzle_hash, + } + } + pub fn inner_puzzle_hash(&self) -> TreeHash where M: ToTreeHash, diff --git a/crates/chia-sdk-driver/src/primitives/nft.rs b/crates/chia-sdk-driver/src/primitives/nft.rs index 6bc0aae1..e4fbc03e 100644 --- a/crates/chia-sdk-driver/src/primitives/nft.rs +++ b/crates/chia-sdk-driver/src/primitives/nft.rs @@ -75,9 +75,12 @@ where M: Clone, N: ToTreeHash, { - let mut info = self.info.clone().with_metadata(metadata); - info.p2_puzzle_hash = p2_puzzle_hash; - info.current_owner = owner; + let info = self + .info + .clone() + .with_p2_puzzle_hash(p2_puzzle_hash) + .with_owner(owner) + .with_metadata(metadata); let inner_puzzle_hash = info.inner_puzzle_hash(); diff --git a/crates/chia-sdk-driver/src/primitives/nft/nft_info.rs b/crates/chia-sdk-driver/src/primitives/nft/nft_info.rs index ecd7c273..a17cd1cc 100644 --- a/crates/chia-sdk-driver/src/primitives/nft/nft_info.rs +++ b/crates/chia-sdk-driver/src/primitives/nft/nft_info.rs @@ -122,6 +122,20 @@ impl NftInfo { } } + pub fn with_p2_puzzle_hash(self, p2_puzzle_hash: Bytes32) -> Self { + Self { + p2_puzzle_hash, + ..self + } + } + + pub fn with_owner(self, owner: Option) -> Self { + Self { + current_owner: owner, + ..self + } + } + pub fn inner_puzzle_hash(&self) -> TreeHash where M: ToTreeHash, diff --git a/crates/chia-sdk-test/src/simulator.rs b/crates/chia-sdk-test/src/simulator.rs index 5ff0f152..9f1a1983 100644 --- a/crates/chia-sdk-test/src/simulator.rs +++ b/crates/chia-sdk-test/src/simulator.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use chia_bls::{PublicKey, SecretKey}; +use chia_bls::{DerivableKey, PublicKey, SecretKey}; use chia_consensus::{ consensus_constants::ConsensusConstants, gen::validation_error::ErrorCode, spendbundle_validation::validate_clvm_and_signature, @@ -85,6 +85,18 @@ impl Simulator { Ok((sk, pk, p2, coin)) } + pub fn child_p2( + &mut self, + amount: u64, + child: u32, + ) -> Result<(SecretKey, PublicKey, Bytes32, Coin), bip39::Error> { + let sk = test_secret_key()?.derive_unhardened(child); + let pk = sk.public_key(); + let p2 = StandardArgs::curry_tree_hash(pk).into(); + let coin = self.new_coin(p2, amount); + Ok((sk, pk, p2, coin)) + } + pub(crate) fn hint_coin(&mut self, coin_id: Bytes32, hint: Bytes32) { self.hinted_coins.entry(hint).or_default().insert(coin_id); }