diff --git a/ctv_emulators/src/connections/federated.rs b/ctv_emulators/src/connections/federated.rs index fc7e7c2d..f82ae1b6 100644 --- a/ctv_emulators/src/connections/federated.rs +++ b/ctv_emulators/src/connections/federated.rs @@ -6,6 +6,8 @@ //! join together CTVEmulators as a multisig +use sapio_base::Policy; + use super::*; /// Creates a multi-condition emulator with a certain threshold. /// It implements CTVEmulator so that it itself can be used as a trait object. @@ -25,13 +27,13 @@ impl FederatedEmulatorConnection { } impl CTVEmulator for FederatedEmulatorConnection { - fn get_signer_for(&self, h: Sha256) -> Result { + fn get_signer_for(&self, h: Sha256) -> Result { let v = self .emulators .iter() .map(|e| e.get_signer_for(h)) - .collect::, EmulatorError>>()?; - Ok(Clause::Threshold(self.threshold as usize, v)) + .collect::, EmulatorError>>()?; + Ok(Policy::Threshold(self.threshold as usize, v)) } fn sign( &self, diff --git a/ctv_emulators/src/connections/hd.rs b/ctv_emulators/src/connections/hd.rs index ebae7de0..97f814e2 100644 --- a/ctv_emulators/src/connections/hd.rs +++ b/ctv_emulators/src/connections/hd.rs @@ -97,10 +97,11 @@ impl HDOracleEmulatorConnection { } } +use sapio_base::Policy; use tokio::{runtime::Handle, sync::Mutex}; impl CTVEmulator for HDOracleEmulatorConnection { - fn get_signer_for(&self, h: Sha256) -> Result { - Ok(Clause::Key(self.derive(h)?.to_x_only_pub())) + fn get_signer_for(&self, h: Sha256) -> Result { + Ok(Policy::Key(self.derive(h)?.to_x_only_pub())) } fn sign( &self, diff --git a/emulator-trait/src/emulator.rs b/emulator-trait/src/emulator.rs index 1bd05b7f..18581378 100644 --- a/emulator-trait/src/emulator.rs +++ b/emulator-trait/src/emulator.rs @@ -7,7 +7,9 @@ //! definitions of emulator traits required to use as a trait object in low-level libraries. use bitcoin::hashes::sha256; use bitcoin::util::psbt::PartiallySignedTransaction; +use sapio_base::miniscript::policy::Concrete; pub use sapio_base::Clause; +use sapio_base::Policy; use std::fmt; use std::sync::Arc; /// Errors that an emulator might throw @@ -43,7 +45,7 @@ impl From for EmulatorError { pub trait CTVEmulator: Sync + Send { /// For a given transaction hash, gets the corresponding Clause that the /// Emulator would satisfy. - fn get_signer_for(&self, h: sha256::Hash) -> Result; + fn get_signer_for(&self, h: sha256::Hash) -> Result; /// Adds the Emulators signature to the PSBT, if any. fn sign( &self, @@ -58,8 +60,8 @@ pub type NullEmulator = Arc; /// a type tag that can be tossed inside an Arc to get CTV pub struct CTVAvailable; impl CTVEmulator for CTVAvailable { - fn get_signer_for(&self, h: sha256::Hash) -> Result { - Ok(Clause::TxTemplate(h)) + fn get_signer_for(&self, h: sha256::Hash) -> Result { + Ok(Policy::TxTemplate(h)) } fn sign( &self, diff --git a/sapio-base/src/consts.rs b/sapio-base/src/consts.rs new file mode 100644 index 00000000..8e1f85fb --- /dev/null +++ b/sapio-base/src/consts.rs @@ -0,0 +1,6 @@ +// TODO: Fix the const fn version +use bitcoin::blockdata::opcodes::all; +///pub const FALSE_PATTERN: &[u8] = &[all::OP_PUSHBYTES_0.into_u8()]; +pub const FALSE_PATTERN: &[u8] = &[0]; +///pub const TRUE_PATTERN: &[u8] = &[all::OP_PUSHNUM_1.into_u8()]; +pub const TRUE_PATTERN: &[u8] = &[81]; diff --git a/sapio-base/src/lib.rs b/sapio-base/src/lib.rs index 4563ba76..0ea6dc30 100644 --- a/sapio-base/src/lib.rs +++ b/sapio-base/src/lib.rs @@ -8,9 +8,20 @@ #![deny(missing_docs)] /// Extra functionality for working with Bitcoin types pub mod util; -use bitcoin::XOnlyPublicKey; -pub use util::CTVHash; +use std::{ + borrow::BorrowMut, + cell::{Cell, RefCell}, + ops::DerefMut, + rc::Rc, +}; + +use bitcoin::{blockdata::opcodes::all::OP_VERIFY, hashes::hex::FromHex, Script, XOnlyPublicKey}; +use consts::TRUE_PATTERN; pub use miniscript; +use miniscript::Tap; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +pub use util::CTVHash; pub mod plugin_args; pub mod simp; @@ -23,9 +34,145 @@ pub mod effects; pub use effects::reverse_path; pub mod serialization_helpers; +/// Any logical script clause +#[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Debug, JsonSchema, Serialize, Deserialize)] +pub enum Clause { + /// Script fragment (must end with 1 or 0 on stack) + Script(bitcoin::Script), + /// Logical Conjunction + And(Box>, Box>), + /// Logical Or + Or(Box>, Box>, u64), +} + +/// constants useful for various purposes +pub mod consts { + // TODO: Fix the const fn version + use bitcoin::blockdata::opcodes::all; + ///pub const FALSE_PATTERN: &[u8] = &[all::OP_PUSHBYTES_0.into_u8()]; + pub const FALSE_PATTERN: &[u8] = &[0]; + ///pub const TRUE_PATTERN: &[u8] = &[all::OP_PUSHNUM_1.into_u8()]; + pub const TRUE_PATTERN: &[u8] = &[81]; +} +impl Clause { + /// Returns true if this [`Clause`] is an OP_TRUE + pub fn is_trivial(&self) -> bool { + match self { + Clause::Script(s) => matches!(s.as_bytes(), consts::TRUE_PATTERN), + _ => false, + } + } + /// creates a trivial fragment + pub fn trivial() -> Clause { + Clause::Script(Script::from(TRUE_PATTERN.to_vec())) + } + /// + pub fn wrap(self) -> Box> { + Box::new(RefCell::new(self)) + } +} +fn join_script_frags<'a, I: Iterator>(i: I) -> Script { + let mut acc = vec![]; + for script in i { + acc.extend_from_slice(script.as_bytes()); + acc.push(OP_VERIFY.into_u8()); + } + // drop last verify + acc.pop(); + Script::from_byte_iter(acc.iter().cloned().map(Ok)).unwrap() +} + +fn assign_numbers_inner(p: &mut Clause, n: &mut u64) { + match p { + Clause::Script(v) => (), + Clause::And(a, b) => { + assign_numbers_inner(a.get_mut(), n); + assign_numbers_inner(b.get_mut(), n); + } + Clause::Or(a, b, c) => { + *c = *n; + *n += 1; + } + } +} +fn assign_numbers(mut clause: Clause) -> (Clause, Vec) { + let mut n = 0; + assign_numbers_inner(&mut clause, &mut n); + (clause, vec![false; n as usize]) +} + +fn compile_opt(clause: &mut Clause, opt: &[bool]) -> Script { + match clause { + Clause::Script(s) => s.clone(), + Clause::And(a, b) => { + join_script_frags([compile_opt(a.get_mut(), opt), compile_opt(b.get_mut(), opt)].iter()) + } + Clause::Or(a, b, idx) => { + if opt[*idx as usize] { + compile_opt(a.get_mut(), opt) + } else { + compile_opt(b.get_mut(), opt) + } + } + } +} + +fn byte_to_bits(u: u8) -> [bool; 8] { + [ + u & 1 > 0, + u & 2 > 0, + u & 4 > 0, + u & 8 > 0, + u & 16 > 0, + u & 32 > 0, + u & 64 > 0, + u & 128 > 0, + ] +} + +/// Fail on complex scripts +#[derive(Debug)] +pub struct ScriptComplexityTooManyOrs; +impl Clause { + /// generates all the leafs by letting each or be satisfied bit-by-bit + pub fn generate_leafs(p: Self) -> Result, ScriptComplexityTooManyOrs> { + let (mut clause, opt) = assign_numbers(p); + if opt.len() > 16 { + return Err(ScriptComplexityTooManyOrs); + } + let lim: u16 = 2u16.pow(opt.len() as u32); + let mut out = vec![]; + for x in 0..lim { + // double check the bit order TODO: + let bytes = x.to_be_bytes(); + let v = [byte_to_bits(bytes[1]), byte_to_bits(bytes[0])].concat(); + out.push(compile_opt(&mut clause, &v[0..opt.len()])); + } + Ok(out) + } +} + /// Concrete Instantiation of Miniscript Policy. Because we need to be able to generate exact /// transactions, we only work with `bitcoin::PublicKey` types. -pub type Clause = miniscript::policy::concrete::Policy; +pub type Policy = miniscript::policy::concrete::Policy; + +/// Concrete Instantiation of Miniscript Policy. Because we need to be able to generate exact +/// transactions, we only work with `bitcoin::PublicKey` types. +pub type Pol = miniscript::policy::concrete::Policy; + +/// Helper for things that can become clauses +pub trait IntoClause { + /// convert to clause + fn to_clause(&self) -> Result; +} +impl IntoClause for Pol { + fn to_clause(&self) -> Result { + Ok(Clause::Script( + self.compile::().map_err(|_| ())?.encode(), + )) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/sapio-base/src/timelocks.rs b/sapio-base/src/timelocks.rs index a34a113e..de6bcbe8 100644 --- a/sapio-base/src/timelocks.rs +++ b/sapio-base/src/timelocks.rs @@ -115,6 +115,10 @@ mod alias { pub use alias::*; mod trait_impls { + use miniscript::policy::Concrete; + + use crate::Policy; + use super::*; impl Absolutivity for Rel { const IS_ABSOLUTE: bool = false; @@ -169,17 +173,17 @@ mod trait_impls { } } - impl From> for Clause + impl From> for Policy where A: Absolutivity, TT: TimeType, { - fn from(lt: LockTime) -> Clause { + fn from(lt: LockTime) -> Policy { match (A::IS_ABSOLUTE, TT::IS_HEIGHT) { - (true, true) => Clause::After(lt.0), - (true, false) => Clause::After(lt.0), - (false, true) => Clause::Older(lt.0), - (false, false) => Clause::Older(lt.0), + (true, true) => Policy::After(lt.0), + (true, false) => Policy::After(lt.0), + (false, true) => Policy::Older(lt.0), + (false, false) => Policy::Older(lt.0), } } } @@ -244,7 +248,7 @@ mod trait_impls { } } - impl From for Clause { + impl From for Policy { fn from(lt: AnyRelTimeLock) -> Self { match lt { AnyRelTimeLock::RH(a) => a.into(), @@ -252,7 +256,7 @@ mod trait_impls { } } } - impl From for Clause { + impl From for Policy { fn from(lt: AnyAbsTimeLock) -> Self { match lt { AnyAbsTimeLock::AH(a) => a.into(), @@ -260,7 +264,7 @@ mod trait_impls { } } } - impl From for Clause { + impl From for Policy { fn from(lt: AnyTimeLock) -> Self { match lt { AnyTimeLock::A(a) => a.into(), diff --git a/sapio-contrib/src/contracts/basic_examples.rs b/sapio-contrib/src/contracts/basic_examples.rs index 528a2638..612d5af3 100644 --- a/sapio-contrib/src/contracts/basic_examples.rs +++ b/sapio-contrib/src/contracts/basic_examples.rs @@ -29,11 +29,11 @@ struct ExampleA { impl ExampleA { #[guard] fn timeout(self, _ctx: sapio::Context) { - Clause::Older(100) + Pol::Older(100) } #[guard(cached)] fn signed(self, _ctx: sapio::Context) { - Clause::And(vec![Clause::Key(self.alice), Clause::Key(self.bob)]) + Pol::And(vec![Pol::Key(self.alice), Pol::Key(self.bob)]) } } @@ -79,9 +79,9 @@ struct ExampleB { impl ExampleB { #[guard(cached)] fn all_signed(self, _ctx: Context) { - Clause::Threshold( + Pol::Threshold( T::get_n(self.threshold, self.participants.len() as u8) as usize, - self.participants.iter().map(|k| Clause::Key(*k)).collect(), + self.participants.iter().map(|k| Pol::Key(*k)).collect(), ) } } @@ -135,7 +135,7 @@ pub struct ExampleCompileIf { impl ExampleCompileIf { #[guard] fn cooperate(self, _ctx: Context) { - Clause::And(vec![Clause::Key(self.alice), Clause::Key(self.bob)]) + Pol::And(vec![Pol::Key(self.alice), Pol::Key(self.bob)]) } /// `should_escrow` disables any branch depending on it. If not set, /// it checks to make the branch required. This is done in a conflict-free way; diff --git a/sapio-contrib/src/contracts/channel.rs b/sapio-contrib/src/contracts/channel.rs index 709af1f3..8f23eef2 100644 --- a/sapio-contrib/src/contracts/channel.rs +++ b/sapio-contrib/src/contracts/channel.rs @@ -10,7 +10,7 @@ use bitcoin::util::amount::CoinAmount; use contract::*; use sapio::*; -use sapio_base::Clause; +use sapio_base::Pol; use sapio_macros::guard; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -228,11 +228,11 @@ where { #[guard] fn timeout(self, _ctx: Context) { - Clause::Older(100) + Pol::Older(100) } #[guard(cached)] fn signed(self, _ctx: Context) { - Clause::And(vec![Clause::Key(self.alice), Clause::Key(self.bob)]) + Pol::And(vec![Pol::Key(self.alice), Pol::Key(self.bob)]) } #[continuation(guarded_by = "[Self::signed]", coerce_args = "coerce_args")] diff --git a/sapio-contrib/src/contracts/coin_pool.rs b/sapio-contrib/src/contracts/coin_pool.rs index 1bfb0b65..5e214de9 100644 --- a/sapio-contrib/src/contracts/coin_pool.rs +++ b/sapio-contrib/src/contracts/coin_pool.rs @@ -10,7 +10,7 @@ use sapio::contract::*; use sapio::util::amountrange::AmountF64; use sapio::*; use sapio_base::timelocks::AnyRelTimeLock; -use sapio_base::Clause; +use sapio_base::Pol; use schemars::JsonSchema; use serde::Deserialize; @@ -21,7 +21,7 @@ type Payouts = Vec<(Arc>, AmountF64)>; /// cooperatively share a UTXO. pub struct CoinPool { /// The list of stakeholders - pub clauses: Vec, + pub clauses: Vec, /// How to refund people if no update agreed on pub refunds: Payouts, } @@ -71,7 +71,7 @@ impl CoinPool { #[guard] /// everyone has signed off on the transaction fn all_approve(self, _ctx: Context) { - Clause::Threshold(self.clauses.len(), self.clauses.clone()) + Pol::Threshold(self.clauses.len(), self.clauses.clone()) } /// move the coins to the next state -- payouts may recursively contain pools itself #[continuation( diff --git a/sapio-contrib/src/contracts/derivatives/call.rs b/sapio-contrib/src/contracts/derivatives/call.rs index a2e75249..da724cb7 100644 --- a/sapio-contrib/src/contracts/derivatives/call.rs +++ b/sapio-contrib/src/contracts/derivatives/call.rs @@ -58,7 +58,7 @@ impl<'a> TryFrom> for GenericBetArguments<'a> { amount: max_amount_bitcoin, outcomes, oracle: v.operator_api.get_oracle(), - cooperate: Clause::And(vec![key, user]), + cooperate: Pol::And(vec![key, user]), symbol: v.symbol, }) } diff --git a/sapio-contrib/src/contracts/derivatives/dlc.rs b/sapio-contrib/src/contracts/derivatives/dlc.rs index 8a7c6a20..de79ed20 100644 --- a/sapio-contrib/src/contracts/derivatives/dlc.rs +++ b/sapio-contrib/src/contracts/derivatives/dlc.rs @@ -13,7 +13,7 @@ use bitcoin::XOnlyPublicKey; use contract::*; use sapio::template::Template; use sapio::*; -use sapio_base::Clause; +use sapio_base::Pol; use std::sync::Arc; struct Event(String); @@ -59,7 +59,7 @@ impl DLCContract { fn cooperate(&self, _ctx: Context) { // TODO: Add a 2nd musig_cooperate that works with whatever gets standardized // Keep the non-musig path in case we can't do a multi-round protocol... - Clause::And(self.parties.iter().cloned().map(Clause::Key).collect()) + Pol::And(self.parties.iter().cloned().map(Pol::Key).collect()) } #[then] fn payout(&self, mut ctx: Context) { @@ -94,11 +94,11 @@ impl DLCContract { v.1.combine(&v.0) .map_err(|_| CompilationError::TerminateCompilation)?; } - let guard = Clause::Threshold( + let guard = Pol::Threshold( self.oracles.0, oracles .iter() - .map(|(_, oracle_k)| Ok(Clause::Key(XOnlyPublicKey::from(*oracle_k)))) + .map(|(_, oracle_k)| Ok(Pol::Key(XOnlyPublicKey::from(*oracle_k)))) .collect::, CompilationError>>()?, ); let mut tmpl = new_ctx.derive_num(i)?.template().add_guard(guard); diff --git a/sapio-contrib/src/contracts/eltoo_channel.rs b/sapio-contrib/src/contracts/eltoo_channel.rs index aa94120b..c37b2943 100644 --- a/sapio-contrib/src/contracts/eltoo_channel.rs +++ b/sapio-contrib/src/contracts/eltoo_channel.rs @@ -14,7 +14,7 @@ use sapio::contract::error::CompilationError; use sapio::template::Output; use sapio::*; use sapio_base::timelocks::RelHeight; -use sapio_base::Clause; +use sapio_base::Pol; use sapio_macros::compile_if; use bitcoin; @@ -46,14 +46,14 @@ struct OpenChannel { impl OpenChannel { #[guard] fn signed_update(self, _ctx: Context) { - Clause::And(vec![Clause::Key(self.alice_u), Clause::Key(self.bob_u)]) + Pol::And(vec![Pol::Key(self.alice_u), Pol::Key(self.bob_u)]) } #[guard] fn newer_sequence_check(self, _ctx: Context) { if let Some(prior) = self.pending_update.as_ref() { AbsTime::try_from(prior.sequence.get() + 1) - .map(Clause::from) - .unwrap_or(Clause::Unsatisfiable) + .map(Pol::from) + .unwrap_or(Pol::Unsatisfiable) } else { START_OF_TIME.into() } @@ -131,7 +131,7 @@ impl OpenChannel { #[guard] fn sign_cooperative_close(self, _ctx: Context) { - Clause::And(vec![Clause::Key(self.alice), Clause::Key(self.bob)]) + Pol::And(vec![Pol::Key(self.alice), Pol::Key(self.bob)]) } #[compile_if] diff --git a/sapio-contrib/src/contracts/federated_sidechain.rs b/sapio-contrib/src/contracts/federated_sidechain.rs index 5f1a86aa..69c47533 100644 --- a/sapio-contrib/src/contracts/federated_sidechain.rs +++ b/sapio-contrib/src/contracts/federated_sidechain.rs @@ -8,7 +8,7 @@ use bitcoin::util::amount::CoinAmount; use sapio::contract::*; use sapio::*; -use sapio_base::Clause; +use sapio_base::Pol; use sapio_macros::guard; use schemars::*; use serde::*; @@ -84,14 +84,14 @@ impl StateDependentActions for FederatedPegIn { impl StateDependentActions for FederatedPegIn { #[guard] fn finish_recovery(self, _ctx: Context) { - Clause::And(vec![ - Clause::Older(4725 /* 4 weeks? */), - Clause::Threshold( + Pol::And(vec![ + Pol::Older(4725 /* 4 weeks? */), + Pol::Threshold( self.thresh_recovery, self.keys_recovery .iter() .cloned() - .map(Clause::Key) + .map(Pol::Key) .collect(), ), ]) @@ -101,21 +101,21 @@ impl StateDependentActions for FederatedPegIn { impl FederatedPegIn { #[guard] fn recovery_signed(self, _ctx: Context) { - Clause::Threshold( + Pol::Threshold( self.thresh_recovery, self.keys_recovery .iter() .cloned() - .map(Clause::Key) + .map(Pol::Key) .collect(), ) } #[guard] fn normal_signed(self, _ctx: Context) { - Clause::Threshold( + Pol::Threshold( self.thresh_normal, - self.keys.iter().cloned().map(Clause::Key).collect(), + self.keys.iter().cloned().map(Pol::Key).collect(), ) } } diff --git a/sapio-contrib/src/contracts/hodl_chicken.rs b/sapio-contrib/src/contracts/hodl_chicken.rs index c6da26dd..a0db3e13 100644 --- a/sapio-contrib/src/contracts/hodl_chicken.rs +++ b/sapio-contrib/src/contracts/hodl_chicken.rs @@ -28,7 +28,7 @@ use bitcoin::util::amount::Amount; use sapio::contract::*; use sapio::*; -use sapio_base::Clause; +use sapio_base::Pol; use sapio_macros::guard; use schemars::*; use serde::*; @@ -89,11 +89,11 @@ impl TryFrom for HodlChickenInner { impl HodlChickenInner { #[guard] fn alice_is_a_chicken(self, _ctx: Context) { - Clause::Key(self.alice_key) + Pol::Key(self.alice_key) } #[guard] fn bob_is_a_chicken(self, _ctx: Context) { - Clause::Key(self.bob_key) + Pol::Key(self.bob_key) } #[then(guarded_by = "[Self::alice_is_a_chicken]")] fn alice_redeem(self, ctx: sapio::Context) { diff --git a/sapio-contrib/src/contracts/mod.rs b/sapio-contrib/src/contracts/mod.rs index 03e309e4..ac26c1b7 100644 --- a/sapio-contrib/src/contracts/mod.rs +++ b/sapio-contrib/src/contracts/mod.rs @@ -29,3 +29,4 @@ pub mod tic_tac_toe; pub mod treepay; pub mod undo_send; pub mod vault; +pub mod bitvm; \ No newline at end of file diff --git a/sapio-contrib/src/contracts/op_return_chain.rs b/sapio-contrib/src/contracts/op_return_chain.rs index 17f72843..70037d78 100644 --- a/sapio-contrib/src/contracts/op_return_chain.rs +++ b/sapio-contrib/src/contracts/op_return_chain.rs @@ -9,7 +9,7 @@ use bitcoin::Amount; use sapio::contract::*; use sapio::util::amountrange::AmountF64; use sapio::*; -use sapio_base::Clause; +use sapio_base::Pol; use sapio_macros::guard; use schemars::JsonSchema; use serde::Deserialize; @@ -33,7 +33,7 @@ impl ChainReturn { /// everyone has signed off on the transaction #[guard] fn approved(self, _ctx: Context) { - Clause::Key(self.pk) + Pol::Key(self.pk) } /// move the coins to the next state -- payouts may recursively contain pools itself #[continuation( diff --git a/sapio-contrib/src/contracts/readme_contracts.rs b/sapio-contrib/src/contracts/readme_contracts.rs index bb7e7fa6..4801abeb 100644 --- a/sapio-contrib/src/contracts/readme_contracts.rs +++ b/sapio-contrib/src/contracts/readme_contracts.rs @@ -9,7 +9,7 @@ use bitcoin::util::amount::CoinAmount; use sapio::contract::*; use sapio::*; use sapio_base::timelocks::RelTime; -use sapio_base::Clause; +use sapio_base::Pol; use sapio_macros::guard; use schemars::*; use serde::*; @@ -27,7 +27,7 @@ pub struct PayToPublicKey { impl PayToPublicKey { #[guard] fn with_key(self, _ctx: Context) { - Clause::Key(self.key) + Pol::Key(self.key) } } @@ -53,13 +53,13 @@ pub struct BasicEscrow { impl BasicEscrow { #[guard] fn redeem(self, _ctx: Context) { - Clause::Threshold( + Pol::Threshold( 1, vec![ - Clause::Threshold(2, vec![Clause::Key(self.alice), Clause::Key(self.bob)]), - Clause::And(vec![ - Clause::Key(self.escrow), - Clause::Threshold(1, vec![Clause::Key(self.alice), Clause::Key(self.bob)]), + Pol::Threshold(2, vec![Pol::Key(self.alice), Pol::Key(self.bob)]), + Pol::And(vec![ + Pol::Key(self.escrow), + Pol::Threshold(1, vec![Pol::Key(self.alice), Pol::Key(self.bob)]), ]), ], ) @@ -88,14 +88,14 @@ pub struct BasicEscrow2 { impl BasicEscrow2 { #[guard] fn use_escrow(self, _ctx: Context) { - Clause::And(vec![ - Clause::Key(self.escrow), - Clause::Threshold(2, vec![Clause::Key(self.alice), Clause::Key(self.bob)]), + Pol::And(vec![ + Pol::Key(self.escrow), + Pol::Threshold(2, vec![Pol::Key(self.alice), Pol::Key(self.bob)]), ]) } #[guard] fn cooperate(self, _ctx: Context) { - Clause::And(vec![Clause::Key(self.alice), Clause::Key(self.bob)]) + Pol::And(vec![Pol::Key(self.alice), Pol::Key(self.bob)]) } } @@ -120,7 +120,7 @@ pub struct TrustlessEscrow { impl TrustlessEscrow { #[guard] fn cooperate(self, _ctx: Context) { - Clause::And(vec![Clause::Key(self.alice), Clause::Key(self.bob)]) + Pol::And(vec![Pol::Key(self.alice), Pol::Key(self.bob)]) } #[then] fn use_escrow(self, ctx: sapio::Context) { diff --git a/sapio-contrib/src/contracts/staked_signer.rs b/sapio-contrib/src/contracts/staked_signer.rs index 003468c5..ca674c0f 100644 --- a/sapio-contrib/src/contracts/staked_signer.rs +++ b/sapio-contrib/src/contracts/staked_signer.rs @@ -9,7 +9,7 @@ use bitcoin::XOnlyPublicKey; use sapio::contract::*; use sapio::*; use sapio_base::timelocks::AnyRelTimeLock; -use sapio_base::Clause; +use sapio_base::Pol; use sapio_macros::guard; use schemars::*; use serde::*; @@ -93,7 +93,7 @@ impl StakerInterface for Staker { /// redeeming key #[guard] fn begin_redeem_key(self, _ctx: Context) { - Clause::Key(self.redeeming_key) + Pol::Key(self.redeeming_key) } /// begin redemption process #[then(guarded_by = "[Self::begin_redeem_key]")] @@ -115,18 +115,18 @@ impl StakerInterface for Staker { /// staking key #[guard] fn staking_key(self, _ctx: Context) { - Clause::Key(self.signing_key) + Pol::Key(self.signing_key) } } impl StakerInterface for Staker { #[guard] fn finish_redeem_key(self, _ctx: Context) { - Clause::And(vec![Clause::Key(self.redeeming_key), self.timeout.into()]) + Pol::And(vec![Pol::Key(self.redeeming_key), self.timeout.into()]) } #[guard] fn staking_key(self, _ctx: Context) { - Clause::Key(self.signing_key) + Pol::Key(self.signing_key) } } diff --git a/sapio/src/contract/abi/object/bind.rs b/sapio/src/contract/abi/object/bind.rs index 66644ca6..cfe5d0ef 100644 --- a/sapio/src/contract/abi/object/bind.rs +++ b/sapio/src/contract/abi/object/bind.rs @@ -50,7 +50,7 @@ impl Object { Object { root_path, continue_apis, - descriptor, + taproot_spend_info, ctv_to_tx, suggested_txs, metadata, @@ -99,37 +99,14 @@ impl Object { blockdata.lookup_output(&tx_in.previous_output).ok(); } // Missing other Witness Info. - match descriptor { - Some(SupportedDescriptors::Pk(d)) => { - psbtx.inputs[0].witness_script = Some(d.explicit_script()?); + if let Some(info) = taproot_spend_info { + let inp = &mut psbtx.inputs[0]; + for item in info.as_script_map().keys() { + let cb = info.control_block(item).expect("Must be present"); + inp.tap_scripts.insert(cb.clone(), item.clone()); } - Some(SupportedDescriptors::XOnly(Descriptor::Tr(t))) => { - let mut builder = TaprootBuilder::new(); - let mut added = false; - for (depth, ms) in t.iter_scripts() { - added = true; - let script = ms.encode(); - builder = builder.add_leaf(depth, script)?; - } - let info = if added { - builder.finalize(&secp, *t.internal_key())? - } else { - TaprootSpendInfo::new_key_spend( - &secp, - *t.internal_key(), - None, - ) - }; - let inp = &mut psbtx.inputs[0]; - for item in info.as_script_map().keys() { - let cb = - info.control_block(item).expect("Must be present"); - inp.tap_scripts.insert(cb.clone(), item.clone()); - } - inp.tap_merkle_root = info.merkle_root(); - inp.tap_internal_key = Some(info.internal_key()); - } - _ => (), + inp.tap_merkle_root = info.merkle_root(); + inp.tap_internal_key = Some(info.internal_key()); } psbtx = emulator.sign(psbtx)?; let final_tx = psbtx.clone().extract_tx(); diff --git a/sapio/src/contract/abi/object/mod.rs b/sapio/src/contract/abi/object/mod.rs index 8787fcb2..5c9d2b1f 100644 --- a/sapio/src/contract/abi/object/mod.rs +++ b/sapio/src/contract/abi/object/mod.rs @@ -7,6 +7,8 @@ //! Object is the output of Sapio Compilation & can be linked to a specific coin pub mod error; +use bitcoin::util::taproot::TaprootSpendInfo; +use bitcoin::Script; pub use error::*; pub mod bind; pub mod descriptors; @@ -67,7 +69,7 @@ impl ObjectMetadata { pub(crate) fn add_guard_simps( mut self, all_guard_simps: BTreeMap< - policy::Concrete, + Clause, Vec>>, >, ) -> Result { @@ -135,11 +137,12 @@ pub struct Object { pub address: ExtendedAddress, /// The Object's descriptor -- if there is one known/available #[serde( - rename = "known_descriptor", + rename = "taproot_spend_info", skip_serializing_if = "Option::is_none", default )] - pub descriptor: Option, + #[schemars(with="Option")] + pub taproot_spend_info: Option, /// The amount_range safe to send this object pub amount_range: AmountRange, /// metadata generated for this contract @@ -159,7 +162,7 @@ impl Object { PathFragment::Named(SArc(Arc::new("".into()))), )), address: address.into(), - descriptor: None, + taproot_spend_info: None, amount_range: a.unwrap_or_else(|| { let mut a = AmountRange::new(); a.update_range(Amount::min_value()); @@ -195,7 +198,7 @@ impl Object { PathFragment::Named(SArc(Arc::new("".into()))), )), address: ExtendedAddress::make_op_return(data)?, - descriptor: None, + taproot_spend_info: None, amount_range: AmountRange::new(), metadata: Default::default(), }) @@ -217,7 +220,7 @@ impl Object { PathFragment::Named(SArc(Arc::new("".into()))), )), address: d.address(bitcoin::Network::Bitcoin).unwrap().into(), - descriptor: Some(d.into()), + taproot_spend_info: None, amount_range: a.unwrap_or_else(|| { let mut a = AmountRange::new(); a.update_range(Amount::min_value()); diff --git a/sapio/src/contract/actions/finish.rs b/sapio/src/contract/actions/finish.rs index efd4fdcb..fb7c7335 100644 --- a/sapio/src/contract/actions/finish.rs +++ b/sapio/src/contract/actions/finish.rs @@ -59,9 +59,9 @@ pub struct FinishOrFunc<'a, ContractSelf, StatefulArguments, SpecificArgs, WebAP /// (if negative trait bounds, could remove!) pub f: PhantomData, /// if txtmpls returned by the func should modify guards. - pub returned_txtmpls_modify_guards: bool, + pub returned_transaction_templates_can_modify_parent_script: bool, /// extract a clause from the txtmpl - pub extract_clause_from_txtmpl: + pub extract_script_preconditions_from_transaction_template: fn(&Template, &Context) -> Result, CompilationError>, } @@ -97,9 +97,9 @@ pub trait CallableAsFoF { /// Get the RootSchema for calling this with an update fn get_schema(&self) -> &Option>; /// get if txtmpls returned by the func should modify guards. - fn get_returned_txtmpls_modify_guards(&self) -> bool; + fn returned_transaction_templates_can_modify_parent_script(&self) -> bool; /// extract a clause from the txtmpl - fn get_extract_clause_from_txtmpl( + fn extract_script_preconditions_from_transaction_template( &self, ) -> fn(&Template, &Context) -> Result, CompilationError>; /// rename this object @@ -130,13 +130,13 @@ impl CallableAsFoF &Option> { &self.schema } - fn get_returned_txtmpls_modify_guards(&self) -> bool { - self.returned_txtmpls_modify_guards + fn returned_transaction_templates_can_modify_parent_script(&self) -> bool { + self.returned_transaction_templates_can_modify_parent_script } - fn get_extract_clause_from_txtmpl( + fn extract_script_preconditions_from_transaction_template( &self, ) -> fn(&Template, &Context) -> Result, CompilationError> { - self.extract_clause_from_txtmpl + self.extract_script_preconditions_from_transaction_template } fn rename(&mut self, a: Arc) { @@ -182,14 +182,14 @@ where fn get_schema(&self) -> &Option> { &self.schema } - fn get_returned_txtmpls_modify_guards(&self) -> bool { - self.returned_txtmpls_modify_guards + fn returned_transaction_templates_can_modify_parent_script(&self) -> bool { + self.returned_transaction_templates_can_modify_parent_script } - fn get_extract_clause_from_txtmpl( + fn extract_script_preconditions_from_transaction_template( &self, ) -> fn(&Template, &Context) -> Result, CompilationError> { - self.extract_clause_from_txtmpl + self.extract_script_preconditions_from_transaction_template } fn rename(&mut self, a: Arc) { diff --git a/sapio/src/contract/actions/guard.rs b/sapio/src/contract/actions/guard.rs index c59f4b30..73f17ddc 100644 --- a/sapio/src/contract/actions/guard.rs +++ b/sapio/src/contract/actions/guard.rs @@ -19,18 +19,10 @@ use sapio_base::{ /// If bool = true, the computation of the guard is cached, which is useful if e.g. Guard /// must contact a remote server or it should be the same across calls *for a given contract /// instance*. -pub enum Guard { - /// Cache Variant should only be called one time per contract and the result saved - Cache( - fn(&ContractSelf, Context) -> Clause, - Option>, - ), - /// Fresh Variant may be called repeatedly - Fresh( - fn(&ContractSelf, Context) -> Clause, - Option>, - ), -} +pub struct Guard( + pub fn(&ContractSelf, Context) -> Clause, + pub Option>, +); /// A Function that can be used to generate metadata for a Guard pub type SimpGen = diff --git a/sapio/src/contract/actions/then.rs b/sapio/src/contract/actions/then.rs index 5fc36931..6bba40b9 100644 --- a/sapio/src/contract/actions/then.rs +++ b/sapio/src/contract/actions/then.rs @@ -13,6 +13,7 @@ use crate::contract::actions::GuardList; use crate::contract::actions::{FinishOrFunc, WebAPIDisabled}; use crate::template::Template; use sapio_base::Clause; +use std::cell::RefCell; use std::marker::PhantomData; use std::sync::Arc; @@ -59,8 +60,8 @@ impl<'a, ContractSelf, StatefulArgs> From> coerce_args: ThenFuncTypeTag::coerce_args, schema: None, f: PhantomData::default(), - returned_txtmpls_modify_guards: true, - extract_clause_from_txtmpl: ctv_clause_extractor, + returned_transaction_templates_can_modify_parent_script: true, + extract_script_preconditions_from_transaction_template: ctv_clause_extractor, // TODO: Maybe Then should be able to get simps? simp_gen: None, } @@ -73,8 +74,9 @@ fn ctv_clause_extractor(t: &Template, ctx: &Context) -> Result, C ctx.ctv_emulator(h) } else { let mut g = t.guards.clone(); - g.push(ctx.ctv_emulator(h)?); - Ok(Clause::And(g)) + let r = ctx.ctv_emulator(h)?.wrap(); + let acc = Clause::And(g.pop().unwrap().wrap(), r); + Ok(g.into_iter().fold(acc, |acc, f| Clause::And(acc.wrap(), f.wrap()))) } .map(Some) } diff --git a/sapio/src/contract/compiler/cache.rs b/sapio/src/contract/compiler/cache.rs index 3de78473..73f37bfe 100644 --- a/sapio/src/contract/compiler/cache.rs +++ b/sapio/src/contract/compiler/cache.rs @@ -10,6 +10,7 @@ use super::InternalCompilerTag; use crate::contract::actions::Guard; use crate::contract::actions::SimpGen; use crate::contract::CompilationError; +use sapio_base::consts::TRUE_PATTERN; use sapio_base::effects::PathFragment; use sapio_base::simp::GuardLT; use sapio_base::simp::SIMPAttachableAt; @@ -19,7 +20,6 @@ use std::sync::Arc; pub type GuardSimps = Vec>>; pub(crate) enum CacheEntry { - Cached(Clause, GuardSimps), Fresh(fn(&T, Context) -> Clause, Option>), } @@ -41,11 +41,7 @@ impl GuardCache { simp_ctx: Context, ) -> Result>, CompilationError> { match g { - Some(Guard::Cache(f, Some(simp_gen))) => { - Ok(Some(CacheEntry::Cached(f(t, ctx), simp_gen(t, simp_ctx)?))) - } - Some(Guard::Cache(f, None)) => Ok(Some(CacheEntry::Cached(f(t, ctx), vec![]))), - Some(Guard::Fresh(f, simp_gen)) => Ok(Some(CacheEntry::Fresh(f, simp_gen))), + Some(Guard(f, simp_gen)) => Ok(Some(CacheEntry::Fresh(f, simp_gen))), None => Ok(None), } } @@ -71,7 +67,6 @@ impl GuardCache { std::collections::btree_map::Entry::Occupied(ref mut o) => o.get_mut(), }; match r { - Some(CacheEntry::Cached(s, v)) => Ok(Some((s.clone(), v.to_vec()))), Some(CacheEntry::Fresh(f, s)) => Ok(Some(( f(t, ctx), match s { @@ -84,7 +79,7 @@ impl GuardCache { } } -pub(crate) fn create_guards( +pub(crate) fn get_script_preconditions_for( self_ref: &T, mut ctx: Context, guards: &[fn() -> Option>], @@ -102,14 +97,18 @@ pub(crate) fn create_guards( let mut clauses: Vec<_> = v .iter() .map(|x| &x.0) - .filter(|x| **x != Clause::Trivial) + .filter(|x| Clause::is_trivial(x)) .cloned() .collect(); // no point in using any Trivials if clauses.is_empty() { - Ok((Clause::Trivial, v)) + Ok((Clause::trivial(), v)) } else if clauses.len() == 1 { Ok((clauses.pop().unwrap(), v)) } else { - Ok((Clause::And(clauses), v)) + let start = Clause::And(clauses.pop().unwrap().wrap(), clauses.pop().unwrap().wrap()); + let tree = clauses + .into_iter() + .fold(start, |a, b| Clause::And(a.wrap(), b.wrap())); + Ok((tree, v)) } } diff --git a/sapio/src/contract/compiler/mod.rs b/sapio/src/contract/compiler/mod.rs index 96f463b5..42ab4532 100644 --- a/sapio/src/contract/compiler/mod.rs +++ b/sapio/src/contract/compiler/mod.rs @@ -5,6 +5,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. //! The primary compilation traits and types +use super::actions::conditional_compile; use super::actions::ConditionalCompileType; use super::AnyContract; use super::CompilationError; @@ -15,15 +16,26 @@ use crate::contract::actions::conditional_compile::CCILWrapper; use crate::contract::actions::CallableAsFoF; use crate::contract::TxTmplIt; use crate::util::amountrange::AmountRange; +use bitcoin::blockdata::opcodes::all::OP_VERIFY; +use bitcoin::hashes::hex::FromHex; use bitcoin::schnorr::TweakedPublicKey; +use bitcoin::secp256k1::Secp256k1; +use bitcoin::util::taproot::LeafVersion; +use bitcoin::util::taproot::TaprootBuilder; +use bitcoin::Address; +use bitcoin::Script; use bitcoin::XOnlyPublicKey; use miniscript::*; use sapio_base::effects::EffectDB; use sapio_base::effects::EffectPath; use sapio_base::effects::PathFragment; use sapio_base::miniscript; +use sapio_base::miniscript::miniscript::decode; use sapio_base::serialization_helpers::SArc; use sapio_base::Clause; +use serde::Deserialize; +use std::borrow::BorrowMut; +use std::cell::RefCell; use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; mod cache; @@ -77,14 +89,14 @@ enum Nullable { } const UNIQUE_DERIVE_PANIC_MSG: &str = "Must be a valid derivation or internal invariant not held"; -fn compute_all_effects( +fn create_state_transition_iterator( mut top_effect_ctx: Context, self_ref: &C, - func: &dyn CallableAsFoF, + state_transition: &dyn CallableAsFoF, ) -> TxTmplIt { let default_applied_effect_ctx = top_effect_ctx.derive(PathFragment::DefaultEffect)?; - let def = func.call(self_ref, default_applied_effect_ctx, Default::default())?; - if !func.web_api() { + let def = state_transition.call(self_ref, default_applied_effect_ctx, Default::default())?; + if !state_transition.web_api() { return Ok(def); } let mut applied_effects_ctx = top_effect_ctx.derive(PathFragment::Effects)?; @@ -98,7 +110,7 @@ fn compute_all_effects( let c = applied_effects_ctx .derive(PathFragment::Named(SArc(k.clone()))) .expect(UNIQUE_DERIVE_PANIC_MSG); - let w = func.call_json(self_ref, c, arg.clone())?; + let w = state_transition.call_json(self_ref, c, arg.clone())?; Ok(Box::new(v.chain(w))) }); r @@ -152,23 +164,23 @@ where /// TODO: Better Document Semantics fn compile(&self, mut ctx: Context) -> Result { let self_ref = self.get_inner_ref(); - let mut guard_clauses = GuardCache::new(); + let mut script_precondition_compilation_cache = GuardCache::new(); // The below maps track metadata that is useful for consumers / verification. // track transactions that are *guaranteed* via CTV - let mut comitted_txns = BTreeMap::new(); + let mut committed_txn_map = BTreeMap::new(); // All other transactions - let mut other_txns = BTreeMap::new(); + let mut uncommitted_txn_map = BTreeMap::new(); // the min and max amount of funds spendable in the transactions - let mut amount_range = AmountRange::new(); + let mut required_amount_range = AmountRange::new(); // amount ensuring that the funds required don't get tweaked // during recompilation passes // TODO: Maybe do not just cloned? let amount_range_ctx = ctx.derive(PathFragment::Cloned)?; let ensured_amount = self.ensure_amount(amount_range_ctx)?; - amount_range.update_range(ensured_amount); + required_amount_range.update_range(ensured_amount); // The code for then_fns and finish_or_fns is very similar, differing // only in that then_fns have a CTV enforcing the contract and @@ -178,30 +190,37 @@ where // we need a unique context for each. let mut action_ctx = ctx.derive(PathFragment::Action)?; let mut renamer = Renamer::new(); - let all_values = self + let all_state_transition_functions = self .then_fns() .iter() - .filter_map(|func| func()) + .filter_map(|state_transition| state_transition()) // We currently need to allocate for the the Callable as a // trait object since it only exists temporarily. // TODO: Without allocations? .map(|x| -> Box> { Box::new(x) }) - .chain(self.finish_or_fns().iter().filter_map(|func| func())) - .map(|mut x| { - let new_name = Arc::new(renamer.get_name(x.get_name().as_ref())); - x.rename(new_name.clone()); + .chain( + self.finish_or_fns() + .iter() + .filter_map(|state_transition| state_transition()), + ); + let rename_state_transitions_uniquely = + all_state_transition_functions.map(|mut state_transition| { + let new_name = Arc::new(renamer.get_name(state_transition.get_name().as_ref())); + state_transition.rename(new_name.clone()); let name = PathFragment::Named(SArc(new_name)); - let f_ctx = action_ctx.derive(name).expect(UNIQUE_DERIVE_PANIC_MSG); - (f_ctx, x) - }) - // flat_map will discard any - // skippable / never branches here - .flat_map(|(mut f_ctx, func)| { - let mut this_ctx = f_ctx + let state_transition_context = + action_ctx.derive(name).expect(UNIQUE_DERIVE_PANIC_MSG); + (state_transition_context, state_transition) + }); + // flat_map will discard any + // skippable / never branches here + let filtered_conditional_compilation = rename_state_transitions_uniquely.flat_map( + |(mut state_transition_context, state_transition)| { + let mut this_ctx = state_transition_context // this should always be Ok(_) .derive(PathFragment::CondCompIf) .expect(UNIQUE_DERIVE_PANIC_MSG); - match CCILWrapper(func.get_conditional_compile_if()) + match CCILWrapper(state_transition.get_conditional_compile_if()) .assemble(self_ref, &mut this_ctx) { // Throw errors @@ -210,50 +229,80 @@ where } // Non nullable ConditionalCompileType::Required | ConditionalCompileType::NoConstraint => { - Some(Ok((f_ctx, func, Nullable::No))) + Some(Ok(( + state_transition_context, + state_transition, + Nullable::No, + ))) } // Nullable - ConditionalCompileType::Nullable => Some(Ok((f_ctx, func, Nullable::Yes))), + ConditionalCompileType::Nullable => Some(Ok(( + state_transition_context, + state_transition, + Nullable::Yes, + ))), // Drop these ConditionalCompileType::Skippable | ConditionalCompileType::Never => None, } - }) + }, + ); + let all_script_predicates = filtered_conditional_compilation .map(|r| { - let (mut f_ctx, func, nullability) = r?; - let gctx = f_ctx.derive(PathFragment::Guard)?; - let simp_ctx = f_ctx.derive(PathFragment::Metadata)?; + let (mut state_transition_context, state_transition, nullability_enabled) = r?; + let script_precondition_context = + state_transition_context.derive(PathFragment::Guard)?; + let interactive_metadata_context = + state_transition_context.derive(PathFragment::Metadata)?; // TODO: Suggested path frag? - let (guards, guard_metadata) = - create_guards(self_ref, gctx, func.get_guard(), &mut guard_clauses)?; - let effect_ctx = f_ctx.derive(if func.get_returned_txtmpls_modify_guards() { - PathFragment::Next - } else { - PathFragment::Suggested - })?; - let effect_path = effect_ctx.path().clone(); - let transactions = compute_all_effects(effect_ctx, self_ref, func.as_ref()); + let (script_precondition, script_precondition_metadata) = + get_script_preconditions_for( + self_ref, + script_precondition_context, + state_transition.get_guard(), + &mut script_precondition_compilation_cache, + )?; + // If the txtmpls that are returned from this state transition + // modify guards, then it is a CTV based state transition and it should be + // labelled as "Next" + let effect_ctx = state_transition_context.derive( + if state_transition.returned_transaction_templates_can_modify_parent_script() { + PathFragment::Next + } else { + PathFragment::Suggested + }, + )?; + let continuation_path = effect_ctx.path().clone(); + let transactions = create_state_transition_iterator( + effect_ctx, + self_ref, + state_transition.as_ref(), + )?; // If no guards and not CTV, then nothing gets added (not // interpreted as Trivial True) // - If CTV and no guards, just CTV added. // - If CTV and guards, CTV & guards added. // it would be an error if any of r_txtmpls is an error // instead of just an empty iterator. - let txtmpl_clauses = transactions? - .map(|r_txtmpl| { - let txtmpl = r_txtmpl?; - let h = txtmpl.hash(); - amount_range.update_range(txtmpl.max); + let transaction_script_preconditions = transactions + .map(|tx_template_or_error| { + let tx_template = tx_template_or_error?; + let tx_checktemplateverify_hash = tx_template.hash(); + required_amount_range.update_range(tx_template.max); // Add the addition guards to these clauses - let txtmpl = if func.get_returned_txtmpls_modify_guards() { - &mut comitted_txns + let stored_tx_template = if state_transition + .returned_transaction_templates_can_modify_parent_script() + { + &mut committed_txn_map } else { - &mut other_txns + &mut uncommitted_txn_map } - .entry(h) - .or_insert(txtmpl); + .entry(tx_checktemplateverify_hash) + .or_insert(tx_template); - let extractor = func.get_extract_clause_from_txtmpl(); - (extractor)(txtmpl, &ctx) + state_transition.extract_script_preconditions_from_transaction_template()( + stored_tx_template, + &ctx, + ) }) // Drop None values .filter_map(|s| s.transpose()) @@ -261,30 +310,43 @@ where .collect::, CompilationError>>()?; // N.B. the order of the matches below is significant - Ok(if func.get_returned_txtmpls_modify_guards() { - let r = ( - None, - combine_txtmpls(nullability, txtmpl_clauses, guards)?, - guard_metadata, - ); - r - } else { - let mut cp = - ContinuationPoint::at(func.get_schema().clone(), effect_path.clone()); - for simp in func.gen_simps(self_ref, simp_ctx)? { - cp = cp.add_simp(simp.as_ref())?; - } - let v = optimizer_flatten_and_compile(guards)?; - (Some((SArc(effect_path), cp)), v, guard_metadata) - }) + Ok( + if state_transition.returned_transaction_templates_can_modify_parent_script() { + let r = ( + None, + combine_txtmpls( + nullability_enabled, + transaction_script_preconditions, + script_precondition, + )?, + script_precondition_metadata, + ); + r + } else { + let simps = + state_transition.gen_simps(self_ref, interactive_metadata_context)?; + let continuation_point = simps.iter().try_fold( + ContinuationPoint::at( + state_transition.get_schema().clone(), + continuation_path.clone(), + ), + |c, simp| c.add_simp(simp.as_ref()), + )?; + let v = sapio_base::Clause::generate_leafs(script_precondition)?; + ( + Some((SArc(continuation_path), continuation_point)), + v, + script_precondition_metadata, + ) + }, + ) }) - .collect::>, _)>, CompilationError>>( - )?; + .collect::, _)>, CompilationError>>()?; let mut continue_apis = ContinueAPIs::default(); let mut clause_accumulator = vec![]; let mut all_guard_simps: BTreeMap = Default::default(); - for (v, b, c) in all_values { + for (v, b, c) in all_script_predicates { continue_apis.extend(std::iter::once(v)); clause_accumulator.push(b); for (pol, mut simps) in c { @@ -296,7 +358,7 @@ where guard_simps.dedup_by(|a, b| std::ptr::eq(a, b)) } - let branches: Vec> = { + let branches: Vec