Skip to content

Commit

Permalink
added p2_conditions_options_layer
Browse files Browse the repository at this point in the history
  • Loading branch information
cameroncooper committed Nov 25, 2024
1 parent a66f8b0 commit adc01c5
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 1 deletion.
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 @@ -2,6 +2,7 @@ mod cat_layer;
mod did_layer;
mod nft_ownership_layer;
mod nft_state_layer;
mod p2_conditions_options_layer;
mod p2_delegated_conditions_layer;
mod p2_delegated_singleton_layer;
mod p2_one_of_many;
Expand All @@ -15,6 +16,7 @@ pub use cat_layer::*;
pub use did_layer::*;
pub use nft_ownership_layer::*;
pub use nft_state_layer::*;
pub use p2_conditions_options_layer::*;
pub use p2_delegated_conditions_layer::*;
pub use p2_delegated_singleton_layer::*;
pub use p2_one_of_many::*;
Expand Down
191 changes: 191 additions & 0 deletions crates/chia-sdk-driver/src/layers/p2_conditions_options_layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use chia_protocol::Coin;
use chia_sdk_types::Conditions;
use clvm_traits::{FromClvm, ToClvm};
use clvm_utils::{CurriedProgram, TreeHash};
use clvmr::{Allocator, NodePtr};
use hex_literal::hex;

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

/// The p2 conditions options [`Layer`] allows a predetermined set of conditions to be chosen at spend time.
/// To do so, a list of conditions lists are provided when the coin are created. Then, one of the conditions
/// is selected when the coin is spent.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct P2ConditionsOptionsLayer<T = NodePtr> {
pub options: Vec<Conditions<T>>,
}

impl<T> P2ConditionsOptionsLayer<T> {
pub fn new(options: Vec<Conditions<T>>) -> Self {
Self { options }
}

pub fn spend(&self, ctx: &mut SpendContext, coin: Coin, option: u16) -> Result<(), DriverError>
where
T: ToClvm<Allocator> + FromClvm<Allocator> + Clone,
{
let puzzle = self.construct_puzzle(ctx)?;
let solution = self.construct_solution(ctx, P2ConditionsOptionsSolution { option })?;
ctx.spend(coin, Spend { puzzle, solution })
}
}

impl<T> Layer for P2ConditionsOptionsLayer<T>
where
T: ToClvm<Allocator> + FromClvm<Allocator> + Clone,
{
type Solution = P2ConditionsOptionsSolution;

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_CONDITIONS_OPTIONS_PUZZLE_HASH {
return Ok(None);
}

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

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

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

fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
let curried = CurriedProgram {
program: ctx.p2_conditions_options_puzzle()?,
args: P2ConditionsOptionsArgs {
options: self.options.clone(),
},
};
ctx.alloc(&curried)
}

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

#[cfg(test)]
mod tests {
use chia_sdk_test::Simulator;
use chia_sdk_types::run_puzzle;
use rstest::rstest;

use crate::StandardLayer;

use super::*;

#[test]
fn test_conditions_options_layer() -> anyhow::Result<()> {
let mut ctx = SpendContext::new();

let layer = P2ConditionsOptionsLayer::new(vec![
Conditions::new().remark(NodePtr::NIL),
Conditions::new().create_coin_announcement(b"hello".to_vec().into()),
]);

let ptr = layer.construct_puzzle(&mut ctx)?;
let puzzle = Puzzle::parse(&ctx.allocator, ptr);
let roundtrip = P2ConditionsOptionsLayer::<NodePtr>::parse_puzzle(&ctx.allocator, puzzle)?
.expect("invalid P2 conditions options layer");

assert_eq!(roundtrip.options, layer.options);

Ok(())
}

#[rstest]
fn test_conditions_options_spend(#[values(0, 1)] option: u16) -> anyhow::Result<()> {
let mut sim = Simulator::new();
let ctx = &mut SpendContext::new();
let (sk, pk, _puzzle_hash, coin) = sim.new_p2(1)?;
let p2 = StandardLayer::new(pk);

let conditions0 = Conditions::new().create_coin_announcement(b"hello".to_vec().into());
let conditions1 = Conditions::new().create_coin_announcement(b"goodbye".to_vec().into());
let layer = P2ConditionsOptionsLayer::new(vec![conditions0.clone(), conditions1.clone()]);

let ptr = layer.construct_puzzle(ctx)?;
let p2_puzzle_hash = ctx.tree_hash(ptr);

p2.spend(
ctx,
coin,
Conditions::new().create_coin(p2_puzzle_hash.into(), 1, Vec::new()),
)?;

let option_coin = Coin::new(coin.coin_id(), p2_puzzle_hash.into(), 1);
layer.spend(ctx, option_coin, option)?;
sim.spend_coins(ctx.take(), &[sk])?;

let solution = layer.construct_solution(ctx, P2ConditionsOptionsSolution { option })?;

let output = run_puzzle(&mut ctx.allocator, ptr, solution)?;
let output = ctx.extract::<Conditions>(output)?;

if option == 0 {
assert_eq!(output, conditions0);
} else {
assert_eq!(output, conditions1);
}

Ok(())
}
}

#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)]
#[clvm(curry)]
pub struct P2ConditionsOptionsArgs<T = NodePtr> {
pub options: Vec<Conditions<T>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ToClvm, FromClvm)]
#[clvm(solution)]
pub struct P2ConditionsOptionsSolution {
pub option: u16,
}

/*
; this puzzle takes a list of conditions lists and lets the spender select which one to use
(mod (CONDITIONS_OPTIONS option_index)
; helper to pick an item from a list
(defun select (items idx)
(if items
(if idx
(select (r items) (- idx 1)) ; continue to get the selected index
(f items) ; idx 0 returns first element
)
(x) ; no items to choose from
)
)
; entry point
(select CONDITIONS_OPTIONS option_index)
)
*/

pub const P2_CONDITIONS_OPTIONS_PUZZLE: [u8; 111] = hex!(
"
ff02ffff01ff02ff02ffff04ff02ffff04ff05ffff04ff0bff8080808080ffff
04ffff01ff02ffff03ff05ffff01ff02ffff03ff0bffff01ff02ff02ffff04ff
02ffff04ff0dffff04ffff11ff0bffff010180ff8080808080ffff010980ff01
80ffff01ff088080ff0180ff018080
"
);

pub const P2_CONDITIONS_OPTIONS_PUZZLE_HASH: TreeHash = TreeHash::new(hex!(
"e82e42b272a903ddd9c279d291487655e4e08883829dbb8086af91bd9b8afc3e"
));
11 changes: 10 additions & 1 deletion crates/chia-sdk-driver/src/spend_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ use clvm_utils::{tree_hash, TreeHash};
use clvmr::{serde::node_from_bytes, Allocator, NodePtr};

use crate::{
DriverError, Spend, P2_DELEGATED_CONDITIONS_PUZZLE, P2_DELEGATED_CONDITIONS_PUZZLE_HASH,
DriverError, Spend, P2_CONDITIONS_OPTIONS_PUZZLE, P2_CONDITIONS_OPTIONS_PUZZLE_HASH,
P2_DELEGATED_CONDITIONS_PUZZLE, P2_DELEGATED_CONDITIONS_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,
};
Expand Down Expand Up @@ -191,6 +192,14 @@ impl SpendContext {
)
}

/// Allocate the p2 conditions options puzzle and return its pointer.
pub fn p2_conditions_options_puzzle(&mut self) -> Result<NodePtr, DriverError> {
self.puzzle(
P2_CONDITIONS_OPTIONS_PUZZLE_HASH,
&P2_CONDITIONS_OPTIONS_PUZZLE,
)
}

/// Allocate the p2 one of many puzzle and return its pointer.
pub fn p2_one_of_many_puzzle(&mut self) -> Result<NodePtr, DriverError> {
self.puzzle(P2_ONE_OF_MANY_PUZZLE_HASH, &P2_ONE_OF_MANY_PUZZLE)
Expand Down

0 comments on commit adc01c5

Please sign in to comment.