Skip to content

Commit

Permalink
p2_delegated_singleton_layer
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Sep 25, 2024
1 parent 2e8eb34 commit 6903419
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 21 deletions.
2 changes: 2 additions & 0 deletions crates/chia-sdk-driver/src/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod did_layer;
mod nft_ownership_layer;
mod nft_state_layer;
mod p2_delegated_conditions_layer;
mod p2_delegated_singleton_layer;
mod p2_one_of_many;
mod p2_singleton;
mod royalty_transfer_layer;
Expand All @@ -15,6 +16,7 @@ pub use did_layer::*;
pub use nft_ownership_layer::*;
pub use nft_state_layer::*;
pub use p2_delegated_conditions_layer::*;
pub use p2_delegated_singleton_layer::*;
pub use p2_one_of_many::*;
pub use p2_singleton::*;
pub use royalty_transfer_layer::*;
Expand Down
194 changes: 194 additions & 0 deletions crates/chia-sdk-driver/src/layers/p2_delegated_singleton_layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use chia_protocol::{Bytes32, Coin};
use chia_puzzles::singleton::{SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_PUZZLE_HASH};
use clvm_traits::{FromClvm, ToClvm};
use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
use clvmr::{Allocator, NodePtr};
use hex_literal::hex;

use crate::{DriverError, Layer, Puzzle, Spend, SpendContext};

/// The p2 delegated singleton [`Layer`] allows for requiring that a singleton
/// be spent alongside this coin to authorize it, while also outputting conditions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct P2DelegatedSingletonLayer {
pub launcher_id: Bytes32,
}

impl P2DelegatedSingletonLayer {
pub fn new(launcher_id: Bytes32) -> Self {
Self { launcher_id }
}

pub fn spend(
&self,
ctx: &mut SpendContext,
coin_id: Bytes32,
singleton_inner_puzzle_hash: Bytes32,
delegated_spend: Spend,
) -> Result<Spend, DriverError> {
let puzzle = self.construct_puzzle(ctx)?;
let solution = self.construct_solution(
ctx,
P2DelegatedSingletonSolution {
singleton_inner_puzzle_hash,
coin_id,
delegated_puzzle: delegated_spend.puzzle,
delegated_solution: delegated_spend.solution,
},
)?;
Ok(Spend { puzzle, solution })
}

pub fn spend_coin(
&self,
ctx: &mut SpendContext,
coin: Coin,
singleton_inner_puzzle_hash: Bytes32,
delegated_spend: Spend,
) -> Result<(), DriverError> {
let coin_spend = self.construct_coin_spend(
ctx,
coin,
P2DelegatedSingletonSolution {
singleton_inner_puzzle_hash,
coin_id: coin.coin_id(),
delegated_puzzle: delegated_spend.puzzle,
delegated_solution: delegated_spend.solution,
},
)?;
ctx.insert(coin_spend);
Ok(())
}
}

impl Layer for P2DelegatedSingletonLayer {
type Solution = P2DelegatedSingletonSolution<NodePtr, NodePtr>;

fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
let Some(puzzle) = puzzle.as_curried() else {
return Ok(None);
};

if puzzle.mod_hash != P2_DELEGATED_SINGLETON_PUZZLE_HASH {
return Ok(None);
}

let args = P2DelegatedSingletonArgs::from_clvm(allocator, puzzle.args)?;

if args.singleton_mod_hash != SINGLETON_TOP_LAYER_PUZZLE_HASH.into()
|| args.launcher_puzzle_hash != SINGLETON_LAUNCHER_PUZZLE_HASH.into()
{
return Err(DriverError::InvalidSingletonStruct);
}

Ok(Some(Self {
launcher_id: args.launcher_id,
}))
}

fn parse_solution(
allocator: &Allocator,
solution: NodePtr,
) -> Result<Self::Solution, DriverError> {
Ok(P2DelegatedSingletonSolution::from_clvm(
allocator, solution,
)?)
}

fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
let curried = CurriedProgram {
program: ctx.p2_delegated_singleton_puzzle()?,
args: P2DelegatedSingletonArgs::new(self.launcher_id),
};
ctx.alloc(&curried)
}

fn construct_solution(
&self,
ctx: &mut SpendContext,
solution: Self::Solution,
) -> Result<NodePtr, DriverError> {
ctx.alloc(&solution)
}
}

impl ToTreeHash for P2DelegatedSingletonLayer {
fn tree_hash(&self) -> TreeHash {
P2DelegatedSingletonArgs::curry_tree_hash(self.launcher_id)
}
}

// (mod (SINGLETON_MOD_HASH LAUNCHER_ID LAUNCHER_PUZZLE_HASH singleton_inner_puzzle_hash delegated_puzzle delegated_solution my_id)

#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)]
#[clvm(curry)]
pub struct P2DelegatedSingletonArgs {
pub singleton_mod_hash: Bytes32,
pub launcher_id: Bytes32,
pub launcher_puzzle_hash: Bytes32,
}

impl P2DelegatedSingletonArgs {
pub fn new(launcher_id: Bytes32) -> Self {
Self {
singleton_mod_hash: SINGLETON_TOP_LAYER_PUZZLE_HASH.into(),
launcher_id,
launcher_puzzle_hash: SINGLETON_LAUNCHER_PUZZLE_HASH.into(),
}
}

pub fn curry_tree_hash(launcher_id: Bytes32) -> TreeHash {
CurriedProgram {
program: P2_DELEGATED_SINGLETON_PUZZLE_HASH,
args: Self::new(launcher_id),
}
.tree_hash()
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)]
#[clvm(list)]
pub struct P2DelegatedSingletonSolution<P, S> {
pub singleton_inner_puzzle_hash: Bytes32,
pub delegated_puzzle: P,
pub delegated_solution: S,
pub coin_id: Bytes32,
}

pub const P2_DELEGATED_SINGLETON_PUZZLE: [u8; 508] = hex!(
"
ff02ffff01ff02ff16ffff04ff02ffff04ffff04ffff04ff28ffff04ffff0bff
ff02ff2effff04ff02ffff04ff05ffff04ff2fffff04ffff02ff3effff04ff02
ffff04ffff04ff05ffff04ff0bff178080ff80808080ff808080808080ff8201
7f80ff808080ffff04ffff04ff14ffff04ffff02ff3effff04ff02ffff04ff5f
ff80808080ff808080ffff04ffff04ff10ffff04ff82017fff808080ff808080
80ffff04ffff02ff5fff81bf80ff8080808080ffff04ffff01ffffff46ff3f02
ff3cff0401ffff01ff02ff02ffff03ff05ffff01ff02ff3affff04ff02ffff04
ff0dffff04ffff0bff2affff0bff3cff2c80ffff0bff2affff0bff2affff0bff
3cff1280ff0980ffff0bff2aff0bffff0bff3cff8080808080ff8080808080ff
ff010b80ff0180ffff02ffff03ff05ffff01ff04ff09ffff02ff16ffff04ff02
ffff04ff0dffff04ff0bff808080808080ffff010b80ff0180ffff0bff2affff
0bff3cff3880ffff0bff2affff0bff2affff0bff3cff1280ff0580ffff0bff2a
ffff02ff3affff04ff02ffff04ff07ffff04ffff0bff3cff3c80ff8080808080
ffff0bff3cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ff
ff02ff3effff04ff02ffff04ff09ff80808080ffff02ff3effff04ff02ffff04
ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080
"
);

pub const P2_DELEGATED_SINGLETON_PUZZLE_HASH: TreeHash = TreeHash::new(hex!(
"2cadfbf73f1ff120d708ad2fefad1c78eefb8d874231bc87eac7c2df5eeb904a"
));

#[cfg(test)]
mod tests {
use super::*;

use crate::assert_puzzle_hash;

#[test]
fn test_puzzle_hash() -> anyhow::Result<()> {
assert_puzzle_hash!(P2_DELEGATED_SINGLETON_PUZZLE => P2_DELEGATED_SINGLETON_PUZZLE_HASH);
Ok(())
}
}
15 changes: 15 additions & 0 deletions crates/chia-sdk-driver/src/layers/standard_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ impl StandardLayer {
let spend = self.spend_with_conditions(ctx, conditions)?;
ctx.spend(coin, spend)
}

pub fn delegated_inner_spend(
&self,
ctx: &mut SpendContext,
spend: Spend,
) -> Result<Spend, DriverError> {
self.construct_spend(
ctx,
StandardSolution {
original_public_key: None,
delegated_puzzle: spend.puzzle,
solution: spend.solution,
},
)
}
}

impl Layer for StandardLayer {
Expand Down
12 changes: 10 additions & 2 deletions crates/chia-sdk-driver/src/spend_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use clvmr::{serde::node_from_bytes, Allocator, NodePtr};

use crate::{
DriverError, Spend, P2_DELEGATED_CONDITIONS_PUZZLE, P2_DELEGATED_CONDITIONS_PUZZLE_HASH,
P2_ONE_OF_MANY_PUZZLE, P2_ONE_OF_MANY_PUZZLE_HASH, P2_SINGLETON_PUZZLE,
P2_SINGLETON_PUZZLE_HASH,
P2_DELEGATED_SINGLETON_PUZZLE, P2_DELEGATED_SINGLETON_PUZZLE_HASH, P2_ONE_OF_MANY_PUZZLE,
P2_ONE_OF_MANY_PUZZLE_HASH, P2_SINGLETON_PUZZLE, P2_SINGLETON_PUZZLE_HASH,
};

/// A wrapper around [`Allocator`] that caches puzzles and keeps track of a list of [`CoinSpend`].
Expand Down Expand Up @@ -201,6 +201,14 @@ impl SpendContext {
self.puzzle(P2_SINGLETON_PUZZLE_HASH, &P2_SINGLETON_PUZZLE)
}

/// Allocate the p2 delegated singleton puzzle and return its pointer.
pub fn p2_delegated_singleton_puzzle(&mut self) -> Result<NodePtr, DriverError> {
self.puzzle(
P2_DELEGATED_SINGLETON_PUZZLE_HASH,
&P2_DELEGATED_SINGLETON_PUZZLE,
)
}

/// Preload a puzzle into the cache.
pub fn preload(&mut self, puzzle_hash: TreeHash, ptr: NodePtr) {
self.puzzles.insert(puzzle_hash, ptr);
Expand Down
5 changes: 3 additions & 2 deletions napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ export interface Spend {
puzzle: ClvmPtr
solution: ClvmPtr
}
export declare function spendP2Standard(clvm: ClvmAllocator, syntheticKey: Uint8Array, conditions: Array<ClvmPtr>): Spend
export declare function spendP2Singleton(clvm: ClvmAllocator, launcherId: Uint8Array, coinId: Uint8Array, singletonInnerPuzzleHash: Uint8Array): Spend
export declare function delegatedSpendForConditions(clvm: ClvmAllocator, conditions: Array<ClvmPtr>): Spend
export declare function spendP2Standard(clvm: ClvmAllocator, syntheticKey: Uint8Array, delegatedSpend: Spend): Spend
export declare function spendP2DelegatedSingleton(clvm: ClvmAllocator, launcherId: Uint8Array, coinId: Uint8Array, singletonInnerPuzzleHash: Uint8Array, delegatedSpend: Spend): Spend
export declare function compareBytes(a: Uint8Array, b: Uint8Array): boolean
export declare function sha256(bytes: Uint8Array): Uint8Array
export declare function fromHexRaw(hex: string): Uint8Array
Expand Down
5 changes: 3 additions & 2 deletions napi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { ClvmAllocator, ClvmPtr, curryTreeHash, toCoinId, parseNftInfo, parseUnspentNft, spendNft, mintNfts, spendP2Standard, spendP2Singleton, compareBytes, sha256, fromHexRaw, fromHex, toHex } = nativeBinding
const { ClvmAllocator, ClvmPtr, curryTreeHash, toCoinId, parseNftInfo, parseUnspentNft, spendNft, mintNfts, delegatedSpendForConditions, spendP2Standard, spendP2DelegatedSingleton, compareBytes, sha256, fromHexRaw, fromHex, toHex } = nativeBinding

module.exports.ClvmAllocator = ClvmAllocator
module.exports.ClvmPtr = ClvmPtr
Expand All @@ -320,8 +320,9 @@ module.exports.parseNftInfo = parseNftInfo
module.exports.parseUnspentNft = parseUnspentNft
module.exports.spendNft = spendNft
module.exports.mintNfts = mintNfts
module.exports.delegatedSpendForConditions = delegatedSpendForConditions
module.exports.spendP2Standard = spendP2Standard
module.exports.spendP2Singleton = spendP2Singleton
module.exports.spendP2DelegatedSingleton = spendP2DelegatedSingleton
module.exports.compareBytes = compareBytes
module.exports.sha256 = sha256
module.exports.fromHexRaw = fromHexRaw
Expand Down
53 changes: 38 additions & 15 deletions napi/src/spend.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use chia::{bls::PublicKey, clvm_traits::FromClvm};
use chia_wallet_sdk::{Condition, Conditions, P2Singleton, SpendWithConditions, StandardLayer};
use chia::{
bls::PublicKey,
clvm_traits::{clvm_quote, ToClvm},
};
use chia_wallet_sdk::{self as sdk, P2DelegatedSingletonLayer, StandardLayer};
use clvmr::NodePtr;
use napi::bindgen_prelude::*;

Expand All @@ -14,28 +17,43 @@ pub struct Spend {
pub solution: ClassInstance<ClvmPtr>,
}

#[napi]
pub fn delegated_spend_for_conditions(
env: Env,
clvm: &mut ClvmAllocator,
conditions: Vec<ClassInstance<ClvmPtr>>,
) -> Result<Spend> {
let conditions: Vec<NodePtr> = conditions.into_iter().map(|ptr| ptr.0).collect();

let delegated_puzzle = clvm_quote!(conditions)
.to_clvm(&mut clvm.0)
.map_err(|error| Error::from_reason(error.to_string()))?;

Ok(Spend {
puzzle: ClvmPtr(delegated_puzzle).into_instance(env)?,
solution: ClvmPtr(NodePtr::NIL).into_instance(env)?,
})
}

#[napi]
pub fn spend_p2_standard(
env: Env,
clvm: &mut ClvmAllocator,
synthetic_key: Uint8Array,
conditions: Vec<ClassInstance<ClvmPtr>>,
delegated_spend: Spend,
) -> Result<Spend> {
clvm.with_context(|ctx| {
let synthetic_key = PublicKey::from_js(synthetic_key)?;
let p2 = StandardLayer::new(synthetic_key);

let mut spend_conditions = Conditions::new();

for condition in conditions {
spend_conditions = spend_conditions.with(
Condition::<NodePtr>::from_clvm(&ctx.allocator, condition.0)
.map_err(|error| Error::from_reason(error.to_string()))?,
);
}

let spend = p2
.spend_with_conditions(ctx, spend_conditions)
.delegated_inner_spend(
ctx,
sdk::Spend {
puzzle: delegated_spend.puzzle.0,
solution: delegated_spend.solution.0,
},
)
.map_err(|error| Error::from_reason(error.to_string()))?;

Ok(Spend {
Expand All @@ -46,21 +64,26 @@ pub fn spend_p2_standard(
}

#[napi]
pub fn spend_p2_singleton(
pub fn spend_p2_delegated_singleton(
env: Env,
clvm: &mut ClvmAllocator,
launcher_id: Uint8Array,
coin_id: Uint8Array,
singleton_inner_puzzle_hash: Uint8Array,
delegated_spend: Spend,
) -> Result<Spend> {
clvm.with_context(|ctx| {
let p2 = P2Singleton::new(launcher_id.into_rust()?);
let p2 = P2DelegatedSingletonLayer::new(launcher_id.into_rust()?);

let spend = p2
.spend(
ctx,
coin_id.into_rust()?,
singleton_inner_puzzle_hash.into_rust()?,
sdk::Spend {
puzzle: delegated_spend.puzzle.0,
solution: delegated_spend.solution.0,
},
)
.map_err(|error| Error::from_reason(error.to_string()))?;

Expand Down

0 comments on commit 6903419

Please sign in to comment.