use chia_protocol::{Bytes32, Coin};
use chia_puzzle_types::{LineageProof, cat::CatArgs, singleton::SingletonSolution};
use chia_sdk_types::{
conditions::CreateCoin,
puzzles::{P2DelegatedBySingletonLayerArgs, P2DelegatedBySingletonLayerSolution},
run_puzzle,
};
use clvm_traits::{FromClvm, ToClvm, clvm_list, clvm_quote, match_tuple};
use clvm_utils::TreeHash;
use clvmr::{Allocator, NodePtr};
use crate::{
ActionLayer, Cat, CatInfo, CatSpend, DriverError, Layer, P2DelegatedBySingletonLayer, Spend,
SpendContext,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use]
pub struct Reserve {
pub coin: Coin,
pub asset_id: Bytes32,
pub proof: LineageProof,
pub inner_puzzle_hash: Bytes32,
pub controller_singleton_struct_hash: Bytes32,
pub nonce: u64,
}
pub trait Reserveful {
fn reserve_amount(&self, index: u64) -> u64;
}
impl Reserve {
pub fn new(
parent_coin_id: Bytes32,
proof: LineageProof,
asset_id: Bytes32,
controller_singleton_struct_hash: Bytes32,
nonce: u64,
amount: u64,
) -> Self {
let inner_puzzle_hash = P2DelegatedBySingletonLayerArgs::curry_tree_hash(
controller_singleton_struct_hash,
nonce,
);
Self {
coin: Coin::new(
parent_coin_id,
CatArgs::curry_tree_hash(asset_id, inner_puzzle_hash).into(),
amount,
),
proof,
asset_id,
inner_puzzle_hash: inner_puzzle_hash.into(),
controller_singleton_struct_hash,
nonce,
}
}
pub fn puzzle_hash(
asset_id: Bytes32,
controller_singleton_struct_hash: Bytes32,
nonce: u64,
) -> TreeHash {
CatArgs::curry_tree_hash(
asset_id,
P2DelegatedBySingletonLayerArgs::curry_tree_hash(
controller_singleton_struct_hash,
nonce,
),
)
}
pub fn construct_inner_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
let layer =
P2DelegatedBySingletonLayer::new(self.controller_singleton_struct_hash, self.nonce);
layer.construct_puzzle(ctx)
}
pub fn to_cat(&self) -> Cat {
Cat::new(
self.coin,
Some(self.proof),
CatInfo::new(self.asset_id, None, self.inner_puzzle_hash),
)
}
pub fn inner_spend(
&self,
ctx: &mut SpendContext,
controller_singleton_inner_puzzle_hash: Bytes32,
delegated_puzzle: NodePtr,
delegated_solution: NodePtr,
) -> Result<Spend, DriverError> {
P2DelegatedBySingletonLayer::new(self.controller_singleton_struct_hash, self.nonce)
.construct_spend(
ctx,
P2DelegatedBySingletonLayerSolution {
singleton_inner_puzzle_hash: controller_singleton_inner_puzzle_hash,
delegated_puzzle,
delegated_solution,
},
)
}
pub fn delegated_puzzle_for_finalizer_controller<S>(
&self,
ctx: &mut SpendContext,
controlelr_initial_state: S,
controller_solution: NodePtr,
) -> Result<NodePtr, DriverError>
where
S: ToClvm<Allocator> + FromClvm<Allocator> + Clone + Reserveful,
{
let controller_solution = ctx.extract::<SingletonSolution<NodePtr>>(controller_solution)?;
let inner_solution =
ActionLayer::<S, NodePtr>::parse_solution(ctx, controller_solution.inner_solution)?;
let mut state = (NodePtr::NIL, controlelr_initial_state);
let mut reserve_conditions = Vec::new();
for raw_action in inner_solution.action_spends {
let actual_solution = ctx.alloc(&clvm_list!(state, raw_action.solution))?;
let output = run_puzzle(ctx, raw_action.puzzle, actual_solution)?;
let (new_state, conditions) =
ctx.extract::<match_tuple!((NodePtr, S), Vec<(i64, NodePtr)>)>(output)?;
state = new_state;
for (opcode, cond) in conditions.iter().rev() {
if *opcode == -42 {
reserve_conditions.push(*cond);
}
}
}
let new_reserve_amount = state.1.reserve_amount(0);
let cc = CreateCoin::new(
self.inner_puzzle_hash,
new_reserve_amount,
ctx.hint(self.inner_puzzle_hash)?,
);
reserve_conditions.insert(0, ctx.alloc(&cc)?);
let delegated_puzzle = ctx.alloc(&clvm_quote!(reserve_conditions))?;
Ok(delegated_puzzle)
}
pub fn cat_spend_for_reserve_finalizer_controller<S>(
&self,
ctx: &mut SpendContext,
controlelr_initial_state: S,
controller_singleton_inner_puzzle_hash: Bytes32,
controller_solution: NodePtr,
) -> Result<CatSpend, DriverError>
where
S: ToClvm<Allocator> + FromClvm<Allocator> + Clone + Reserveful,
{
let delegated_puzzle = self.delegated_puzzle_for_finalizer_controller(
ctx,
controlelr_initial_state,
controller_solution,
)?;
Ok(CatSpend::new(
self.to_cat(),
self.inner_spend(
ctx,
controller_singleton_inner_puzzle_hash,
delegated_puzzle,
NodePtr::NIL,
)?,
))
}
pub fn child(&self, child_amount: u64) -> Self {
Self::new(
self.coin.coin_id(),
LineageProof {
parent_parent_coin_info: self.coin.parent_coin_info,
parent_inner_puzzle_hash: self.inner_puzzle_hash,
parent_amount: self.coin.amount,
},
self.asset_id,
self.controller_singleton_struct_hash,
self.nonce,
child_amount,
)
}
}