Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions crates/sdk/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/// Dummy signature, which is used for fee estimation
/// A placeholder for a witness signature that the signer replaces, for example at the end of a
/// [`ProgramSignature::at_path`](crate::transaction::ProgramSignature::at_path) path.
pub const DUMMY_SIGNATURE: [u8; 64] = [1; 64];

/// Minimal acceptable fee for nodes to relay a transaction
/// The smallest policy-asset surplus `Signer::finalize_strict` accepts, and the smallest blinded
/// change output the signer creates. It isn't a minimum fee: the fee follows the fee rate and can
/// be smaller.
pub const MIN_FEE: u64 = 10;

/// The smallest explicit output Elements relays by default for any standard script.
Expand Down
16 changes: 9 additions & 7 deletions crates/sdk/src/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,28 @@ use crate::program::TrackerLogLevel;

static GLOBAL_CONFIG: OnceLock<GlobalConfig> = OnceLock::new();

/// A structure to represent the global configuration settings for the application.
/// The process-wide verbosity of program execution.
///
/// Verbosity `Debug` or `Trace` also compiles programs with debug symbols, which changes their
/// CMRs and addresses. An application that works with contracts deployed by others should leave
/// verbosity at `None`, or set [`Program::with_debug_symbols`](crate::program::Program::with_debug_symbols)
/// on each program.
#[derive(Clone, Copy, Debug, Default)]
pub struct GlobalConfig {
verbosity: Verbosity,
}

impl GlobalConfig {
/// Sets the global configuration for the SDK.
///
/// This function allows setting a global configuration which includes
/// the logging level for `simplicity` contracts execution.
/// It must be called exactly once during the application's lifetime.
/// Sets the process-wide verbosity. It can be set only once per process.
///
/// # Errors
/// Returns an error containing the newly created `GlobalConfig` if the global configuration has already been initialized.
pub fn set_global_config(verbosity: Verbosity) -> Result<(), GlobalConfig> {
GLOBAL_CONFIG.set(GlobalConfig { verbosity })
}

/// Returns the default log level if `GLOBAL_CONFIG` is not initialized
/// The tracker log level for the configured verbosity, or `TrackerLogLevel::None` if it was
/// never set.
pub fn get_log_level() -> TrackerLogLevel {
GLOBAL_CONFIG
.get()
Expand Down
117 changes: 80 additions & 37 deletions crates/sdk/src/program/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ use super::error::ProgramError;
use crate::provider::SimplicityNetwork;
use crate::utils::{check_budget, hash_script, tap_data_hash, tr_unspendable_key};

/// Executes `simplicity` programs at runtime.
/// What the [`Signer`](crate::signer::Signer) needs from a program to sign for and finalize a
/// program input.
///
/// This trait defines a core behavior related to testing and execution.
/// [`Program`] implements it, as does `&T` for any `T: ProgramTrait`.
/// [`ProgramInput`](crate::transaction::ProgramInput) holds a `Box<dyn ProgramTrait>`, which is
/// `Clone`.
pub trait ProgramTrait: DynClone + Send + Sync {
/// Retrieves the types of arguments required by a `simplicity` program.
///
Expand All @@ -36,25 +39,36 @@ pub trait ProgramTrait: DynClone + Send + Sync {
/// Returns a `ProgramError` if parsing or generating ABI metadata fails.
fn get_witness_types(&self) -> Result<WitnessTypes, ProgramError>;

/// Constructs the Elements environment for a specified input index, PST, and network for further program execution.
/// The Simplicity transaction environment for input `input_index` of `pst`: what the
/// program's transaction jets, such as `jet::sig_all_hash()`, read.
///
/// # Errors
/// Returns a `ProgramError` if the input index is out of bounds or if the script pubkey of the UTXO mismatches the expected program script.
/// Returns `ProgramError::MissingWitnessUtxo` if an input of `pst` doesn't carry the output it
/// spends, `ProgramError::UtxoIndexOutOfBounds` if `input_index` is past the inputs, and
/// `ProgramError::ScriptPubkeyMismatch` if the input doesn't spend this program's address.
fn get_env(
&self,
pst: &PartiallySignedTransaction,
input_index: usize,
network: &SimplicityNetwork,
) -> Result<ElementsEnv<Arc<Transaction>>, ProgramError>;

/// Executes a Simplicity program for the given input index of a partially signed transaction.
/// Runs the program locally on input `input_index` of `pst` with `witness`: satisfies it,
/// prunes it, and runs the pruned program on the Bit Machine. Returns the pruned program and
/// its output.
///
/// This function evaluates a Simplicity script associated with a specific transaction input
/// in a given network, producing the result of the computation along with the redeem node
/// used during execution.
/// Doesn't check the witness budget; [`ProgramTrait::finalize`] does. With [`Verbosity`](crate::global::Verbosity)
/// `Debug` or `Trace`, the execution output is buffered, not printed: call
/// [`ProgramLogger::flush_logs`] to print it.
///
/// # Errors
/// Returns a `ProgramError` if loading the program, satisfying the witness, retrieving the environment, or executing the `BitMachine` fails.
/// - `ProgramError::WitnessSatisfaction` if the witness doesn't fit the program's witness types.
/// - `ProgramError::Pruning` if the program fails. A failed `assert!` or jet is
/// `ExecutionError::JetFailed`. Reaching a pruned branch, for example with `unwrap(None)`, is
/// `ExecutionError::ReachedPrunedBranch`. [`Program`] reports a failure in the Bit Machine run
/// after pruning with this variant too.
/// - `ProgramError::BitMachineCreation` if the program exceeds the Bit Machine's limits.
/// - The errors of [`ProgramTrait::get_env`] and of compiling the program.
fn execute(
&self,
pst: &PartiallySignedTransaction,
Expand All @@ -63,14 +77,16 @@ pub trait ProgramTrait: DynClone + Send + Sync {
network: &SimplicityNetwork,
) -> Result<(Arc<RedeemNode>, Value), ProgramError>;

/// Finalizes and returns `pruned_witness` as output after executing the program on certain parameters.
/// Runs the program as [`ProgramTrait::execute`] does and returns the input's witness stack:
/// `[witness, program, cmr, control_block]`.
///
/// Implementations must compare the pruned program's static cost with the budget from the
/// exact serialized witness stack they return. Use [`check_budget`] for this check,
/// as the built-in `Program` does.
///
/// # Errors
/// Returns a `ProgramError` if execution, control block construction or budget validation fails.
/// Returns the errors of [`ProgramTrait::execute`], and `ProgramError::InsufficientBudget` if the
/// witness stack doesn't pay for the program's execution cost.
fn finalize(
&self,
pst: &PartiallySignedTransaction,
Expand All @@ -81,6 +97,9 @@ pub trait ProgramTrait: DynClone + Send + Sync {
}

/// Static description of a program's inputs and integer literals. Implemented by `include_simf!()`.
///
/// The fuzz engine uses it to generate arguments and witnesses for a program it knows only by
/// type.
pub trait ProgramSchema {
/// Generated arguments struct.
type Arguments;
Expand All @@ -94,22 +113,31 @@ pub trait ProgramSchema {
/// Witness names and types.
fn witness_types() -> Vec<(TemplateProgramWitness, ResolvedType)>;

/// Integer literals in the source.
/// The integer literals in the source, sorted and without duplicates.
fn literals() -> Vec<U256>;
}

/// A trait for creating instances of a program. The `ProgramFactory` trait defines a mechanism
/// for constructing and returning a program instance of a type that implements `AsRef<Program>`.
/// Only by generic struct name we have a possibility to create an instance of a program.
/// Creates a program of type `P` from arguments, for code that knows the program only by type,
/// such as the fuzz engine. Implemented by `include_simf!()`.
pub trait ProgramFactory<P: AsRef<Program> + Sized> {
/// Instantiates a program with the given arguments.
fn instantiate_program(args: impl Into<Arguments>) -> Box<P>;
}

/// Represents a program structure containing its public key, compiled program, and associated storage.
/// The compiled program with its CMR and the taproot spend info act as caches, filled on first use.
/// A `SimplicityHL` program with its arguments, its Taproot internal key, and its storage slots.
///
/// The program compiles on first use, and the result is cached and shared between clones. The
/// getters that need the compiled program panic if it doesn't compile. Call [`Program::load`]
/// to get the compile error instead.
///
/// The program's address is a Taproot output. Its script tree holds the program as one leaf and
/// one hidden leaf per storage slot, `tagged_hash("TapData", slot)`. The tree is left-folded:
/// `tapbranch(tapbranch(tapbranch(program, slot0), slot1), slot2)`. So the source, the arguments
/// and the debug-symbol setting determine the CMR, and the address also depends on the internal
/// key and the storage.
///
/// Abstraction giving the power to execute Simplicity contracts without specifying any additional parameters.
/// Code generated by `simplex build` wraps a `Program` in one type per contract, which reaches it
/// with `as_ref()`.
#[derive(Clone)]
pub struct Program {
source: Arc<str>,
Expand Down Expand Up @@ -287,10 +315,14 @@ impl Program {
/// The width of a storage slot.
pub const STORAGE_SLOT_BYTES: usize = 32;

/// Each slot deepens the taproot tree by one and the tree is at most 128 layers deep.
/// The most storage slots a program can have. Each slot deepens the Taproot tree by one, and
/// the tree is at most 128 layers deep.
pub const MAX_STORAGE_SLOTS: usize = 128;

/// Creates a new instance of the struct with the provided source string and arguments.
/// Creates a program from `SimplicityHL` source and its `param::` arguments, without compiling
/// it.
///
/// The Taproot internal key is [`tr_unspendable_key`], and there aren't any storage slots.
#[must_use]
pub fn new(source: impl Into<Arc<str>>, arguments: impl Into<Arguments>) -> Self {
Self {
Expand All @@ -304,8 +336,7 @@ impl Program {
}
}

/// Sets the `pub_key` field of the struct to the provided `XOnlyPublicKey` value and returns the updated builder instance.
/// This is used to set the taproot public key for the program.
/// Sets the Taproot internal key, which changes the address.
#[must_use]
pub fn with_taproot_pubkey(mut self, pub_key: XOnlyPublicKey) -> Self {
self.pub_key = pub_key;
Expand All @@ -314,7 +345,11 @@ impl Program {
self
}

/// Builds this program in the mode the protocol declares.
/// Compiles the program with or without debug symbols, overriding [`GlobalConfig`].
///
/// Debug symbols change the CMR and so the address. A program whose address must match one
/// computed elsewhere, such as a deployed contract, should set this explicitly, because
/// verbosity `Debug` or `Trace` turns debug symbols on by default.
#[must_use]
pub fn with_debug_symbols(mut self, include: bool) -> Self {
self.include_debug_symbols = Some(include);
Expand All @@ -325,7 +360,7 @@ impl Program {
self
}

/// Sets storage capacity for further usage.
/// Replaces the storage with `capacity` zeroed slots.
///
/// # Panics
/// Panics if `capacity` exceeds `MAX_STORAGE_SLOTS`.
Expand Down Expand Up @@ -383,19 +418,19 @@ impl Program {
&self.arguments
}

/// Returns the number of storage chunks for a program.
/// The number of storage slots.
#[must_use]
pub fn get_storage_len(&self) -> usize {
self.storage.len()
}

/// Returns storage as a whole array of 32-byte chunks.
/// The storage slots, each [`Program::STORAGE_SLOT_BYTES`] long.
#[must_use]
pub fn get_storage(&self) -> &[Vec<u8>] {
&self.storage
}

/// Returns storage value at a certain index.
/// The storage slot at `index`.
///
/// # Panics
/// Panics if the `index` is out of bounds for the initiated storage.
Expand All @@ -404,10 +439,10 @@ impl Program {
self.storage[index].clone()
}

/// Returns a taproot address for a defined `SimplicityNetwork`.
/// The program's unconfidential Taproot address on `network`.
///
/// # Panics
/// Panics if generating the taproot spending information fails.
/// Panics if the program doesn't compile.
#[must_use]
pub fn get_tr_address(&self, network: &SimplicityNetwork) -> Address {
let spend_info = self.taproot_spending_info().unwrap_or_else(|error| panic!("{error}"));
Expand All @@ -421,19 +456,27 @@ impl Program {
)
}

/// Retrieves the `ScriptPubKey` associated with the Simplicity address for the specified network.
/// The script of the program's address on `network`, which locks the outputs the program
/// controls.
///
/// # Panics
/// Panics if the program doesn't compile.
#[must_use]
pub fn get_script_pubkey(&self, network: &SimplicityNetwork) -> Script {
self.get_tr_address(network).script_pubkey()
}

/// Retrieves the 32-byte `ScriptPubKey` hash associated with the Simplicity address for the specified network.
/// The SHA-256 hash of [`Program::get_script_pubkey`], as Esplora's `scripthash` endpoints
/// and the `jet::output_script_hash` family use.
///
/// # Panics
/// Panics if the program doesn't compile.
#[must_use]
pub fn get_script_hash(&self, network: &SimplicityNetwork) -> [u8; 32] {
hash_script(&self.get_script_pubkey(network))
}

/// Compiles the program and returns its Commitment Merkle Root.
/// The program's Commitment Merkle Root.
///
/// # Panics
/// Panics if the `SimplicityHL` compilation fails.
Expand All @@ -442,7 +485,7 @@ impl Program {
self.cmr().unwrap_or_else(|error| panic!("{error}")).to_byte_array()
}

/// Returns the 32-byte tapleaf hash of the program's Simplicity script.
/// The tapleaf hash of the program's leaf. Storage doesn't change it.
///
/// # Panics
/// Panics if the `SimplicityHL` compilation fails.
Expand All @@ -453,7 +496,7 @@ impl Program {
taproot::TapLeafHash::from_script(&script, version).to_byte_array()
}

/// Retrieves program ABI metadata for argument types.
/// The names and types of the program's `param::` arguments.
///
/// # Errors
/// Returns a `ProgramError` if compilation fails or generating ABI metadata fails.
Expand All @@ -466,18 +509,18 @@ impl Program {
Ok(abi_meta.param_types)
}

/// Retrieves the witness types of the compiled program.
/// The names and types of the program's `witness::` fields.
///
/// # Errors
/// Returns a `ProgramError` if compilation fails.
pub fn get_witness_types(&self) -> Result<WitnessTypes, ProgramError> {
Ok(self.load()?.witness_types().shallow_clone())
}

/// Compiles program by providing saved `Arguments`.
/// Compiles the program, or returns the cached result of an earlier compilation.
///
/// # Errors
/// Retruns an error we have problems in a program compilation.
/// Returns `ProgramError::Compilation` if the source doesn't compile with these arguments.
pub fn load(&self) -> Result<&CompiledProgram, ProgramError> {
Ok(&self.load_with_cmr()?.0)
}
Expand Down
16 changes: 11 additions & 5 deletions crates/sdk/src/program/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,20 @@ pub enum ProgramError {
#[error("Failed to satisfy witness: {0}")]
WitnessSatisfaction(String),

/// Error indicating pruning the node tree during execution failed safely.
/// The program failed. `ExecutionError::JetFailed` is a failed `assert!` or jet, and
/// `ExecutionError::ReachedPrunedBranch` is, for example, `unwrap(None)`.
///
/// [`Program`](super::Program) reports both a failure during pruning and a failure in the Bit
/// Machine run after pruning with this variant.
#[error("Failed to prune program: {0}")]
Pruning(#[from] simplicityhl::simplicity::bit_machine::ExecutionError),

/// Error thrown when the bit machine cannot be initialized due to complexity or limit restrictions.
#[error("Failed to construct a Bit Machine with enough space: {0}")]
BitMachineCreation(#[from] simplicityhl::simplicity::bit_machine::LimitError),

/// Error thrown during bit machine execution due to underlying logical or environment validation errors.
/// The Bit Machine rejected the pruned program. [`Program`](super::Program) doesn't return
/// this variant; it reports such failures as `Pruning`.
#[error("Failed to execute program on the Bit Machine: {0}")]
Execution(simplicityhl::simplicity::bit_machine::ExecutionError),

Expand All @@ -59,12 +64,13 @@ pub enum ProgramError {
utxo_count: usize,
},

/// Error indicating the script pubkey present on the targeted UTXO differs from the expectation.
/// The input spends an output whose script isn't the program's.
#[error("Script pubkey mismatch: expected hash {expected_hash}, got {actual_hash}")]
ScriptPubkeyMismatch {
/// The expected CMR (Commitment Merkle Root) hash.
/// The HASH160 of the script of the program's address, not the SHA-256 that
/// [`Program::get_script_hash`](super::Program::get_script_hash) returns.
expected_hash: String,
/// The actual CMR hash present in the script pubkey.
/// The HASH160 of the script of the spent output.
actual_hash: String,
},

Expand Down
10 changes: 7 additions & 3 deletions crates/sdk/src/program/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ pub struct ProgramInfo {
pub trace_buffer: Vec<String>,
}

/// Logger state for buffering execution output.
/// Buffers program execution output, per input and per thread, until it is printed.
///
/// Buffers are flushed to stderr only on successful transaction finalization,
/// discarding output from intermediate estimation passes.
/// With [`Verbosity`](crate::global::Verbosity) `Debug` or `Trace`, every
/// [`ProgramTrait::execute`](super::ProgramTrait::execute) replaces its input's buffer, so only
/// the last run's output is kept. `Signer::finalize` and `estimate_fee` print the buffers after
/// their last signing pass, and `finalize_strict` does when it succeeds. Code that calls
/// `ProgramTrait::execute` or `Signer::sign_tx` directly must call [`ProgramLogger::flush_logs`]
/// to see the output. `Signer::sign_witnesses` doesn't run any program.
pub struct ProgramLogger {
/// A mapping of program ids to program logs
pub logs: HashMap<usize, ProgramInfo>,
Expand Down
2 changes: 1 addition & 1 deletion crates/sdk/src/program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pub mod core;
/// Error types and definitions for program compilation, manipulation, and execution failures.
pub mod error;
/// Program execution's specific logger
/// Buffering of the output of program execution.
pub mod logger;

pub use core::{Program, ProgramFactory, ProgramSchema, ProgramTrait};
Expand Down
Loading
Loading