diff --git a/Cargo.lock b/Cargo.lock index 03b67be5..fecd8e39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1063,6 +1063,8 @@ dependencies = [ "kaspa-consensus-core", "kaspa-txscript", "kaspa-txscript-errors", + "silverscript-abi", + "silverscript-debug-artifact", "silverscript-lang", ] @@ -1250,7 +1252,10 @@ dependencies = [ "kaspa-txscript-errors", "serde", "serde_json", + "silverscript-abi", + "silverscript-debug-artifact", "silverscript-lang", + "thiserror 1.0.69", ] [[package]] @@ -3703,6 +3708,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3815,6 +3830,29 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "silverscript-abi" +version = "0.1.0" +dependencies = [ + "blake3", + "faster-hex 0.10.0", + "kaspa-txscript", + "serde", + "serde_bytes", + "serde_json", + "silverscript-lang", + "thiserror 1.0.69", +] + +[[package]] +name = "silverscript-debug-artifact" +version = "0.1.0" +dependencies = [ + "serde", + "silverscript-abi", + "silverscript-lang", +] + [[package]] name = "silverscript-lang" version = "0.1.0" @@ -3842,6 +3880,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "silverscript-abi", "thiserror 1.0.69", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 224c9624..aaee24d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] members = [ "silverscript-lang", + "silverscript-abi", + "silverscript-debug-artifact", "debugger/session", "debugger/cli", ] @@ -13,7 +15,7 @@ edition = "2024" license = "ISC" authors = ["Kaspa developers"] repository = "" -rust-version = "1.85.0" +rust-version = "1.94.0" [workspace.dependencies] # Keep all rusty-kaspa crates on this shared revision because v2.0.1 predates @@ -29,6 +31,7 @@ kaspa-txscript-zk-sdk = { git = "https://github.com/kaspanet/rusty-kaspa", rev = kaspa-txscript-errors = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "a41a333b08848f41bf737b72592e463a6011b8ac" } blake2b_simd = "1.0.2" blake3 = "1.8.5" +faster-hex = "0.10" indexmap = "2.7.0" rand = "0.8.5" secp256k1 = { version = "0.29.0", features = [ @@ -36,4 +39,10 @@ secp256k1 = { version = "0.29.0", features = [ "rand-std", "serde", ] } +serde = { version = "1.0", features = ["derive"] } +serde_bytes = "0.11" +serde_json = "1.0" +silverscript-lang = { path = "silverscript-lang" } +silverscript-abi = { path = "silverscript-abi" } +silverscript-debug-artifact = { path = "silverscript-debug-artifact" } thiserror = "1.0.61" diff --git a/debugger/cli/Cargo.toml b/debugger/cli/Cargo.toml index 7c288061..ee46dd6d 100644 --- a/debugger/cli/Cargo.toml +++ b/debugger/cli/Cargo.toml @@ -13,7 +13,9 @@ path = "src/main.rs" [dependencies] debugger-session = { path = "../session" } -silverscript-lang = { path = "../../silverscript-lang" } +silverscript-abi.workspace = true +silverscript-debug-artifact.workspace = true +silverscript-lang.workspace = true kaspa-consensus-core.workspace = true kaspa-txscript.workspace = true kaspa-txscript-errors.workspace = true diff --git a/debugger/cli/src/main.rs b/debugger/cli/src/main.rs index a2137de2..d5e38cc7 100644 --- a/debugger/cli/src/main.rs +++ b/debugger/cli/src/main.rs @@ -1,10 +1,10 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fs; use std::io::{self, BufRead, Write}; use std::path::{Path, PathBuf}; use clap::Parser; -use debugger_session::args::{parse_call_args, parse_call_args_with_prefix, parse_ctor_args, parse_hex_bytes, parse_state_value}; +use debugger_session::args::{parse_artifact_args, parse_ctor_args, parse_hex_bytes, parse_state_value}; use debugger_session::covenant::{CovenantBinding as DebugCovenantBinding, ResolvedCovenantCallTarget, resolve_covenant_call_target}; use debugger_session::session::{DebugEngine, DebugSession, DebugValue, ShadowTxContext, Variable, VariableOrigin}; use debugger_session::test_runner::{ @@ -23,10 +23,10 @@ use kaspa_txscript::caches::Cache; use kaspa_txscript::covenants::CovenantsContext; use kaspa_txscript::script_builder::ScriptBuilder; use kaspa_txscript::{EngineCtx, EngineFlags, pay_to_script_hash_script}; -use silverscript_lang::ast::{ - ContractAst, Expr, ExprKind, STATE_TYPE_NAME, StateFieldExpr, TypeBase, TypeRef, parse_contract_ast, parse_type_ref, -}; -use silverscript_lang::compiler::{CompileOptions, CompiledContract, compile_contract, compile_contract_ast}; +use silverscript_abi::{ArtifactValue, SilContractArtifact, TypeArtifact, encode_contract_entry_sig_script}; +use silverscript_debug_artifact::{SilDebugArtifact, compile_contract as compile_debug_artifact}; +use silverscript_lang::ast::{ContractAst, Expr, ExprKind, STATE_TYPE_NAME, TypeBase, parse_contract_ast, parse_type_ref}; +use silverscript_lang::compiler::{CompileOptions, compile_contract_ast}; const PROMPT: &str = "(sdb) "; @@ -62,18 +62,21 @@ fn compile_bytecode_for_ctor_args( return Ok(bytecode.clone()); } let ctor_args = parse_ctor_args(parsed_contract, raw_ctor_args)?; - let compiled = compile_contract(source, &ctor_args, CompileOptions { record_debug_infos: true, ..Default::default() })?; - cache.insert(raw_ctor_args.to_vec(), compiled.bytecode.clone()); - Ok(compiled.bytecode) + let artifact_ctor_args = ctor_args.iter().map(expr_to_artifact_value).collect::, _>>()?; + let compiled = compile_debug_artifact(source, &artifact_ctor_args, CompileOptions::default())?; + let bytecode = compiled.contract(&parsed_contract.name).ok_or("compiled artifact has no contract")?.compiled.bytecode.clone(); + cache.insert(raw_ctor_args.to_vec(), bytecode.clone()); + Ok(bytecode) } fn compile_contract_for_raw_ctor_args<'i>( source: &'i str, parsed_contract: &ContractAst<'i>, raw_ctor_args: &[String], -) -> Result, Box> { +) -> Result, Box> { let ctor_args = parse_ctor_args(parsed_contract, raw_ctor_args)?; - Ok(compile_contract(source, &ctor_args, CompileOptions { record_debug_infos: true, ..Default::default() })?) + let artifact_ctor_args = ctor_args.iter().map(expr_to_artifact_value).collect::, _>>()?; + Ok(compile_debug_artifact(source, &artifact_ctor_args, CompileOptions::default())?) } fn expr_to_debug_value(expr: &Expr<'_>) -> Result { @@ -116,116 +119,116 @@ fn expr_to_debug_value(expr: &Expr<'_>) -> Result { } } -fn debug_value_to_expr(value: &DebugValue, struct_name: Option<&str>) -> Option> { - Some(match value { - DebugValue::Int(value) => Expr::int(*value), - DebugValue::Temporal(value) => Expr::temporal(*value), - DebugValue::Bool(value) => Expr::new(ExprKind::Bool(*value), Default::default()), - DebugValue::Bytes(bytes) => Expr::bytes(bytes.clone()), - DebugValue::String(value) => Expr::new(ExprKind::String(value.clone()), Default::default()), +fn expr_to_artifact_value(expr: &Expr<'_>) -> Result { + match &expr.kind { + ExprKind::Int(value) | ExprKind::Temporal(value) | ExprKind::DateLiteral(value) => Ok(ArtifactValue::Int(*value)), + ExprKind::Bool(value) => Ok(ArtifactValue::Bool(*value)), + ExprKind::Byte(value) => Ok(ArtifactValue::Byte(*value)), + ExprKind::String(value) => Ok(ArtifactValue::Text(value.clone())), + ExprKind::Array { type_ref, values } if matches!(type_ref.base, TypeBase::Byte) && type_ref.array_dims.len() == 1 => { + let bytes = values + .iter() + .map(|value| match value.kind { + ExprKind::Byte(byte) => Ok(byte), + _ => Err(format!("{} contains a non-byte value", type_ref.type_name())), + }) + .collect::, _>>()?; + Ok(ArtifactValue::Bytes(bytes)) + } + ExprKind::Array { values, .. } => { + Ok(ArtifactValue::Array(values.iter().map(expr_to_artifact_value).collect::, _>>()?)) + } + ExprKind::StructLiteral { fields, .. } => Ok(ArtifactValue::Object( + fields + .iter() + .map(|field| Ok((field.name.clone(), expr_to_artifact_value(&field.expr)?))) + .collect::, String>>()?, + )), + _ => Err("constructor argument is not a concrete artifact value".to_string()), + } +} + +fn debug_value_to_artifact_value(value: &DebugValue) -> Result { + match value { + DebugValue::Int(value) | DebugValue::Temporal(value) => Ok(ArtifactValue::Int(*value)), + DebugValue::Bool(value) => Ok(ArtifactValue::Bool(*value)), + DebugValue::Bytes(bytes) => Ok(ArtifactValue::Bytes(bytes.clone())), + DebugValue::String(value) => Ok(ArtifactValue::Text(value.clone())), DebugValue::Array(values) => { - Expr::inferred_array(values.iter().map(|value| debug_value_to_expr(value, struct_name)).collect::>>()?)? + Ok(ArtifactValue::Array(values.iter().map(debug_value_to_artifact_value).collect::, _>>()?)) } - DebugValue::Object(fields) => Expr::new( - ExprKind::StructLiteral { - name: struct_name?.to_string(), - fields: fields - .iter() - .map(|(name, value)| { - Some(StateFieldExpr { - name: name.clone(), - expr: debug_value_to_expr(value, None)?, - span: Default::default(), - name_span: Default::default(), - }) - }) - .collect::>>()?, - name_span: Default::default(), - }, - Default::default(), - ), - DebugValue::Unknown(_) => return None, - }) + DebugValue::Object(fields) => Ok(ArtifactValue::Object( + fields + .iter() + .map(|(name, value)| Ok((name.clone(), debug_value_to_artifact_value(value)?))) + .collect::>()?, + )), + DebugValue::Unknown(description) => Err(format!("cannot encode unknown debugger value '{description}'")), + } } -fn is_state_type(type_ref: &TypeRef) -> bool { - type_ref.is_custom() && matches!(&type_ref.base, TypeBase::Custom(name) if name == "State") +fn is_state_type(ty: &TypeArtifact) -> bool { + matches!(ty, TypeArtifact::Struct { name } if name == STATE_TYPE_NAME) } -fn is_state_array_type(type_ref: &TypeRef) -> bool { - matches!(&type_ref.base, TypeBase::Custom(name) if name == "State") - && type_ref.array_dims.len() == 1 - && type_ref.is_dynamic_array() +fn is_state_array_type(ty: &TypeArtifact) -> bool { + matches!(ty, TypeArtifact::DynamicArray { item } if is_state_type(item)) } fn synthesized_covenant_prefix_args( - compiled: &CompiledContract<'_>, + contract: &SilContractArtifact, entrypoint_name: &str, target: &ResolvedCovenantCallTarget, output_states: Option<&[DebugValue]>, -) -> Result>, Box> { +) -> Result, Box> { if target.binding == DebugCovenantBinding::Cov && entrypoint_name.starts_with("__delegate_") { return Ok(Vec::new()); } - let function = compiled - .ast - .functions - .iter() - .find(|function| function.name == entrypoint_name) - .ok_or("generated covenant entrypoint not found")?; - let Some(first_param) = function.params.first() else { + let entry = contract.entry(entrypoint_name).ok_or("generated covenant entrypoint not found")?; + let Some(first_param) = entry.params.first() else { return Ok(Vec::new()); }; let states = output_states.ok_or("missing output states needed to synthesize covenant verification arguments")?; - if is_state_type(&first_param.type_ref) { + if is_state_type(&first_param.ty) { if states.len() != 1 { return Err(format!("expected exactly 1 output State for '{entrypoint_name}', got {}", states.len()).into()); } - return Ok(vec![ - debug_value_to_expr(&states[0], Some(STATE_TYPE_NAME)).ok_or("failed to materialize synthesized output State")?, - ]); + return Ok(vec![debug_value_to_artifact_value(&states[0])?]); } - if is_state_array_type(&first_param.type_ref) { - return Ok(vec![Expr::array( - first_param.type_ref.clone(), - states - .iter() - .map(|state| debug_value_to_expr(state, Some(STATE_TYPE_NAME))) - .collect::>>() - .ok_or("failed to materialize synthesized output State[]")?, - )]); + if is_state_array_type(&first_param.ty) { + return Ok(vec![ArtifactValue::Array(states.iter().map(debug_value_to_artifact_value).collect::, _>>()?)]); } Ok(Vec::new()) } fn build_covenant_input_sigscript<'i>( - compiled: &CompiledContract<'i>, + compiled: &SilDebugArtifact<'i>, target: &ResolvedCovenantCallTarget, is_leader: bool, raw_args: &[String], output_states: Option<&[DebugValue]>, ) -> Result, Box> { + if compiled.abi.contracts.len() != 1 { + return Err("debugger requires an artifact containing exactly one contract".into()); + } + let (contract_name, contract) = compiled.abi.contracts.first_key_value().expect("contract count was checked"); let entrypoint_name = target.generated_entrypoint_name_for(is_leader); + let entry = contract.entry(&entrypoint_name).ok_or("generated covenant entrypoint not found")?; let typed_args = if target.binding == DebugCovenantBinding::Cov && !is_leader { - parse_call_args(&compiled.ast, &entrypoint_name, raw_args)? + parse_artifact_args(&compiled.abi, contract, &entry.params, raw_args)? } else { - let function = compiled - .ast - .functions - .iter() - .find(|function| function.name == entrypoint_name) - .ok_or("generated covenant entrypoint not found")?; - if raw_args.len() == function.params.len() { - parse_call_args(&compiled.ast, &entrypoint_name, raw_args)? + if raw_args.len() == entry.params.len() { + parse_artifact_args(&compiled.abi, contract, &entry.params, raw_args)? } else { - let prefix_args = synthesized_covenant_prefix_args(compiled, &entrypoint_name, target, output_states)?; - parse_call_args_with_prefix(&compiled.ast, &entrypoint_name, prefix_args, raw_args)? + let mut args = synthesized_covenant_prefix_args(contract, &entrypoint_name, target, output_states)?; + args.extend(parse_artifact_args(&compiled.abi, contract, &entry.params[args.len()..], raw_args)?); + args } }; - Ok(compiled.build_sig_script(&entrypoint_name, typed_args)?) + Ok(encode_contract_entry_sig_script(&compiled.abi, contract_name, &entrypoint_name, &typed_args)?) } fn resolve_state_for_ctor_args( @@ -271,29 +274,31 @@ fn materialize_bytecode_for_explicit_state( raw_state: &str, ) -> Result, Box> { let instance_args = parse_ctor_args(parsed_contract, raw_instance_args)?; + let artifact_instance_args = instance_args.iter().map(expr_to_artifact_value).collect::, _>>()?; let state = parse_state_value(parsed_contract, raw_state)?; let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; - let base_compiled = compile_contract(source, &instance_args, compile_opts)?; + let base_compiled = compile_debug_artifact(source, &artifact_instance_args, compile_opts)?; + let base_contract = base_compiled.contract(&parsed_contract.name).ok_or("compiled artifact has no contract")?; let materialized_contract = contract_with_explicit_state(parsed_contract, &state)?; let materialized = compile_contract_ast(&materialized_contract, &instance_args, compile_opts)?; - let base_start = base_compiled.state_layout.start; - let base_end = base_start + base_compiled.state_layout.len; + let base_start = base_contract.compiled.state_span.offset; + let base_end = base_start + base_contract.compiled.state_span.len; let materialized_start = materialized.state_layout.start; let materialized_end = materialized_start + materialized.state_layout.len; - if base_compiled.state_layout.len != materialized.state_layout.len { + if base_contract.compiled.state_span.len != materialized.state_layout.len { return Err("explicit state changes encoded bytecode size; provide raw script_hex instead".into()); } - if base_compiled.bytecode.len() < base_end || materialized.bytecode.len() < materialized_end { + if base_contract.compiled.bytecode.len() < base_end || materialized.bytecode.len() < materialized_end { return Err("state layout exceeds compiled bytecode length".into()); } - if base_compiled.bytecode[..base_start] != materialized.bytecode[..materialized_start] - || base_compiled.bytecode[base_end..] != materialized.bytecode[materialized_end..] + if base_contract.compiled.bytecode[..base_start] != materialized.bytecode[..materialized_start] + || base_contract.compiled.bytecode[base_end..] != materialized.bytecode[materialized_end..] { return Err("explicit state changed non-state bytecode; provide raw script_hex instead".into()); } - let mut bytecode = base_compiled.bytecode; + let mut bytecode = base_contract.compiled.bytecode.clone(); bytecode[base_start..base_end].copy_from_slice(&materialized.bytecode[materialized_start..materialized_end]); Ok(bytecode) } @@ -732,25 +737,26 @@ fn main() -> Result<(), Box> { } let ctor_args = parse_ctor_args(&parsed_contract, &raw_ctor_args)?; + let artifact_ctor_args = ctor_args.iter().map(expr_to_artifact_value).collect::, _>>()?; let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; - let compiled = compile_contract(&source, &ctor_args, compile_opts)?; - let debug_info = compiled.debug_info.clone(); + let compiled = compile_debug_artifact(&source, &artifact_ctor_args, compile_opts)?; + let contract_artifact = compiled.contract(&parsed_contract.name).ok_or("compiled artifact has no contract")?; let mut ctor_bytecode_cache = HashMap::, Vec>::new(); let mut ctor_state_cache = HashMap::, DebugValue>::new(); let mut explicit_state_cache = HashMap::::new(); - ctor_bytecode_cache.insert(raw_ctor_args.clone(), compiled.bytecode.clone()); + ctor_bytecode_cache.insert(raw_ctor_args.clone(), contract_artifact.compiled.bytecode.clone()); if !parsed_contract.fields.is_empty() { let root_state = resolve_state_for_ctor_args(&parsed_contract, &raw_ctor_args, &mut ctor_state_cache)?; ctor_state_cache.insert(raw_ctor_args.clone(), root_state); } let selected_name = if selected_name.is_empty() { - compiled.abi.first().map(|entry| entry.name.clone()).ok_or("contract has no functions")? + contract_artifact.entries.first_key_value().map(|(name, _)| name.clone()).ok_or("contract has no functions")? } else { selected_name }; - let covenant_target = resolve_covenant_call_target(&parsed_contract, &compiled, &selected_name); + let covenant_target = resolve_covenant_call_target(&parsed_contract, contract_artifact, &selected_name); let covenant_binding = covenant_target.as_ref().map(|target| target.binding); let enable_covenant_session_mode = covenant_target.is_some(); @@ -882,6 +888,7 @@ fn main() -> Result<(), Box> { }); let active_input_ctor_raw = tx.inputs[tx.active_input_index].constructor_args.clone().unwrap_or_else(|| raw_ctor_args.clone()); let active_compiled = compile_contract_for_raw_ctor_args(&source, &parsed_contract, &active_input_ctor_raw)?; + let active_contract = active_compiled.contract(&parsed_contract.name).ok_or("compiled artifact has no contract")?; let active_is_cov_leader = companion_leader_index.map(|index| index == tx.active_input_index).unwrap_or(true); let active_sigscript = if let Some(target) = covenant_target.as_ref() { match target.binding { @@ -897,8 +904,9 @@ fn main() -> Result<(), Box> { )?, } } else { - let typed_args = parse_call_args(&active_compiled.ast, &selected_name, &raw_args)?; - active_compiled.build_sig_script(&selected_name, typed_args)? + let entry = active_contract.entry(&selected_name).ok_or_else(|| format!("entry '{selected_name}' not found"))?; + let typed_args = parse_artifact_args(&active_compiled.abi, active_contract, &entry.params, &raw_args)?; + encode_contract_entry_sig_script(&active_compiled.abi, &parsed_contract.name, &selected_name, &typed_args)? }; let mut tx_inputs = Vec::with_capacity(tx.inputs.len()); @@ -943,8 +951,11 @@ fn main() -> Result<(), Box> { let active_utxo = populated_tx.utxo(tx.active_input_index).ok_or_else(|| format!("missing utxo entry for input {}", tx.active_input_index))?; let active_covenant_input_state = input_covenant_states.get(tx.active_input_index).cloned().flatten(); - let active_lockscript = - input_redeem_scripts.get(tx.active_input_index).cloned().flatten().unwrap_or_else(|| compiled.bytecode.clone()); + let active_lockscript = input_redeem_scripts + .get(tx.active_input_index) + .cloned() + .flatten() + .unwrap_or_else(|| active_contract.compiled.bytecode.clone()); let covenant_input_states = active_utxo.covenant_id.and_then(|covenant_id| { let mut values = Vec::new(); for (input_covenant_id, covenant_input_state) in input_covenant_ids.iter().zip(input_covenant_states.iter()) { @@ -968,7 +979,8 @@ fn main() -> Result<(), Box> { utxo_entry: active_utxo, covenants_ctx: &cov_ctx, }; - let mut session = DebugSession::full(&active_sigscript, &active_lockscript, &source, debug_info, engine)? + let active_debug_info = active_compiled.debug_info(&parsed_contract.name).cloned(); + let mut session = DebugSession::full(&active_sigscript, &active_lockscript, &source, active_debug_info, engine)? .with_shadow_tx_context(shadow_tx_context); if enable_covenant_session_mode { session = session.with_covenant_mode(covenant_param_value, covenant_target); @@ -997,7 +1009,7 @@ fn main() -> Result<(), Box> { } } } else { - println!("Stepping through {} bytes of bytecode", compiled.bytecode.len()); + println!("Stepping through {} bytes of bytecode", contract_artifact.compiled.bytecode.len()); session.run_to_first_executed_statement()?; let mut pending_console_output = session.take_console_output(); let console_output = Vec::new(); @@ -1010,16 +1022,17 @@ fn main() -> Result<(), Box> { #[cfg(test)] mod tests { use super::*; - use silverscript_lang::ast::ArrayDim; #[test] fn state_array_type_requires_one_dynamic_dimension() { - let state = || TypeBase::Custom("State".to_string()); - - assert!(is_state_array_type(&TypeRef { base: state(), array_dims: vec![ArrayDim::Dynamic] })); - assert!(!is_state_array_type(&TypeRef { base: state(), array_dims: Vec::new() })); - assert!(!is_state_array_type(&TypeRef { base: state(), array_dims: vec![ArrayDim::Fixed(2)] })); - assert!(!is_state_array_type(&TypeRef { base: state(), array_dims: vec![ArrayDim::Dynamic, ArrayDim::Dynamic] })); + let state = || TypeArtifact::Struct { name: STATE_TYPE_NAME.to_string() }; + + assert!(is_state_array_type(&TypeArtifact::DynamicArray { item: Box::new(state()) })); + assert!(!is_state_array_type(&state())); + assert!(!is_state_array_type(&TypeArtifact::FixedArray { item: Box::new(state()), len: 2 })); + assert!(!is_state_array_type(&TypeArtifact::DynamicArray { + item: Box::new(TypeArtifact::DynamicArray { item: Box::new(state()) }), + })); } #[test] diff --git a/debugger/session/Cargo.toml b/debugger/session/Cargo.toml index 76fa8405..f668d0dc 100644 --- a/debugger/session/Cargo.toml +++ b/debugger/session/Cargo.toml @@ -12,13 +12,16 @@ name = "debugger_session" path = "src/lib.rs" [dependencies] -silverscript-lang = { path = "../../silverscript-lang" } +silverscript-abi.workspace = true +silverscript-debug-artifact.workspace = true +silverscript-lang.workspace = true kaspa-consensus-core.workspace = true kaspa-txscript.workspace = true kaspa-txscript-errors.workspace = true serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" faster-hex = "0.10" +thiserror.workspace = true [dev-dependencies] kaspa-addresses.workspace = true diff --git a/debugger/session/src/args.rs b/debugger/session/src/args.rs index 9b6ed275..2a5f7a6d 100644 --- a/debugger/session/src/args.rs +++ b/debugger/session/src/args.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use serde_json::Value; +use silverscript_abi::{ArtifactValue, ParamArtifact, SilAbiArtifact, SilContractArtifact, TypeArtifact}; use silverscript_lang::ast::{ArrayDim, ContractAst, Expr, ExprKind, ParamAst, StateFieldExpr, TypeBase, TypeRef}; use silverscript_lang::span; @@ -18,7 +19,7 @@ pub fn parse_hex_bytes(raw: &str) -> Result, String> { if hex_str.is_empty() { return Ok(vec![]); } - let normalized = if hex_str.len() % 2 != 0 { format!("0{hex_str}") } else { hex_str.to_string() }; + let normalized = if !hex_str.len().is_multiple_of(2) { format!("0{hex_str}") } else { hex_str.to_string() }; if !normalized.chars().all(|ch| ch.is_ascii_hexdigit()) { return Err(format!("invalid hex bytes '{raw}'")); } @@ -271,6 +272,170 @@ pub fn parse_ctor_args(parsed_contract: &ContractAst<'_>, raw_ctor_args: &[Strin parse_params(&parsed_contract.params, &shapes, raw_ctor_args) } +fn artifact_struct_fields<'a>( + abi: &'a SilAbiArtifact, + contract: &'a SilContractArtifact, + name: &str, +) -> Option> { + if contract.runtime_state.source == name { + return Some(contract.runtime_state.fields.iter().map(|field| (field.name.as_str(), &field.ty)).collect()); + } + abi.structs.get(name).map(|state| state.fields.iter().map(|field| (field.name.as_str(), &field.ty)).collect()) +} + +fn parse_artifact_json_value( + value: &Value, + ty: &TypeArtifact, + abi: &SilAbiArtifact, + contract: &SilContractArtifact, +) -> Result { + match ty { + TypeArtifact::Int | TypeArtifact::Temporal => { + let Value::Number(value) = value else { + return Err(format!("expected {}, got {value}", artifact_type_name(ty))); + }; + Ok(ArtifactValue::Int(value.as_i64().ok_or_else(|| "invalid int value".to_string())?)) + } + TypeArtifact::Bool => match value { + Value::Bool(value) => Ok(ArtifactValue::Bool(*value)), + Value::String(value) if value == "true" || value == "false" => Ok(ArtifactValue::Bool(value == "true")), + _ => Err(format!("expected bool, got {value}")), + }, + TypeArtifact::Byte => { + let Value::Number(value) = value else { + return Err(format!("expected byte, got {value}")); + }; + let value = value.as_u64().ok_or_else(|| "invalid byte value".to_string())?; + u8::try_from(value).map(ArtifactValue::Byte).map_err(|_| format!("byte expects value in 0..=255, got {value}")) + } + TypeArtifact::Bytes | TypeArtifact::FixedBytes { .. } | TypeArtifact::Pubkey | TypeArtifact::Sig | TypeArtifact::Datasig => { + let Value::String(raw) = value else { + return Err(format!("expected {}, got {value}", artifact_type_name(ty))); + }; + let bytes = parse_hex_bytes(raw)?; + if let Some(expected) = artifact_byte_len(ty) + && bytes.len() != expected + { + return Err(format!("{} expects {expected} bytes, got {}", artifact_type_name(ty), bytes.len())); + } + Ok(ArtifactValue::Bytes(bytes)) + } + TypeArtifact::Text => match value { + Value::String(value) => Ok(ArtifactValue::Text(value.clone())), + _ => Err(format!("expected string, got {value}")), + }, + TypeArtifact::FixedArray { item, len } => { + let Value::Array(values) = value else { + return Err(format!("expected {}, got {value}", artifact_type_name(ty))); + }; + if values.len() != *len { + return Err(format!("{} expects {len} elements, got {}", artifact_type_name(ty), values.len())); + } + Ok(ArtifactValue::Array( + values.iter().map(|value| parse_artifact_json_value(value, item, abi, contract)).collect::, _>>()?, + )) + } + TypeArtifact::DynamicArray { item } => { + let Value::Array(values) = value else { + return Err(format!("expected {}, got {value}", artifact_type_name(ty))); + }; + Ok(ArtifactValue::Array( + values.iter().map(|value| parse_artifact_json_value(value, item, abi, contract)).collect::, _>>()?, + )) + } + TypeArtifact::Struct { name } => { + let Value::Object(values) = value else { + return Err(format!("expected struct {name}, got {value}")); + }; + let fields = artifact_struct_fields(abi, contract, name).ok_or_else(|| format!("unknown struct '{name}'"))?; + if let Some(extra) = values.keys().find(|field| !fields.iter().any(|(name, _)| name == &field.as_str())) { + return Err(format!("unknown struct field '{extra}'")); + } + Ok(ArtifactValue::Object( + fields + .into_iter() + .map(|(name, ty)| { + let value = values.get(name).ok_or_else(|| format!("struct field '{name}' must be initialized"))?; + Ok((name.to_string(), parse_artifact_json_value(value, ty, abi, contract)?)) + }) + .collect::>()?, + )) + } + } +} + +fn parse_raw_artifact_arg( + raw: &str, + ty: &TypeArtifact, + abi: &SilAbiArtifact, + contract: &SilContractArtifact, +) -> Result { + match ty { + TypeArtifact::Int | TypeArtifact::Temporal => parse_int_arg(raw).map(ArtifactValue::Int), + TypeArtifact::Byte => { + let bytes = parse_hex_bytes(raw)?; + if let [byte] = bytes.as_slice() { + Ok(ArtifactValue::Byte(*byte)) + } else { + Err(format!("byte expects 1 byte, got {}", bytes.len())) + } + } + _ => parse_artifact_json_value(&Value::String(raw.to_string()), ty, abi, contract), + } +} + +fn artifact_byte_len(ty: &TypeArtifact) -> Option { + match ty { + TypeArtifact::FixedBytes { len } => Some(*len), + TypeArtifact::Pubkey => Some(32), + TypeArtifact::Sig => Some(65), + TypeArtifact::Datasig => Some(64), + _ => None, + } +} + +fn artifact_type_name(ty: &TypeArtifact) -> String { + match ty { + TypeArtifact::Int => "int".to_string(), + TypeArtifact::Temporal => "temporal".to_string(), + TypeArtifact::Bool => "bool".to_string(), + TypeArtifact::Byte => "byte".to_string(), + TypeArtifact::Bytes => "byte[]".to_string(), + TypeArtifact::Text => "string".to_string(), + TypeArtifact::Pubkey => "pubkey".to_string(), + TypeArtifact::Sig => "sig".to_string(), + TypeArtifact::Datasig => "datasig".to_string(), + TypeArtifact::FixedBytes { len } => format!("byte[{len}]"), + TypeArtifact::FixedArray { item, len } => format!("{}[{len}]", artifact_type_name(item)), + TypeArtifact::DynamicArray { item } => format!("{}[]", artifact_type_name(item)), + TypeArtifact::Struct { name } => name.clone(), + } +} + +pub fn parse_artifact_args( + abi: &SilAbiArtifact, + contract: &SilContractArtifact, + params: &[ParamArtifact], + raw_args: &[String], +) -> Result, String> { + if params.len() != raw_args.len() { + return Err(format!("function expects {} arguments, got {}", params.len(), raw_args.len())); + } + params + .iter() + .zip(raw_args) + .map(|(param, raw)| { + if raw.starts_with('[') || raw.starts_with('{') { + let value = serde_json::from_str(raw) + .map_err(|err| format!("invalid {} arg '{raw}': {err}", artifact_type_name(¶m.ty)))?; + parse_artifact_json_value(&value, ¶m.ty, abi, contract) + } else { + parse_raw_artifact_arg(raw, ¶m.ty, abi, contract) + } + }) + .collect() +} + pub fn parse_call_args(contract: &ContractAst<'_>, function_name: &str, raw_args: &[String]) -> Result>, String> { let function = contract .functions @@ -323,8 +488,10 @@ pub fn parse_state_value(contract: &ContractAst<'_>, raw_state: &str) -> Result< #[cfg(test)] mod tests { - use super::{parse_call_args, parse_ctor_args, parse_state_value}; + use super::{parse_artifact_args, parse_call_args, parse_ctor_args, parse_state_value}; + use silverscript_abi::ArtifactValue; use silverscript_lang::ast::{ExprKind, parse_contract_ast}; + use silverscript_lang::compiler::compile_to_sil_abi_artifact; fn debug_shapes_contract() -> silverscript_lang::ast::ContractAst<'static> { parse_contract_ast( @@ -466,4 +633,48 @@ mod tests { assert!(fields.iter().any(|field| field.name == "active")); assert!(fields.iter().any(|field| field.name == "tag")); } + + #[test] + fn parses_json_byte_numbers_as_decimal_and_rejects_strings() { + let source = r#" + contract Demo() { + struct S { + byte marker; + } + + entry main(S value) { + require(true); + } + + entry scalar(byte marker) { + require(true); + } + } + "#; + let abi = compile_to_sil_abi_artifact(source, &[]).expect("contract compiles"); + let contract = abi.contract("Demo").expect("contract exists"); + let params = &contract.entry("main").expect("entry exists").params; + + let parse_marker = |raw: &str| -> Result { + let values = parse_artifact_args(&abi, contract, params, &[raw.to_string()]).expect("structured byte argument parses"); + let ArtifactValue::Object(fields) = &values[0] else { + panic!("expected struct argument"); + }; + Ok(fields.get("marker").cloned().expect("marker field exists")) + }; + + assert_eq!(parse_marker(r#"{"marker":10}"#).unwrap(), ArtifactValue::Byte(10)); + assert_eq!(parse_marker(r#"{"marker":255}"#).unwrap(), ArtifactValue::Byte(255)); + assert!( + parse_artifact_args(&abi, contract, params, &[r#"{"marker":"10"}"#.to_string()]) + .expect_err("JSON byte strings are not numeric values") + .contains("expected byte") + ); + + let scalar_params = &contract.entry("scalar").expect("scalar entry exists").params; + assert_eq!( + parse_artifact_args(&abi, contract, scalar_params, &["10".to_string()]).expect("raw byte argument parses"), + vec![ArtifactValue::Byte(0x10)] + ); + } } diff --git a/debugger/session/src/covenant.rs b/debugger/session/src/covenant.rs index 5f38d353..6704e594 100644 --- a/debugger/session/src/covenant.rs +++ b/debugger/session/src/covenant.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; +use silverscript_abi::SilContractArtifact; use silverscript_lang::ast::{ContractAst, FunctionAst}; -use silverscript_lang::compiler::CompiledContract; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CovenantBinding { @@ -50,10 +50,10 @@ impl ResolvedCovenantCallTarget { } pub fn display_name_for(&self, function_name: &str) -> Option<&str> { - if let Some(body) = &self.delegate_body { - if body.policy_function_name == function_name { - return Some(body.source_name.as_str()); - } + if let Some(body) = &self.delegate_body + && body.policy_function_name == function_name + { + return Some(body.source_name.as_str()); } (self.policy_function_name == function_name || self.matches_generated_name(function_name)).then_some(self.source_name.as_str()) } @@ -74,14 +74,14 @@ impl ResolvedCovenantCallTarget { pub fn resolve_covenant_call_target<'i>( contract: &ContractAst<'i>, - compiled: &CompiledContract<'i>, + artifact: &SilContractArtifact, function_name: &str, ) -> Option { let function = contract.functions.iter().find(|function| function.name == function_name && is_covenant_source_function(function))?; - let generated_entrypoint_name = compiled.covenant_decl_entrypoint_name(function_name, true)?.to_string(); - let nonleader_entrypoint_name = compiled.covenant_decl_entrypoint_name(function_name, false)?.to_string(); + let generated_entrypoint_name = artifact.cov_decl_to_abi.get(function_name)?.clone(); + let nonleader_entrypoint_name = artifact.delegate_entry_abi.clone().unwrap_or_else(|| generated_entrypoint_name.clone()); let binding = if generated_entrypoint_name == nonleader_entrypoint_name { CovenantBinding::Auth } else { CovenantBinding::Cov }; let delegate_entrypoint_name = (binding == CovenantBinding::Cov).then_some(nonleader_entrypoint_name); let delegate_body = (binding == CovenantBinding::Cov) diff --git a/debugger/session/src/presentation.rs b/debugger/session/src/presentation.rs index ebe23a5e..5d4c0a92 100644 --- a/debugger/session/src/presentation.rs +++ b/debugger/session/src/presentation.rs @@ -137,10 +137,10 @@ pub fn format_failure_report(report: &FailureReport, format_var: &dyn Fn(&str, & out.push_str(&format!("{pad} |\n")); - if line_idx > 0 { - if let Some(prev) = source_lines.get(line_idx - 1) { - out.push_str(&format!("{:>w$} | {prev}\n", span.line - 1)); - } + if line_idx > 0 + && let Some(prev) = source_lines.get(line_idx - 1) + { + out.push_str(&format!("{:>w$} | {prev}\n", span.line - 1)); } if let Some(line_text) = source_lines.get(line_idx) { diff --git a/debugger/session/src/session.rs b/debugger/session/src/session.rs index c91034ea..5337fdbe 100644 --- a/debugger/session/src/session.rs +++ b/debugger/session/src/session.rs @@ -7,6 +7,8 @@ use kaspa_txscript::covenants::CovenantsContext; use kaspa_txscript::script_builder::ScriptBuilder; use kaspa_txscript::{DynOpcodeImplementation, EngineCtx, EngineFlags, TxScriptEngine, parse_script}; use serde::{Deserialize, Serialize}; +use silverscript_debug_artifact::SilDebugArtifact; +use thiserror::Error; use silverscript_lang::ast::{ ContractAst, Expr, ExprKind, StateFieldExpr, TypeBase, TypeRef, UnarySuffixKind, parse_contract_ast, parse_expression_ast, @@ -29,6 +31,16 @@ pub type DebugReused = SigHashReusedValuesUnsync; pub type DebugOpcode<'a> = DynOpcodeImplementation, DebugReused>; pub type DebugEngine<'a> = TxScriptEngine<'a, DebugTx<'a>, DebugReused>; +#[derive(Debug, Error)] +pub enum DebugArtifactSessionError { + #[error("debug artifact has no contract '{0}'")] + MissingContract(String), + #[error("debug artifact has no debug information for contract '{0}'")] + MissingDebugInfo(String), + #[error(transparent)] + Script(#[from] kaspa_txscript_errors::TxScriptError), +} + #[derive(Clone, Copy)] pub struct ShadowTxContext<'a> { pub tx: &'a DebugTx<'a>, @@ -197,6 +209,21 @@ type ShadowResolution<'i> = (ShadowBindings, EvalEnv<'i>, StackBindings, EvalTyp impl<'a, 'i> DebugSession<'a, 'i> { // --- Session construction + stepping --- + /// Creates a debug session from one contract in a debug artifact. + pub fn from_artifact( + sigscript: &[u8], + artifact: &'i SilDebugArtifact<'i>, + contract_name: &str, + engine: DebugEngine<'a>, + ) -> Result { + let contract = + artifact.contract(contract_name).ok_or_else(|| DebugArtifactSessionError::MissingContract(contract_name.to_string()))?; + let debug_info = artifact + .debug_info(contract_name) + .ok_or_else(|| DebugArtifactSessionError::MissingDebugInfo(contract_name.to_string()))?; + Ok(Self::full(sigscript, &contract.compiled.bytecode, &debug_info.source, Some(debug_info.clone()), engine)?) + } + /// Creates a debug session simulating a full transaction spend. /// Executes sigscript first to seed the stack, then debugs the compiled bytecode. pub fn full( @@ -412,11 +439,11 @@ impl<'a, 'i> DebugSession<'a, 'i> { return Ok(None); } let offset = self.current_byte_offset(); - if self.engine.is_executing() { - if let Some(index) = self.initial_step_index_for_offset(offset, None) { - self.mark_step_executed(index); - return Ok(Some(self.state())); - } + if self.engine.is_executing() + && let Some(index) = self.initial_step_index_for_offset(offset, None) + { + self.mark_step_executed(index); + return Ok(Some(self.state())); } if self.step_opcode()?.is_none() { return Ok(None); @@ -434,10 +461,10 @@ impl<'a, 'i> DebugSession<'a, 'i> { if self.step_into()?.is_none() { return Ok(None); } - if let Some(step) = self.current_timeline_step() { - if self.step_hits_breakpoint(step) { - return Ok(Some(self.state())); - } + if let Some(step) = self.current_timeline_step() + && self.step_hits_breakpoint(step) + { + return Ok(Some(self.state())); } } } @@ -1309,18 +1336,16 @@ impl<'a, 'i> DebugSession<'a, 'i> { } fn steppable_step_index_for_offset(&self, offset: usize, min_sequence: Option) -> Option { - if let Some(index) = self.current_step_index { - if let Some(step) = self.step_at_order(index) { - if !self.is_post_inline_call_source(step) { - if let Some(boundary_index) = self.find_steppable_step_index(|candidate| { - candidate.bytecode_start == offset - && step.bytecode_end == offset - && min_sequence.is_none_or(|min_sequence| candidate.sequence >= min_sequence) - }) { - return Some(boundary_index); - } - } - } + if let Some(index) = self.current_step_index + && let Some(step) = self.step_at_order(index) + && !self.is_post_inline_call_source(step) + && let Some(boundary_index) = self.find_steppable_step_index(|candidate| { + candidate.bytecode_start == offset + && step.bytecode_end == offset + && min_sequence.is_none_or(|min_sequence| candidate.sequence >= min_sequence) + }) + { + return Some(boundary_index); } self.find_steppable_step_index(|step| { @@ -1378,19 +1403,19 @@ impl<'a, 'i> DebugSession<'a, 'i> { fn next_steppable_step_index(&self, from: Option, predicate: impl Fn(&DebugStep<'i>) -> bool) -> Option { let start = from.map(|index| index.saturating_add(1)).unwrap_or(0); let min_sequence = from.and_then(|index| self.step_at_order(index).map(|step| step.sequence)); - if let Some(index) = from { - if let Some(step) = self.step_at_order(index) { - if matches!(step.kind, StepKind::InlineCallEnter { .. }) { - if let Some(index) = self.find_post_inline_source_after(step, min_sequence, true, &predicate) { - return Some(index); - } - } + if let Some(index) = from + && let Some(step) = self.step_at_order(index) + { + if matches!(step.kind, StepKind::InlineCallEnter { .. }) + && let Some(index) = self.find_post_inline_source_after(step, min_sequence, true, &predicate) + { + return Some(index); + } - if matches!(step.kind, StepKind::InlineCallEnter { .. }) || self.is_post_inline_call_source(step) { - if let Some(index) = self.find_post_inline_source_after(step, min_sequence, false, &predicate) { - return Some(index); - } - } + if (matches!(step.kind, StepKind::InlineCallEnter { .. }) || self.is_post_inline_call_source(step)) + && let Some(index) = self.find_post_inline_source_after(step, min_sequence, false, &predicate) + { + return Some(index); } } for index in start..self.step_order.len() { @@ -1873,10 +1898,10 @@ impl<'a, 'i> DebugSession<'a, 'i> { /// Decodes raw bytes into a typed debug value based on the type name. fn decode_value_by_type(type_name: &str, bytes: Vec) -> Result { - if let Some(element_type) = type_name.strip_suffix("[]") { - if let Some(element_size) = fixed_array_element_size(element_type) { - return decode_known_width_array(type_name, bytes, element_type, element_size); - } + if let Some(element_type) = type_name.strip_suffix("[]") + && let Some(element_size) = fixed_array_element_size(element_type) + { + return decode_known_width_array(type_name, bytes, element_type, element_size); } match type_name { @@ -1895,7 +1920,7 @@ fn decode_known_width_array(type_name: &str, bytes: Vec, element_type: &str, if element_size == 0 { return Err(format!("array element type '{type_name}' has zero width")); } - if bytes.len() % element_size != 0 { + if !bytes.len().is_multiple_of(element_size) { return Err(format!("encoded value for '{type_name}' has invalid length {}", bytes.len())); } diff --git a/debugger/session/tests/debug_session_tests.rs b/debugger/session/tests/debug_session_tests.rs index febb15c6..9a3c42bf 100644 --- a/debugger/session/tests/debug_session_tests.rs +++ b/debugger/session/tests/debug_session_tests.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::error::Error; use kaspa_consensus_core::Hash; @@ -19,10 +19,38 @@ use debugger_session::{ format_value, session::{DebugSession, DebugValue, ShadowTxContext}, }; -use silverscript_lang::ast::{Expr, parse_contract_ast, parse_type_ref}; -use silverscript_lang::compiler::{CompileOptions, compile_contract, struct_object}; +use silverscript_abi::{ArtifactValue, SilContractArtifact, encode_contract_entry_sig_script}; +use silverscript_debug_artifact::{SilDebugArtifact, compile_contract}; +use silverscript_lang::ast::parse_contract_ast; +use silverscript_lang::compiler::CompileOptions; use silverscript_lang::debug_info::StepKind; +fn single_contract<'a>(artifact: &'a SilDebugArtifact<'_>) -> &'a SilContractArtifact { + let Some(contract) = artifact.abi.contracts.values().next().filter(|_| artifact.abi.contracts.len() == 1) else { + panic!("expected exactly one contract"); + }; + contract +} + +fn bytecode<'a>(artifact: &'a SilDebugArtifact<'_>) -> &'a [u8] { + &single_contract(artifact).compiled.bytecode +} + +fn encode_entry_sig_script( + artifact: &SilDebugArtifact<'_>, + entry_name: &str, + args: &[ArtifactValue], +) -> Result, silverscript_abi::CodecError> { + let Some((contract_name, _)) = artifact.abi.contracts.first_key_value().filter(|_| artifact.abi.contracts.len() == 1) else { + panic!("expected exactly one contract"); + }; + encode_contract_entry_sig_script(&artifact.abi, contract_name, entry_name, args) +} + +fn artifact_object(fields: impl IntoIterator) -> ArtifactValue { + fields.into_iter().map(|(name, value)| (name.to_string(), value)).collect::>().into() +} + const IF_STATEMENT_CONTRACT: &str = r#"pragma silverscript ^0.1.0; contract IfStatement(int x, int y) { @@ -47,21 +75,15 @@ fn with_session(mut f: F) -> Result<(), Box> where F: FnMut(&mut DebugSession<'_, '_>) -> Result<(), Box>, { - with_session_for_source( - IF_STATEMENT_CONTRACT, - vec![Expr::int(3), Expr::int(10)], - "hello", - vec![Expr::int(5), Expr::int(5)], - &mut f, - ) + with_session_for_source(IF_STATEMENT_CONTRACT, vec![3.into(), 10.into()], "hello", vec![5.into(), 5.into()], &mut f) } // Generic harness that compiles a contract and boots a debugger session for a selected function call. fn with_session_for_source( source: &str, - ctor_args: Vec>, + ctor_args: Vec, function_name: &str, - function_args: Vec>, + function_args: Vec, mut f: F, ) -> Result<(), Box> where @@ -73,7 +95,6 @@ where // Compile with debug metadata enabled so line steps and variable updates are available. let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; let compiled = compile_contract(source, &ctor_args, compile_opts)?; - let debug_info = compiled.debug_info.clone(); let sig_cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); @@ -82,13 +103,16 @@ where let flags = EngineFlags { covenants_enabled: true, ..Default::default() }; let engine = debugger_session::session::DebugEngine::new(ctx, flags); - let entry = compiled.entry_by_name(function_name).ok_or_else(|| format!("function '{function_name}' not found"))?; - - assert_eq!(entry.inputs.len(), function_args.len()); + let entry = parsed_contract + .functions + .iter() + .find(|function| function.entrypoint && function.name == function_name) + .ok_or_else(|| format!("function '{function_name}' not found"))?; + assert_eq!(entry.params.len(), function_args.len()); // Seed the stack with sigscript arguments, then execute the bytecode in debug mode. - let sigscript = compiled.build_sig_script(function_name, function_args)?; - let mut session = DebugSession::full(&sigscript, &compiled.bytecode, source, debug_info, engine)?; + let sigscript = encode_entry_sig_script(&compiled, function_name, &function_args)?; + let mut session = DebugSession::from_artifact(&sigscript, &compiled, &parsed_contract.name, engine)?; f(&mut session) } @@ -122,7 +146,7 @@ contract ConsoleStep() { } "#; - with_session_for_source(source, vec![], "inspect", vec![Expr::int(2), Expr::int(3)], |session| { + with_session_for_source(source, vec![], "inspect", vec![2.into(), 3.into()], |session| { session.run_to_first_executed_statement()?; assert_eq!(session.take_console_output(), vec!["sum 5"]); @@ -177,7 +201,7 @@ contract BP() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(1)], |session| { + with_session_for_source(source, vec![], "main", vec![1.into()], |session| { session.run_to_first_executed_statement()?; // Line 8 is inside a multiline `require(...)` span and should still be hit. assert!(session.add_breakpoint(8), "expected breakpoint line to be valid"); @@ -203,7 +227,7 @@ contract Shadow(int x) { } "#; - with_session_for_source(source, vec![Expr::int(7)], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![7.into()], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; // Function param `x` should shadow constructor constant `x` in visible debugger variables. @@ -232,7 +256,7 @@ contract ShadowMath(int fee) { } "#; - with_session_for_source(source, vec![Expr::int(2)], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![2.into()], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; session.step_over()?; @@ -263,7 +287,7 @@ contract FieldOffset(int c) { } "#; - with_session_for_source(source, vec![Expr::int(2)], "main", vec![Expr::int(5)], |session| { + with_session_for_source(source, vec![2.into()], "main", vec![5.into()], |session| { session.run_to_first_executed_statement()?; let a = session.variable_by_name("a")?; @@ -289,7 +313,7 @@ contract FieldMath(int c) { } "#; - with_session_for_source(source, vec![Expr::int(2)], "main", vec![Expr::int(5)], |session| { + with_session_for_source(source, vec![2.into()], "main", vec![5.into()], |session| { session.run_to_first_executed_statement()?; for _ in 0..4 { @@ -319,7 +343,7 @@ contract Virtuals() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; let first = session.current_step().ok_or("missing first location")?; assert!(matches!(first.kind, StepKind::Source {})); @@ -349,7 +373,7 @@ contract OpcodeCursor() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; let start = session.current_span().ok_or("missing start span")?; assert_eq!(start.line, 5); @@ -386,7 +410,7 @@ contract VirtualBp() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; assert!(session.add_breakpoint(6), "line with assignment should be a valid breakpoint"); let hit = session.continue_to_breakpoint()?; @@ -410,7 +434,7 @@ contract LocalVars() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; assert!(session.variable_by_name("x").is_err(), "x should not exist before its statement executes"); @@ -457,7 +481,7 @@ contract InlineCalls() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; let start = session.current_step().ok_or("missing start step")?; assert_eq!(start.span.line, 10); @@ -470,7 +494,7 @@ contract InlineCalls() { Ok(()) })?; - with_session_for_source(source, vec![], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; session.step_into()?; let mut in_callee = session.current_span().ok_or("missing span in callee")?; @@ -510,7 +534,7 @@ contract Repeat() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(0)], |session| { + with_session_for_source(source, vec![], "main", vec![0.into()], |session| { session.run_to_first_executed_statement()?; let start = session.current_span().ok_or("missing start span")?; assert_eq!(start.line, 10, "first source step should be caller line, not callee internals"); @@ -536,7 +560,7 @@ contract Repeat() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(0)], |session| { + with_session_for_source(source, vec![], "main", vec![0.into()], |session| { session.run_to_first_executed_statement()?; let mut lines = vec![session.current_span().ok_or("missing initial span")?.line]; @@ -618,7 +642,7 @@ contract DebugPoC(int const) { } "#; - with_session_for_source(source, vec![Expr::int(0)], "main", vec![Expr::int(0), Expr::int(0)], |session| { + with_session_for_source(source, vec![0.into()], "main", vec![0.into(), 0.into()], |session| { session.run_to_first_executed_statement()?; let initial = session.current_step().ok_or("missing initial location")?; @@ -664,19 +688,17 @@ contract InlineParams() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(4)], |session| { + with_session_for_source(source, vec![], "main", vec![4.into()], |session| { session.run_to_first_executed_statement()?; let mut saw_inline_param = false; for _ in 0..8 { let in_callee = session.call_stack().iter().any(|name| name == "add1"); - if in_callee { - if let Ok(x) = session.variable_by_name("x") { - let rendered = format_value(&x.type_name, &x.value); - assert_eq!(rendered, "4", "inline param x should reflect caller-provided value"); - saw_inline_param = true; - break; - } + if in_callee && let Ok(x) = session.variable_by_name("x") { + let rendered = format_value(&x.type_name, &x.value); + assert_eq!(rendered, "4", "inline param x should reflect caller-provided value"); + saw_inline_param = true; + break; } if session.step_into()?.is_none() { break; @@ -706,7 +728,7 @@ contract InlineEval() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(4)], |session| { + with_session_for_source(source, vec![], "main", vec![4.into()], |session| { session.run_to_first_executed_statement()?; assert!(session.add_breakpoint(6), "expected inline callee line to accept a breakpoint"); @@ -745,7 +767,7 @@ contract ScopeKinds(int init_amount) { } "#; - with_session_for_source(source, vec![Expr::int(7)], "main", vec![Expr::int(3)], |session| { + with_session_for_source(source, vec![7.into()], "main", vec![3.into()], |session| { session.run_to_first_executed_statement()?; let vars = session.list_variables()?; @@ -783,23 +805,17 @@ contract StepVisibility(int init_amount) { } "#; - with_session_for_source( - source, - vec![Expr::int(7)], - "inspect", - vec![Expr::int(3), Expr::array(parse_type_ref("int[]")?, vec![Expr::int(4)])], - |session| { - session.run_to_first_executed_statement()?; - session.current_span().ok_or("missing starting span")?; + with_session_for_source(source, vec![7.into()], "inspect", vec![3.into(), ArtifactValue::Array(vec![4.into()])], |session| { + session.run_to_first_executed_statement()?; + session.current_span().ok_or("missing starting span")?; - session.step_over()?; - session.current_span().ok_or("missing span after step")?; + session.step_over()?; + session.current_span().ok_or("missing span after step")?; - let base = session.variable_by_name("base")?; - assert_eq!(format_value(&base.type_name, &base.value), "11"); - Ok(()) - }, - ) + let base = session.variable_by_name("base")?; + assert_eq!(format_value(&base.type_name, &base.value), "11"); + Ok(()) + }) } #[test] @@ -825,47 +841,41 @@ contract ShiftedBindings() { } "#; - with_session_for_source( - source, - vec![], - "inspect", - vec![Expr::int(3), Expr::array(parse_type_ref("int[]")?, vec![Expr::int(4), Expr::int(5)])], - |session| { - session.run_to_first_executed_statement()?; + with_session_for_source(source, vec![], "inspect", vec![3.into(), ArtifactValue::Array(vec![4.into(), 5.into()])], |session| { + session.run_to_first_executed_statement()?; - session.step_over()?; - let call_line = session.current_span().ok_or("missing inline-call span")?.line; + session.step_over()?; + let call_line = session.current_span().ok_or("missing inline-call span")?.line; - for _ in 0..6 { - if session.current_span().is_some_and(|span| span.line > call_line) { - break; - } - if session.step_over()?.is_none() { - break; - } + for _ in 0..6 { + if session.current_span().is_some_and(|span| span.line > call_line) { + break; } + if session.step_over()?.is_none() { + break; + } + } - let current_line = session.current_span().ok_or("missing post-call span")?.line; - assert!(current_line > call_line, "expected to step past inline call"); + let current_line = session.current_span().ok_or("missing post-call span")?.line; + assert!(current_line > call_line, "expected to step past inline call"); - let amount = session.variable_by_name("amount")?; - assert_eq!(format_value(&amount.type_name, &amount.value), "11"); + let amount = session.variable_by_name("amount")?; + assert_eq!(format_value(&amount.type_name, &amount.value), "11"); - let delta = session.variable_by_name("delta")?; - assert_eq!(format_value(&delta.type_name, &delta.value), "3"); + let delta = session.variable_by_name("delta")?; + assert_eq!(format_value(&delta.type_name, &delta.value), "3"); - let values = session.variable_by_name("values")?; - assert_eq!(format_value(&values.type_name, &values.value), "[4, 5]"); + let values = session.variable_by_name("values")?; + assert_eq!(format_value(&values.type_name, &values.value), "[4, 5]"); - let base = session.variable_by_name("base")?; - assert_eq!(format_value(&base.type_name, &base.value), "15"); + let base = session.variable_by_name("base")?; + assert_eq!(format_value(&base.type_name, &base.value), "15"); - let after = session.variable_by_name("after")?; - assert_eq!(format_value(&after.type_name, &after.value), "20"); + let after = session.variable_by_name("after")?; + assert_eq!(format_value(&after.type_name, &after.value), "20"); - Ok(()) - }, - ) + Ok(()) + }) } #[test] @@ -888,7 +898,7 @@ contract StructuredEvalState() { source, vec![], "inspect", - vec![struct_object("State", vec![("amount", Expr::int(5)), ("active", Expr::bool(true)), ("tag", Expr::bytes(vec![0xaa]))])], + vec![artifact_object([("amount", 5.into()), ("active", true.into()), ("tag", vec![0xaau8].into())])], |session| { session.run_to_first_executed_statement()?; @@ -927,13 +937,10 @@ contract StructuredEvalStateArray() { source, vec![], "inspect", - vec![Expr::array( - parse_type_ref("State[]")?, - vec![ - struct_object("State", vec![("amount", Expr::int(5)), ("active", Expr::bool(true)), ("tag", Expr::bytes(vec![0xaa]))]), - struct_object("State", vec![("amount", Expr::int(7)), ("active", Expr::bool(true)), ("tag", Expr::bytes(vec![0xaa]))]), - ], - )], + vec![ArtifactValue::Array(vec![ + artifact_object([("amount", 5.into()), ("active", true.into()), ("tag", vec![0xaau8].into())]), + artifact_object([("amount", 7.into()), ("active", true.into()), ("tag", vec![0xaau8].into())]), + ])], |session| { session.run_to_first_executed_statement()?; @@ -980,7 +987,7 @@ contract StructuredEvalPair() { source, vec![], "inspect", - vec![struct_object("Pair", vec![("amount", Expr::int(9)), ("code", Expr::bytes(vec![0x12, 0x34]))])], + vec![artifact_object([("amount", 9.into()), ("code", vec![0x12u8, 0x34].into())])], |session| { session.run_to_first_executed_statement()?; @@ -1025,7 +1032,7 @@ contract InlineStructuredEval() { source, vec![], "inspect", - vec![struct_object("State", vec![("amount", Expr::int(5)), ("active", Expr::bool(true)), ("tag", Expr::bytes(vec![0xaa]))])], + vec![artifact_object([("amount", 5.into()), ("active", true.into()), ("tag", vec![0xaau8].into())])], |session| { session.run_to_first_executed_statement()?; @@ -1076,16 +1083,15 @@ contract MissingStructuredSource() { "#; let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; - let compiled = compile_contract(source, &[], compile_opts)?; - let mut debug_info = compiled.debug_info.clone().ok_or("missing debug info")?; - debug_info.source.clear(); + let mut compiled = compile_contract(source, &[], compile_opts)?; + compiled.contract_debug_info.get_mut("MissingStructuredSource").ok_or("missing debug info")?.source.clear(); let sig_cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); let ctx = EngineCtx::new(&sig_cache).with_reused(&reused_values); let engine = debugger_session::session::DebugEngine::new(ctx, EngineFlags { covenants_enabled: true, ..Default::default() }); - let sigscript = compiled.build_sig_script("inspect", vec![struct_object("State", vec![("amount", Expr::int(7))])])?; - let mut session = DebugSession::full(&sigscript, &compiled.bytecode, "", Some(debug_info), engine)?; + let sigscript = encode_entry_sig_script(&compiled, "inspect", &[artifact_object([("amount", 7.into())])])?; + let mut session = DebugSession::from_artifact(&sigscript, &compiled, "MissingStructuredSource", engine)?; session.run_to_first_executed_statement()?; let (type_name, value) = session.evaluate_expression("next.amount")?; @@ -1116,7 +1122,7 @@ contract NestedArgs() { } "#; - with_session_for_source(source, vec![], "main", vec![Expr::int(0)], |session| { + with_session_for_source(source, vec![], "main", vec![0.into()], |session| { session.run_to_first_executed_statement()?; let start = session.current_step().ok_or("missing start step")?; assert_eq!(start.span.line, 15); @@ -1183,11 +1189,11 @@ contract LoopStepOver() { let mut saw_first_iteration = false; for _ in 0..8 { - if let Ok(i) = session.variable_by_name("i") { - if format_value(&i.type_name, &i.value) == "0" { - saw_first_iteration = true; - break; - } + if let Ok(i) = session.variable_by_name("i") + && format_value(&i.type_name, &i.value) == "0" + { + saw_first_iteration = true; + break; } session.step_over()?.ok_or("expected to reach first loop iteration")?; } @@ -1200,11 +1206,11 @@ contract LoopStepOver() { let mut saw_second_iteration = false; for _ in 0..8 { - if let Ok(i) = session.variable_by_name("i") { - if format_value(&i.type_name, &i.value) == "1" { - saw_second_iteration = true; - break; - } + if let Ok(i) = session.variable_by_name("i") + && format_value(&i.type_name, &i.value) == "1" + { + saw_second_iteration = true; + break; } if session.step_over()?.is_none() { break; @@ -1636,8 +1642,7 @@ contract CovLocal() { let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; let compiled = compile_contract(source, &[], compile_opts)?; - let debug_info = compiled.debug_info.clone(); - let sigscript = compiled.build_sig_script("main", vec![])?; + let sigscript = encode_entry_sig_script(&compiled, "main", &[])?; let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([0x44u8; 32]), index: 0 }, @@ -1650,7 +1655,7 @@ contract CovLocal() { let covenant_id = Hash::from_bytes([0x11u8; 32]); let utxo_entry = - UtxoEntry::new(1000, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), Some(covenant_id)); + UtxoEntry::new(1000, ScriptPublicKey::new(0, bytecode(&compiled).to_vec().into()), 0, tx.is_coinbase(), Some(covenant_id)); let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry]); let cov_ctx = CovenantsContext::from_tx(&populated_tx)?; @@ -1670,8 +1675,7 @@ contract CovLocal() { let shadow_ctx = ShadowTxContext { tx: &populated_tx, input: input_ref, input_index: 0, utxo_entry: utxo_ref, covenants_ctx: &cov_ctx }; - let mut session = - DebugSession::full(&sigscript, &compiled.bytecode, source, debug_info, engine)?.with_shadow_tx_context(shadow_ctx); + let mut session = DebugSession::from_artifact(&sigscript, &compiled, "CovLocal", engine)?.with_shadow_tx_context(shadow_ctx); session.run_to_first_executed_statement()?; for _ in 0..4 { @@ -1701,8 +1705,7 @@ contract CovEval() { let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; let compiled = compile_contract(source, &[], compile_opts)?; - let debug_info = compiled.debug_info.clone(); - let sigscript = compiled.build_sig_script("main", vec![])?; + let sigscript = encode_entry_sig_script(&compiled, "main", &[])?; let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([0x44u8; 32]), index: 0 }, @@ -1715,7 +1718,7 @@ contract CovEval() { let covenant_id = Hash::from_bytes([0x22u8; 32]); let utxo_entry = - UtxoEntry::new(1000, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), Some(covenant_id)); + UtxoEntry::new(1000, ScriptPublicKey::new(0, bytecode(&compiled).to_vec().into()), 0, tx.is_coinbase(), Some(covenant_id)); let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry]); let cov_ctx = CovenantsContext::from_tx(&populated_tx)?; @@ -1736,8 +1739,7 @@ contract CovEval() { let shadow_ctx = ShadowTxContext { tx: &populated_tx, input: input_ref, input_index: 0, utxo_entry: utxo_ref, covenants_ctx: &cov_ctx }; - let mut session = - DebugSession::full(&sigscript, &compiled.bytecode, source, debug_info, engine)?.with_shadow_tx_context(shadow_ctx); + let mut session = DebugSession::from_artifact(&sigscript, &compiled, "CovEval", engine)?.with_shadow_tx_context(shadow_ctx); session.run_to_first_executed_statement()?; let (type_name, value) = session.evaluate_expression("OpInputCovenantId(this.activeInputIndex)")?; @@ -1774,7 +1776,8 @@ fn covenant_debugger_resolves_overridden_public_entrypoint_names() -> Result<(), let parsed = parse_contract_ast(source)?; let compiled = compile_contract(source, &[], CompileOptions::default())?; - let target = resolve_covenant_call_target(&parsed, &compiled, "transferPolicy").ok_or("missing covenant call target")?; + let target = + resolve_covenant_call_target(&parsed, single_contract(&compiled), "transferPolicy").ok_or("missing covenant call target")?; assert_eq!(target.generated_entrypoint_name, "transfer"); assert_eq!(target.generated_entrypoint_name_for(true), "transfer"); @@ -1785,7 +1788,7 @@ fn covenant_debugger_resolves_overridden_public_entrypoint_names() -> Result<(), assert_eq!(delegate_body.source_name, "authorizeDelegate"); assert_eq!(delegate_body.policy_function_name, "__covenant_delegate_policy_authorizeDelegate"); assert_eq!(target.display_name_for(&delegate_body.policy_function_name), Some("authorizeDelegate")); - assert!(resolve_covenant_call_target(&parsed, &compiled, "authorizeDelegate").is_none()); + assert!(resolve_covenant_call_target(&parsed, single_contract(&compiled), "authorizeDelegate").is_none()); Ok(()) } @@ -1820,11 +1823,11 @@ contract Routed() { let parsed_contract = parse_contract_ast(source)?; let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; let compiled = compile_contract(source, &[], compile_opts)?; - let target = resolve_covenant_call_target(&parsed_contract, &compiled, "transferPolicy").ok_or("missing covenant call target")?; - let delegate_sigscript = - compiled.build_sig_script(&target.generated_entrypoint_name_for(false), vec![Expr::dynamic_bytes(vec![7])])?; + let target = resolve_covenant_call_target(&parsed_contract, single_contract(&compiled), "transferPolicy") + .ok_or("missing covenant call target")?; + let delegate_sigscript = encode_entry_sig_script(&compiled, &target.generated_entrypoint_name_for(false), &[vec![7u8].into()])?; let mut input_sigscript = delegate_sigscript.clone(); - input_sigscript.extend_from_slice(&push_redeem_script(&compiled.bytecode)); + input_sigscript.extend_from_slice(&push_redeem_script(bytecode(&compiled))); let inputs = vec![ TransactionInput { @@ -1844,8 +1847,8 @@ contract Routed() { let tx = Transaction::new(1, inputs, vec![output], 0, Default::default(), 0, vec![]); let covenant_id = Hash::from_bytes([0x88u8; 32]); let utxos = vec![ - UtxoEntry::new(1000, pay_to_script_hash_script(&compiled.bytecode), 0, tx.is_coinbase(), Some(covenant_id)), - UtxoEntry::new(1000, pay_to_script_hash_script(&compiled.bytecode), 0, tx.is_coinbase(), Some(covenant_id)), + UtxoEntry::new(1000, pay_to_script_hash_script(bytecode(&compiled)), 0, tx.is_coinbase(), Some(covenant_id)), + UtxoEntry::new(1000, pay_to_script_hash_script(bytecode(&compiled)), 0, tx.is_coinbase(), Some(covenant_id)), ]; let populated_tx = PopulatedTransaction::new(&tx, utxos); let cov_ctx = CovenantsContext::from_tx(&populated_tx)?; @@ -1865,7 +1868,7 @@ contract Routed() { ); let shadow_ctx = ShadowTxContext { tx: &populated_tx, input: input_ref, input_index: 1, utxo_entry: utxo_ref, covenants_ctx: &cov_ctx }; - let mut session = DebugSession::full(&delegate_sigscript, &compiled.bytecode, source, compiled.debug_info.clone(), engine)? + let mut session = DebugSession::from_artifact(&delegate_sigscript, &compiled, "Routed", engine)? .with_shadow_tx_context(shadow_ctx) .with_covenant_mode(None, Some(target)); @@ -1908,20 +1911,18 @@ contract CovDebugDemo(int initial_value) { let parsed_contract = parse_contract_ast(source)?; let compile_opts = CompileOptions { record_debug_infos: true, ..Default::default() }; - let compiled0 = compile_contract(source, &[Expr::int(10)], compile_opts)?; - let compiled1 = compile_contract(source, &[Expr::int(20)], compile_opts)?; - let leader_args = vec![Expr::array( - parse_type_ref("State[]")?, - vec![struct_object("State", vec![("value", Expr::int(30))]), struct_object("State", vec![("value", Expr::int(40))])], - )]; - let leader_target = - resolve_covenant_call_target(&parsed_contract, &compiled0, "rebalance").ok_or("missing covenant call target")?; - let leader_sigscript = compiled0.build_sig_script(&leader_target.generated_entrypoint_name, leader_args)?; + let compiled0 = compile_contract(source, &[10.into()], compile_opts)?; + let compiled1 = compile_contract(source, &[20.into()], compile_opts)?; + let leader_args = + vec![ArtifactValue::Array(vec![artifact_object([("value", 30.into())]), artifact_object([("value", 40.into())])])]; + let leader_target = resolve_covenant_call_target(&parsed_contract, single_contract(&compiled0), "rebalance") + .ok_or("missing covenant call target")?; + let leader_sigscript = encode_entry_sig_script(&compiled0, &leader_target.generated_entrypoint_name, &leader_args)?; let mut leader_input_sigscript = leader_sigscript.clone(); - leader_input_sigscript.extend_from_slice(&push_redeem_script(&compiled0.bytecode)); - let delegate_sigscript = compiled1.build_sig_script(&leader_target.generated_entrypoint_name_for(false), vec![])?; + leader_input_sigscript.extend_from_slice(&push_redeem_script(bytecode(&compiled0))); + let delegate_sigscript = encode_entry_sig_script(&compiled1, &leader_target.generated_entrypoint_name_for(false), &[])?; let mut delegate_input_sigscript = delegate_sigscript.clone(); - delegate_input_sigscript.extend_from_slice(&push_redeem_script(&compiled1.bytecode)); + delegate_input_sigscript.extend_from_slice(&push_redeem_script(bytecode(&compiled1))); let covenant_id = Hash::from_bytes([0x33u8; 32]); let inputs = vec![ @@ -1938,25 +1939,25 @@ contract CovDebugDemo(int initial_value) { compute_commit: SigopCount(0).into(), }, ]; - let next0 = compile_contract(source, &[Expr::int(30)], compile_opts)?; - let next1 = compile_contract(source, &[Expr::int(40)], compile_opts)?; + let next0 = compile_contract(source, &[30.into()], compile_opts)?; + let next1 = compile_contract(source, &[40.into()], compile_opts)?; let outputs = vec![ TransactionOutput { value: 1000, - script_public_key: pay_to_script_hash_script(&next0.bytecode), + script_public_key: pay_to_script_hash_script(bytecode(&next0)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id }), }, TransactionOutput { value: 1000, - script_public_key: pay_to_script_hash_script(&next1.bytecode), + script_public_key: pay_to_script_hash_script(bytecode(&next1)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id }), }, ]; let tx = Transaction::new(1, inputs, outputs, 0, Default::default(), 0, vec![]); let utxos = vec![ - UtxoEntry::new(1000, pay_to_script_hash_script(&compiled0.bytecode), 0, tx.is_coinbase(), Some(covenant_id)), - UtxoEntry::new(1000, pay_to_script_hash_script(&compiled1.bytecode), 0, tx.is_coinbase(), Some(covenant_id)), + UtxoEntry::new(1000, pay_to_script_hash_script(bytecode(&compiled0)), 0, tx.is_coinbase(), Some(covenant_id)), + UtxoEntry::new(1000, pay_to_script_hash_script(bytecode(&compiled1)), 0, tx.is_coinbase(), Some(covenant_id)), ]; let populated_tx = PopulatedTransaction::new(&tx, utxos); let cov_ctx = CovenantsContext::from_tx(&populated_tx)?; @@ -1978,7 +1979,7 @@ contract CovDebugDemo(int initial_value) { let shadow_ctx = ShadowTxContext { tx: &populated_tx, input: input_ref, input_index: 0, utxo_entry: utxo_ref, covenants_ctx: &cov_ctx }; - let mut session = DebugSession::full(&leader_sigscript, &compiled0.bytecode, source, compiled0.debug_info.clone(), engine)? + let mut session = DebugSession::from_artifact(&leader_sigscript, &compiled0, "CovDebugDemo", engine)? .with_shadow_tx_context(shadow_ctx) .with_covenant_mode(Some(DebugValue::Array(vec![covenant_debug_value(10), covenant_debug_value(20)])), Some(leader_target)); diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index b1d7c3e0..d88b458a 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -91,29 +91,27 @@ If your contract has constructor parameters, you can provide their values via a silverc contract.sil --constructor-args args.json ``` -The `args.json` file should contain an array of constructor argument expressions. For example: +The `args.json` file should contain an array of portable ABI values. For example: ```json [ - {"kind": "byte[]", "data": [1, 2, 3, 4]}, - {"kind": "int", "data": 12345} + {"kind": "bytes", "value": [1, 2, 3, 4]}, + {"kind": "int", "value": 12345} ] ``` -The compiled JSON output includes: -- `contract_name`: The name of the contract -- `compiler_version`: The SilverScript compiler version that produced the artifact -- `bytecode`: The compiled bytecode (as an array of bytes) -- `ast`: The abstract syntax tree of the parsed contract -- `abi`: An array of entries with their parameter types +The output is a portable `SilAbiArtifact` JSON document containing: + +- `schema_version`: The portable ABI schema version +- `states`: Struct definitions referenced by contract inputs and runtime state +- `contracts`: The compiled contract, its entries, dispatch tags, script, template hash, and state span ### Programmatic Compilation You can also compile contracts programmatically using the SilverScript Rust library: ```rust -use silverscript_lang::compiler::{compile_contract, CompileOptions}; -use silverscript_lang::ast::Expr; +use silverscript_lang::compiler::sil_abi_artifact; fn main() -> Result<(), Box> { let source = r#" @@ -127,16 +125,12 @@ fn main() -> Result<(), Box> { "#; // Constructor arguments (x = 100) - let constructor_args = vec![Expr::Int(100)]; - - // Compile with default options - let options = CompileOptions::default(); - let compiled = compile_contract(source, &constructor_args, options)?; - - println!("Contract name: {}", compiled.contract_name); - println!("Compiler version: {}", compiled.compiler_version); - println!("Bytecode length: {} bytes", compiled.bytecode.len()); - println!("ABI: {:?}", compiled.abi); + let artifact = sil_abi_artifact(source, &[100.into()])?; + let (contract_name, contract) = artifact.contracts.get_key_value("MyContract").expect("contract exists"); + + println!("Contract name: {contract_name}"); + println!("Bytecode length: {} bytes", contract.compiled.bytecode.len()); + println!("Entries: {:?}", contract.entries); Ok(()) } @@ -147,7 +141,8 @@ fn main() -> Result<(), Box> { After compiling a contract, you can build signature scripts for its entries: ```rust -use silverscript_lang::ast::Expr; +use silverscript_abi::encode_contract_entry_sig_script; +use silverscript_lang::compiler::sil_abi_artifact; let source = r#" pragma silverscript ^0.1.0; @@ -167,31 +162,34 @@ let source = r#" let sender_pk = vec![3u8; 32]; let recipient_pk = vec![4u8; 32]; let timeout = 1640000000000i64; -let compiled = compile_contract( +let artifact = sil_abi_artifact( source, - &[sender_pk.into(), recipient_pk.into(), Expr::temporal(timeout)], - CompileOptions::default() + &[sender_pk.into(), recipient_pk.into(), timeout.into()], )?; // Build sigscript for multiple entrypoints let sig = vec![5u8; 65]; // For the 'transfer' entrypoint -let transfer_sigscript = compiled.build_sig_script( +let transfer_sigscript = encode_contract_entry_sig_script( + &artifact, + "TransferWithTimeout", "transfer", - vec![sig.clone().into()] + &[sig.clone().into()], )?; // transfer_sigscript contains: <4-byte KCC-01 dispatch tag> // For the 'reclaim' entrypoint -let reclaim_sigscript = compiled.build_sig_script( +let reclaim_sigscript = encode_contract_entry_sig_script( + &artifact, + "TransferWithTimeout", "reclaim", - vec![sig.into()] + &[sig.into()], )?; // reclaim_sigscript contains: <4-byte KCC-01 dispatch tag> ``` -The `build_sig_script` method automatically: +`encode_contract_entry_sig_script` automatically: - Validates argument count and types - Encodes arguments properly for the Kaspa script stack - Appends the KCC-01 function-signature dispatch tag diff --git a/silverscript-abi/Cargo.toml b/silverscript-abi/Cargo.toml new file mode 100644 index 00000000..3df8c66b --- /dev/null +++ b/silverscript-abi/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "silverscript-abi" +version = "0.1.0" +edition = "2024" +rust-version.workspace = true + +[dependencies] +blake3 = { workspace = true } +faster-hex = { workspace = true } +kaspa-txscript = { workspace = true } +serde = { workspace = true } +serde_bytes = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +# Keep the Sil compiler test-only; production ABI code must not depend on it. +silverscript-lang = { workspace = true } diff --git a/silverscript-abi/src/json.rs b/silverscript-abi/src/json.rs new file mode 100644 index 00000000..fef2970e --- /dev/null +++ b/silverscript-abi/src/json.rs @@ -0,0 +1,184 @@ +//! JSON formatting for portable Sil artifacts. + +use std::io::{self, Write}; + +use serde::Serialize; +use serde_json::ser::{Formatter, PrettyFormatter}; + +const BYTES_PER_LINE: usize = 64; +const INDENT: &[u8] = b" "; + +/// Serialize a value as readable JSON while keeping byte arrays compact. +pub fn to_pretty_json(value: &T) -> serde_json::Result +where + T: ?Sized + Serialize, +{ + let mut output = Vec::new(); + let formatter = ByteArrayPrettyFormatter::new(); + let mut serializer = serde_json::Serializer::with_formatter(&mut output, formatter); + value.serialize(&mut serializer)?; + Ok(String::from_utf8(output).expect("the JSON serializer emits UTF-8")) +} + +struct ByteArrayPrettyFormatter<'a> { + inner: PrettyFormatter<'a>, + depth: usize, + indent: &'a [u8], +} + +impl ByteArrayPrettyFormatter<'static> { + fn new() -> Self { + Self { inner: PrettyFormatter::with_indent(INDENT), depth: 0, indent: INDENT } + } +} + +macro_rules! delegate { + ($name:ident $(, $arg:ident: $ty:ty)*) => { + fn $name(&mut self, writer: &mut W, $($arg: $ty),*) -> io::Result<()> + where + W: ?Sized + Write, + { + self.inner.$name(writer, $($arg),*) + } + }; +} + +impl Formatter for ByteArrayPrettyFormatter<'_> { + delegate!(begin_array_value, first: bool); + delegate!(end_array_value); + delegate!(begin_object_key, first: bool); + delegate!(begin_object_value); + delegate!(end_object_value); + + // Keep byte arrays compact without changing ordinary array formatting. + fn write_byte_array(&mut self, writer: &mut W, bytes: &[u8]) -> io::Result<()> + where + W: ?Sized + Write, + { + if bytes.len() <= BYTES_PER_LINE { + writer.write_all(b"[")?; + write_bytes(writer, bytes)?; + return writer.write_all(b"]"); + } + + writer.write_all(b"[")?; + for (index, chunk) in bytes.chunks(BYTES_PER_LINE).enumerate() { + writer.write_all(b"\n")?; + write_indent(writer, self.depth + 1, self.indent)?; + write_bytes(writer, chunk)?; + if index + 1 < bytes.len().div_ceil(BYTES_PER_LINE) { + writer.write_all(b",")?; + } + } + writer.write_all(b"\n")?; + write_indent(writer, self.depth, self.indent)?; + writer.write_all(b"]") + } + + // Delegate ordinary formatting while mirroring PrettyFormatter's container depth. + fn begin_array(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + Write, + { + self.depth += 1; + self.inner.begin_array(writer) + } + + fn end_array(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + Write, + { + self.depth -= 1; + self.inner.end_array(writer) + } + + fn begin_object(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + Write, + { + self.depth += 1; + self.inner.begin_object(writer) + } + + fn end_object(&mut self, writer: &mut W) -> io::Result<()> + where + W: ?Sized + Write, + { + self.depth -= 1; + self.inner.end_object(writer) + } +} + +fn write_bytes(writer: &mut W, bytes: &[u8]) -> io::Result<()> +where + W: ?Sized + Write, +{ + for (index, byte) in bytes.iter().enumerate() { + if index > 0 { + writer.write_all(b", ")?; + } + write!(writer, "{byte}")?; + } + Ok(()) +} + +fn write_indent(writer: &mut W, depth: usize, indent: &[u8]) -> io::Result<()> +where + W: ?Sized + Write, +{ + for _ in 0..depth { + writer.write_all(indent)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use serde::Serialize; + + use super::to_pretty_json; + + #[derive(Serialize)] + struct Bytes(#[serde(with = "serde_bytes")] Vec); + + #[derive(Serialize)] + struct Fixture { + empty: Bytes, + exact_line: Bytes, + wrapped: Bytes, + nested: Vec, + ordinary: Vec, + } + + #[test] + fn formats_byte_arrays_without_compacting_ordinary_arrays() { + let fixture = Fixture { + empty: Bytes(Vec::new()), + exact_line: Bytes((0..64).collect()), + wrapped: Bytes((0..65).collect()), + nested: vec![Bytes(vec![7, 8, 9])], + ordinary: vec![1, 2, 3], + }; + + let json = to_pretty_json(&fixture).expect("fixture serializes"); + assert_eq!( + json, + r#"{ + "empty": [], + "exact_line": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63], + "wrapped": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64 + ], + "nested": [ + [7, 8, 9] + ], + "ordinary": [ + 1, + 2, + 3 + ] +}"# + ); + } +} diff --git a/silverscript-abi/src/lib.rs b/silverscript-abi/src/lib.rs new file mode 100644 index 00000000..2f1607f3 --- /dev/null +++ b/silverscript-abi/src/lib.rs @@ -0,0 +1,1823 @@ +//! Portable ABI and codec data for generated Silverscript contracts. +//! +//! This crate owns only bytecode-facing facts: contract scripts, entrypoint +//! dispatch tags and parameters, runtime state field order, structural field types, +//! template state spans and hashes, and the codec for encoding those values. +//! +//! It must not know why a field exists. Argent coordination semantics such as +//! hidden template fields, route-family tables, route roots, observed actors, +//! witness purposes, and any future covenant-routing meaning belong in the +//! outer `argent-artifact` crate. Keep that boundary sharp so this ABI can be +//! replaced by a native Silverscript portable artifact later. + +use std::collections::{BTreeMap, BTreeSet}; + +use blake3::Hasher as Blake3Hasher; +use kaspa_txscript::{ + EngineFlags, deserialize_i64 as deserialize_script_i64, + opcodes::codes::{ + Op0 as OP_0, Op1 as OP_1, Op1Negate as OP_1_NEGATE, Op16 as OP_16, OpData1 as OP_DATA_1, OpData75 as OP_DATA_75, + OpPushData1 as OP_PUSH_DATA_1, OpPushData2 as OP_PUSH_DATA_2, OpPushData4 as OP_PUSH_DATA_4, + }, + script_builder::{ScriptBuilder, ScriptBuilderError}, + serialize_i64 as serialize_script_i64, +}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use thiserror::Error; + +mod json; + +pub use json::to_pretty_json; + +pub const SIL_ABI_SCHEMA_VERSION: u32 = 1; +const TEMPLATE_PART_LENGTH_BYTES: usize = 8; + +/// A Sil entrypoint's fixed-width signature dispatch tag. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DispatchTag([u8; 4]); + +impl DispatchTag { + /// Borrow the four bytes placed in the entry signature script. + pub const fn as_bytes(&self) -> &[u8; 4] { + &self.0 + } + + /// Return the four bytes placed in the entry signature script. + pub const fn into_bytes(self) -> [u8; 4] { + self.0 + } + + /// Parse the eight-character representation stored in portable artifacts. + pub fn from_hex(hex: &str) -> Result { + if hex.len() != 8 { + return Err(DispatchTagParseError::InvalidLength(hex.len())); + } + + let mut bytes = [0; 4]; + faster_hex::hex_decode(hex.as_bytes(), &mut bytes).map_err(|err| DispatchTagParseError::InvalidHex(err.to_string()))?; + Ok(Self(bytes)) + } + + /// Encode the tag using the portable artifact's lowercase hex form. + pub fn to_hex(&self) -> String { + encode_hex(&self.0) + } +} + +impl From<[u8; 4]> for DispatchTag { + fn from(bytes: [u8; 4]) -> Self { + Self(bytes) + } +} + +impl Serialize for DispatchTag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_hex()) + } +} + +impl<'de> Deserialize<'de> for DispatchTag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let hex = String::deserialize(deserializer)?; + Self::from_hex(&hex).map_err(serde::de::Error::custom) + } +} + +/// An invalid portable dispatch-tag representation. +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum DispatchTagParseError { + #[error("dispatch tag must contain exactly 8 hexadecimal characters, found {0}")] + InvalidLength(usize), + #[error("invalid dispatch tag hex: {0}")] + InvalidHex(String), +} + +/// Calculate the canonical hash of a state-bearing Silverscript template. +pub fn template_hash(prefix: &[u8], suffix: &[u8]) -> [u8; 32] { + let prefix_len = i64::try_from(prefix.len()).unwrap(); + let suffix_len = i64::try_from(suffix.len()).unwrap(); + let encoded_prefix_len = serialize_script_i64(prefix_len, Some(TEMPLATE_PART_LENGTH_BYTES)).unwrap(); + let encoded_suffix_len = serialize_script_i64(suffix_len, Some(TEMPLATE_PART_LENGTH_BYTES)).unwrap(); + + Blake3Hasher::new().update(&encoded_prefix_len).update(prefix).update(&encoded_suffix_len).update(suffix).finalize().into() +} + +#[derive(Debug, Error, Clone, PartialEq, Eq)] +#[error("unsupported {artifact} schema version {found}; expected {supported}")] +pub struct ArtifactVersionError { + pub artifact: &'static str, + pub supported: u32, + pub found: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct StructArtifact { + pub fields: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FieldArtifact { + pub name: String, + #[serde(rename = "type")] + pub ty: TypeArtifact, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ParamArtifact { + pub name: String, + #[serde(rename = "type")] + pub ty: TypeArtifact, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum TypeArtifact { + Int, + Temporal, + Bool, + Byte, + Bytes, + #[serde(rename = "string")] + Text, + Pubkey, + Sig, + Datasig, + FixedBytes { + len: usize, + }, + FixedArray { + item: Box, + len: usize, + }, + DynamicArray { + item: Box, + }, + Struct { + name: String, + }, +} + +impl TypeArtifact { + pub fn from_parts(name: &str, array_len: Option) -> Self { + match (name, array_len) { + ("byte", Some(len)) => Self::FixedBytes { len }, + (_, Some(len)) => Self::FixedArray { item: Box::new(Self::scalar(name)), len }, + (_, None) => Self::scalar(name), + } + } + + pub fn dynamic_array(item: Self) -> Self { + Self::DynamicArray { item: Box::new(item) } + } + + fn scalar(name: &str) -> Self { + match name { + "int" => Self::Int, + "temporal" => Self::Temporal, + "bool" => Self::Bool, + "byte" => Self::Byte, + "bytes" => Self::Bytes, + "string" => Self::Text, + "pubkey" => Self::Pubkey, + "sig" => Self::Sig, + "datasig" => Self::Datasig, + _ => Self::Struct { name: name.to_string() }, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SilAbiArtifact { + pub schema_version: u32, + pub compiler_version: String, + pub structs: BTreeMap, + pub contracts: BTreeMap, +} + +impl SilAbiArtifact { + pub fn check_schema_version(&self) -> std::result::Result<(), ArtifactVersionError> { + if self.schema_version == SIL_ABI_SCHEMA_VERSION { + Ok(()) + } else { + Err(ArtifactVersionError { artifact: "Sil ABI artifact", supported: SIL_ABI_SCHEMA_VERSION, found: self.schema_version }) + } + } + + /// Verify each compiled script, runtime-state span, and template hash. + pub fn verify(&self) -> std::result::Result<(), SilAbiVerificationError> { + self.check_schema_version()?; + for (contract_name, contract) in &self.contracts { + verify_compiled_contract(self, contract_name, contract)?; + } + Ok(()) + } + + pub fn contract(&self, name: &str) -> Option<&SilContractArtifact> { + self.contracts.get(name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SilContractArtifact { + pub source_path: String, + pub runtime_state: RuntimeStateArtifact, + pub entries: BTreeMap, + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub cov_decl_to_abi: BTreeMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delegate_entry_abi: Option, + pub compiled: CompiledContractArtifact, +} + +impl SilContractArtifact { + pub fn entry(&self, name: &str) -> Option<&SilEntryArtifact> { + self.entries.get(name) + } + + pub fn cov_binding_leader_decl_entry(&self, name: &str) -> Option<&SilEntryArtifact> { + self.entries.get(self.cov_decl_to_abi.get(name)?) + } + + pub fn auth_decl_entry(&self, name: &str) -> Option<&SilEntryArtifact> { + self.entries.get(self.cov_decl_to_abi.get(name)?) + } + + /// Resolve a source covenant declaration to its public entrypoint. + pub fn covenant_decl_entry(&self, name: &str, is_leader: bool) -> Option<&SilEntryArtifact> { + let leader = self.entries.get(self.cov_decl_to_abi.get(name)?)?; + if is_leader || self.delegate_entry_abi.is_none() { Some(leader) } else { self.entries.get(self.delegate_entry_abi.as_ref()?) } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RuntimeStateArtifact { + pub source: String, + pub fields: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RuntimeFieldArtifact { + pub name: String, + #[serde(rename = "type")] + pub ty: TypeArtifact, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SilEntryArtifact { + pub dispatch_tag: DispatchTag, + pub params: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CompiledContractArtifact { + /// Compiled contract bytes. + #[serde(with = "serde_bytes")] + pub bytecode: Vec, + #[serde(with = "serde_bytes")] + pub template_hash: [u8; 32], + pub state_span: StateSpanArtifact, +} + +impl CompiledContractArtifact { + /// Split decoded script bytes into the template prefix, runtime state, and + /// template suffix declared by this artifact. + pub fn script_parts<'a>(&self, script: &'a [u8]) -> Option<(&'a [u8], &'a [u8], &'a [u8])> { + let state_end = self.state_span.offset.checked_add(self.state_span.len)?; + Some((script.get(..self.state_span.offset)?, script.get(self.state_span.offset..state_end)?, script.get(state_end..)?)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CompiledTemplateArtifact { + pub prefix: Vec, + pub suffix: Vec, + pub hash: [u8; 32], +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct StateSpanArtifact { + pub offset: usize, + pub len: usize, +} + +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum SilAbiVerificationError { + #[error(transparent)] + Version(#[from] ArtifactVersionError), + #[error("contract `{contract}` has state span offset {offset} length {len}, but its compiled script has {script_len} bytes")] + InvalidStateSpan { contract: String, offset: usize, len: usize, script_len: usize }, + #[error("contract `{contract}` runtime state field `{field}` has unsupported type `{ty}`")] + UnsupportedRuntimeStateType { contract: String, field: String, ty: String }, + #[error("contract `{contract}` state span does not encode its runtime state: {message}")] + InvalidRuntimeStateEncoding { contract: String, message: String }, + #[error("contract `{contract}` state span is not canonically encoded")] + NonCanonicalRuntimeStateEncoding { contract: String }, + #[error("contract `{contract}` template hash mismatch: expected `{expected}`, found `{found}`")] + TemplateHashMismatch { contract: String, expected: String, found: String }, + #[error("contract `{contract}` entries `{first}` and `{second}` have the same dispatch tag `{tag}`")] + DispatchTagCollision { contract: String, first: String, second: String, tag: String }, + #[error("contract `{contract}` metadata references unknown entry `{entry}`")] + UnknownEntryReference { contract: String, entry: String }, +} + +pub type CodecResult = std::result::Result; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "kind", content = "value", rename_all = "snake_case")] +pub enum ArtifactValue { + Int(i64), + Bool(bool), + Byte(u8), + Bytes(Vec), + Text(String), + Array(Vec), + Object(BTreeMap), +} + +impl From for ArtifactValue { + fn from(value: i64) -> Self { + Self::Int(value) + } +} + +impl From for ArtifactValue { + fn from(value: i32) -> Self { + Self::Int(i64::from(value)) + } +} + +impl From for ArtifactValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +impl From for ArtifactValue { + fn from(value: u8) -> Self { + Self::Byte(value) + } +} + +impl From> for ArtifactValue { + fn from(value: Vec) -> Self { + Self::Bytes(value) + } +} + +impl From> for ArtifactValue { + fn from(value: Vec) -> Self { + Self::Array(value.into_iter().map(Self::Int).collect()) + } +} + +impl From for ArtifactValue { + fn from(value: String) -> Self { + Self::Text(value) + } +} + +impl From<&str> for ArtifactValue { + fn from(value: &str) -> Self { + Self::Text(value.to_string()) + } +} + +impl From> for ArtifactValue { + fn from(value: Vec) -> Self { + Self::Array(value) + } +} + +impl From> for ArtifactValue { + fn from(value: BTreeMap) -> Self { + Self::Object(value) + } +} + +#[derive(Debug, Error, Clone, PartialEq)] +pub enum CodecError { + #[error("unknown contract `{0}`")] + UnknownContract(String), + #[error("unknown entry `{contract}::{entry}`")] + UnknownEntry { contract: String, entry: String }, + #[error("unknown struct `{0}`")] + UnknownStruct(String), + #[error("entry `{entry}` expects {expected} arguments, got {actual}")] + WrongArgumentCount { entry: String, expected: usize, actual: usize }, + #[error("missing field `{0}`")] + MissingField(String), + #[error("unknown field `{0}`")] + UnknownField(String), + #[error("duplicate field `{0}`")] + DuplicateField(String), + #[error("expected {expected}, got {actual}")] + TypeMismatch { expected: String, actual: String }, + #[error("unsupported artifact type `{0}`")] + UnsupportedType(String), + #[error("`{name}` expects {expected} bytes, got {actual}")] + InvalidLength { name: String, expected: usize, actual: usize }, + #[error("number {value} does not fit in {size} bytes")] + InvalidNumber { value: i64, size: usize }, + #[error("invalid hex: {0}")] + InvalidHex(#[from] faster_hex::Error), + #[error("script builder error: {0}")] + ScriptBuilder(#[from] ScriptBuilderError), + #[error("invalid push-only script: {0}")] + InvalidPush(String), + #[error("state script has {len} trailing bytes at offset {offset}")] + TrailingStateBytes { offset: usize, len: usize }, +} + +pub fn encode_contract_entry_sig_script( + abi: &SilAbiArtifact, + contract_name: &str, + entry_name: &str, + args: &[ArtifactValue], +) -> CodecResult> { + let contract = abi.contracts.get(contract_name).ok_or_else(|| CodecError::UnknownContract(contract_name.to_string()))?; + let entry = contract + .entries + .get(entry_name) + .ok_or_else(|| CodecError::UnknownEntry { contract: contract_name.to_string(), entry: entry_name.to_string() })?; + encode_entry_sig_script(abi, contract_name, contract, entry_name, entry, args) +} + +pub fn encode_contract_covenant_decl_sig_script( + abi: &SilAbiArtifact, + contract_name: &str, + declaration_name: &str, + is_leader: bool, + args: &[ArtifactValue], +) -> CodecResult> { + let contract = abi.contracts.get(contract_name).ok_or_else(|| CodecError::UnknownContract(contract_name.to_string()))?; + let entry_name = if is_leader || contract.delegate_entry_abi.is_none() { + contract.cov_decl_to_abi.get(declaration_name) + } else { + contract.delegate_entry_abi.as_ref() + } + .ok_or_else(|| CodecError::UnknownEntry { contract: contract_name.to_string(), entry: declaration_name.to_string() })?; + let entry = contract + .entries + .get(entry_name) + .ok_or_else(|| CodecError::UnknownEntry { contract: contract_name.to_string(), entry: declaration_name.to_string() })?; + encode_entry_sig_script(abi, contract_name, contract, entry_name, entry, args) +} + +pub fn encode_entry_sig_script( + abi: &SilAbiArtifact, + contract_name: &str, + contract: &SilContractArtifact, + entry_name: &str, + entry: &SilEntryArtifact, + args: &[ArtifactValue], +) -> CodecResult> { + let params = entry_params(entry); + if params.len() != args.len() { + return Err(CodecError::WrongArgumentCount { + entry: format!("{contract_name}::{entry_name}"), + expected: params.len(), + actual: args.len(), + }); + } + + let ctx = TypeContext::new(abi, contract); + let mut builder = script_builder(); + for ((name, ty), value) in params.iter().zip(args) { + push_sig_arg(&mut builder, &ctx, name, ty, value)?; + } + builder.add_data(entry.dispatch_tag.as_bytes())?; + Ok(builder.drain()) +} + +pub fn encode_runtime_state_script( + abi: &SilAbiArtifact, + runtime_state: &RuntimeStateArtifact, + values: &BTreeMap, +) -> CodecResult> { + for name in values.keys() { + if runtime_state.fields.iter().all(|field| &field.name != name) { + return Err(CodecError::UnknownField(name.clone())); + } + } + + let mut builder = script_builder(); + for field in &runtime_state.fields { + let value = values.get(&field.name).ok_or_else(|| CodecError::MissingField(field.name.clone()))?; + for (leaf_ty, leaf_value) in flatten_sruct_value(abi, &field.name, &field.ty, value)? { + let payload = encode_state_payload(&field.name, &leaf_ty, &leaf_value)?; + builder.add_data_with_push_opcode(&payload)?; + } + } + Ok(builder.drain()) +} + +pub fn decode_runtime_state_script( + abi: &SilAbiArtifact, + runtime_state: &RuntimeStateArtifact, + state_script: &[u8], +) -> CodecResult> { + let pushes = parse_pushes(state_script)?; + let field_leaf_types = + runtime_state.fields.iter().map(|field| flatten_struct_types(abi, &field.ty)).collect::>>()?; + let expected_pushes = field_leaf_types.iter().map(Vec::len).sum::(); + if pushes.len() < expected_pushes { + return Err(CodecError::InvalidPush(format!("expected {expected_pushes} state pushes, got {}", pushes.len()))); + } + if pushes.len() > expected_pushes { + let offset = pushes[expected_pushes].0; + return Err(CodecError::TrailingStateBytes { offset, len: state_script.len() - offset }); + } + + let mut values = BTreeMap::new(); + let mut pushes = pushes.into_iter(); + for (field, leaf_types) in runtime_state.fields.iter().zip(field_leaf_types) { + let leaf_values = leaf_types + .iter() + .map(|leaf_ty| { + let (_, payload) = pushes.next().expect("state push count was checked"); + decode_state_payload(&field.name, leaf_ty, &payload) + }) + .collect::>>()?; + let (value, consumed) = reconstruct_struct_value(abi, &field.name, &field.ty, &leaf_values)?; + if consumed != leaf_values.len() { + return Err(CodecError::UnsupportedType(format!("invalid flattened state layout for {}", field.name))); + } + values.insert(field.name.clone(), value); + } + Ok(values) +} + +fn flatten_struct_types(abi: &SilAbiArtifact, ty: &TypeArtifact) -> CodecResult> { + fn flatten(abi: &SilAbiArtifact, ty: &TypeArtifact, visiting: &mut BTreeSet) -> CodecResult> { + match ty { + TypeArtifact::Struct { name } => { + if !visiting.insert(name.clone()) { + return Err(CodecError::UnsupportedType(format!("cyclic struct {name}"))); + } + let structure = abi.structs.get(name).ok_or_else(|| CodecError::UnknownStruct(name.clone()))?; + let mut leaves = Vec::new(); + for field in &structure.fields { + leaves.extend(flatten(abi, &field.ty, visiting)?); + } + visiting.remove(name); + Ok(leaves) + } + TypeArtifact::FixedArray { item, len } => { + let item_leaves = flatten(abi, item, visiting)?; + if is_single_state_leaf(item, &item_leaves) { + Ok(vec![ty.clone()]) + } else { + Ok(item_leaves.into_iter().map(|item| array_type(item, Some(*len))).collect()) + } + } + TypeArtifact::DynamicArray { item } => { + let item_leaves = flatten(abi, item, visiting)?; + if is_single_state_leaf(item, &item_leaves) { + Ok(vec![ty.clone()]) + } else { + Ok(item_leaves.into_iter().map(|item| array_type(item, None)).collect()) + } + } + _ => Ok(vec![ty.clone()]), + } + } + + flatten(abi, ty, &mut BTreeSet::new()) +} + +fn flatten_sruct_value( + abi: &SilAbiArtifact, + name: &str, + ty: &TypeArtifact, + value: &ArtifactValue, +) -> CodecResult> { + fn flatten( + abi: &SilAbiArtifact, + name: &str, + ty: &TypeArtifact, + value: &ArtifactValue, + visiting: &mut BTreeSet, + ) -> CodecResult> { + match ty { + TypeArtifact::Struct { name: struct_name } => { + if !visiting.insert(struct_name.clone()) { + return Err(CodecError::UnsupportedType(format!("cyclic struct {struct_name}"))); + } + let structure = abi.structs.get(struct_name).ok_or_else(|| CodecError::UnknownStruct(struct_name.clone()))?; + let fields = object_fields(value)?; + assert_no_extra_fields(fields, &structure.fields)?; + let mut leaves = Vec::new(); + for field in &structure.fields { + let field_value = fields.get(&field.name).ok_or_else(|| CodecError::MissingField(field.name.clone()))?; + leaves.extend(flatten(abi, &field.name, &field.ty, field_value, visiting)?); + } + visiting.remove(struct_name); + Ok(leaves) + } + TypeArtifact::FixedArray { item, len } => flatten_struct_array(abi, name, item, Some(*len), value, visiting), + TypeArtifact::DynamicArray { item } => flatten_struct_array(abi, name, item, None, value, visiting), + _ => Ok(vec![(ty.clone(), value.clone())]), + } + } + + fn flatten_struct_array( + abi: &SilAbiArtifact, + name: &str, + item: &TypeArtifact, + fixed_len: Option, + value: &ArtifactValue, + visiting: &mut BTreeSet, + ) -> CodecResult> { + let item_leaf_types = flatten_struct_types(abi, item)?; + if is_single_state_leaf(item, &item_leaf_types) { + return Ok(vec![(array_type(item.clone(), fixed_len), value.clone())]); + } + + let values = expect_array(value)?; + if let Some(expected) = fixed_len { + require_len(name, expected, values.len())?; + } + let grouped_values = group_flattened_array_items(abi, name, item, values, &item_leaf_types, visiting)?; + + Ok(item_leaf_types + .into_iter() + .zip(grouped_values) + .map(|(item, values)| (array_type(item, fixed_len), ArtifactValue::Array(values))) + .collect()) + } + + fn group_flattened_array_items( + abi: &SilAbiArtifact, + name: &str, + item: &TypeArtifact, + values: &[ArtifactValue], + item_leaf_types: &[TypeArtifact], + visiting: &mut BTreeSet, + ) -> CodecResult>> { + let mut grouped_values = vec![Vec::with_capacity(values.len()); item_leaf_types.len()]; + for value in values { + let item_leaves = flatten(abi, name, item, value, visiting)?; + if item_leaves.len() != item_leaf_types.len() { + return Err(invalid_flattened_state_layout(name)); + } + for ((expected_ty, group), (actual_ty, value)) in item_leaf_types.iter().zip(&mut grouped_values).zip(item_leaves) { + if expected_ty != &actual_ty { + return Err(invalid_flattened_state_layout(name)); + } + group.push(value); + } + } + Ok(grouped_values) + } + + flatten(abi, name, ty, value, &mut BTreeSet::new()) +} + +fn is_single_state_leaf(item: &TypeArtifact, item_leaf_types: &[TypeArtifact]) -> bool { + item_leaf_types == std::slice::from_ref(item) +} + +fn array_type(item: TypeArtifact, fixed_len: Option) -> TypeArtifact { + match fixed_len { + Some(len) => TypeArtifact::FixedArray { item: Box::new(item), len }, + None => TypeArtifact::DynamicArray { item: Box::new(item) }, + } +} + +fn invalid_flattened_state_layout(name: &str) -> CodecError { + CodecError::UnsupportedType(format!("invalid flattened state layout for {name}")) +} + +fn reconstruct_struct_value( + abi: &SilAbiArtifact, + name: &str, + ty: &TypeArtifact, + leaves: &[ArtifactValue], +) -> CodecResult<(ArtifactValue, usize)> { + match ty { + TypeArtifact::Struct { name: struct_name } => { + let structure = abi.structs.get(struct_name).ok_or_else(|| CodecError::UnknownStruct(struct_name.clone()))?; + let mut fields = BTreeMap::new(); + let mut consumed = 0; + for field in &structure.fields { + let (value, field_consumed) = reconstruct_struct_value(abi, &field.name, &field.ty, &leaves[consumed..])?; + consumed += field_consumed; + fields.insert(field.name.clone(), value); + } + Ok((ArtifactValue::Object(fields), consumed)) + } + TypeArtifact::FixedArray { item, len } => reconstruct_struct_array(abi, name, item, Some(*len), leaves), + TypeArtifact::DynamicArray { item } => reconstruct_struct_array(abi, name, item, None, leaves), + _ => leaves + .first() + .cloned() + .map(|value| (value, 1)) + .ok_or_else(|| CodecError::InvalidPush(format!("missing flattened state value for {name}"))), + } +} + +fn reconstruct_struct_array( + abi: &SilAbiArtifact, + name: &str, + item: &TypeArtifact, + expected_len: Option, + leaves: &[ArtifactValue], +) -> CodecResult<(ArtifactValue, usize)> { + let item_leaf_types = flatten_struct_types(abi, item)?; + if item_leaf_types.as_slice() == std::slice::from_ref(item) { + return leaves + .first() + .cloned() + .map(|value| (value, 1)) + .ok_or_else(|| CodecError::InvalidPush(format!("missing flattened state value for {name}"))); + } + if item_leaf_types.is_empty() { + return Err(CodecError::UnsupportedType(format!("zero-field struct array {name}"))); + } + + let leaf_values = leaves + .get(..item_leaf_types.len()) + .ok_or_else(|| CodecError::InvalidPush(format!("missing flattened state values for {name}")))?; + let arrays = leaf_values.iter().map(expect_array).collect::>>()?; + let actual_len = arrays[0].len(); + if let Some(expected) = expected_len { + require_len(name, expected, actual_len)?; + } + for array in &arrays[1..] { + require_len(name, actual_len, array.len())?; + } + + let mut values = Vec::with_capacity(actual_len); + for index in 0..actual_len { + let item_leaves = arrays.iter().map(|array| array[index].clone()).collect::>(); + let (value, consumed) = reconstruct_struct_value(abi, name, item, &item_leaves)?; + if consumed != item_leaves.len() { + return Err(CodecError::UnsupportedType(format!("invalid flattened state layout for {name}"))); + } + values.push(value); + } + Ok((ArtifactValue::Array(values), item_leaf_types.len())) +} + +pub fn encode_struct_payload( + abi: &SilAbiArtifact, + contract: &SilContractArtifact, + struct_name: &str, + values: &BTreeMap, +) -> CodecResult> { + let ctx = TypeContext::new(abi, contract); + encode_struct_fields_payload(ctx.struct_by_name(struct_name)?, values) +} + +pub fn decode_hex(hex: &str) -> CodecResult> { + let mut bytes = vec![0; hex.len() / 2]; + faster_hex::hex_decode(hex.as_bytes(), &mut bytes)?; + Ok(bytes) +} + +pub fn encode_hex(bytes: &[u8]) -> String { + let mut out = vec![0; bytes.len() * 2]; + faster_hex::hex_encode(bytes, &mut out).expect("hex output buffer is exactly twice the input length"); + String::from_utf8(out).expect("hex is always valid ascii") +} + +fn verify_compiled_contract( + abi: &SilAbiArtifact, + contract_name: &str, + contract: &SilContractArtifact, +) -> std::result::Result<(), SilAbiVerificationError> { + let script = &contract.compiled.bytecode; + + for entry_name in contract.cov_decl_to_abi.values().chain(contract.delegate_entry_abi.iter()) { + if !contract.entries.contains_key(entry_name) { + return Err(SilAbiVerificationError::UnknownEntryReference { + contract: contract_name.to_string(), + entry: entry_name.clone(), + }); + } + } + + let mut entries_by_tag = BTreeMap::::new(); + for (entry_name, entry) in &contract.entries { + let tag = entry.dispatch_tag; + if let Some(first) = entries_by_tag.insert(tag, entry_name) { + return Err(SilAbiVerificationError::DispatchTagCollision { + contract: contract_name.to_string(), + first: first.to_string(), + second: entry_name.clone(), + tag: tag.to_hex(), + }); + } + } + + let span = &contract.compiled.state_span; + let Some((prefix, state_script, suffix)) = contract.compiled.script_parts(script) else { + return Err(SilAbiVerificationError::InvalidStateSpan { + contract: contract_name.to_string(), + offset: span.offset, + len: span.len, + script_len: script.len(), + }); + }; + for field in &contract.runtime_state.fields { + if !is_supported_runtime_state_type(abi, &field.ty) { + return Err(SilAbiVerificationError::UnsupportedRuntimeStateType { + contract: contract_name.to_string(), + field: field.name.clone(), + ty: type_name(&field.ty), + }); + } + } + let state_values = decode_runtime_state_script(abi, &contract.runtime_state, state_script).map_err(|err| { + SilAbiVerificationError::InvalidRuntimeStateEncoding { contract: contract_name.to_string(), message: err.to_string() } + })?; + let canonical_state = encode_runtime_state_script(abi, &contract.runtime_state, &state_values).map_err(|err| { + SilAbiVerificationError::InvalidRuntimeStateEncoding { contract: contract_name.to_string(), message: err.to_string() } + })?; + if canonical_state != state_script { + return Err(SilAbiVerificationError::NonCanonicalRuntimeStateEncoding { contract: contract_name.to_string() }); + } + let expected_hash = template_hash(prefix, suffix); + if contract.compiled.template_hash != expected_hash { + return Err(SilAbiVerificationError::TemplateHashMismatch { + contract: contract_name.to_string(), + expected: encode_hex(&expected_hash), + found: encode_hex(&contract.compiled.template_hash), + }); + } + Ok(()) +} + +fn is_supported_runtime_state_type(abi: &SilAbiArtifact, ty: &TypeArtifact) -> bool { + fn is_supported(abi: &SilAbiArtifact, ty: &TypeArtifact, visiting: &mut BTreeSet) -> bool { + match ty { + TypeArtifact::Struct { name } => { + if !visiting.insert(name.clone()) { + // We don't support cyclic structs. + return false; + } + let supported = abi + .structs + .get(name) + .is_some_and(|structure| structure.fields.iter().all(|field| is_supported(abi, &field.ty, visiting))); + visiting.remove(name); + supported + } + TypeArtifact::FixedArray { item, .. } => is_supported(abi, item, visiting), + _ => fixed_payload_len(ty).is_some(), + } + } + + is_supported(abi, ty, &mut BTreeSet::new()) +} + +fn entry_params(entry: &SilEntryArtifact) -> Vec<(&str, &TypeArtifact)> { + entry.params.iter().map(|param| (param.name.as_str(), ¶m.ty)).collect() +} + +struct TypeContext<'a> { + structs: &'a BTreeMap, + runtime_state: StructArtifact, +} + +impl<'a> TypeContext<'a> { + fn new(abi: &'a SilAbiArtifact, contract: &'a SilContractArtifact) -> Self { + Self { + structs: &abi.structs, + runtime_state: StructArtifact { + fields: contract + .runtime_state + .fields + .iter() + .map(|field| FieldArtifact { name: field.name.clone(), ty: field.ty.clone() }) + .collect(), + }, + } + } + + fn struct_by_name(&self, name: &str) -> CodecResult<&StructArtifact> { + if name == "State" { + return Ok(&self.runtime_state); + } + self.structs.get(name).ok_or_else(|| CodecError::UnknownStruct(name.to_string())) + } +} + +fn script_builder() -> ScriptBuilder { + ScriptBuilder::with_flags(EngineFlags { covenants_enabled: true, ..Default::default() }) +} + +fn push_sig_arg( + builder: &mut ScriptBuilder, + ctx: &TypeContext<'_>, + name: &str, + ty: &TypeArtifact, + value: &ArtifactValue, +) -> CodecResult<()> { + match ty { + TypeArtifact::Struct { name: struct_name } => { + let fields = object_fields(value)?; + push_struct_fields(builder, ctx, ctx.struct_by_name(struct_name)?, fields) + } + TypeArtifact::FixedArray { item, len } if matches!(item.as_ref(), TypeArtifact::Struct { .. }) => { + push_struct_array_fields(builder, ctx, item, Some(*len), value) + } + TypeArtifact::DynamicArray { item } if matches!(item.as_ref(), TypeArtifact::Struct { .. }) => { + push_struct_array_fields(builder, ctx, item, None, value) + } + TypeArtifact::Int | TypeArtifact::Temporal => push_i64(builder, expect_int(value)?), + TypeArtifact::Bool => push_i64(builder, i64::from(expect_bool(value)?)), + TypeArtifact::Byte => { + push_data(builder, &[expect_byte(value)?])?; + Ok(()) + } + TypeArtifact::Bytes => { + push_data(builder, expect_bytes(value)?)?; + Ok(()) + } + TypeArtifact::Text => { + push_data(builder, expect_text(value)?.as_bytes())?; + Ok(()) + } + TypeArtifact::Pubkey => push_fixed_bytes(builder, name, value, 32), + TypeArtifact::Sig => push_fixed_bytes(builder, name, value, 65), + TypeArtifact::Datasig => push_fixed_bytes(builder, name, value, 64), + TypeArtifact::FixedBytes { len } => push_fixed_bytes(builder, name, value, *len), + TypeArtifact::FixedArray { item, len } => { + let payload = encode_array_payload(name, item, Some(*len), value)?; + push_data(builder, &payload)?; + Ok(()) + } + TypeArtifact::DynamicArray { item } => { + let payload = encode_array_payload(name, item, None, value)?; + push_data(builder, &payload)?; + Ok(()) + } + } +} + +fn push_struct_fields( + builder: &mut ScriptBuilder, + ctx: &TypeContext<'_>, + structure: &StructArtifact, + fields: &BTreeMap, +) -> CodecResult<()> { + assert_no_extra_fields(fields, &structure.fields)?; + for field in &structure.fields { + let value = fields.get(&field.name).ok_or_else(|| CodecError::MissingField(field.name.clone()))?; + push_sig_arg(builder, ctx, &field.name, &field.ty, value)?; + } + Ok(()) +} + +fn push_struct_array_fields( + builder: &mut ScriptBuilder, + ctx: &TypeContext<'_>, + item: &TypeArtifact, + expected_len: Option, + value: &ArtifactValue, +) -> CodecResult<()> { + let TypeArtifact::Struct { name } = item else { + return Err(CodecError::UnsupportedType(type_name(item))); + }; + let structure = ctx.struct_by_name(name)?; + let values = expect_array(value)?; + if let Some(expected) = expected_len { + require_len(name, expected, values.len())?; + } + + let mut object_values = Vec::with_capacity(values.len()); + for value in values { + let fields = object_fields(value)?; + assert_no_extra_fields(fields, &structure.fields)?; + object_values.push(fields); + } + + for field in &structure.fields { + let mut field_values = Vec::with_capacity(object_values.len()); + for object in &object_values { + field_values.push(object.get(&field.name).ok_or_else(|| CodecError::MissingField(field.name.clone()))?.clone()); + } + push_sig_arg( + builder, + ctx, + &field.name, + &TypeArtifact::DynamicArray { item: Box::new(field.ty.clone()) }, + &ArtifactValue::Array(field_values), + )?; + } + Ok(()) +} + +fn encode_state_payload(name: &str, ty: &TypeArtifact, value: &ArtifactValue) -> CodecResult> { + match ty { + TypeArtifact::Bytes => Ok(expect_bytes(value)?.to_vec()), + TypeArtifact::Text => Ok(expect_text(value)?.as_bytes().to_vec()), + TypeArtifact::DynamicArray { item } => encode_array_payload(name, item, None, value), + _ => encode_fixed_payload(name, ty, value), + } +} + +fn encode_struct_fields_payload(structure: &StructArtifact, fields: &BTreeMap) -> CodecResult> { + assert_no_extra_fields(fields, &structure.fields)?; + let mut out = Vec::new(); + for field in &structure.fields { + let value = fields.get(&field.name).ok_or_else(|| CodecError::MissingField(field.name.clone()))?; + out.extend(encode_state_payload(&field.name, &field.ty, value)?); + } + Ok(out) +} + +fn decode_state_payload(name: &str, ty: &TypeArtifact, payload: &[u8]) -> CodecResult { + match ty { + TypeArtifact::Int | TypeArtifact::Temporal => { + require_len(name, 8, payload.len())?; + Ok(ArtifactValue::Int(deserialize_fixed_i64(payload)?)) + } + TypeArtifact::Bool => { + require_len(name, 1, payload.len())?; + match payload[0] { + 0 => Ok(ArtifactValue::Bool(false)), + 1 => Ok(ArtifactValue::Bool(true)), + value => Err(CodecError::TypeMismatch { expected: "bool byte 0 or 1".to_string(), actual: value.to_string() }), + } + } + TypeArtifact::Byte => { + require_len(name, 1, payload.len())?; + Ok(ArtifactValue::Byte(payload[0])) + } + TypeArtifact::Bytes => Ok(ArtifactValue::Bytes(payload.to_vec())), + TypeArtifact::Text => String::from_utf8(payload.to_vec()) + .map(ArtifactValue::Text) + .map_err(|err| CodecError::TypeMismatch { expected: "utf-8 string".to_string(), actual: err.to_string() }), + TypeArtifact::Pubkey => decode_fixed_bytes(name, payload, 32), + TypeArtifact::Sig => decode_fixed_bytes(name, payload, 65), + TypeArtifact::Datasig => decode_fixed_bytes(name, payload, 64), + TypeArtifact::FixedBytes { len } => decode_fixed_bytes(name, payload, *len), + TypeArtifact::FixedArray { item, len } => decode_array_payload(name, item, Some(*len), payload), + TypeArtifact::DynamicArray { item } => decode_array_payload(name, item, None, payload), + TypeArtifact::Struct { name } => Err(CodecError::UnsupportedType(format!("state struct {name}"))), + } +} + +fn decode_fixed_bytes(name: &str, payload: &[u8], expected: usize) -> CodecResult { + require_len(name, expected, payload.len())?; + Ok(ArtifactValue::Bytes(payload.to_vec())) +} + +fn decode_array_payload(name: &str, item: &TypeArtifact, expected_len: Option, payload: &[u8]) -> CodecResult { + let item_len = fixed_payload_len(item).ok_or_else(|| CodecError::UnsupportedType(type_name(item)))?; + if item_len == 0 || !payload.len().is_multiple_of(item_len) { + return Err(CodecError::InvalidLength { name: name.to_string(), expected: item_len, actual: payload.len() }); + } + let actual_len = payload.len() / item_len; + if let Some(expected) = expected_len { + require_len(name, expected, actual_len)?; + } + payload + .chunks_exact(item_len) + .map(|chunk| decode_state_payload(name, item, chunk)) + .collect::>>() + .map(ArtifactValue::Array) +} + +fn encode_array_payload(name: &str, item: &TypeArtifact, expected_len: Option, value: &ArtifactValue) -> CodecResult> { + if matches!(item, TypeArtifact::Struct { .. }) { + return Err(CodecError::UnsupportedType(type_name(item))); + } + let values = expect_array(value)?; + if let Some(expected) = expected_len { + require_len(name, expected, values.len())?; + } + let mut out = Vec::new(); + for value in values { + out.extend(encode_fixed_payload(name, item, value)?); + } + Ok(out) +} + +fn encode_fixed_payload(name: &str, ty: &TypeArtifact, value: &ArtifactValue) -> CodecResult> { + match ty { + TypeArtifact::Int | TypeArtifact::Temporal => serialize_fixed_i64(expect_int(value)?, 8), + TypeArtifact::Bool => Ok(vec![u8::from(expect_bool(value)?)]), + TypeArtifact::Byte => Ok(vec![expect_byte(value)?]), + TypeArtifact::Pubkey => fixed_bytes(name, value, 32), + TypeArtifact::Sig => fixed_bytes(name, value, 65), + TypeArtifact::Datasig => fixed_bytes(name, value, 64), + TypeArtifact::FixedBytes { len } => fixed_bytes(name, value, *len), + TypeArtifact::FixedArray { item, len } => encode_array_payload(name, item, Some(*len), value), + TypeArtifact::Bytes | TypeArtifact::Text | TypeArtifact::DynamicArray { .. } | TypeArtifact::Struct { .. } => { + Err(CodecError::UnsupportedType(type_name(ty))) + } + } +} + +fn fixed_payload_len(ty: &TypeArtifact) -> Option { + match ty { + TypeArtifact::Int | TypeArtifact::Temporal => Some(8), + TypeArtifact::Bool => Some(1), + TypeArtifact::Byte => Some(1), + TypeArtifact::Pubkey => Some(32), + TypeArtifact::Sig => Some(65), + TypeArtifact::Datasig => Some(64), + TypeArtifact::FixedBytes { len } => Some(*len), + TypeArtifact::FixedArray { item, len } => fixed_payload_len(item).and_then(|item_len| item_len.checked_mul(*len)), + TypeArtifact::Bytes | TypeArtifact::Text | TypeArtifact::DynamicArray { .. } | TypeArtifact::Struct { .. } => None, + } +} + +fn push_fixed_bytes(builder: &mut ScriptBuilder, name: &str, value: &ArtifactValue, expected: usize) -> CodecResult<()> { + let bytes = fixed_bytes(name, value, expected)?; + push_data(builder, &bytes)?; + Ok(()) +} + +fn fixed_bytes(name: &str, value: &ArtifactValue, expected: usize) -> CodecResult> { + let bytes = expect_bytes(value)?; + require_len(name, expected, bytes.len())?; + Ok(bytes.to_vec()) +} + +fn push_i64(builder: &mut ScriptBuilder, value: i64) -> CodecResult<()> { + if value == i64::MIN { + return Err(CodecError::InvalidNumber { value, size: 8 }); + } + builder.add_i64(value)?; + Ok(()) +} + +fn push_data(builder: &mut ScriptBuilder, data: &[u8]) -> CodecResult<()> { + builder.add_data(data)?; + Ok(()) +} + +fn parse_pushes(script: &[u8]) -> CodecResult)>> { + let mut pushes = Vec::new(); + let mut offset = 0; + while offset < script.len() { + let start = offset; + let opcode = script[offset]; + offset += 1; + let len = match opcode { + OP_0 => { + pushes.push((start, Vec::new())); + continue; + } + OP_DATA_1..=OP_DATA_75 => opcode as usize, + OP_PUSH_DATA_1 => { + let bytes = read_len(script, &mut offset, 1)?; + bytes[0] as usize + } + OP_PUSH_DATA_2 => { + let bytes = read_len(script, &mut offset, 2)?; + u16::from_le_bytes([bytes[0], bytes[1]]) as usize + } + OP_PUSH_DATA_4 => { + let bytes = read_len(script, &mut offset, 4)?; + u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize + } + OP_1_NEGATE | OP_1..=OP_16 => { + return Err(CodecError::InvalidPush(format!("small-int opcode {opcode:#x} is not valid in state span"))); + } + _ => return Err(CodecError::InvalidPush(format!("opcode {opcode:#x} is not a push-data opcode"))), + }; + let data = read_len(script, &mut offset, len)?.to_vec(); + pushes.push((start, data)); + } + Ok(pushes) +} + +fn read_len<'a>(script: &'a [u8], offset: &mut usize, len: usize) -> CodecResult<&'a [u8]> { + let end = offset.checked_add(len).ok_or_else(|| CodecError::InvalidPush("push length overflow".to_string()))?; + if end > script.len() { + return Err(CodecError::InvalidPush(format!( + "push at offset {} needs {} bytes, only {} remain", + *offset, + len, + script.len().saturating_sub(*offset) + ))); + } + let bytes = &script[*offset..end]; + *offset = end; + Ok(bytes) +} + +fn serialize_fixed_i64(value: i64, size: usize) -> CodecResult> { + serialize_script_i64(value, Some(size)).map(|bytes| bytes.into_vec()).map_err(|_| CodecError::InvalidNumber { value, size }) +} + +fn deserialize_fixed_i64(bytes: &[u8]) -> CodecResult { + deserialize_script_i64(bytes, false).map_err(|err| CodecError::InvalidPush(err.to_string())) +} + +fn expect_int(value: &ArtifactValue) -> CodecResult { + match value { + ArtifactValue::Int(value) => Ok(*value), + other => type_mismatch("int", other), + } +} + +fn expect_bool(value: &ArtifactValue) -> CodecResult { + match value { + ArtifactValue::Bool(value) => Ok(*value), + other => type_mismatch("bool", other), + } +} + +fn expect_byte(value: &ArtifactValue) -> CodecResult { + match value { + ArtifactValue::Byte(value) => Ok(*value), + other => type_mismatch("byte", other), + } +} + +fn expect_bytes(value: &ArtifactValue) -> CodecResult<&[u8]> { + match value { + ArtifactValue::Bytes(value) => Ok(value), + other => type_mismatch("bytes", other), + } +} + +fn expect_text(value: &ArtifactValue) -> CodecResult<&str> { + match value { + ArtifactValue::Text(value) => Ok(value), + other => type_mismatch("string", other), + } +} + +fn expect_array(value: &ArtifactValue) -> CodecResult<&[ArtifactValue]> { + match value { + ArtifactValue::Array(value) => Ok(value), + other => type_mismatch("array", other), + } +} + +fn object_fields(value: &ArtifactValue) -> CodecResult<&BTreeMap> { + match value { + ArtifactValue::Object(fields) => Ok(fields), + other => type_mismatch("object", other), + } +} + +fn type_mismatch(expected: &str, actual: &ArtifactValue) -> CodecResult { + Err(CodecError::TypeMismatch { expected: expected.to_string(), actual: value_name(actual).to_string() }) +} + +fn value_name(value: &ArtifactValue) -> &'static str { + match value { + ArtifactValue::Int(_) => "int", + ArtifactValue::Bool(_) => "bool", + ArtifactValue::Byte(_) => "byte", + ArtifactValue::Bytes(_) => "bytes", + ArtifactValue::Text(_) => "string", + ArtifactValue::Array(_) => "array", + ArtifactValue::Object(_) => "object", + } +} + +fn assert_no_extra_fields(fields: &BTreeMap, expected: &[FieldArtifact]) -> CodecResult<()> { + for name in fields.keys() { + if expected.iter().all(|field| &field.name != name) { + return Err(CodecError::UnknownField(name.clone())); + } + } + Ok(()) +} + +fn require_len(name: &str, expected: usize, actual: usize) -> CodecResult<()> { + if expected == actual { Ok(()) } else { Err(CodecError::InvalidLength { name: name.to_string(), expected, actual }) } +} + +fn type_name(ty: &TypeArtifact) -> String { + match ty { + TypeArtifact::Int => "int".to_string(), + TypeArtifact::Temporal => "temporal".to_string(), + TypeArtifact::Bool => "bool".to_string(), + TypeArtifact::Byte => "byte".to_string(), + TypeArtifact::Bytes => "bytes".to_string(), + TypeArtifact::Text => "string".to_string(), + TypeArtifact::Pubkey => "pubkey".to_string(), + TypeArtifact::Sig => "sig".to_string(), + TypeArtifact::Datasig => "datasig".to_string(), + TypeArtifact::FixedBytes { len } => format!("byte[{len}]"), + TypeArtifact::FixedArray { item, len } => format!("{}[{len}]", type_name(item)), + TypeArtifact::DynamicArray { item } => format!("{}[]", type_name(item)), + TypeArtifact::Struct { name } => name.clone(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn artifact_values_support_ergonomic_from_conversions() { + let values: Vec = vec![1.into(), vec![2u8; 4].into(), true.into(), 3u8.into(), "ready".into()]; + assert_eq!( + values, + vec![ + ArtifactValue::Int(1), + ArtifactValue::Bytes(vec![2; 4]), + ArtifactValue::Bool(true), + ArtifactValue::Byte(3), + ArtifactValue::Text("ready".to_string()), + ] + ); + assert_eq!(ArtifactValue::from(vec![ArtifactValue::Int(1)]), ArtifactValue::Array(vec![ArtifactValue::Int(1)])); + assert_eq!(ArtifactValue::from(vec![1i64, 2]), ArtifactValue::Array(vec![ArtifactValue::Int(1), ArtifactValue::Int(2)])); + assert_eq!( + ArtifactValue::from(BTreeMap::from([("value".to_string(), ArtifactValue::Int(1))])), + ArtifactValue::Object(BTreeMap::from([("value".to_string(), ArtifactValue::Int(1))])) + ); + } + + // Locks the ABI copy to Sil's canonical implementation. Ideally, Sil will + // expose this from a small shared core crate instead of requiring a copy. + #[test] + fn template_hash_matches_silverscript() { + let cases: &[(&[u8], &[u8])] = &[(b"", b""), (b"a", b"bc"), (b"ab", b"c"), (&[0, 1, 2, 3], &[0xff, 0x80, 0x40])]; + + for (prefix, suffix) in cases { + assert_eq!(template_hash(prefix, suffix), silverscript_lang::template::template_hash(prefix, suffix)); + } + + let kcc1_vectors: &[(&[u8], &[u8], &str)] = &[ + (&[], &[], "e572dff82304700b856a555ac3a4558d0df3646a3727816500270a93c66aac1e"), + (b"a", b"bc", "405e183e2494cdbe2df89349cc0ffa5b77fb885ad97a1d5660ecd0692ef8142a"), + (b"ab", b"c", "a0968c014f3fc7bd1a7d9a8d1ad1177eb379bd2f05e56309eb4e20347c5e7eba"), + (&[0x00, 0xff], &[0x10, 0x00, 0x80], "6616a66757315de0221cb2acba729113cebde31f8d3ca7fa93878a0584b96905"), + ]; + for (prefix, suffix, expected) in kcc1_vectors { + assert_eq!(encode_hex(&template_hash(prefix, suffix)), *expected); + } + } + + #[test] + fn verifies_compiled_contract_against_its_script() { + let mut abi = tiny_sil_abi(); + let prefix = [0xaa]; + abi.contracts.get_mut("Foo").unwrap().runtime_state.fields = + vec![RuntimeFieldArtifact { name: "value".to_string(), ty: TypeArtifact::Byte }]; + let state = encode_runtime_state_script( + &abi, + &abi.contracts["Foo"].runtime_state, + &BTreeMap::from([("value".to_string(), ArtifactValue::Byte(2))]), + ) + .expect("state encodes"); + let suffix = [0xbb, 0xcc]; + let contract = abi.contracts.get_mut("Foo").unwrap(); + contract.compiled.bytecode = [prefix.as_slice(), state.as_slice(), suffix.as_slice()].concat(); + contract.compiled.template_hash = template_hash(&prefix, &suffix); + contract.compiled.state_span = StateSpanArtifact { offset: prefix.len(), len: state.len() }; + + abi.verify().expect("compiled template matches its contract script"); + + abi.contracts.get_mut("Foo").unwrap().compiled.template_hash = [0; 32]; + assert!(matches!( + abi.verify(), + Err(SilAbiVerificationError::TemplateHashMismatch { ref contract, .. }) if contract == "Foo" + )); + + abi.contracts.get_mut("Foo").unwrap().compiled.template_hash = template_hash(&prefix, &suffix); + abi.contracts.get_mut("Foo").unwrap().compiled.bytecode = [&[0xff], state.as_slice(), suffix.as_slice()].concat(); + assert!(matches!( + abi.verify(), + Err(SilAbiVerificationError::TemplateHashMismatch { ref contract, .. }) if contract == "Foo" + )); + } + + #[test] + fn rejects_invalid_dispatch_tags_during_deserialization() { + let wrong_length = serde_json::from_str::(r#""00""#).expect_err("short tag should fail"); + assert!(wrong_length.to_string().contains("exactly 8 hexadecimal characters")); + + let invalid_hex = serde_json::from_str::(r#""zzzzzzzz""#).expect_err("invalid hex should fail"); + assert!(invalid_hex.to_string().contains("invalid dispatch tag hex")); + } + + #[test] + fn rejects_colliding_dispatch_tags() { + let mut abi = tiny_sil_abi(); + let step_tag = abi.contracts["Foo"].entries["step"].dispatch_tag; + abi.contracts.get_mut("Foo").unwrap().entries.get_mut("other").unwrap().dispatch_tag = step_tag; + assert!(matches!( + abi.verify(), + Err(SilAbiVerificationError::DispatchTagCollision { ref contract, ref first, ref second, .. }) + if contract == "Foo" && first == "other" && second == "step" + )); + } + + #[test] + fn rejects_unknown_entry_metadata_references() { + let mut abi = tiny_sil_abi(); + abi.contracts.get_mut("Foo").unwrap().cov_decl_to_abi.insert("spend".to_string(), "missing".to_string()); + + assert_eq!( + abi.verify(), + Err(SilAbiVerificationError::UnknownEntryReference { contract: "Foo".to_string(), entry: "missing".to_string() }) + ); + } + + #[test] + fn rejects_state_spans_that_do_not_match_the_runtime_state() { + let mut abi = tiny_sil_abi(); + abi.contracts.get_mut("Foo").unwrap().runtime_state.fields = + vec![RuntimeFieldArtifact { name: "value".to_string(), ty: TypeArtifact::Byte }]; + let prefix = [0xaa]; + let state = encode_runtime_state_script( + &abi, + &abi.contracts["Foo"].runtime_state, + &BTreeMap::from([("value".to_string(), ArtifactValue::Byte(2))]), + ) + .expect("state encodes"); + let suffix = [0xbb]; + let contract = abi.contracts.get_mut("Foo").unwrap(); + contract.compiled.bytecode = [prefix.as_slice(), state.as_slice(), suffix.as_slice()].concat(); + + contract.compiled.state_span = StateSpanArtifact { offset: 0, len: prefix.len() + state.len() }; + contract.compiled.template_hash = template_hash(&[], &suffix); + assert!(matches!( + abi.verify(), + Err(SilAbiVerificationError::InvalidRuntimeStateEncoding { ref contract, .. }) if contract == "Foo" + )); + + let contract = abi.contracts.get_mut("Foo").unwrap(); + contract.compiled.state_span = StateSpanArtifact { offset: prefix.len(), len: state.len() - 1 }; + contract.compiled.template_hash = template_hash(&prefix, &[state[state.len() - 1], suffix[0]]); + assert!(matches!( + abi.verify(), + Err(SilAbiVerificationError::InvalidRuntimeStateEncoding { ref contract, .. }) if contract == "Foo" + )); + } + + #[test] + fn rejects_noncanonical_and_variable_runtime_state_encodings() { + let mut abi = tiny_sil_abi(); + let contract = abi.contracts.get_mut("Foo").unwrap(); + contract.runtime_state.fields = vec![RuntimeFieldArtifact { name: "value".to_string(), ty: TypeArtifact::Byte }]; + let prefix = [0xaa]; + let state = [OP_PUSH_DATA_1, 1, 2]; + let suffix = [0xbb]; + contract.compiled.bytecode = [prefix.as_slice(), state.as_slice(), suffix.as_slice()].concat(); + contract.compiled.template_hash = template_hash(&prefix, &suffix); + contract.compiled.state_span = StateSpanArtifact { offset: prefix.len(), len: state.len() }; + assert_eq!(abi.verify(), Err(SilAbiVerificationError::NonCanonicalRuntimeStateEncoding { contract: "Foo".to_string() })); + + abi.contracts.get_mut("Foo").unwrap().runtime_state.fields[0].ty = TypeArtifact::Bytes; + assert_eq!( + abi.verify(), + Err(SilAbiVerificationError::UnsupportedRuntimeStateType { + contract: "Foo".to_string(), + field: "value".to_string(), + ty: "bytes".to_string(), + }) + ); + } + + #[test] + fn validates_struct_runtime_state_types_recursively() { + let mut abi = tiny_sil_abi(); + abi.structs = BTreeMap::from([ + ( + "Inner".to_string(), + StructArtifact { fields: vec![FieldArtifact { name: "value".to_string(), ty: TypeArtifact::Byte }] }, + ), + ( + "Outer".to_string(), + StructArtifact { + fields: vec![ + FieldArtifact { name: "inner".to_string(), ty: TypeArtifact::Struct { name: "Inner".to_string() } }, + FieldArtifact { + name: "values".to_string(), + ty: TypeArtifact::FixedArray { item: Box::new(TypeArtifact::Int), len: 2 }, + }, + ], + }, + ), + ]); + + assert!(is_supported_runtime_state_type(&abi, &TypeArtifact::Struct { name: "Outer".to_string() })); + + abi.structs.get_mut("Inner").unwrap().fields[0].ty = TypeArtifact::Bytes; + assert!(!is_supported_runtime_state_type(&abi, &TypeArtifact::Struct { name: "Outer".to_string() })); + + abi.structs.get_mut("Inner").unwrap().fields[0].ty = TypeArtifact::Struct { name: "Outer".to_string() }; + assert!(!is_supported_runtime_state_type(&abi, &TypeArtifact::Struct { name: "Outer".to_string() })); + } + + #[test] + fn encodes_sigscript_arguments_canonically() { + let artifact = tiny_sil_abi(); + let sigscript = encode_contract_entry_sig_script( + &artifact, + "Foo", + "step", + &[ArtifactValue::Int(17), ArtifactValue::Bytes(vec![1, 2, 3, 4]), ArtifactValue::Bool(true), ArtifactValue::Byte(1)], + ) + .expect("sigscript encodes"); + + assert_eq!(encode_hex(&sigscript), "011104010203045151042c49ed65"); + } + + #[test] + fn encodes_temporal_values_with_integer_payloads() { + assert_eq!(TypeArtifact::from_parts("temporal", None), TypeArtifact::Temporal); + assert_eq!(serde_json::to_value(TypeArtifact::Temporal).unwrap(), serde_json::json!({ "kind": "temporal" })); + + let mut artifact = tiny_sil_abi(); + artifact.contracts.get_mut("Foo").unwrap().entries.get_mut("other").unwrap().params = + vec![param("at", TypeArtifact::Temporal), param("history", TypeArtifact::dynamic_array(TypeArtifact::Temporal))]; + let history = ArtifactValue::Array(vec![ArtifactValue::Int(1), ArtifactValue::Int(-2)]); + let sigscript = encode_contract_entry_sig_script(&artifact, "Foo", "other", &[ArtifactValue::Int(-5), history.clone()]) + .expect("temporal arguments encode"); + let pushes = parse_pushes(&sigscript).expect("signature script contains only data pushes"); + assert_eq!( + pushes.iter().map(|(_, data)| data.clone()).collect::>(), + vec![ + vec![0x85], + [serialize_fixed_i64(1, 8).unwrap(), serialize_fixed_i64(-2, 8).unwrap()].concat(), + vec![0xde, 0xad, 0xbe, 0xef], + ] + ); + + let runtime_state = RuntimeStateArtifact { + source: "ClockState".to_string(), + fields: vec![ + RuntimeFieldArtifact { name: "at".to_string(), ty: TypeArtifact::Temporal }, + RuntimeFieldArtifact { + name: "history".to_string(), + ty: TypeArtifact::FixedArray { item: Box::new(TypeArtifact::Temporal), len: 2 }, + }, + ], + }; + let values = BTreeMap::from([("at".to_string(), ArtifactValue::Int(-5)), ("history".to_string(), history)]); + let encoded = encode_runtime_state_script(&artifact, &runtime_state, &values).expect("temporal state encodes"); + assert_eq!(decode_runtime_state_script(&artifact, &runtime_state, &encoded).expect("temporal state decodes"), values); + } + + #[test] + fn hex_helpers_use_faster_hex_and_report_invalid_input() { + assert_eq!(decode_hex("01110401020304515100").expect("hex decodes"), vec![1, 17, 4, 1, 2, 3, 4, 81, 81, 0]); + assert!(matches!(decode_hex("abc"), Err(CodecError::InvalidHex(_)))); + assert!(matches!(decode_hex("zz"), Err(CodecError::InvalidHex(_)))); + } + + #[test] + fn encodes_struct_payload_without_push_framing() { + let mut abi = tiny_sil_abi(); + abi.structs.insert( + "Memory".to_string(), + StructArtifact { + fields: vec![ + FieldArtifact { name: "hunger".to_string(), ty: TypeArtifact::Int }, + FieldArtifact { name: "tag".to_string(), ty: TypeArtifact::FixedBytes { len: 2 } }, + ], + }, + ); + let values = BTreeMap::from([ + ("hunger".to_string(), ArtifactValue::Int(17)), + ("tag".to_string(), ArtifactValue::Bytes(vec![0xaa, 0xbb])), + ]); + + let payload = encode_struct_payload(&abi, abi.contract("Foo").expect("contract exists"), "Memory", &values) + .expect("struct payload encodes"); + + assert_eq!(encode_hex(&payload), "1100000000000000aabb"); + } + + #[test] + fn deserializes_sil_abi_without_argent_coordination_metadata() { + let json = r#" + { + "schema_version": 1, + "compiler_version": "0.1.0", + "structs": { + "FooState": { + "fields": [{ "name": "count", "type": { "kind": "int" } }] + } + }, + "contracts": { + "Foo": { + "source_path": "sil/Foo.sil", + "runtime_state": { + "source": "FooState", + "fields": [{ "name": "count", "type": { "kind": "int" } }] + }, + "entries": { + "step": { + "dispatch_tag": "2c49ed65", + "params": [{ "name": "amount", "type": { "kind": "int" } }] + } + }, + "compiled": { + "bytecode": [0], + "template_hash": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "state_span": { "offset": 0, "len": 1 } + } + } + } + } + "#; + + let abi: SilAbiArtifact = serde_json::from_str(json).expect("sil abi should deserialize"); + abi.check_schema_version().expect("sil abi schema version should be supported"); + assert_eq!(abi.contract("Foo").expect("contract exists").compiled.bytecode, vec![0]); + assert_eq!( + abi.contract("Foo").and_then(|contract| contract.entry("step")).map(|entry| entry.dispatch_tag), + Some(DispatchTag::from([0x2c, 0x49, 0xed, 0x65])) + ); + assert_eq!( + abi.contract("Foo").and_then(|contract| contract.entry("step")).map(|entry| entry.params[0].name.as_str()), + Some("amount") + ); + let serialized = serde_json::to_value(&abi).expect("Sil ABI artifact serializes"); + assert_eq!(serialized["contracts"]["Foo"]["entries"]["step"]["dispatch_tag"], "2c49ed65"); + assert_eq!(serialized["contracts"]["Foo"]["compiled"]["bytecode"], serde_json::json!([0])); + assert_eq!( + serialized["contracts"]["Foo"]["compiled"]["template_hash"], + serde_json::to_value([0u8; 32]).expect("template hash serializes") + ); + } + + #[test] + fn pretty_json_wraps_byte_arrays_at_fixed_boundaries() { + let mut abi = tiny_sil_abi(); + abi.contracts.get_mut("Foo").unwrap().compiled.bytecode = (0..=64).collect(); + + let json = to_pretty_json(&abi).expect("Sil ABI artifact serializes"); + let lines = json.lines().collect::>(); + let bytecode_line = lines.iter().position(|line| line.trim() == r#""bytecode": ["#).expect("bytecode field exists"); + assert_eq!(lines[bytecode_line + 1].split(", ").count(), 64); + assert_eq!(lines[bytecode_line + 2].trim(), "64"); + assert_eq!(lines[bytecode_line + 3].trim(), "],"); + assert!(lines.iter().any(|line| line.trim_start().starts_with(r#""template_hash": [0, 0, 0"#))); + + let decoded: SilAbiArtifact = serde_json::from_str(&json).expect("Sil ABI artifact deserializes"); + assert_eq!(decoded.contracts["Foo"].compiled.bytecode, (0..=64).collect::>()); + } + + #[test] + fn rejects_sigscript_ints_that_do_not_fit_silverscript_script_number() { + let artifact = tiny_sil_abi(); + let err = encode_contract_entry_sig_script( + &artifact, + "Foo", + "step", + &[ArtifactValue::Int(i64::MIN), ArtifactValue::Bytes(vec![1, 2, 3, 4]), ArtifactValue::Bool(true), ArtifactValue::Byte(1)], + ) + .expect_err("i64::MIN needs 9 bytes and should match txscript rejection"); + + assert_eq!(err, CodecError::InvalidNumber { value: i64::MIN, size: 8 }); + } + + #[test] + fn round_trips_runtime_state_script() { + let abi = tiny_sil_abi(); + let runtime_state = RuntimeStateArtifact { + source: "FooState".to_string(), + fields: vec![ + RuntimeFieldArtifact { name: "gen__foo_template".to_string(), ty: TypeArtifact::FixedBytes { len: 32 } }, + RuntimeFieldArtifact { name: "count".to_string(), ty: TypeArtifact::Int }, + RuntimeFieldArtifact { name: "flag".to_string(), ty: TypeArtifact::Bool }, + ], + }; + let values = BTreeMap::from([ + ("gen__foo_template".to_string(), ArtifactValue::Bytes(vec![7; 32])), + ("count".to_string(), ArtifactValue::Int(-5)), + ("flag".to_string(), ArtifactValue::Bool(true)), + ]); + + let encoded = encode_runtime_state_script(&abi, &runtime_state, &values).expect("state encodes"); + let decoded = decode_runtime_state_script(&abi, &runtime_state, &encoded).expect("state decodes"); + + assert_eq!(encode_hex(&encoded), format!("20{}0805000000000000800101", "07".repeat(32))); + assert_eq!(decoded, values); + assert_eq!(encode_runtime_state_script(&abi, &runtime_state, &decoded).expect("state re-encodes"), encoded); + + let mut extra = values; + extra.insert("extra".to_string(), ArtifactValue::Int(1)); + assert_eq!( + encode_runtime_state_script(&abi, &runtime_state, &extra).expect_err("extra fields should be rejected"), + CodecError::UnknownField("extra".to_string()) + ); + } + + #[test] + fn round_trips_nested_struct_and_struct_array_runtime_state() { + let mut abi = tiny_sil_abi(); + abi.structs = BTreeMap::from([ + ( + "Inner".to_string(), + StructArtifact { fields: vec![FieldArtifact { name: "marker".to_string(), ty: TypeArtifact::Byte }] }, + ), + ( + "Outer".to_string(), + StructArtifact { + fields: vec![ + FieldArtifact { name: "inner".to_string(), ty: TypeArtifact::Struct { name: "Inner".to_string() } }, + FieldArtifact { name: "active".to_string(), ty: TypeArtifact::Bool }, + ], + }, + ), + ( + "Pair".to_string(), + StructArtifact { + fields: vec![ + FieldArtifact { name: "amount".to_string(), ty: TypeArtifact::Int }, + FieldArtifact { name: "code".to_string(), ty: TypeArtifact::FixedBytes { len: 2 } }, + ], + }, + ), + ]); + let runtime_state = RuntimeStateArtifact { + source: "FooState".to_string(), + fields: vec![ + RuntimeFieldArtifact { name: "current".to_string(), ty: TypeArtifact::Struct { name: "Outer".to_string() } }, + RuntimeFieldArtifact { + name: "pairs".to_string(), + ty: TypeArtifact::FixedArray { item: Box::new(TypeArtifact::Struct { name: "Pair".to_string() }), len: 2 }, + }, + ], + }; + let pair = |amount, code| { + ArtifactValue::Object(BTreeMap::from([ + ("amount".to_string(), ArtifactValue::Int(amount)), + ("code".to_string(), ArtifactValue::Bytes(code)), + ])) + }; + let values = BTreeMap::from([ + ( + "current".to_string(), + ArtifactValue::Object(BTreeMap::from([ + ("inner".to_string(), ArtifactValue::Object(BTreeMap::from([("marker".to_string(), ArtifactValue::Byte(7))]))), + ("active".to_string(), ArtifactValue::Bool(true)), + ])), + ), + ("pairs".to_string(), ArtifactValue::Array(vec![pair(11, vec![0xaa, 0xbb]), pair(12, vec![0xcc, 0xdd])])), + ]); + + let encoded = encode_runtime_state_script(&abi, &runtime_state, &values).expect("structured state encodes"); + let pushes = parse_pushes(&encoded).expect("structured state uses push-only encoding"); + + assert_eq!( + pushes.into_iter().map(|(_, payload)| payload).collect::>(), + vec![ + vec![7], + vec![1], + [serialize_fixed_i64(11, 8).unwrap(), serialize_fixed_i64(12, 8).unwrap()].concat(), + vec![0xaa, 0xbb, 0xcc, 0xdd], + ] + ); + assert_eq!(decode_runtime_state_script(&abi, &runtime_state, &encoded).expect("structured state decodes"), values); + } + + fn tiny_sil_abi() -> SilAbiArtifact { + SilAbiArtifact { + schema_version: SIL_ABI_SCHEMA_VERSION, + compiler_version: "0.1.0".to_string(), + structs: BTreeMap::new(), + contracts: BTreeMap::from([( + "Foo".to_string(), + SilContractArtifact { + source_path: "sil/Foo.sil".to_string(), + runtime_state: RuntimeStateArtifact { source: "FooState".to_string(), fields: Vec::new() }, + entries: BTreeMap::from([ + ( + "step".to_string(), + SilEntryArtifact { + dispatch_tag: DispatchTag::from([0x2c, 0x49, 0xed, 0x65]), + params: vec![ + param("n", TypeArtifact::Int), + param("hash", TypeArtifact::FixedBytes { len: 4 }), + param("flag", TypeArtifact::Bool), + param("b", TypeArtifact::Byte), + ], + }, + ), + ( + "other".to_string(), + SilEntryArtifact { dispatch_tag: DispatchTag::from([0xde, 0xad, 0xbe, 0xef]), params: Vec::new() }, + ), + ]), + cov_decl_to_abi: BTreeMap::new(), + delegate_entry_abi: None, + compiled: CompiledContractArtifact { + bytecode: Vec::new(), + template_hash: [0; 32], + state_span: StateSpanArtifact { offset: 0, len: 0 }, + }, + }, + )]), + } + } + + fn param(name: &str, ty: TypeArtifact) -> ParamArtifact { + ParamArtifact { name: name.to_string(), ty } + } +} diff --git a/silverscript-debug-artifact/Cargo.toml b/silverscript-debug-artifact/Cargo.toml new file mode 100644 index 00000000..fe4df80a --- /dev/null +++ b/silverscript-debug-artifact/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "silverscript-debug-artifact" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lib] +name = "silverscript_debug_artifact" +path = "src/lib.rs" + +[dependencies] +serde.workspace = true +silverscript-abi.workspace = true +silverscript-lang.workspace = true + diff --git a/silverscript-debug-artifact/src/lib.rs b/silverscript-debug-artifact/src/lib.rs new file mode 100644 index 00000000..75df1662 --- /dev/null +++ b/silverscript-debug-artifact/src/lib.rs @@ -0,0 +1,78 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; +use silverscript_abi::{ArtifactValue, SilAbiArtifact, SilContractArtifact}; +use silverscript_lang::ast::parse_contract_ast; +use silverscript_lang::compiler::{ + CompileOptions, CompilerError, artifact_value_to_expr, compile_contract as compile_internal_contract, + sil_abi_artifact_from_compiled, +}; +use silverscript_lang::debug_info::DebugInfo; + +/// A portable SilverScript ABI artifact accompanied by compiler debug metadata +/// for each contract it contains. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SilDebugArtifact<'i> { + pub abi: SilAbiArtifact, + pub contract_debug_info: BTreeMap>, +} + +impl<'i> SilDebugArtifact<'i> { + pub fn contract(&self, name: &str) -> Option<&SilContractArtifact> { + self.abi.contract(name) + } + + pub fn debug_info(&self, contract_name: &str) -> Option<&DebugInfo<'i>> { + self.contract_debug_info.get(contract_name) + } +} + +/// Compiles one contract into its portable ABI and associated debug metadata. +pub fn compile_contract<'i>( + source: &'i str, + constructor_args: &[ArtifactValue], + mut options: CompileOptions, +) -> Result, CompilerError> { + let contract = parse_contract_ast(source)?; + if constructor_args.len() != contract.params.len() { + return Err(CompilerError::Unsupported(format!( + "constructor argument count mismatch: expected {}, got {}", + contract.params.len(), + constructor_args.len() + ))); + } + let constructor_args = constructor_args + .iter() + .zip(&contract.params) + .map(|(value, param)| artifact_value_to_expr(value, ¶m.type_ref, &contract)) + .collect::, CompilerError>>()?; + + // A debug artifact always records debug information regardless of the + // caller's normal compilation preference. + options.record_debug_infos = true; + let compiled = compile_internal_contract(source, &constructor_args, options)?; + let abi = sil_abi_artifact_from_compiled(&compiled, &constructor_args)?; + let contract_name = compiled.contract_name.clone(); + let debug_info = compiled + .debug_info + .ok_or_else(|| CompilerError::Unsupported(format!("compiled contract '{contract_name}' has no debug information")))?; + + Ok(SilDebugArtifact { abi, contract_debug_info: BTreeMap::from([(contract_name, debug_info)]) }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compiles_portable_artifact_with_per_contract_debug_info() { + let source = "contract C(int initial) { int value = initial; entry main() { require(value > 0); } }"; + let artifact = compile_contract(source, &[1.into()], CompileOptions::default()).expect("debug artifact compiles"); + + let contract = artifact.contract("C").expect("portable contract exists"); + assert!(!contract.compiled.bytecode.is_empty()); + let debug_info = artifact.debug_info("C").expect("contract debug information exists"); + assert_eq!(debug_info.source, source); + assert!(!debug_info.steps.is_empty()); + } +} diff --git a/silverscript-lang/Cargo.toml b/silverscript-lang/Cargo.toml index 4dc50652..14f1b920 100644 --- a/silverscript-lang/Cargo.toml +++ b/silverscript-lang/Cargo.toml @@ -30,6 +30,7 @@ serde_json = "1.0" clap = { version = "4.5.60", features = ["derive"] } faster-hex = "0.10" semver = "1.0" +silverscript-abi = { path = "../silverscript-abi" } [dev-dependencies] kaspa-addresses.workspace = true diff --git a/silverscript-lang/src/ast/mod.rs b/silverscript-lang/src/ast/mod.rs index e0e39017..f0be7d60 100644 --- a/silverscript-lang/src/ast/mod.rs +++ b/silverscript-lang/src/ast/mod.rs @@ -1604,14 +1604,14 @@ fn parse_function_definition<'i>(pair: Pair<'i, Rule>) -> Result let mut return_types = Vec::new(); let mut returns_tuple = false; let mut return_type_spans = Vec::new(); - if let Some(next) = inner.peek() { - if next.as_rule() == Rule::return_type_list { - let return_pair = inner.next().expect("checked"); - returns_tuple = return_pair.as_str().trim_start_matches(':').trim_start().starts_with('('); - let (types, spans) = parse_return_type_list(return_pair)?; - return_types = types; - return_type_spans = spans; - } + if let Some(next) = inner.peek() + && next.as_rule() == Rule::return_type_list + { + let return_pair = inner.next().expect("checked"); + returns_tuple = return_pair.as_str().trim_start_matches(':').trim_start().starts_with('('); + let (types, spans) = parse_return_type_list(return_pair)?; + return_types = types; + return_type_spans = spans; } let Identifier { name, span: name_span } = parse_identifier(name_pair)?; @@ -2581,7 +2581,7 @@ fn parse_hex_literal<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError fn parse_hex_bytes(pair: &Pair<'_, Rule>) -> Result, CompilerError> { let raw = pair.as_str(); let trimmed = raw.trim_start_matches("0x").trim_start_matches("0X"); - let normalized = if trimmed.len() % 2 != 0 { format!("0{trimmed}") } else { trimmed.to_string() }; + let normalized = if !trimmed.len().is_multiple_of(2) { format!("0{trimmed}") } else { trimmed.to_string() }; (0..normalized.len()) .step_by(2) .map(|i| u8::from_str_radix(&normalized[i..i + 2], 16)) diff --git a/silverscript-lang/src/bin/silverc.rs b/silverscript-lang/src/bin/silverc.rs index 668d144f..166a4bb2 100644 --- a/silverscript-lang/src/bin/silverc.rs +++ b/silverscript-lang/src/bin/silverc.rs @@ -3,8 +3,9 @@ use std::fs; use std::path::{Path, PathBuf}; use clap::Parser; -use silverscript_lang::ast::{Expr, parse_contract_ast}; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::ast::parse_contract_ast; +use silverscript_lang::compiler::compile_to_sil_abi_artifact; #[derive(Debug, Parser)] #[command( @@ -71,16 +72,15 @@ fn run() -> Result<(), String> { let constructor_args = if let Some(path) = &cli.constructor_args { let json = fs::read_to_string(path).map_err(|err| format!("failed to read {}: {err}", path.display()))?; - serde_json::from_str::>(&json) + serde_json::from_str::>(&json) .map_err(|err| format!("failed to parse constructor args {}: {err}", path.display()))? } else { Vec::new() }; - let compiled = - compile_contract(&source, &constructor_args, CompileOptions::default()).map_err(|err| format!("compile error: {err}"))?; + let artifact = compile_to_sil_abi_artifact(&source, &constructor_args).map_err(|err| format!("compile error: {err}"))?; - let json = serde_json::to_string_pretty(&compiled).map_err(|err| format!("failed to serialize output: {err}"))?; + let json = silverscript_abi::to_pretty_json(&artifact).map_err(|err| format!("failed to serialize output: {err}"))?; let target = resolve_output_target(&cli, &cli.src, false); emit_output(&json, target)?; diff --git a/silverscript-lang/src/compiler/abi.rs b/silverscript-lang/src/compiler/abi.rs new file mode 100644 index 00000000..2873e8b5 --- /dev/null +++ b/silverscript-lang/src/compiler/abi.rs @@ -0,0 +1,279 @@ +use std::collections::{BTreeMap, HashMap, HashSet}; + +use silverscript_abi::{ + ArtifactValue, CompiledContractArtifact, FieldArtifact, ParamArtifact, RuntimeFieldArtifact, RuntimeStateArtifact, + SIL_ABI_SCHEMA_VERSION, SilAbiArtifact, SilContractArtifact, SilEntryArtifact, StateSpanArtifact, StructArtifact, TypeArtifact, +}; + +use super::*; + +fn artifact_values_to_constructor_args<'i>( + values: &[ArtifactValue], + contract: &ContractAst<'i>, +) -> Result>, CompilerError> { + if values.len() != contract.params.len() { + return Err(CompilerError::Unsupported(format!( + "constructor argument count mismatch: expected {}, got {}", + contract.params.len(), + values.len() + ))); + } + + values.iter().zip(&contract.params).map(|(value, param)| artifact_value_to_expr(value, ¶m.type_ref, contract)).collect() +} + +/// Converts a portable ABI value to a concrete expression of the declared SilverScript type. +pub fn artifact_value_to_expr<'i>( + value: &ArtifactValue, + expected_type: &TypeRef, + contract: &ContractAst<'i>, +) -> Result, CompilerError> { + if expected_type.is_array() { + if matches!(expected_type.base, TypeBase::Byte) && expected_type.array_dims.len() == 1 { + let ArtifactValue::Bytes(bytes) = value else { + return Err(artifact_value_type_mismatch(value, expected_type)); + }; + return Ok(Expr::array(expected_type.clone(), bytes.iter().copied().map(Expr::byte).collect())); + } + + let ArtifactValue::Array(values) = value else { + return Err(artifact_value_type_mismatch(value, expected_type)); + }; + let element_type = expected_type + .array_element_type() + .ok_or_else(|| CompilerError::Unsupported(format!("invalid array type '{}'", expected_type.type_name())))?; + let values = values + .iter() + .map(|value| artifact_value_to_expr(value, &element_type, contract)) + .collect::, CompilerError>>()?; + return Ok(Expr::array(expected_type.clone(), values)); + } + + match (&expected_type.base, value) { + (TypeBase::Int, ArtifactValue::Int(value)) => Ok(Expr::int(*value)), + (TypeBase::Temporal, ArtifactValue::Int(value)) => Ok(Expr::temporal(*value)), + (TypeBase::Bool, ArtifactValue::Bool(value)) => Ok(Expr::bool(*value)), + (TypeBase::Byte, ArtifactValue::Byte(value)) => Ok(Expr::byte(*value)), + (TypeBase::String, ArtifactValue::Text(value)) => Ok(Expr::string(value)), + (TypeBase::Pubkey | TypeBase::Sig | TypeBase::Datasig, ArtifactValue::Bytes(value)) => Ok(Expr::bytes(value.clone())), + (TypeBase::Custom(name), ArtifactValue::Object(values)) => { + let struct_ = contract + .structs + .iter() + .find(|struct_| struct_.name == *name) + .ok_or_else(|| CompilerError::Unsupported(format!("unknown struct '{name}'")))?; + let expected_fields = struct_.fields.iter().map(|field| field.name.as_str()).collect::>(); + if let Some(field) = values.keys().find(|field| !expected_fields.contains(field.as_str())) { + return Err(CompilerError::Unsupported(format!("unknown field '{name}.{field}'"))); + } + let fields = struct_ + .fields + .iter() + .map(|field| { + let value = values + .get(&field.name) + .ok_or_else(|| CompilerError::Unsupported(format!("missing field '{name}.{}'", field.name)))?; + Ok(StateFieldExpr { + name: field.name.clone(), + expr: artifact_value_to_expr(value, &field.type_ref, contract)?, + span: Default::default(), + name_span: Default::default(), + }) + }) + .collect::, CompilerError>>()?; + Ok(Expr::new(ExprKind::StructLiteral { name: name.clone(), fields, name_span: Default::default() }, Default::default())) + } + (TypeBase::Tuple(_), _) => { + Err(CompilerError::Unsupported(format!("portable ABI values do not support tuple type '{}'", expected_type.type_name()))) + } + _ => Err(artifact_value_type_mismatch(value, expected_type)), + } +} + +fn artifact_value_type_mismatch(value: &ArtifactValue, expected_type: &TypeRef) -> CompilerError { + let actual = match value { + ArtifactValue::Int(_) => "int", + ArtifactValue::Bool(_) => "bool", + ArtifactValue::Byte(_) => "byte", + ArtifactValue::Bytes(_) => "bytes", + ArtifactValue::Text(_) => "string", + ArtifactValue::Array(_) => "array", + ArtifactValue::Object(_) => "object", + }; + CompilerError::Unsupported(format!("cannot convert artifact {actual} to {}", expected_type.type_name())) +} + +/// Compiles one SilverScript contract into a complete portable ABI artifact. +pub fn compile_to_sil_abi_artifact(source: &str, constructor_args: &[ArtifactValue]) -> Result { + compile_to_sil_abi_artifact_with_options(source, constructor_args, CompileOptions::default()) +} + +/// Compiles one SilverScript contract into a complete portable ABI artifact +/// using the requested compiler options. +pub fn compile_to_sil_abi_artifact_with_options( + source: &str, + constructor_args: &[ArtifactValue], + options: CompileOptions, +) -> Result { + let contract = parse_contract_ast(source)?; + let constructor_args = artifact_values_to_constructor_args(constructor_args, &contract)?; + let compiled = compile_contract(source, &constructor_args, options)?; + sil_abi_artifact_from_compiled(&compiled, &constructor_args) +} + +/// Builds a portable ABI artifact from an already compiled contract. +pub fn sil_abi_artifact_from_compiled<'i>( + compiled: &CompiledContract<'i>, + constructor_args: &[Expr<'i>], +) -> Result { + let constants = artifact_constants(compiled, constructor_args); + let structs = compiled + .ast + .structs + .iter() + .map(|struct_| { + let fields = struct_ + .fields + .iter() + .map(|field| Ok(FieldArtifact { name: field.name.clone(), ty: type_artifact(&field.type_ref, &constants)? })) + .collect::, CompilerError>>()?; + Ok((struct_.name.clone(), StructArtifact { fields })) + }) + .collect::, CompilerError>>()?; + let contract = contract_artifact_from_compiled(compiled, constructor_args)?; + + Ok(SilAbiArtifact { + schema_version: SIL_ABI_SCHEMA_VERSION, + compiler_version: compiled.compiler_version.clone(), + structs, + contracts: BTreeMap::from([(compiled.contract_name.clone(), contract)]), + }) +} + +fn contract_artifact_from_compiled<'i>( + compiled: &CompiledContract<'i>, + constructor_args: &[Expr<'i>], +) -> Result { + let constants = artifact_constants(compiled, constructor_args); + + let runtime_fields = compiled + .ast + .fields + .iter() + .map(|field| Ok(RuntimeFieldArtifact { name: field.name.clone(), ty: type_artifact(&field.type_ref, &constants)? })) + .collect::, CompilerError>>()?; + let entries = compiled + .ast + .functions + .iter() + .filter(|function| function.entrypoint) + .map(|function| { + let dispatch_tag = compiled.dispatch_tags.get(&function.name).copied().ok_or_else(|| { + CompilerError::Unsupported(format!( + "compiled contract '{}' has no dispatch tag for function '{}'", + compiled.contract_name, function.name + )) + })?; + let params = function + .params + .iter() + .map(|param| Ok(ParamArtifact { name: param.name.clone(), ty: type_artifact(¶m.type_ref, &constants)? })) + .collect::, CompilerError>>()?; + Ok((function.name.clone(), SilEntryArtifact { dispatch_tag: dispatch_tag.into(), params })) + }) + .collect::, CompilerError>>()?; + let artifact_entry = |entry_name: &str| { + entries.contains_key(entry_name).then(|| entry_name.to_string()).ok_or_else(|| { + CompilerError::Unsupported(format!( + "compiled contract '{}' has no portable ABI entry for generated function '{}'", + compiled.contract_name, entry_name + )) + }) + }; + let cov_decl_to_abi = compiled + .covenant_entrypoints + .iter() + .map(|(name, entrypoint)| Ok((name.clone(), artifact_entry(entrypoint)?))) + .collect::>()?; + let delegate_entry_abi = compiled.delegate_entrypoint.as_deref().map(artifact_entry).transpose()?; + + let layout = compiled.state_layout; + let state_end = checked_add(layout.start, layout.len)?; + if layout.start > compiled.bytecode.len() || state_end > compiled.bytecode.len() { + return Err(CompilerError::Unsupported(format!( + "compiled contract '{}' reported invalid state span start={} len={} for script len={}", + compiled.contract_name, + layout.start, + layout.len, + compiled.bytecode.len() + ))); + } + + let template_hash = compiled.template_hash(); + Ok(SilContractArtifact { + source_path: format!("sil/{}.sil", compiled.contract_name), + runtime_state: RuntimeStateArtifact { source: STATE_TYPE_NAME.to_string(), fields: runtime_fields }, + entries, + cov_decl_to_abi, + delegate_entry_abi, + compiled: CompiledContractArtifact { + bytecode: compiled.bytecode.clone(), + template_hash, + state_span: StateSpanArtifact { offset: layout.start, len: layout.len }, + }, + }) +} + +fn artifact_constants<'i>(compiled: &CompiledContract<'i>, constructor_args: &[Expr<'i>]) -> HashMap> { + let mut constants: HashMap> = + compiled.ast.constants.iter().map(|constant| (constant.name.clone(), constant.expr.clone())).collect(); + constants.extend(compiled.ast.params.iter().zip(constructor_args).map(|(param, value)| (param.name.clone(), value.clone()))); + constants +} + +fn type_artifact<'i>(type_ref: &TypeRef, constants: &HashMap>) -> Result { + let mut artifact = match &type_ref.base { + TypeBase::Int => TypeArtifact::Int, + TypeBase::Temporal => TypeArtifact::Temporal, + TypeBase::Bool => TypeArtifact::Bool, + TypeBase::Byte => TypeArtifact::Byte, + TypeBase::String => TypeArtifact::Text, + TypeBase::Pubkey => TypeArtifact::Pubkey, + TypeBase::Sig => TypeArtifact::Sig, + TypeBase::Datasig => TypeArtifact::Datasig, + TypeBase::Custom(name) => TypeArtifact::Struct { name: name.clone() }, + TypeBase::Tuple(_) => { + return Err(CompilerError::Unsupported(format!("portable ABI does not support tuple type '{}'", type_ref.type_name()))); + } + }; + + for dimension in &type_ref.array_dims { + let len = + match dimension { + ArrayDim::Dynamic => None, + ArrayDim::Fixed(len) => Some(*len), + ArrayDim::Constant(name) => { + let value = constants + .get(name) + .ok_or_else(|| CompilerError::UndefinedIdentifier(name.clone())) + .and_then(|expr| eval_const_int(expr, constants))?; + Some(usize::try_from(value).map_err(|_| { + CompilerError::Unsupported(format!("array size constant '{name}' must be a non-negative integer")) + })?) + } + ArrayDim::Inferred => { + return Err(CompilerError::Unsupported( + "portable ABI array dimension was not inferred during compilation".to_string(), + )); + } + }; + artifact = match (artifact, len) { + (TypeArtifact::Byte, Some(len)) => TypeArtifact::FixedBytes { len }, + (TypeArtifact::Byte, None) => TypeArtifact::Bytes, + (item, Some(len)) => TypeArtifact::FixedArray { item: Box::new(item), len }, + (item, None) => TypeArtifact::DynamicArray { item: Box::new(item) }, + }; + } + + Ok(artifact) +} diff --git a/silverscript-lang/src/compiler/compile.rs b/silverscript-lang/src/compiler/compile.rs index 983bff45..a11af9a3 100644 --- a/silverscript-lang/src/compiler/compile.rs +++ b/silverscript-lang/src/compiler/compile.rs @@ -78,9 +78,9 @@ pub(super) fn compile_contract_impl<'i>( let mut lowered_constants = flatten_constructor_args_env(&covenant_lowered_contract.params, constructor_args, &structs)?; lowered_constants.extend(lowered_contract.constants.iter().map(|constant| (constant.name.clone(), constant.expr.clone()))); - let BuiltAbi { function_abi_entries, cov_decl_to_abi, delegate_entry_abi } = - build_abi(&covenant_lowered_contract, &constants, &structs, &covenant_abi_names)?; - if function_abi_entries.is_empty() { + let EntrypointMetadata { dispatches, covenant_entrypoints, delegate_entrypoint } = + build_entrypoint_metadata(&covenant_lowered_contract, &constants, &structs, &covenant_abi_names)?; + if dispatches.is_empty() { return Err(CompilerError::Unsupported("contract has no entries".to_string())); } let entrypoint_functions: Vec<&FunctionAst<'i>> = lowered_contract.functions.iter().filter(|func| func.entrypoint).collect(); @@ -89,7 +89,7 @@ pub(super) fn compile_contract_impl<'i>( // dispatch tag: verify no collisions and insert tags to global state let mut entrypoints_by_tag = HashMap::::new(); - for entrypoint in &function_abi_entries { + for entrypoint in &dispatches { let tag = entrypoint.dispatch_tag; if let Some(existing) = entrypoints_by_tag.insert(tag, entrypoint.name.as_str()) { return Err(CompilerError::EntrypointDispatchTagCollision { f1: existing.to_string(), f2: entrypoint.name.clone() }); @@ -107,7 +107,7 @@ pub(super) fn compile_contract_impl<'i>( &lowered_contract, &lowered_constants, bytecode_size, - &function_abi_entries, + &dispatches, &structs, &struct_array_param_groups, &mut debug_recorder, @@ -118,9 +118,9 @@ pub(super) fn compile_contract_impl<'i>( return Ok(build_compiled_contract( &lowered_contract, &artifact_contract, - function_abi_entries.clone(), - &cov_decl_to_abi, - delegate_entry_abi.as_ref(), + &dispatches, + &covenant_entrypoints, + delegate_entrypoint.as_deref(), bytecode, state_layout, debug_info, @@ -132,9 +132,9 @@ pub(super) fn compile_contract_impl<'i>( return Ok(build_compiled_contract( &lowered_contract, &artifact_contract, - function_abi_entries.clone(), - &cov_decl_to_abi, - delegate_entry_abi.as_ref(), + &dispatches, + &covenant_entrypoints, + delegate_entrypoint.as_deref(), bytecode, state_layout, debug_info, @@ -250,7 +250,7 @@ fn compile_contract_bytecode_iteration<'i>( lowered_contract: &ContractAst<'i>, lowered_constants: &HashMap>, bytecode_size: Option, - function_abi_entries: &[FunctionAbiEntry], + dispatches: &[EntrypointDispatch], structs: &StructRegistry, struct_array_param_groups: &StructArrayParamGroups, debug_recorder: &mut DebugRecorder<'i>, @@ -273,7 +273,7 @@ fn compile_contract_bytecode_iteration<'i>( bytecode_size, debug_recorder, )?; - let bytecode = build_contract_bytecode(debug_recorder, &state_push_bytecode, &compiled_entrypoints, function_abi_entries)?; + let bytecode = build_contract_bytecode(debug_recorder, &state_push_bytecode, &compiled_entrypoints, dispatches)?; let entrypoints = lowered_contract.functions.iter().filter(|function| function.entrypoint).collect::>(); validate_signature_script_limits(&bytecode, &entrypoints, lowered_constants)?; Ok((bytecode, state_layout)) @@ -313,7 +313,7 @@ fn build_contract_bytecode( debug_recorder: &mut DebugRecorder<'_>, state_push_bytecode: &[u8], compiled_entrypoints: &[(String, Vec)], - function_abi_entries: &[FunctionAbiEntry], + dispatches: &[EntrypointDispatch], ) -> Result, CompilerError> { let mut builder = script_builder(); if !state_push_bytecode.is_empty() { @@ -326,7 +326,7 @@ fn build_contract_bytecode( let total = compiled_entrypoints.len(); let dispatch_tag_by_entry_name = - function_abi_entries.iter().map(|entry| (entry.name.as_str(), entry.dispatch_tag)).collect::>(); + dispatches.iter().map(|entry| (entry.name.as_str(), entry.dispatch_tag)).collect::>(); for (entrypoint_index, (name, bytecode)) in compiled_entrypoints.iter().enumerate() { let dispatch_tag = dispatch_tag_by_entry_name.get(name.as_str()).expect("compiled entrypoint must have an ABI entry"); @@ -353,9 +353,9 @@ fn build_contract_bytecode( fn build_compiled_contract<'i>( lowered_contract: &ContractAst<'i>, covenant_lowered_contract: &ContractAst<'i>, - function_abi_entries: Vec, - cov_decl_to_abi: &BTreeMap, - delegate_entry_abi: Option<&FunctionAbiEntry>, + dispatches: &[EntrypointDispatch], + covenant_entrypoints: &BTreeMap, + delegate_entrypoint: Option<&str>, bytecode: Vec, state_layout: CompiledStateLayout, debug_info: Option>, @@ -365,9 +365,9 @@ fn build_compiled_contract<'i>( compiler_version: COMPILER_VERSION.to_string(), bytecode, ast: covenant_lowered_contract.clone(), - abi: function_abi_entries, - cov_decl_to_abi: cov_decl_to_abi.clone(), - delegate_entry_abi: delegate_entry_abi.cloned(), + dispatch_tags: dispatches.iter().map(|entry| (entry.name.clone(), entry.dispatch_tag)).collect(), + covenant_entrypoints: covenant_entrypoints.clone(), + delegate_entrypoint: delegate_entrypoint.map(str::to_string), state_layout, debug_info, } diff --git a/silverscript-lang/src/compiler/compile/helpers.rs b/silverscript-lang/src/compiler/compile/helpers.rs index 1751e260..bd47903a 100644 --- a/silverscript-lang/src/compiler/compile/helpers.rs +++ b/silverscript-lang/src/compiler/compile/helpers.rs @@ -1,10 +1,10 @@ use super::*; use crate::compiler::covenant_declarations::CovenantDeclarationAbiNames; -pub(super) struct BuiltAbi { - pub(super) function_abi_entries: Vec, - pub(super) cov_decl_to_abi: BTreeMap, - pub(super) delegate_entry_abi: Option, +pub(super) struct EntrypointMetadata { + pub(super) dispatches: Vec, + pub(super) covenant_entrypoints: BTreeMap, + pub(super) delegate_entrypoint: Option, } pub(super) fn compile_contract_fields<'i>( @@ -98,67 +98,62 @@ fn write_dispatch_type_name<'i>( Ok(()) } -pub(super) fn build_abi<'i>( +pub(super) fn build_entrypoint_metadata<'i>( contract: &ContractAst<'i>, constants: &HashMap>, structs: &StructRegistry, covenant_abi_names: &CovenantDeclarationAbiNames, -) -> Result { +) -> Result { let source_name_by_entrypoint = covenant_abi_names .entrypoints .iter() .map(|(source_name, entrypoint_name)| (entrypoint_name.as_str(), source_name.as_str())) .collect::>(); let delegate_entrypoint = covenant_abi_names.delegate_entrypoint.as_deref(); - let mut function_abi_entries = Vec::new(); - let mut cov_decl_to_abi = BTreeMap::new(); - let mut delegate_entry_abi = None; + let mut dispatches = Vec::new(); + let mut covenant_entrypoints = BTreeMap::new(); + let mut built_delegate_entrypoint = None; for func in contract.functions.iter().filter(|func| func.entrypoint) { - let input_specs = func + let signature_types = func .params .iter() .map(|param| { let type_ref = resolve_abi_type_ref(¶m.type_ref, constants, &func.name, ¶m.name)?; - let type_name = type_ref.type_name(); let mut signature_type = String::new(); write_dispatch_type_name(&type_ref, structs, constants, &mut signature_type)?; - Ok((FunctionInputAbi { name: param.name.clone(), type_name }, signature_type)) + Ok(signature_type) }) .collect::, CompilerError>>()?; - let (inputs, signature_types): (Vec<_>, Vec<_>) = input_specs.into_iter().unzip(); - - // dispatch tag creation let signature = format!("{}({})", func.name, signature_types.join(",")); let hash = blake3::hash(signature.as_bytes()); - let mut dispatch_tag = [0u8; 4]; + let mut dispatch_tag = [0; 4]; dispatch_tag.copy_from_slice(&hash.as_bytes()[..4]); - - let entry = FunctionAbiEntry { name: func.name.clone(), inputs, dispatch_tag }; + let entry = EntrypointDispatch { name: func.name.clone(), dispatch_tag }; if let Some(source_name) = source_name_by_entrypoint.get(func.name.as_str()) { - cov_decl_to_abi.insert((*source_name).to_string(), entry.clone()); + covenant_entrypoints.insert((*source_name).to_string(), func.name.clone()); } if delegate_entrypoint == Some(func.name.as_str()) { - delegate_entry_abi = Some(entry.clone()); + built_delegate_entrypoint = Some(func.name.clone()); } - function_abi_entries.push(entry); + dispatches.push(entry); } if let Some((source_name, entrypoint_name)) = - covenant_abi_names.entrypoints.iter().find(|(source_name, _)| !cov_decl_to_abi.contains_key(*source_name)) + covenant_abi_names.entrypoints.iter().find(|(source_name, _)| !covenant_entrypoints.contains_key(*source_name)) { return Err(CompilerError::Unsupported(format!( - "generated covenant entrypoint '{entrypoint_name}' for declaration '{source_name}' is missing from the ABI" + "generated covenant entrypoint '{entrypoint_name}' for declaration '{source_name}' is missing from dispatch metadata" ))); } - if let Some(entrypoint_name) = delegate_entrypoint.filter(|_| delegate_entry_abi.is_none()) { + if let Some(entrypoint_name) = delegate_entrypoint.filter(|_| built_delegate_entrypoint.is_none()) { return Err(CompilerError::Unsupported(format!( - "generated covenant delegate entrypoint '{entrypoint_name}' is missing from the ABI" + "generated covenant delegate entrypoint '{entrypoint_name}' is missing from dispatch metadata" ))); } - Ok(BuiltAbi { function_abi_entries, cov_decl_to_abi, delegate_entry_abi }) + Ok(EntrypointMetadata { dispatches, covenant_entrypoints, delegate_entrypoint: built_delegate_entrypoint }) } pub(super) fn resolve_artifact_struct_type_refs<'i>( @@ -275,20 +270,20 @@ pub(super) fn encode_value_with_constant_size<'i>( } _ => { // Handle fixed-size byte arrays like byte[N] - if let (Some(inner_type), Some(size)) = (type_ref.array_element_type(), array_type_size(type_ref, constants)?) { - if inner_type.is_byte() { - let ExprKind::Array { values, .. } = &value.kind else { - return Err(array_literal_encoding_error(value)); - }; - if values.len() != size { - return Err(CompilerError::Unsupported("array literal element type mismatch".to_string())); - } - return values - .iter() - .map(|value| encode_value_with_constant_size(value, &inner_type, constants)) - .collect::, _>>() - .map(|chunks| chunks.concat()); + if let (Some(inner_type), Some(size)) = (type_ref.array_element_type(), array_type_size(type_ref, constants)?) + && inner_type.is_byte() + { + let ExprKind::Array { values, .. } = &value.kind else { + return Err(array_literal_encoding_error(value)); + }; + if values.len() != size { + return Err(CompilerError::Unsupported("array literal element type mismatch".to_string())); } + return values + .iter() + .map(|value| encode_value_with_constant_size(value, &inner_type, constants)) + .collect::, _>>() + .map(|chunks| chunks.concat()); } // Handle nested fixed-size arrays with known element sizes. diff --git a/silverscript-lang/src/compiler/debug_recording.rs b/silverscript-lang/src/compiler/debug_recording.rs index 63cb3627..cf6ffa79 100644 --- a/silverscript-lang/src/compiler/debug_recording.rs +++ b/silverscript-lang/src/compiler/debug_recording.rs @@ -351,11 +351,9 @@ impl<'i> DebugRecorder<'i> { (active.start_statement_slot(stmt, bytecode_start), true) }; - if is_new_slot { - if let Some(entrypoint) = active.active_entrypoint_mut() { - entrypoint.emit_inline_call_resumes(slot.statement_index, bytecode_start); - entrypoint.emit_inline_call_enters(slot.statement_index, bytecode_start); - } + if is_new_slot && let Some(entrypoint) = active.active_entrypoint_mut() { + entrypoint.emit_inline_call_resumes(slot.statement_index, bytecode_start); + entrypoint.emit_inline_call_enters(slot.statement_index, bytecode_start); } active.statement_debug_state_stack.push(slot); } diff --git a/silverscript-lang/src/compiler/mod.rs b/silverscript-lang/src/compiler/mod.rs index 0037eb23..6f06efef 100644 --- a/silverscript-lang/src/compiler/mod.rs +++ b/silverscript-lang/src/compiler/mod.rs @@ -1,7 +1,5 @@ use std::collections::{BTreeMap, HashMap}; -use kaspa_txscript::EngineFlags; -use kaspa_txscript::script_builder::ScriptBuilder; use serde::{Deserialize, Serialize}; use crate::ast::{ @@ -13,6 +11,7 @@ pub(crate) use crate::checked_arithmetic::{checked_add, checked_div, checked_mul use crate::debug_info::{DebugInfo, DebugNamedValue}; pub use crate::errors::{CompilerError, ErrorSpan}; use crate::span; +mod abi; mod array_append; mod builtin_types; mod compile; @@ -31,6 +30,9 @@ mod type_check; mod type_system; mod validate_output_state; +pub use abi::{ + artifact_value_to_expr, compile_to_sil_abi_artifact, compile_to_sil_abi_artifact_with_options, sil_abi_artifact_from_compiled, +}; use compile::compile_contract_impl; pub use compile::compile_debug_expr; pub(crate) use compile::resolve_constant_references; @@ -87,16 +89,9 @@ pub struct CompileOptions { pub record_debug_infos: bool, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct FunctionInputAbi { +#[derive(Debug, Clone, PartialEq, Eq)] +struct EntrypointDispatch { pub name: String, - pub type_name: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct FunctionAbiEntry { - pub name: String, - pub inputs: Vec, pub dispatch_tag: DispatchTag, } @@ -114,13 +109,13 @@ pub struct CompiledContract<'i> { pub compiler_version: String, pub bytecode: Vec, pub ast: ContractAst<'i>, - pub abi: Vec, - /// Public leader/auth ABI entries keyed by their pre-lowering covenant declaration names. + pub dispatch_tags: BTreeMap, + /// Generated leader/auth entrypoint names keyed by their pre-lowering covenant declaration names. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub cov_decl_to_abi: BTreeMap, - /// The shared delegate ABI entry generated for a cov-bound contract. + covenant_entrypoints: BTreeMap, + /// The shared delegate entrypoint generated for a cov-bound contract. #[serde(default, skip_serializing_if = "Option::is_none")] - pub delegate_entry_abi: Option, + delegate_entrypoint: Option, pub state_layout: CompiledStateLayout, pub debug_info: Option>, } @@ -210,261 +205,11 @@ pub fn struct_object<'i>(name: &str, fields: Vec<(&str, Expr<'i>)>) -> Expr<'i> } impl<'i> CompiledContract<'i> { - pub fn entry_by_name(&self, name: &str) -> Option<&FunctionAbiEntry> { - self.abi.iter().find(|entry| entry.name == name) - } - /// Calculate the canonical hash of this contract's state template. pub fn template_hash(&self) -> [u8; 32] { let state_end = self.state_layout.start + self.state_layout.len; crate::template::template_hash(&self.bytecode[..self.state_layout.start], &self.bytecode[state_end..]) } - - pub fn build_sig_script(&self, function_name: &str, args: Vec>) -> Result, CompilerError> { - let structs = build_struct_registry(&self.ast)?; - // ABI parameter types and artifact struct fields have already had - // constant array dimensions resolved to fixed dimensions during - // compilation, so argument validation does not need a constants map. - let constants = HashMap::new(); - let function = self - .entry_by_name(function_name) - .ok_or_else(|| CompilerError::Unsupported(format!("function '{}' not found", function_name)))?; - - if function.inputs.len() != args.len() { - return Err(CompilerError::Unsupported(format!( - "function '{}' expects {} arguments", - function_name, - function.inputs.len() - ))); - } - - let mut builder = ScriptBuilder::with_flags(EngineFlags { covenants_enabled: true, ..Default::default() }); - for (input, arg) in function.inputs.iter().zip(args) { - let type_ref = parse_type_ref(&input.type_name)?; - push_typed_sigscript_arg(&mut builder, arg, &type_ref, &structs, &constants).map_err(|err| { - CompilerError::Unsupported(format!("function argument '{}' expects {} ({err})", input.name, input.type_name)) - })?; - } - - builder.add_data(&function.dispatch_tag)?; - - Ok(builder.drain()) - } - - pub fn build_sig_script_for_covenant_decl( - &self, - function_name: &str, - args: Vec>, - options: CovenantDeclCallOptions, - ) -> Result, CompilerError> { - let entrypoint = self.covenant_decl_entrypoint_name(function_name, options.is_leader).ok_or_else(|| { - let metadata_hint = if self.cov_decl_to_abi.is_empty() { - "; the compiled artifact may use an outdated schema without covenant declaration ABI metadata" - } else { - "" - }; - CompilerError::Unsupported(format!("covenant declaration '{function_name}' not found{metadata_hint}")) - })?; - self.build_sig_script(entrypoint, args) - } - - /// Resolves a source covenant declaration to its final public ABI entrypoint. - /// For cov-bound declarations, `is_leader` selects the leader or shared - /// delegate wrapper. Auth-bound declarations resolve to the same wrapper in - /// either case. - pub fn covenant_decl_entrypoint_name(&self, function_name: &str, is_leader: bool) -> Option<&str> { - let entry = self.cov_decl_to_abi.get(function_name)?; - if is_leader || self.delegate_entry_abi.is_none() { - return Some(entry.name.as_str()); - } - - self.delegate_entry_abi.as_ref().map(|entry| entry.name.as_str()) - } -} - -fn push_typed_sigscript_arg<'i>( - builder: &mut ScriptBuilder, - arg: Expr<'i>, - type_ref: &TypeRef, - structs: &StructRegistry, - constants: &HashMap>, -) -> Result<(), CompilerError> { - validate_sigscript_arg(&arg, type_ref, structs, constants)?; - - if is_struct_array(type_ref, structs) { - return push_struct_array_sigscript_arg(builder, arg, type_ref, structs, constants); - } - - if is_struct(type_ref, structs) { - return push_struct_sigscript_arg(builder, arg, structs, constants); - } - - if type_ref.is_array() { - return push_array_sigscript_arg(builder, arg, type_ref, constants); - } - - push_sigscript_non_array_arg(builder, arg) -} - -fn validate_sigscript_arg<'i>( - arg: &Expr<'i>, - type_ref: &TypeRef, - structs: &StructRegistry, - constants: &HashMap>, -) -> Result<(), CompilerError> { - // Signature-script construction receives already classified AST values. - // Unlike source-language checking, do not reinterpret an integer literal - // as a byte: callers must use Expr::byte so encoding retains byte semantics. - validate_explicit_sigscript_bytes(arg, type_ref)?; - - let types = HashMap::new(); - let functions = HashMap::new(); - let type_context = type_check::TypeCheckContext { types: &types, structs, constants, functions: &functions, contract_fields: &[] }; - type_check::check_expr(arg, Some(type_ref), &type_context)?; - Ok(()) -} - -fn validate_explicit_sigscript_bytes(arg: &Expr<'_>, type_ref: &TypeRef) -> Result<(), CompilerError> { - if !matches!(type_ref.base, TypeBase::Byte) { - return Ok(()); - } - - if type_ref.is_byte() { - return if matches!(arg.kind, ExprKind::Byte(_)) { Ok(()) } else { Err(CompilerError::TypeMismatch) }; - } - - if let (Some(element_type), ExprKind::Array { values, .. }) = (type_ref.array_element_type(), &arg.kind) { - for value in values { - validate_explicit_sigscript_bytes(value, &element_type)?; - } - } - Ok(()) -} - -fn push_struct_sigscript_arg<'i>( - builder: &mut ScriptBuilder, - arg: Expr<'i>, - structs: &StructRegistry, - constants: &HashMap>, -) -> Result<(), CompilerError> { - let ExprKind::StructLiteral { name, fields, .. } = arg.kind else { - return Err(CompilerError::Unsupported("signature script struct arguments must be object literals".to_string())); - }; - let item = structs.get(&name).ok_or_else(|| CompilerError::Unsupported(format!("unknown struct '{name}'")))?; - let mut provided = fields.into_iter().map(|field| (field.name, field.expr)).collect::>(); - - for field in &item.fields { - let value = provided - .remove(&field.name) - .ok_or_else(|| CompilerError::Unsupported(format!("struct field '{}' must be initialized", field.name)))?; - push_typed_sigscript_arg(builder, value, &field.type_ref, structs, constants)?; - } - - if let Some(extra) = provided.keys().next() { - return Err(CompilerError::Unsupported(format!("unknown struct field '{}'", extra))); - } - Ok(()) -} - -fn push_struct_array_sigscript_arg<'i>( - builder: &mut ScriptBuilder, - arg: Expr<'i>, - type_ref: &TypeRef, - structs: &StructRegistry, - constants: &HashMap>, -) -> Result<(), CompilerError> { - let element_type = type_ref - .array_element_type() - .ok_or_else(|| CompilerError::Unsupported("signature script struct array argument requires an array type".to_string()))?; - let struct_name = struct_name(&element_type, structs).ok_or_else(|| { - CompilerError::Unsupported("signature script struct array argument requires a struct element type".to_string()) - })?; - let item = structs.get(struct_name).ok_or_else(|| CompilerError::Unsupported(format!("unknown struct '{struct_name}'")))?; - let dimension = type_ref - .array_size() - .cloned() - .ok_or_else(|| CompilerError::Unsupported("signature script struct array argument requires an array type".to_string()))?; - let ExprKind::Array { values, .. } = arg.kind else { - return Err(CompilerError::Unsupported("signature script struct array arguments must be array literals".to_string())); - }; - - let mut objects = Vec::with_capacity(values.len()); - for value in values { - let ExprKind::StructLiteral { name, fields: entries, .. } = value.kind else { - return Err(CompilerError::Unsupported( - "signature script struct array arguments must contain object literals".to_string(), - )); - }; - if name != struct_name { - return Err(CompilerError::Unsupported(format!("expected struct '{struct_name}', got '{name}'"))); - } - objects.push(entries.into_iter().map(|entry| (entry.name, entry.expr)).collect::>()); - } - - for field in &item.fields { - let field_values = objects - .iter_mut() - .map(|fields| { - fields - .remove(&field.name) - .ok_or_else(|| CompilerError::Unsupported(format!("struct field '{}' must be initialized", field.name))) - }) - .collect::, _>>()?; - let mut field_type = field.type_ref.clone(); - field_type.array_dims.push(dimension.clone()); - push_typed_sigscript_arg(builder, Expr::array(field_type.clone(), field_values), &field_type, structs, constants)?; - } - - if let Some(extra) = objects.iter().find_map(|fields| fields.keys().next()) { - return Err(CompilerError::Unsupported(format!("unknown struct field '{}'", extra))); - } - Ok(()) -} - -fn push_array_sigscript_arg<'i>( - builder: &mut ScriptBuilder, - arg: Expr<'i>, - type_ref: &TypeRef, - constants: &HashMap>, -) -> Result<(), CompilerError> { - match &arg.kind { - ExprKind::Array { values, .. } => { - let bytes = compile::encode_array_literal(values, type_ref, constants)?; - builder.add_data(&bytes)?; - Ok(()) - } - _ => Err(CompilerError::Unsupported("signature script arguments must be literals".to_string())), - } -} - -fn push_sigscript_non_array_arg<'i>(builder: &mut ScriptBuilder, arg: Expr<'i>) -> Result<(), CompilerError> { - match arg.kind { - ExprKind::Int(value) | ExprKind::Temporal(value) => { - builder.add_i64(value)?; - } - ExprKind::Bool(value) => { - builder.add_i64(if value { 1 } else { 0 })?; - } - ExprKind::String(value) => { - builder.add_data(value.as_bytes())?; - } - ExprKind::Byte(value) => { - builder.add_data(&[value])?; - } - // This is not intended for byte-arrays, but for pubkey, datasig, etc. - ExprKind::Array { values, .. } if values.iter().all(|value| matches!(&value.kind, ExprKind::Byte(_))) => { - let bytes: Vec = - values.iter().filter_map(|value| if let ExprKind::Byte(byte) = &value.kind { Some(*byte) } else { None }).collect(); - builder.add_data(&bytes)?; - } - ExprKind::DateLiteral(value) => { - builder.add_i64(value)?; - } - _ => { - return Err(CompilerError::Unsupported("signature script arguments must be literals".to_string())); - } - } - Ok(()) } fn binary_expr<'i>(op: BinaryOp, left: Expr<'i>, right: Expr<'i>) -> Expr<'i> { diff --git a/silverscript-lang/src/compiler/static_check.rs b/silverscript-lang/src/compiler/static_check.rs index 6a7df2ec..4c9b590a 100644 --- a/silverscript-lang/src/compiler/static_check.rs +++ b/silverscript-lang/src/compiler/static_check.rs @@ -796,10 +796,10 @@ fn validate_require_age_daa_statement_shape<'i>( expr: &Expr<'i>, ) -> Result<(), CompilerError> { ctx.check_expr(expr, Some(&TypeRef { base: TypeBase::Int, array_dims: Vec::new() }))?; - if let Some(value) = eval_optional_const_int(expr, ctx.constants)? { - if !(0..(1_i64 << 32)).contains(&value) { - return Err(CompilerError::Unsupported(format!("this.ageDaa value must satisfy 0 <= value < 2^32, got {value}"))); - } + if let Some(value) = eval_optional_const_int(expr, ctx.constants)? + && !(0..(1_i64 << 32)).contains(&value) + { + return Err(CompilerError::Unsupported(format!("this.ageDaa value must satisfy 0 <= value < 2^32, got {value}"))); } Ok(()) } diff --git a/silverscript-lang/src/compiler/structs/scalar_expr.rs b/silverscript-lang/src/compiler/structs/scalar_expr.rs index 3c4b5a3e..816d455d 100644 --- a/silverscript-lang/src/compiler/structs/scalar_expr.rs +++ b/silverscript-lang/src/compiler/structs/scalar_expr.rs @@ -80,10 +80,10 @@ pub(super) fn lower_scalar_expr<'i>( let left_type = scalar_struct_expr_type(left, scope, structs); let right_type = scalar_struct_expr_type(right, scope, structs); if let Some(expected_type) = left_type.as_ref().or(right_type.as_ref()) { - if let Some((left, right)) = left_type.as_ref().zip(right_type.as_ref()) { - if !type_refs_equal(left, right, lowerer.contract_constants)? { - return Err(CompilerError::Unsupported("struct comparison requires matching types".to_string())); - } + if let Some((left, right)) = left_type.as_ref().zip(right_type.as_ref()) + && !type_refs_equal(left, right, lowerer.contract_constants)? + { + return Err(CompilerError::Unsupported("struct comparison requires matching types".to_string())); } let left_leaves = lower_struct_expr(left, expected_type, scope, lowerer)?; let right_leaves = lower_struct_expr(right, expected_type, scope, lowerer)?; diff --git a/silverscript-lang/tests/cashc_valid_examples_tests.rs b/silverscript-lang/tests/cashc_valid_examples_tests.rs index 73f6e7aa..afdf4724 100644 --- a/silverscript-lang/tests/cashc_valid_examples_tests.rs +++ b/silverscript-lang/tests/cashc_valid_examples_tests.rs @@ -1,3 +1,5 @@ +mod common; + use blake2b_simd::Params; use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::hashing::sighash::calc_schnorr_signature_hash; @@ -12,8 +14,8 @@ use kaspa_txscript::script_builder::ScriptBuilder; use kaspa_txscript::{EngineCtx, EngineFlags, TxScriptEngine, pay_to_script_hash_script}; use rand::{RngCore, thread_rng}; use secp256k1::{Keypair, Message, Secp256k1, SecretKey}; -use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, CompiledContract, DispatchTag, compile_contract}; +use silverscript_abi::{ArtifactValue, SilAbiArtifact}; +use silverscript_lang::compiler::{DispatchTag, compile_to_sil_abi_artifact}; use std::fs; fn load_example_source(name: &str) -> String { @@ -42,12 +44,12 @@ fn parse_contract_param_types(source: &str) -> Vec { result } -fn dummy_expr_for_type(type_name: &str) -> Expr<'static> { +fn dummy_artifact_value_for_type(type_name: &str) -> ArtifactValue { if type_name == "int" { return 0i64.into(); } if type_name == "temporal" { - return Expr::temporal(kaspa_txscript::LOCK_TIME_THRESHOLD as i64); + return (kaspa_txscript::LOCK_TIME_THRESHOLD as i64).into(); } if type_name == "bool" { return false.into(); @@ -56,10 +58,10 @@ fn dummy_expr_for_type(type_name: &str) -> Expr<'static> { return String::from("aa").into(); } if type_name == "byte[]" { - return Expr::dynamic_bytes(Vec::new()); + return Vec::::new().into(); } if type_name == "pubkey" { - return vec![0u8; 32].into(); // Converts to Expr::Array of Expr::Byte + return vec![0u8; 32].into(); } if type_name == "sig" { return vec![0u8; 65].into(); @@ -72,15 +74,15 @@ fn dummy_expr_for_type(type_name: &str) -> Expr<'static> { return vec![0u8; size].into(); } // Support byte[N] syntax - if let Some(bracket_pos) = type_name.find('[') { - if type_name.ends_with(']') { - let base_type = &type_name[..bracket_pos]; - let size_str = &type_name[bracket_pos + 1..type_name.len() - 1]; - if base_type == "byte" { - if let Ok(size) = size_str.parse::() { - return vec![0u8; size].into(); - } - } + if let Some(bracket_pos) = type_name.find('[') + && type_name.ends_with(']') + { + let base_type = &type_name[..bracket_pos]; + let size_str = &type_name[bracket_pos + 1..type_name.len() - 1]; + if base_type == "byte" + && let Ok(size) = size_str.parse::() + { + return vec![0u8; size].into(); } } 0i64.into() @@ -127,8 +129,18 @@ fn build_sigscript(args: &[ArgValue], dispatch_tag: DispatchTag) -> Vec { builder.drain() } -fn dispatch_tag_for_compiled(compiled: &CompiledContract<'_>, function_name: &str) -> DispatchTag { - compiled.entry_by_name(function_name).expect("entrypoint resolved").dispatch_tag +fn dispatch_tag(artifact: &SilAbiArtifact, function_name: &str) -> DispatchTag { + let Some(contract) = artifact.contracts.values().next().filter(|_| artifact.contracts.len() == 1) else { + panic!("expected one contract, found {}", artifact.contracts.len()); + }; + contract.entry(function_name).expect("entrypoint resolved").dispatch_tag.into_bytes() +} + +fn bytecode(artifact: &SilAbiArtifact) -> &Vec { + let Some(contract) = artifact.contracts.values().next().filter(|_| artifact.contracts.len() == 1) else { + panic!("expected one contract, found {}", artifact.contracts.len()); + }; + &contract.compiled.bytecode } fn build_p2pk_script(pubkey: &[u8]) -> Vec { @@ -263,12 +275,12 @@ fn runs_cashc_valid_examples() { match example { "bitwise.sil" => { let constructor_args = vec![vec![0u8; 8].into(), vec![0u8; 8].into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -279,12 +291,12 @@ fn runs_cashc_valid_examples() { } "bytes1_equals_byte.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[ArgValue::Int(1), ArgValue::Byte(1)], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -295,10 +307,10 @@ fn runs_cashc_valid_examples() { } "cast_hash_checksig.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -306,8 +318,8 @@ fn runs_cashc_valid_examples() { let keypair = random_keypair(); let pubkey_bytes = keypair.x_only_public_key().0.serialize().to_vec(); let signature = sign_tx(&tx, &reused, &keypair); - let sigscript = - compiled.build_sig_script("hello", vec![pubkey_bytes.into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = common::encode_entry_sig_script(&compiled, "hello", &[pubkey_bytes.into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let result = execute_tx(tx, utxo, reused); assert!(result.is_ok(), "{example} failed: {}", result.unwrap_err()); @@ -315,11 +327,11 @@ fn runs_cashc_valid_examples() { "comments.sil" => { // Unsatisfiable: `myOtherVariable` equals `i`, but the contract requires `myOtherVariable > i`. let constructor_args = vec![0i64.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -334,11 +346,11 @@ fn runs_cashc_valid_examples() { } "correct_pragma.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -354,12 +366,12 @@ fn runs_cashc_valid_examples() { "covenant.sil" => { // Unsatisfiable: requires `this.activeScriptPubKey == 0x00`. let constructor_args = vec![1i64.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -371,12 +383,12 @@ fn runs_cashc_valid_examples() { "date_literal.sil" => { // Unsatisfiable: `date("2021-02-17T01:30:00")` is non-zero but the contract requires `d == 0`. let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "test"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "test"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -387,12 +399,12 @@ fn runs_cashc_valid_examples() { } "debug_messages.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[ArgValue::Int(1)], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -404,12 +416,12 @@ fn runs_cashc_valid_examples() { "deep_replace.sil" => { // Unsatisfiable: `a` becomes 3, so `a > b + c + d + e + f` is false. let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -423,11 +435,11 @@ fn runs_cashc_valid_examples() { let recipient_pk = recipient.x_only_public_key().0.serialize().to_vec(); let sender_pk = vec![0u8; 32]; let constructor_args = vec![sender_pk.into(), recipient_pk.clone().into(), 0i64.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "transfer"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "transfer"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -440,13 +452,13 @@ fn runs_cashc_valid_examples() { } "double_split.sil" => { let expected_pkh = vec![0u8; 20]; - let compiled = - compile_contract(&source, &[expected_pkh.clone().into()], CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let constructor_args = vec![expected_pkh.clone().into()]; + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -459,12 +471,12 @@ fn runs_cashc_valid_examples() { "force_cast_smaller_bytes.sil" => { // Unsatisfiable: byte[](0x1234) is 2 bytes, so the forced cast has length 2. let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -475,12 +487,12 @@ fn runs_cashc_valid_examples() { } "if_statement.sil" => { let constructor_args = vec![0i64.into(), 2i64.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[ArgValue::Int(1), ArgValue::Int(1)], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -491,12 +503,12 @@ fn runs_cashc_valid_examples() { } "if_statement_number_units-logs.sil" | "if_statement_number_units.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[ArgValue::Int(20_000), ArgValue::Int(1_209_600_000)], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -507,12 +519,12 @@ fn runs_cashc_valid_examples() { } "int_to_byte.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[ArgValue::Int(1), ArgValue::Byte(1)], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -529,12 +541,12 @@ fn runs_cashc_valid_examples() { } else { (vec![], "hello") }; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, function_name); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, function_name); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -550,12 +562,12 @@ fn runs_cashc_valid_examples() { let recipient_pk = recipient.x_only_public_key().0.serialize().to_vec(); let sender_pk = vec![0u8; 32]; let constructor_args = - vec![sender_pk.into(), recipient_pk.clone().into(), Expr::temporal(kaspa_txscript::LOCK_TIME_THRESHOLD as i64)]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "transfer"); + vec![sender_pk.into(), recipient_pk.clone().into(), (kaspa_txscript::LOCK_TIME_THRESHOLD as i64).into()]; + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "transfer"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -568,12 +580,12 @@ fn runs_cashc_valid_examples() { } "multifunction_if_statements.sil" => { let constructor_args = vec![0i64.into(), 2i64.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "transfer"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "transfer"); let sigscript = build_sigscript(&[ArgValue::Int(1), ArgValue::Int(2)], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -584,12 +596,12 @@ fn runs_cashc_valid_examples() { } "multiline_statements.sil" => { let constructor_args = vec![0i64.into(), String::from("World").into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[ArgValue::Int(0), ArgValue::String("Nope".to_string())], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -600,12 +612,12 @@ fn runs_cashc_valid_examples() { } "multiplication.sil" => { let constructor_args = vec![(-1i64).into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -616,12 +628,12 @@ fn runs_cashc_valid_examples() { } "num2bin_variable.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -635,17 +647,17 @@ fn runs_cashc_valid_examples() { let pubkey_bytes = keypair.x_only_public_key().0.serialize().to_vec(); let pkh = Params::new().hash_length(32).to_state().update(pubkey_bytes.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = vec![pkh.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, ); let signature = sign_tx(&tx, &reused, &keypair); - let sigscript = - compiled.build_sig_script("spend", vec![pubkey_bytes.into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = common::encode_entry_sig_script(&compiled, "spend", &[pubkey_bytes.into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let result = execute_tx(tx, utxo, reused); assert!(result.is_ok(), "{example} failed: {}", result.unwrap_err()); @@ -655,17 +667,17 @@ fn runs_cashc_valid_examples() { let pubkey_bytes = keypair.x_only_public_key().0.serialize().to_vec(); let pkh = Params::new().hash_length(32).to_state().update(pubkey_bytes.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = vec![pkh.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, ); let signature = sign_tx(&tx, &reused, &keypair); - let sigscript = - compiled.build_sig_script("spend", vec![pubkey_bytes.into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = common::encode_entry_sig_script(&compiled, "spend", &[pubkey_bytes.into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let result = execute_tx(tx, utxo, reused); assert!(result.is_ok(), "{example} failed: {}", result.unwrap_err()); @@ -673,11 +685,11 @@ fn runs_cashc_valid_examples() { "reassignment.sil" => { // Unsatisfiable: requires sha256(pubkey) == sha256("Hello World" + y). let constructor_args = vec![0i64.into(), String::from("y").into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -693,11 +705,11 @@ fn runs_cashc_valid_examples() { "simple_cast.sil" => { // Unsatisfiable: requires sha256(pubkey) == sha256(byte[]("Hello World" + y) + byte[](pubkey)). let constructor_args = vec![0i64.into(), String::from("y").into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -723,12 +735,12 @@ fn runs_cashc_valid_examples() { let run = |signature: Vec| { let constructor_args = vec![signature.into(), pubkey_bytes.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "cds"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "cds"); let sigscript = build_sigscript(&[ArgValue::Bytes(message.clone())], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -755,12 +767,12 @@ fn runs_cashc_valid_examples() { let run = |signature: Vec| { let constructor_args = vec![signature.into(), pubkey_bytes.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "cds"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "cds"); let sigscript = build_sigscript(&[ArgValue::Bytes(message.clone())], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -776,12 +788,12 @@ fn runs_cashc_valid_examples() { } "simple_constant.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -792,12 +804,12 @@ fn runs_cashc_valid_examples() { } "simple_covenant.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "covenant"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "covenant"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 2, @@ -808,12 +820,12 @@ fn runs_cashc_valid_examples() { } "simple_functions.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "world"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "world"); let sigscript = build_sigscript(&[ArgValue::Int(5)], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -824,12 +836,12 @@ fn runs_cashc_valid_examples() { } "simple_if_statement.sil" => { let constructor_args = vec![0i64.into(), String::from("World").into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[ArgValue::Int(0), ArgValue::String("Hello World".to_string())], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -839,13 +851,13 @@ fn runs_cashc_valid_examples() { assert!(result.is_ok(), "{example} failed: {}", result.unwrap_err()); } "simple_splice.sil" => { - let constructor_args = vec![Expr::dynamic_bytes(vec![0u8; 6])]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let constructor_args = vec![vec![0u8; 6].into()]; + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -857,11 +869,11 @@ fn runs_cashc_valid_examples() { "simple_variables.sil" => { // Unsatisfiable: requires sha256(pubkey) == sha256("Hello World" + y). let constructor_args = vec![0i64.into(), String::from("y").into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -884,8 +896,8 @@ fn runs_cashc_valid_examples() { initial_block_bytes.resize(8, 0); let constructor_args = vec![recipient.clone().into(), funder.clone().into(), pledge_per_block.into(), initial_block_bytes.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "receive"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "receive"); let lock_time = 10u64; let passed_blocks = lock_time as i64 - initial_block_value; @@ -904,7 +916,7 @@ fn runs_cashc_valid_examples() { out.extend_from_slice(&lock_bytes); let mut active_bytecode = Vec::new(); active_bytecode.extend_from_slice(&0u16.to_be_bytes()); - active_bytecode.extend_from_slice(&compiled.bytecode); + active_bytecode.extend_from_slice(bytecode(&compiled)); out.extend_from_slice(&active_bytecode[9..]); out }; @@ -914,7 +926,7 @@ fn runs_cashc_valid_examples() { let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), vec![(output0_value, output0_script), (output1_value, output1_script)], input_value, lock_time, @@ -926,13 +938,13 @@ fn runs_cashc_valid_examples() { } "slice.sil" | "slice_variable_parameter.sil" => { let expected_pkh = vec![0u8; 20]; - let compiled = - compile_contract(&source, &[expected_pkh.clone().into()], CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let constructor_args = vec![expected_pkh.clone().into()]; + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -944,12 +956,12 @@ fn runs_cashc_valid_examples() { } "slice_optimised.sil" => { let constructor_args = vec![vec![0u8; 32].into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -963,12 +975,12 @@ fn runs_cashc_valid_examples() { let mut signature = vec![0u8; 64]; signature.push(0x01); let constructor_args = vec![signature.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -978,13 +990,13 @@ fn runs_cashc_valid_examples() { assert!(result.is_ok(), "{example} failed: {}", result.unwrap_err()); } "split_size.sil" => { - let constructor_args = vec![Expr::dynamic_bytes(b"abcd".to_vec())]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let constructor_args = vec![b"abcd".to_vec().into()]; + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -994,13 +1006,13 @@ fn runs_cashc_valid_examples() { assert!(result.is_ok(), "{example} failed: {}", result.unwrap_err()); } "split_typed.sil" => { - let constructor_args = vec![Expr::dynamic_bytes(b"abcde".to_vec())]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "spend"); + let constructor_args = vec![b"abcde".to_vec().into()]; + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "spend"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -1011,12 +1023,12 @@ fn runs_cashc_valid_examples() { } "string_concatenation.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[ArgValue::String("world".to_string())], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -1028,12 +1040,12 @@ fn runs_cashc_valid_examples() { "string_with_escaped_characters.sil" => { // Unsatisfiable in this runtime: escaped string literals hash differently. let constructor_args = vec![0i64.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "hello"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "hello"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -1045,12 +1057,12 @@ fn runs_cashc_valid_examples() { "tuple_unpacking.sil" => { // Unsatisfiable: split("hello" + "there") yields "hello" and "there", which are not equal. let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "split"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "split"); let sigscript = build_sigscript(&[], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -1061,12 +1073,12 @@ fn runs_cashc_valid_examples() { } "tuple_unpacking_parameter.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "split"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "split"); let sigscript = build_sigscript(&[ArgValue::Bytes(vec![0u8; 32])], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -1077,12 +1089,12 @@ fn runs_cashc_valid_examples() { } "tuple_unpacking_single_side_type.sil" => { let constructor_args = vec![]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag = dispatch_tag_for_compiled(&compiled, "split"); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args).expect("compile succeeds"); + let dispatch_tag = dispatch_tag(&compiled, "split"); let sigscript = build_sigscript(&[ArgValue::Bytes(vec![0u8; 32])], dispatch_tag); let (mut tx, utxo, reused) = build_tx_context( - compiled.bytecode.clone(), - vec![(1_000, compiled.bytecode.clone()), (1_000, compiled.bytecode.clone())], + bytecode(&compiled).clone(), + vec![(1_000, bytecode(&compiled).clone()), (1_000, bytecode(&compiled).clone())], 2_000, 0, 1, @@ -1161,8 +1173,8 @@ fn compiles_cashc_valid_examples() { for example in examples { let source = load_example_source(example); let param_types = parse_contract_param_types(&source); - let constructor_args = param_types.into_iter().map(|t| dummy_expr_for_type(&t)).collect::>(); - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()); + let constructor_args = param_types.into_iter().map(|t| dummy_artifact_value_for_type(&t)).collect::>(); + let compiled = compile_to_sil_abi_artifact(&source, &constructor_args); assert!(compiled.is_ok(), "{example} failed to compile: {}", compiled.unwrap_err()); } } diff --git a/silverscript-lang/tests/chess_apps_tests.rs b/silverscript-lang/tests/chess_apps_tests.rs index a3cfa375..45463e27 100644 --- a/silverscript-lang/tests/chess_apps_tests.rs +++ b/silverscript-lang/tests/chess_apps_tests.rs @@ -1,3 +1,5 @@ +mod common; + use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; @@ -20,14 +22,14 @@ use kaspa_txscript::{ }; use kaspa_txscript_errors::TxScriptError; use secp256k1::{Keypair, Message, Secp256k1, SecretKey}; -use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, CompiledContract, compile_contract}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::compiler::{CompileOptions, compile_to_sil_abi_artifact_with_options}; const DEFAULT_MOVE_TIMEOUT: i64 = 600; struct SizeSnapshot { name: &'static str, - ctor: fn() -> Vec>, + ctor: fn() -> Vec, expected_bytecode_len: usize, // Total complete P2SH sigscript bytes in the runtime-tested transaction. expected_sigscript_len: usize, @@ -108,16 +110,16 @@ fn source_cache() -> &'static Mutex> { CACHE.get_or_init(|| Mutex::new(HashMap::new())) } -fn compiled_contract_cache() -> &'static Mutex>>> { - static CACHE: OnceLock>>>> = OnceLock::new(); +fn compiled_contract_cache() -> &'static Mutex>> { + static CACHE: OnceLock>>> = OnceLock::new(); CACHE.get_or_init(|| Mutex::new(HashMap::new())) } -fn compile_cache_key(source: &'static str, ctor: &[Expr<'static>]) -> String { +fn compile_cache_key(source: &'static str, ctor: &[ArtifactValue]) -> String { format!("{:p}:{}:{}", source.as_ptr(), source.len(), serde_json::to_string(ctor).expect("serialize chess ctor args")) } -fn compile_cached(source: &'static str, ctor: &[Expr<'static>]) -> Arc> { +fn compile_cached(source: &'static str, ctor: &[ArtifactValue]) -> Arc { let key = compile_cache_key(source, ctor); { let cache = compiled_contract_cache().lock().expect("compile cache mutex poisoned"); @@ -126,7 +128,9 @@ fn compile_cached(source: &'static str, ctor: &[Expr<'static>]) -> Arc Hash { blake2b_bytes(&[left.as_slice(), right.as_slice()].concat()) } -fn hash_expr(value: Hash) -> Expr<'static> { - Expr::bytes(hash_bytes(value)) +fn hash_value(value: Hash) -> ArtifactValue { + ArtifactValue::Bytes(hash_bytes(value)) } fn repeated_hash(byte: u8) -> Hash { @@ -272,8 +276,8 @@ fn full_castle_rights() -> [u8; 4] { [1, 1, 1, 1] } -fn castle_rights_expr(rights: [u8; 4]) -> Expr<'static> { - Expr::bytes(rights.to_vec()) +fn castle_rights_value(rights: [u8; 4]) -> ArtifactValue { + ArtifactValue::Bytes(rights.to_vec()) } fn move_piece(board: &mut [u8], from_x: usize, from_y: usize, to_x: usize, to_y: usize) { @@ -315,12 +319,12 @@ fn routes_commitment(route_templates: &[u8]) -> Hash { blake2b_bytes(route_templates) } -fn template_fixture(source: &'static str, ctor: &[Expr<'static>]) -> TemplateFixture { +fn template_fixture(source: &'static str, ctor: &[ArtifactValue]) -> TemplateFixture { let compiled = compile_cached(source, ctor); - let layout = compiled.state_layout; - let prefix = compiled.bytecode[..layout.start].to_vec(); - let suffix = compiled.bytecode[layout.start + layout.len..].to_vec(); - let hash = Hash::from_bytes(compiled.template_hash()); + let layout = common::state_layout(&compiled); + let prefix = common::bytecode(&compiled)[..layout.start].to_vec(); + let suffix = common::bytecode(&compiled)[layout.start + layout.len..].to_vec(); + let hash = Hash::from_bytes(common::template_hash(&compiled)); TemplateFixture { source, prefix, suffix, hash } } @@ -329,24 +333,28 @@ fn fixture() -> &'static MuxChessFixture { FIXTURE.get_or_init(|| { let dummy_board = standard_board(); let game_ctor = vec![ - Expr::bytes(vec![0x11u8; 32]), - Expr::bytes(vec![0x33u8; 32 * 9]), - Expr::bytes(vec![0x21u8; 32]), - Expr::bytes(vec![0x22u8; 32]), - Expr::bytes(dummy_board), - Expr::int(0), - Expr::int(0), - Expr::int(DEFAULT_MOVE_TIMEOUT), - castle_rights_expr(full_castle_rights()), - Expr::int(-1), - Expr::int(-1), - Expr::int(-1), - Expr::int(0), - Expr::int(0), - Expr::int(3), + ArtifactValue::Bytes(vec![0x11u8; 32]), + ArtifactValue::Bytes(vec![0x33u8; 32 * 9]), + ArtifactValue::Bytes(vec![0x21u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Bytes(dummy_board), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + castle_rights_value(full_castle_rights()), + ArtifactValue::Int(-1), + ArtifactValue::Int(-1), + ArtifactValue::Int(-1), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(3), + ]; + let settle_ctor = vec![ + ArtifactValue::Bytes(vec![0x44u8; 32]), + ArtifactValue::Bytes(vec![0x21u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Int(0), ]; - let settle_ctor = - vec![Expr::bytes(vec![0x44u8; 32]), Expr::bytes(vec![0x21u8; 32]), Expr::bytes(vec![0x22u8; 32]), Expr::int(0)]; MuxChessFixture { mux: template_fixture(mux_source(), &game_ctor), @@ -369,23 +377,23 @@ fn compile_state( white_hash: &Hash, black_hash: &Hash, state: GameStateArgs<'_>, -) -> Arc> { +) -> Arc { let ctor = vec![ - hash_expr(fix.mux.hash), - Expr::bytes(packed_route_templates(fix)), - hash_expr(*white_hash), - hash_expr(*black_hash), - Expr::bytes(state.board.to_vec()), - Expr::int(state.turn), - Expr::int(state.status), - Expr::int(DEFAULT_MOVE_TIMEOUT), - castle_rights_expr(state.castle_rights), - Expr::int(state.en_passant_idx), - Expr::int(state.pending_src_idx), - Expr::int(state.pending_dst_idx), - Expr::int(state.pending_promo), - Expr::int(state.recent_castle), - Expr::int(state.draw_state), + hash_value(fix.mux.hash), + ArtifactValue::Bytes(packed_route_templates(fix)), + hash_value(*white_hash), + hash_value(*black_hash), + ArtifactValue::Bytes(state.board.to_vec()), + ArtifactValue::Int(state.turn), + ArtifactValue::Int(state.status), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + castle_rights_value(state.castle_rights), + ArtifactValue::Int(state.en_passant_idx), + ArtifactValue::Int(state.pending_src_idx), + ArtifactValue::Int(state.pending_dst_idx), + ArtifactValue::Int(state.pending_promo), + ArtifactValue::Int(state.recent_castle), + ArtifactValue::Int(state.draw_state), ]; compile_cached(source, &ctor) } @@ -396,25 +404,25 @@ fn compile_settle_state( white_hash: &Hash, black_hash: &Hash, status: i64, -) -> Arc> { - let ctor = vec![hash_expr(*player_template), hash_expr(*white_hash), hash_expr(*black_hash), Expr::int(status)]; +) -> Arc { + let ctor = vec![hash_value(*player_template), hash_value(*white_hash), hash_value(*black_hash), ArtifactValue::Int(status)]; compile_cached(source, &ctor) } -fn compile_player_state(source: &'static str, state: PlayerStateArgs<'_>) -> Arc> { +fn compile_player_state(source: &'static str, state: PlayerStateArgs<'_>) -> Arc { let ctor = vec![ - hash_expr(*state.league_template), - hash_expr(*state.player_template), - hash_expr(*state.mux_template), - hash_expr(*state.routes_commitment), - hash_expr(*state.owner_hash), - hash_expr(*state.player_id), - Expr::int(state.open_games), - Expr::int(state.rating), - Expr::int(state.games), - Expr::int(state.wins), - Expr::int(state.draws), - Expr::int(state.losses), + hash_value(*state.league_template), + hash_value(*state.player_template), + hash_value(*state.mux_template), + hash_value(*state.routes_commitment), + hash_value(*state.owner_hash), + hash_value(*state.player_id), + ArtifactValue::Int(state.open_games), + ArtifactValue::Int(state.rating), + ArtifactValue::Int(state.games), + ArtifactValue::Int(state.wins), + ArtifactValue::Int(state.draws), + ArtifactValue::Int(state.losses), ]; compile_cached(source, &ctor) } @@ -437,13 +445,13 @@ fn player_template_hash(fix: &MuxChessFixture) -> Hash { losses: 0, }, ); - Hash::from_bytes(compiled.template_hash()) + Hash::from_bytes(common::template_hash(&compiled)) } -fn entry_sigscript(compiled: &CompiledContract<'_>, function: &str, args: Vec>) -> Vec { - let sigscript = compiled.build_sig_script(function, args).expect("sigscript builds"); +fn entry_sigscript(compiled: &silverscript_abi::SilAbiArtifact, function: &str, args: Vec) -> Vec { + let sigscript = common::encode_entry_sig_script(compiled, function, &args).expect("sigscript builds"); pay_to_script_hash_signature_script_with_flags( - compiled.bytecode.clone(), + common::bytecode(compiled).clone(), sigscript, EngineFlags { covenants_enabled: true, ..Default::default() }, ) @@ -460,27 +468,27 @@ fn tx_input(index: u32, signature_script: Vec, sig_op_count: u8) -> Transact } fn covenant_output_with_value( - compiled: &CompiledContract<'_>, + compiled: &silverscript_abi::SilAbiArtifact, authorizing_input: u16, covenant_id: Hash, value: u64, ) -> TransactionOutput { TransactionOutput { value, - script_public_key: pay_to_script_hash_script(&compiled.bytecode), + script_public_key: pay_to_script_hash_script(&common::bytecode(compiled)), covenant: Some(CovenantBinding { authorizing_input, covenant_id }), } } -fn covenant_output(compiled: &CompiledContract<'_>, authorizing_input: u16, covenant_id: Hash) -> TransactionOutput { +fn covenant_output(compiled: &silverscript_abi::SilAbiArtifact, authorizing_input: u16, covenant_id: Hash) -> TransactionOutput { covenant_output_with_value(compiled, authorizing_input, covenant_id, 1_000) } -fn covenant_utxo_with_value(compiled: &CompiledContract<'_>, covenant_id: Hash, value: u64) -> UtxoEntry { - UtxoEntry::new(value, pay_to_script_hash_script(&compiled.bytecode), 0, false, Some(covenant_id)) +fn covenant_utxo_with_value(compiled: &silverscript_abi::SilAbiArtifact, covenant_id: Hash, value: u64) -> UtxoEntry { + UtxoEntry::new(value, pay_to_script_hash_script(&common::bytecode(compiled)), 0, false, Some(covenant_id)) } -fn covenant_utxo(compiled: &CompiledContract<'_>, covenant_id: Hash) -> UtxoEntry { +fn covenant_utxo(compiled: &silverscript_abi::SilAbiArtifact, covenant_id: Hash) -> UtxoEntry { covenant_utxo_with_value(compiled, covenant_id, 1_000) } @@ -515,12 +523,12 @@ fn sign_tx_input_schnorr(tx: &Transaction, entries: &[UtxoEntry], input_idx: usi } fn run_route( - active: &CompiledContract<'_>, + active: &silverscript_abi::SilAbiArtifact, selector: i64, mv: MoveArgs, player: &Player, target: &TemplateFixture, - out: &CompiledContract<'_>, + out: &silverscript_abi::SilAbiArtifact, covenant_id: Hash, ) { let placeholder_sig = vec![0u8; 65]; @@ -535,11 +543,11 @@ fn run_route( mv.to_y.into(), mv.promo_piece.into(), 0.into(), - Expr::bytes(placeholder_sig), - Expr::bytes(player.pubkey_bytes.clone()), - hash_expr(player.player_id), - Expr::dynamic_bytes(target.prefix.clone()), - Expr::dynamic_bytes(target.suffix.clone()), + ArtifactValue::Bytes(placeholder_sig), + ArtifactValue::Bytes(player.pubkey_bytes.clone()), + hash_value(player.player_id), + ArtifactValue::Bytes(target.prefix.clone()), + ArtifactValue::Bytes(target.suffix.clone()), ], ); let outputs = vec![covenant_output(out, 0, covenant_id)]; @@ -557,11 +565,11 @@ fn run_route( mv.to_y.into(), mv.promo_piece.into(), 0.into(), - Expr::bytes(sig), - Expr::bytes(player.pubkey_bytes.clone()), - hash_expr(player.player_id), - Expr::dynamic_bytes(target.prefix.clone()), - Expr::dynamic_bytes(target.suffix.clone()), + ArtifactValue::Bytes(sig), + ArtifactValue::Bytes(player.pubkey_bytes.clone()), + hash_value(player.player_id), + ArtifactValue::Bytes(target.prefix.clone()), + ArtifactValue::Bytes(target.suffix.clone()), ], ); assert_sigscript_size("chess_mux.sil", &tx); @@ -571,13 +579,13 @@ fn run_route( fn run_worker_apply( label: &str, - active: &CompiledContract<'_>, - next: &CompiledContract<'_>, + active: &silverscript_abi::SilAbiArtifact, + next: &silverscript_abi::SilAbiArtifact, covenant_id: Hash, mux: &TemplateFixture, ) { let sigscript = - entry_sigscript(active, "apply", vec![Expr::dynamic_bytes(mux.prefix.clone()), Expr::dynamic_bytes(mux.suffix.clone())]); + entry_sigscript(active, "apply", vec![ArtifactValue::Bytes(mux.prefix.clone()), ArtifactValue::Bytes(mux.suffix.clone())]); let outputs = vec![covenant_output(next, 0, covenant_id)]; let entries = vec![covenant_utxo(active, covenant_id)]; let tx = Transaction::new(1, vec![tx_input(0, sigscript, 0)], outputs, 0, Default::default(), 0, vec![]); @@ -588,13 +596,16 @@ fn run_worker_apply( fn run_prep_apply( label: &str, - active: &CompiledContract<'_>, - next: &CompiledContract<'_>, + active: &silverscript_abi::SilAbiArtifact, + next: &silverscript_abi::SilAbiArtifact, covenant_id: Hash, target: &TemplateFixture, ) { - let sigscript = - entry_sigscript(active, "apply", vec![Expr::dynamic_bytes(target.prefix.clone()), Expr::dynamic_bytes(target.suffix.clone())]); + let sigscript = entry_sigscript( + active, + "apply", + vec![ArtifactValue::Bytes(target.prefix.clone()), ArtifactValue::Bytes(target.suffix.clone())], + ); let outputs = vec![covenant_output(next, 0, covenant_id)]; let entries = vec![covenant_utxo(active, covenant_id)]; let tx = Transaction::new(1, vec![tx_input(0, sigscript, 0)], outputs, 0, Default::default(), 0, vec![]); @@ -770,75 +781,80 @@ fn assert_sigscript_size(name: &str, tx: &Transaction) { assert_size_within_noise(&format!("{name} sigscript_len"), actual, expected); } -fn pawn_constructor_args() -> Vec> { +fn pawn_constructor_args() -> Vec { vec![ - Expr::bytes(vec![0x11u8; 32]), - Expr::bytes(sample_route_templates()), - Expr::bytes(vec![0x21u8; 32]), - Expr::bytes(vec![0x22u8; 32]), - Expr::bytes(standard_board()), - Expr::int(0), - Expr::int(0), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::bytes(vec![1u8; 4]), - Expr::int(-1), - Expr::int(12), - Expr::int(28), - Expr::int(0), - Expr::int(0), - Expr::int(3), + ArtifactValue::Bytes(vec![0x11u8; 32]), + ArtifactValue::Bytes(sample_route_templates()), + ArtifactValue::Bytes(vec![0x21u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Bytes(standard_board()), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Bytes(vec![1u8; 4]), + ArtifactValue::Int(-1), + ArtifactValue::Int(12), + ArtifactValue::Int(28), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(3), ] } -fn mux_constructor_args() -> Vec> { +fn mux_constructor_args() -> Vec { vec![ - Expr::bytes(vec![0x11u8; 32]), - Expr::bytes(sample_route_templates()), - Expr::bytes(vec![0x21u8; 32]), - Expr::bytes(vec![0x22u8; 32]), - Expr::bytes(vec![0u8; 64]), - Expr::int(0), - Expr::int(0), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::bytes(vec![1u8; 4]), - Expr::int(-1), - Expr::int(-1), - Expr::int(-1), - Expr::int(0), - Expr::int(0), - Expr::int(3), + ArtifactValue::Bytes(vec![0x11u8; 32]), + ArtifactValue::Bytes(sample_route_templates()), + ArtifactValue::Bytes(vec![0x21u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Bytes(vec![0u8; 64]), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Bytes(vec![1u8; 4]), + ArtifactValue::Int(-1), + ArtifactValue::Int(-1), + ArtifactValue::Int(-1), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(3), ] } -fn settle_constructor_args() -> Vec> { - vec![Expr::bytes(vec![0x31u8; 32]), Expr::bytes(vec![0x21u8; 32]), Expr::bytes(vec![0x22u8; 32]), Expr::int(1)] +fn settle_constructor_args() -> Vec { + vec![ + ArtifactValue::Bytes(vec![0x31u8; 32]), + ArtifactValue::Bytes(vec![0x21u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Int(1), + ] } -fn player_constructor_args() -> Vec> { +fn player_constructor_args() -> Vec { vec![ - Expr::bytes(vec![0x11u8; 32]), - Expr::bytes(vec![0x22u8; 32]), - Expr::bytes(vec![0x33u8; 32]), - Expr::bytes(sample_routes_commitment().as_bytes().to_vec()), - Expr::bytes(vec![0x44u8; 32]), - Expr::bytes(vec![0x55u8; 32]), - Expr::int(0), - Expr::int(1200), - Expr::int(7), - Expr::int(4), - Expr::int(2), - Expr::int(1), + ArtifactValue::Bytes(vec![0x11u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Bytes(vec![0x33u8; 32]), + ArtifactValue::Bytes(sample_routes_commitment().as_bytes().to_vec()), + ArtifactValue::Bytes(vec![0x44u8; 32]), + ArtifactValue::Bytes(vec![0x55u8; 32]), + ArtifactValue::Int(0), + ArtifactValue::Int(1200), + ArtifactValue::Int(7), + ArtifactValue::Int(4), + ArtifactValue::Int(2), + ArtifactValue::Int(1), ] } -fn league_constructor_args() -> Vec> { +fn league_constructor_args() -> Vec { vec![ - Expr::bytes(vec![0x11u8; 32]), - Expr::bytes(vec![0x22u8; 32]), - Expr::bytes(vec![0x33u8; 32]), - Expr::bytes(sample_routes_commitment().as_bytes().to_vec()), - Expr::int(1200), - Expr::bytes(vec![0x44u8; 32]), + ArtifactValue::Bytes(vec![0x11u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Bytes(vec![0x33u8; 32]), + ArtifactValue::Bytes(sample_routes_commitment().as_bytes().to_vec()), + ArtifactValue::Int(1200), + ArtifactValue::Bytes(vec![0x44u8; 32]), ] } @@ -850,9 +866,9 @@ fn chess_apps_compile_and_probe_sizes_within_noise() { let source = local_contract_source(snapshot.name); let ctor = (snapshot.ctor)(); let compiled = compile_cached(source, &ctor); - let (instruction_count, charged_op_count) = bytecode_op_counts(&compiled.bytecode); + let (instruction_count, charged_op_count) = bytecode_op_counts(&common::bytecode(&compiled)); - actual_sizes.push((snapshot.name, compiled.bytecode.len(), instruction_count, charged_op_count)); + actual_sizes.push((snapshot.name, common::bytecode(&compiled).len(), instruction_count, charged_op_count)); } for (name, bytecode_len, instruction_count, charged_op_count) in &actual_sizes { @@ -884,32 +900,32 @@ fn league_register_player_runtime_matches_expected_output_state() { let player_id_domain = b"LeaguePlayerId".to_vec(); let player_template_ctor = vec![ - hash_expr(league_template), - hash_expr(repeated_hash(0x44)), - hash_expr(fix.mux.hash), - hash_expr(routes_commitment), - hash_expr(repeated_hash(0x55)), - hash_expr(repeated_hash(0x77)), - Expr::int(0), - Expr::int(900), - Expr::int(1), - Expr::int(2), - Expr::int(3), - Expr::int(4), + hash_value(league_template), + hash_value(repeated_hash(0x44)), + hash_value(fix.mux.hash), + hash_value(routes_commitment), + hash_value(repeated_hash(0x55)), + hash_value(repeated_hash(0x77)), + ArtifactValue::Int(0), + ArtifactValue::Int(900), + ArtifactValue::Int(1), + ArtifactValue::Int(2), + ArtifactValue::Int(3), + ArtifactValue::Int(4), ]; let player_template_contract = compile_cached(player_source(), &player_template_ctor); - let layout = player_template_contract.state_layout; - let player_prefix = player_template_contract.bytecode[..layout.start].to_vec(); - let player_suffix = player_template_contract.bytecode[layout.start + layout.len..].to_vec(); - let player_template = Hash::from_bytes(player_template_contract.template_hash()); + let layout = common::state_layout(&player_template_contract); + let player_prefix = common::bytecode(&player_template_contract)[..layout.start].to_vec(); + let player_suffix = common::bytecode(&player_template_contract)[layout.start + layout.len..].to_vec(); + let player_template = Hash::from_bytes(common::template_hash(&player_template_contract)); let league_ctor = vec![ - hash_expr(league_template), - hash_expr(player_template), - hash_expr(fix.mux.hash), - hash_expr(routes_commitment), - Expr::int(base_rating), - hash_expr(admin), + hash_value(league_template), + hash_value(player_template), + hash_value(fix.mux.hash), + hash_value(routes_commitment), + ArtifactValue::Int(base_rating), + hash_value(admin), ]; let league = compile_cached(league_source(), &league_ctor); @@ -944,10 +960,10 @@ fn league_register_player_runtime_matches_expected_output_state() { &league, "register_player", vec![ - Expr::bytes(vec![0u8; 65]), - Expr::bytes(owner.pubkey_bytes.clone()), - Expr::dynamic_bytes(player_prefix.clone()), - Expr::dynamic_bytes(player_suffix.clone()), + ArtifactValue::Bytes(vec![0u8; 65]), + ArtifactValue::Bytes(owner.pubkey_bytes.clone()), + ArtifactValue::Bytes(player_prefix.clone()), + ArtifactValue::Bytes(player_suffix.clone()), ], ); let outputs = vec![covenant_output(&league, 0, covenant_id), covenant_output(®istered_player, 0, covenant_id)]; @@ -960,10 +976,10 @@ fn league_register_player_runtime_matches_expected_output_state() { &league, "register_player", vec![ - Expr::bytes(sig), - Expr::bytes(owner.pubkey_bytes), - Expr::dynamic_bytes(player_prefix), - Expr::dynamic_bytes(player_suffix), + ArtifactValue::Bytes(sig), + ArtifactValue::Bytes(owner.pubkey_bytes), + ArtifactValue::Bytes(player_prefix), + ArtifactValue::Bytes(player_suffix), ], ); @@ -1001,10 +1017,10 @@ fn player_start_game_runtime_matches_expected_output_states() { losses: 0, }, ); - let player_layout = player_contract.state_layout; - let player_template = Hash::from_bytes(player_contract.template_hash()); + let player_layout = common::state_layout(&player_contract); + let player_template = Hash::from_bytes(common::template_hash(&player_contract)); let player_prefix_len = player_layout.start as i64; - let player_suffix_len = (player_contract.bytecode.len() - (player_layout.start + player_layout.len)) as i64; + let player_suffix_len = (common::bytecode(&player_contract).len() - (player_layout.start + player_layout.len)) as i64; let white_player = compile_player_state( player_source(), @@ -1097,26 +1113,26 @@ fn player_start_game_runtime_matches_expected_output_states() { &white_player, "start_game", vec![ - Expr::bytes(vec![0u8; 65]), - Expr::bytes(white.pubkey_bytes.clone()), - Expr::int(0), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), - Expr::bytes(route_templates.clone()), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::dynamic_bytes(fix.mux.prefix.clone()), - Expr::dynamic_bytes(fix.mux.suffix.clone()), + ArtifactValue::Bytes(vec![0u8; 65]), + ArtifactValue::Bytes(white.pubkey_bytes.clone()), + ArtifactValue::Int(0), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), + ArtifactValue::Bytes(route_templates.clone()), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Bytes(fix.mux.prefix.clone()), + ArtifactValue::Bytes(fix.mux.suffix.clone()), ], ); let black_placeholder = entry_sigscript( &black_player, "delegate_start_game", vec![ - Expr::bytes(vec![0u8; 65]), - Expr::bytes(black.pubkey_bytes.clone()), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), + ArtifactValue::Bytes(vec![0u8; 65]), + ArtifactValue::Bytes(black.pubkey_bytes.clone()), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), ], ); @@ -1143,26 +1159,26 @@ fn player_start_game_runtime_matches_expected_output_states() { &white_player, "start_game", vec![ - Expr::bytes(white_sig), - Expr::bytes(white.pubkey_bytes), - Expr::int(0), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), - Expr::bytes(route_templates), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::dynamic_bytes(fix.mux.prefix.clone()), - Expr::dynamic_bytes(fix.mux.suffix.clone()), + ArtifactValue::Bytes(white_sig), + ArtifactValue::Bytes(white.pubkey_bytes), + ArtifactValue::Int(0), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), + ArtifactValue::Bytes(route_templates), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Bytes(fix.mux.prefix.clone()), + ArtifactValue::Bytes(fix.mux.suffix.clone()), ], ); tx.inputs[1].signature_script = entry_sigscript( &black_player, "delegate_start_game", vec![ - Expr::bytes(black_sig), - Expr::bytes(black.pubkey_bytes), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), + ArtifactValue::Bytes(black_sig), + ArtifactValue::Bytes(black.pubkey_bytes), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), ], ); @@ -1217,30 +1233,38 @@ fn player_rebalance_requires_a_standalone_covenant_input() { losses: 0, }, ); - let player_layout = player.state_layout; + let player_layout = common::state_layout(&player); let player_prefix_len = player_layout.start as i64; - let player_suffix_len = (player.bytecode.len() - player_layout.start - player_layout.len) as i64; + let player_suffix_len = (common::bytecode(&player).len() - player_layout.start - player_layout.len) as i64; - let placeholder = entry_sigscript(&player, "rebalance", vec![Expr::bytes(vec![0u8; 65]), Expr::bytes(owner.pubkey_bytes.clone())]); + let placeholder = entry_sigscript( + &player, + "rebalance", + vec![ArtifactValue::Bytes(vec![0u8; 65]), ArtifactValue::Bytes(owner.pubkey_bytes.clone())], + ); let entries = vec![covenant_utxo(&player, covenant_id)]; let outputs = vec![covenant_output(&player, 0, covenant_id)]; let mut standalone_tx = Transaction::new(1, vec![tx_input(0, placeholder, 1)], outputs, 0, Default::default(), 0, vec![]); let signature = sign_tx_input_schnorr(&standalone_tx, &entries, 0, &owner); standalone_tx.inputs[0].signature_script = - entry_sigscript(&player, "rebalance", vec![Expr::bytes(signature), Expr::bytes(owner.pubkey_bytes.clone())]); + entry_sigscript(&player, "rebalance", vec![ArtifactValue::Bytes(signature), ArtifactValue::Bytes(owner.pubkey_bytes.clone())]); let standalone_result = execute_input_with_covenants(standalone_tx, entries, 0); assert!(standalone_result.is_ok(), "standalone player rebalance failed: {}", standalone_result.unwrap_err()); - let placeholder = entry_sigscript(&player, "rebalance", vec![Expr::bytes(vec![0u8; 65]), Expr::bytes(owner.pubkey_bytes.clone())]); + let placeholder = entry_sigscript( + &player, + "rebalance", + vec![ArtifactValue::Bytes(vec![0u8; 65]), ArtifactValue::Bytes(owner.pubkey_bytes.clone())], + ); let delegate_placeholder = entry_sigscript( &delegate, "delegate_start_game", vec![ - Expr::bytes(vec![0u8; 65]), - Expr::bytes(delegate_owner.pubkey_bytes.clone()), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), + ArtifactValue::Bytes(vec![0u8; 65]), + ArtifactValue::Bytes(delegate_owner.pubkey_bytes.clone()), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), ], ); let entries = vec![covenant_utxo(&player, covenant_id), covenant_utxo(&delegate, covenant_id)]; @@ -1257,16 +1281,16 @@ fn player_rebalance_requires_a_standalone_covenant_input() { let signature = sign_tx_input_schnorr(&shared_tx, &entries, 0, &owner); let delegate_signature = sign_tx_input_schnorr(&shared_tx, &entries, 1, &delegate_owner); shared_tx.inputs[0].signature_script = - entry_sigscript(&player, "rebalance", vec![Expr::bytes(signature), Expr::bytes(owner.pubkey_bytes)]); + entry_sigscript(&player, "rebalance", vec![ArtifactValue::Bytes(signature), ArtifactValue::Bytes(owner.pubkey_bytes)]); shared_tx.inputs[1].signature_script = entry_sigscript( &delegate, "delegate_start_game", vec![ - Expr::bytes(delegate_signature), - Expr::bytes(delegate_owner.pubkey_bytes), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), + ArtifactValue::Bytes(delegate_signature), + ArtifactValue::Bytes(delegate_owner.pubkey_bytes), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), ], ); let delegate_result = execute_input_with_covenants(shared_tx.clone(), entries.clone(), 1); @@ -1318,29 +1342,37 @@ fn player_retire_requires_a_standalone_covenant_input() { losses: 0, }, ); - let player_layout = player.state_layout; + let player_layout = common::state_layout(&player); let player_prefix_len = player_layout.start as i64; - let player_suffix_len = (player.bytecode.len() - player_layout.start - player_layout.len) as i64; + let player_suffix_len = (common::bytecode(&player).len() - player_layout.start - player_layout.len) as i64; - let placeholder = entry_sigscript(&player, "retire", vec![Expr::bytes(vec![0u8; 65]), Expr::bytes(owner.pubkey_bytes.clone())]); + let placeholder = entry_sigscript( + &player, + "retire", + vec![ArtifactValue::Bytes(vec![0u8; 65]), ArtifactValue::Bytes(owner.pubkey_bytes.clone())], + ); let entries = vec![covenant_utxo(&player, covenant_id)]; let mut standalone_tx = Transaction::new(1, vec![tx_input(0, placeholder, 1)], vec![], 0, Default::default(), 0, vec![]); let signature = sign_tx_input_schnorr(&standalone_tx, &entries, 0, &owner); standalone_tx.inputs[0].signature_script = - entry_sigscript(&player, "retire", vec![Expr::bytes(signature), Expr::bytes(owner.pubkey_bytes.clone())]); + entry_sigscript(&player, "retire", vec![ArtifactValue::Bytes(signature), ArtifactValue::Bytes(owner.pubkey_bytes.clone())]); let standalone_result = execute_input_with_covenants(standalone_tx, entries, 0); assert!(standalone_result.is_ok(), "standalone player retirement failed: {}", standalone_result.unwrap_err()); - let placeholder = entry_sigscript(&player, "retire", vec![Expr::bytes(vec![0u8; 65]), Expr::bytes(owner.pubkey_bytes.clone())]); + let placeholder = entry_sigscript( + &player, + "retire", + vec![ArtifactValue::Bytes(vec![0u8; 65]), ArtifactValue::Bytes(owner.pubkey_bytes.clone())], + ); let delegate_placeholder = entry_sigscript( &delegate, "delegate_start_game", vec![ - Expr::bytes(vec![0u8; 65]), - Expr::bytes(delegate_owner.pubkey_bytes.clone()), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), + ArtifactValue::Bytes(vec![0u8; 65]), + ArtifactValue::Bytes(delegate_owner.pubkey_bytes.clone()), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), ], ); let entries = vec![covenant_utxo(&player, covenant_id), covenant_utxo(&delegate, covenant_id)]; @@ -1356,16 +1388,16 @@ fn player_retire_requires_a_standalone_covenant_input() { let signature = sign_tx_input_schnorr(&shared_tx, &entries, 0, &owner); let delegate_signature = sign_tx_input_schnorr(&shared_tx, &entries, 1, &delegate_owner); shared_tx.inputs[0].signature_script = - entry_sigscript(&player, "retire", vec![Expr::bytes(signature), Expr::bytes(owner.pubkey_bytes)]); + entry_sigscript(&player, "retire", vec![ArtifactValue::Bytes(signature), ArtifactValue::Bytes(owner.pubkey_bytes)]); shared_tx.inputs[1].signature_script = entry_sigscript( &delegate, "delegate_start_game", vec![ - Expr::bytes(delegate_signature), - Expr::bytes(delegate_owner.pubkey_bytes), - Expr::int(DEFAULT_MOVE_TIMEOUT), - Expr::int(player_prefix_len), - Expr::int(player_suffix_len), + ArtifactValue::Bytes(delegate_signature), + ArtifactValue::Bytes(delegate_owner.pubkey_bytes), + ArtifactValue::Int(DEFAULT_MOVE_TIMEOUT), + ArtifactValue::Int(player_prefix_len), + ArtifactValue::Int(player_suffix_len), ], ); let delegate_result = execute_input_with_covenants(shared_tx.clone(), entries.clone(), 1); @@ -1864,8 +1896,8 @@ fn settle_runtime_matches_expected_output_states() { losses: 0, }, ); - let player_layout = player_contract.state_layout; - let player_template = Hash::from_bytes(player_contract.template_hash()); + let player_layout = common::state_layout(&player_contract); + let player_template = Hash::from_bytes(common::template_hash(&player_contract)); let white_player = compile_player_state( player_source(), PlayerStateArgs { @@ -1944,8 +1976,8 @@ fn settle_runtime_matches_expected_output_states() { &routed_settle, "settle", vec![ - Expr::int(player_layout.start as i64), - Expr::int((player_contract.bytecode.len() - player_layout.start - player_layout.len) as i64), + ArtifactValue::Int(player_layout.start as i64), + ArtifactValue::Int((common::bytecode(&player_contract).len() - player_layout.start - player_layout.len) as i64), ], ); let settle_prefix_len = fix.settle.prefix.len() as i64; @@ -1954,16 +1986,21 @@ fn settle_runtime_matches_expected_output_states() { &white_player, "delegate_settle", vec![ - Expr::int(settle_prefix_len), - Expr::int(settle_suffix_len), - hash_expr(fix.settle.hash), - Expr::bytes(route_templates.clone()), + ArtifactValue::Int(settle_prefix_len), + ArtifactValue::Int(settle_suffix_len), + hash_value(fix.settle.hash), + ArtifactValue::Bytes(route_templates.clone()), ], ); let black_delegate_sigscript = entry_sigscript( &black_player, "delegate_settle", - vec![Expr::int(settle_prefix_len), Expr::int(settle_suffix_len), hash_expr(fix.settle.hash), Expr::bytes(route_templates)], + vec![ + ArtifactValue::Int(settle_prefix_len), + ArtifactValue::Int(settle_suffix_len), + hash_value(fix.settle.hash), + ArtifactValue::Bytes(route_templates), + ], ); let outputs = vec![ diff --git a/silverscript-lang/tests/common.rs b/silverscript-lang/tests/common.rs index 599952e0..4f455dd0 100644 --- a/silverscript-lang/tests/common.rs +++ b/silverscript-lang/tests/common.rs @@ -12,12 +12,87 @@ use kaspa_txscript::opcodes::codes::OpTrue; use kaspa_txscript::script_builder::ScriptBuilder; use kaspa_txscript::{EngineCtx, EngineFlags, TxScriptEngine, pay_to_script_hash_script}; use kaspa_txscript_errors::TxScriptError; -use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompiledContract, CovenantDeclCallOptions}; +use silverscript_abi::{ + ArtifactValue, CodecError, CodecResult, SilAbiArtifact, SilContractArtifact, SilEntryArtifact, + encode_contract_covenant_decl_sig_script, encode_contract_entry_sig_script, +}; +use silverscript_lang::compiler::{ + CompileOptions, CompiledStateLayout, CompilerError, CovenantDeclCallOptions, compile_to_sil_abi_artifact_with_options, +}; pub const COV_A: Hash = Hash::from_bytes(*b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); pub const COV_B: Hash = Hash::from_bytes(*b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); +pub fn compile_contract( + source: &str, + constructor_args: &[ArtifactValue], + options: CompileOptions, +) -> Result { + compile_to_sil_abi_artifact_with_options(source, constructor_args, options) +} + +pub fn single_contract(artifact: &SilAbiArtifact) -> &SilContractArtifact { + let Some(contract) = artifact.contracts.values().next().filter(|_| artifact.contracts.len() == 1) else { + panic!("expected exactly one contract, found {}", artifact.contracts.len()); + }; + contract +} + +pub fn bytecode(artifact: &SilAbiArtifact) -> Vec { + single_contract(artifact).compiled.bytecode.clone() +} + +pub fn state_layout(artifact: &SilAbiArtifact) -> CompiledStateLayout { + let span = single_contract(artifact).compiled.state_span; + CompiledStateLayout { start: span.offset, len: span.len } +} + +pub fn entry_by_name<'a>(artifact: &'a SilAbiArtifact, name: &str) -> Option<&'a SilEntryArtifact> { + single_contract(artifact).entry(name) +} + +pub fn template_hash(artifact: &SilAbiArtifact) -> [u8; 32] { + single_contract(artifact).compiled.template_hash +} + +pub fn build_sig_script_for_covenant_decl( + artifact: &SilAbiArtifact, + function_name: &str, + args: Vec, + options: CovenantDeclCallOptions, +) -> Result, CompilerError> { + let Some((contract_name, _)) = artifact.contracts.first_key_value().filter(|_| artifact.contracts.len() == 1) else { + return Err(CompilerError::Unsupported(format!("expected exactly one contract, found {}", artifact.contracts.len()))); + }; + encode_contract_covenant_decl_sig_script(artifact, contract_name, function_name, options.is_leader, &args) + .map_err(|err| CompilerError::Unsupported(err.to_string())) +} + +/// Encodes an invocation for an artifact containing exactly one contract and +/// exactly one public entrypoint. +pub fn encode_single_entry_sig_script(artifact: &SilAbiArtifact, args: &[ArtifactValue]) -> CodecResult> { + let Some((contract_name, contract)) = artifact.contracts.first_key_value().filter(|_| artifact.contracts.len() == 1) else { + return Err(CodecError::UnsupportedType(format!("expected exactly one contract, found {}", artifact.contracts.len()))); + }; + let Some((entry_name, _)) = contract.entries.first_key_value().filter(|_| contract.entries.len() == 1) else { + return Err(CodecError::UnsupportedType(format!( + "expected exactly one entry in contract `{}`, found {}", + contract_name, + contract.entries.len() + ))); + }; + encode_contract_entry_sig_script(artifact, contract_name, entry_name, args) +} + +/// Encodes a named entry invocation for an artifact containing exactly one +/// contract. +pub fn encode_entry_sig_script(artifact: &SilAbiArtifact, entry_name: &str, args: &[ArtifactValue]) -> CodecResult> { + let Some((contract_name, _)) = artifact.contracts.first_key_value().filter(|_| artifact.contracts.len() == 1) else { + return Err(CodecError::UnsupportedType(format!("expected exactly one contract, found {}", artifact.contracts.len()))); + }; + encode_contract_entry_sig_script(artifact, contract_name, entry_name, args) +} + pub fn push_redeem_script(bytecode: &[u8]) -> Vec { ScriptBuilder::with_flags(EngineFlags { covenants_enabled: true, ..Default::default() }) .add_data(bytecode) @@ -25,16 +100,15 @@ pub fn push_redeem_script(bytecode: &[u8]) -> Vec { .drain() } -pub fn covenant_decl_sigscript(compiled: &CompiledContract<'_>, function_name: &str, args: Vec>, is_leader: bool) -> Vec { - let mut sigscript = compiled - .build_sig_script_for_covenant_decl(function_name, args, CovenantDeclCallOptions { is_leader }) +pub fn covenant_decl_sigscript(compiled: &SilAbiArtifact, function_name: &str, args: Vec, is_leader: bool) -> Vec { + let mut sigscript = build_sig_script_for_covenant_decl(compiled, function_name, args, CovenantDeclCallOptions { is_leader }) .expect("build covenant declaration sigscript"); - sigscript.extend_from_slice(&push_redeem_script(&compiled.bytecode)); + sigscript.extend_from_slice(&push_redeem_script(&bytecode(compiled))); sigscript } -pub fn covenant_utxo(compiled: &CompiledContract<'_>, covenant_id: Hash) -> UtxoEntry { - UtxoEntry::new(1_500, pay_to_script_hash_script(&compiled.bytecode), 0, false, Some(covenant_id)) +pub fn covenant_utxo(compiled: &SilAbiArtifact, covenant_id: Hash) -> UtxoEntry { + UtxoEntry::new(1_500, pay_to_script_hash_script(&bytecode(compiled)), 0, false, Some(covenant_id)) } pub fn plain_covenant_output(authorizing_input: u16, covenant_id: Hash) -> TransactionOutput { @@ -81,18 +155,19 @@ pub fn tx_input(index: u32, signature_script: Vec) -> TransactionInput { ) } -pub fn covenant_output(compiled: &CompiledContract<'_>, authorizing_input: u16, covenant_id: Hash) -> TransactionOutput { +pub fn covenant_output(compiled: &SilAbiArtifact, authorizing_input: u16, covenant_id: Hash) -> TransactionOutput { TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&compiled.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(compiled)), covenant: Some(CovenantBinding { authorizing_input, covenant_id }), } } -pub fn compiled_template_parts_and_hash(compiled: &CompiledContract) -> (Vec, Vec, Vec) { - let layout = compiled.state_layout; - let prefix = compiled.bytecode[..layout.start].to_vec(); - let suffix = compiled.bytecode[layout.start + layout.len..].to_vec(); - let template_hash = compiled.template_hash().to_vec(); +pub fn compiled_template_parts_and_hash(compiled: &SilAbiArtifact) -> (Vec, Vec, Vec) { + let layout = state_layout(compiled); + let bytecode = bytecode(compiled); + let prefix = bytecode[..layout.start].to_vec(); + let suffix = bytecode[layout.start + layout.len..].to_vec(); + let template_hash = template_hash(compiled).to_vec(); (prefix, suffix, template_hash) } diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index f56d26e1..adb899f1 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -1,4 +1,5 @@ mod common; +use std::collections::BTreeMap; use std::panic::{AssertUnwindSafe, catch_unwind}; use kaspa_addresses::{Address, Prefix, Version}; @@ -18,21 +19,29 @@ use kaspa_txscript::{ EngineCtx, EngineFlags, SeqCommitAccessor, TxScriptEngine, parse_script, pay_to_address_script, pay_to_script_hash_script, pay_to_script_hash_signature_script_with_flags, script_to_str, serialize_i64, }; +use silverscript_abi::{ArtifactValue, SilAbiArtifact, TypeArtifact}; use silverscript_lang::ast::{ContractAst, Expr, ExprKind, Statement, format_contract_ast, parse_contract_ast, parse_type_ref}; use silverscript_lang::compiler::{ - COMPILER_VERSION, CompileOptions, CompiledContract, CompilerError, CovenantDeclCallOptions, DispatchTag, FunctionAbiEntry, - FunctionInputAbi, compile_contract, compile_contract_ast, compile_debug_expr, generated_covenant_auth_entrypoint_name, - struct_object, + COMPILER_VERSION, CompileOptions, CompiledContract, CompilerError, CovenantDeclCallOptions, DispatchTag, + compile_contract as compile_internal_contract, compile_contract_ast, compile_debug_expr, compile_to_sil_abi_artifact, + generated_covenant_auth_entrypoint_name, sil_abi_artifact_from_compiled, struct_object, }; use silverscript_lang::debug_info::StepKind; use silverscript_lang::template::template_hash; -use crate::common::compiled_template_parts_and_hash; +use common::{ + build_sig_script_for_covenant_decl, bytecode, compile_contract, compiled_template_parts_and_hash, encode_entry_sig_script, + encode_single_entry_sig_script, entry_by_name, single_contract, state_layout, +}; fn script_builder() -> ScriptBuilder { ScriptBuilder::with_flags(EngineFlags { covenants_enabled: true, ..Default::default() }) } +fn artifact_object(fields: impl IntoIterator) -> ArtifactValue { + fields.into_iter().map(|(name, value)| (name.to_string(), value)).collect::>().into() +} + #[test] fn constructors_validate_argument_types() { let cases = [ @@ -60,6 +69,27 @@ fn constructors_validate_argument_types() { } } +#[test] +fn artifact_with_options_preserves_compiled_output_and_state_layout() { + let source = r#" + contract Metadata(int initial) { + int value = initial; + + entry main(int expected) { + require(value == expected); + } + } + "#; + + let default_artifact = compile_to_sil_abi_artifact(source, &[7.into()]).expect("default artifact compiles"); + let default_contract = single_contract(&default_artifact); + assert!(default_contract.compiled.state_span.len > 0, "stateful contracts should expose a non-empty state layout"); + + let artifact = compile_contract(source, &[7.into()], CompileOptions { record_debug_infos: true, ..CompileOptions::default() }) + .expect("artifact with debug recording compiles"); + assert_eq!(artifact, default_artifact, "debug recording should not change the portable artifact"); +} + fn pay_to_script_hash_signature_script( redeem_script: Vec, signature_script: Vec, @@ -260,8 +290,8 @@ fn accepts_missing_pragma_without_version_check() { #[test] fn compiled_contract_includes_compiler_version() { let source = pragma_source(None); - let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("compile succeeds"); - assert_eq!(compiled.compiler_version, COMPILER_VERSION); + let artifact = compile_contract(&source, &[], CompileOptions::default()).expect("compile succeeds"); + assert_eq!(artifact.compiler_version, COMPILER_VERSION); } #[test] @@ -315,15 +345,15 @@ fn accepts_constructor_args_with_matching_types() { } "#; let args = vec![ - Expr::int(7), - Expr::bool(true), - Expr::string("hello".to_string()), - Expr::dynamic_bytes(vec![1u8; 10]), - Expr::byte(2), - Expr::bytes(vec![3u8; 4]), - Expr::bytes(vec![4u8; 32]), - Expr::bytes(vec![5u8; 65]), - Expr::bytes(vec![6u8; 64]), + 7.into(), + true.into(), + "hello".into(), + vec![1u8; 10].into(), + 2u8.into(), + vec![3u8; 4].into(), + vec![4u8; 32].into(), + vec![5u8; 65].into(), + vec![6u8; 64].into(), ]; compile_contract(source, &args, CompileOptions::default()).expect("compile succeeds"); } @@ -347,13 +377,36 @@ fn supports_struct_contract_params_fields_and_constants() { } "#; - let args = vec![struct_object("Pair", vec![("amount", Expr::int(11)), ("code", Expr::bytes(vec![0xab, 0xcd]))])]; + let args = vec![artifact_object([("amount", 11.into()), ("code", vec![0xabu8, 0xcd].into())])]; let compiled = compile_contract(source, &args, CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "top-level struct param/field/constant contract should run: {result:?}"); } +#[test] +fn portable_abi_verifies_struct_contract_field_state_layout() { + let source = r#" + contract StructState(Pair init_pair) { + struct Pair { + int amount; + byte[2] code; + } + + Pair from_param = init_pair; + + entry main() { + require(true); + } + } + "#; + let args = vec![artifact_object([("amount", 11.into()), ("code", vec![0xabu8, 0xcd].into())])]; + + let abi = compile_to_sil_abi_artifact(source, &args).expect("struct state contract compiles to a portable ABI"); + + abi.verify().expect("portable ABI runtime-state metadata matches the flattened state span"); +} + #[test] fn constructor_arguments_are_concrete_values_not_runtime_introspection() { let source = r#" @@ -364,7 +417,7 @@ fn constructor_arguments_are_concrete_values_not_runtime_introspection() { } "#; - let err = compile_contract(source, &[Expr::call("OpTxLockTime", vec![])], CompileOptions::default()) + let err = compile_internal_contract(source, &[Expr::call("OpTxLockTime", vec![])], CompileOptions::default()) .expect_err("constructor arguments must not evaluate runtime expressions"); assert!(err.to_string().contains("constructor argument 'expected_lock_time' must be a concrete value"), "unexpected error: {err}"); let contract = parse_contract_ast(source).expect("contract parses"); @@ -381,12 +434,12 @@ fn constructor_arguments_are_concrete_values_not_runtime_introspection() { } } "#; - let pair = struct_object("Pair", vec![("value", Expr::int(1))]); + let pair = artifact_object([("value", 1.into())]); compile_contract(struct_source, &[pair], CompileOptions::default()) .expect("structs containing only concrete values remain valid constructor arguments"); let runtime_pair = struct_object("Pair", vec![("value", Expr::call("OpTxLockTime", vec![]))]); - compile_contract(struct_source, &[runtime_pair], CompileOptions::default()) + compile_internal_contract(struct_source, &[runtime_pair], CompileOptions::default()) .expect_err("runtime expressions nested in constructor structs must be rejected"); let array_source = r#" @@ -396,12 +449,12 @@ fn constructor_arguments_are_concrete_values_not_runtime_introspection() { } } "#; - let literal_values = Expr::array(parse_type_ref("int[]").expect("array type parses"), vec![Expr::int(1)]); + let literal_values = ArtifactValue::Array(vec![1.into()]); compile_contract(array_source, &[literal_values], CompileOptions::default()) .expect("arrays containing only concrete values remain valid constructor arguments"); let runtime_values = Expr::array(parse_type_ref("int[]").expect("array type parses"), vec![Expr::call("OpTxLockTime", vec![])]); - compile_contract(array_source, &[runtime_values], CompileOptions::default()) + compile_internal_contract(array_source, &[runtime_values], CompileOptions::default()) .expect_err("runtime expressions nested in constructor arrays must be rejected"); } @@ -422,9 +475,9 @@ fn nested_struct_field_path_does_not_alias_underscored_field_name() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let outer = struct_object("Outer", vec![("a", struct_object("Inner", vec![("b", Expr::int(1))])), ("a_b", Expr::int(2))]); - let sigscript = compiled.build_sig_script("main", vec![outer]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let outer = artifact_object([("a", artifact_object([("b", 1.into())])), ("a_b", 2.into())]); + let sigscript = encode_single_entry_sig_script(&compiled, &[outer]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_err(), "different nested and underscored fields must make the require fail"); } @@ -572,7 +625,7 @@ fn compile_contract_omits_debug_info_when_recording_disabled() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_internal_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); assert!(compiled.debug_info.is_none()); } @@ -590,7 +643,7 @@ fn compile_contract_emits_debug_info_scaffold_when_recording_enabled() { "#; let options = CompileOptions { record_debug_infos: true, ..Default::default() }; - let compiled = compile_contract(source, &[Expr::int(11)], options).expect("compile succeeds"); + let compiled = compile_internal_contract(source, &[Expr::int(11)], options).expect("compile succeeds"); let debug_info = compiled.debug_info.expect("debug info should be present"); assert!(!debug_info.steps.is_empty(), "debug recording should emit statement steps again"); @@ -623,7 +676,7 @@ fn compile_contract_debug_info_scaffold_records_dispatch_tag_entrypoint_ranges() "#; let options = CompileOptions { record_debug_infos: true, ..Default::default() }; - let compiled = compile_contract(source, &[], options).expect("compile succeeds"); + let compiled = compile_internal_contract(source, &[], options).expect("compile succeeds"); let debug_info = compiled.debug_info.expect("debug info should be present"); let function_a = debug_info.functions.iter().find(|function| function.name == "a").expect("function range for a"); @@ -651,7 +704,7 @@ fn compile_contract_debug_info_records_inline_boundaries_and_return_bindings() { "#; let options = CompileOptions { record_debug_infos: true, ..Default::default() }; - let compiled = compile_contract(source, &[], options).expect("compile succeeds"); + let compiled = compile_internal_contract(source, &[], options).expect("compile succeeds"); let debug_info = compiled.debug_info.expect("debug info should be present"); let rendered_steps = debug_info .steps @@ -721,7 +774,7 @@ fn compile_contract_debug_info_preserves_structured_scope_inside_inline_calls() "#; let options = CompileOptions { record_debug_infos: true, ..Default::default() }; - let compiled = compile_contract(source, &[], options).expect("compile succeeds"); + let compiled = compile_internal_contract(source, &[], options).expect("compile succeeds"); let debug_info = compiled.debug_info.expect("debug info should be present"); let inline_steps = debug_info @@ -896,8 +949,8 @@ fn branch_heavy_if_else_logic_matches_rust_model_across_cases() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("branch-heavy contract should compile"); - let bytecode_len = compiled.bytecode.len(); - let (instruction_count, charged_op_count) = bytecode_op_counts(&compiled.bytecode); + let bytecode_len = bytecode(&compiled).len(); + let (instruction_count, charged_op_count) = bytecode_op_counts(&bytecode(&compiled)); println!("branch_maze {bytecode_len} / {instruction_count} / {charged_op_count}"); // Snapshot these metrics exactly so compiler codegen changes must consciously // acknowledge their size impact on a branch-heavy stress case. @@ -917,22 +970,22 @@ fn branch_heavy_if_else_logic_matches_rust_model_across_cases() { for (a, b, c, d) in cases { let (expected_x, expected_y, expected_z, expected_score) = branch_maze_expected(a, b, c, d); - let sigscript = compiled - .build_sig_script( - "main", - vec![ - Expr::int(a), - Expr::int(b), - Expr::int(c), - Expr::int(d), - Expr::int(expected_x), - Expr::int(expected_y), - Expr::int(expected_z), - Expr::int(expected_score), - ], - ) - .expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let sigscript = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Int(a), + ArtifactValue::Int(b), + ArtifactValue::Int(c), + ArtifactValue::Int(d), + ArtifactValue::Int(expected_x), + ArtifactValue::Int(expected_y), + ArtifactValue::Int(expected_z), + ArtifactValue::Int(expected_score), + ], + ) + .expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!( result.is_ok(), "branch-heavy case ({a}, {b}, {c}, {d}) should match Rust model ({expected_x}, {expected_y}, {expected_z}, {expected_score}): {result:?}" @@ -941,22 +994,22 @@ fn branch_heavy_if_else_logic_matches_rust_model_across_cases() { let (a, b, c, d) = cases[0]; let (expected_x, expected_y, expected_z, expected_score) = branch_maze_expected(a, b, c, d); - let wrong_sigscript = compiled - .build_sig_script( - "main", - vec![ - Expr::int(a), - Expr::int(b), - Expr::int(c), - Expr::int(d), - Expr::int(expected_x), - Expr::int(expected_y), - Expr::int(expected_z), - Expr::int(expected_score + 1), - ], - ) - .expect("sigscript builds"); - let err = run_bytecode_with_sigscript(compiled.bytecode.clone(), wrong_sigscript) + let wrong_sigscript = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Int(a), + ArtifactValue::Int(b), + ArtifactValue::Int(c), + ArtifactValue::Int(d), + ArtifactValue::Int(expected_x), + ArtifactValue::Int(expected_y), + ArtifactValue::Int(expected_z), + ArtifactValue::Int(expected_score + 1), + ], + ) + .expect("sigscript builds"); + let err = run_bytecode_with_sigscript(bytecode(&compiled).clone(), wrong_sigscript) .expect_err("branch-heavy case with wrong expected output should fail"); assert!(format!("{err:?}").contains("Verify"), "wrong expected output should fail with verify error, got: {err:?}"); } @@ -1038,8 +1091,8 @@ fn sorting_network_over_fixed_array_matches_rust_model_across_cases() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("sorting-network contract should compile"); - let bytecode_len = compiled.bytecode.len(); - let (instruction_count, charged_op_count) = bytecode_op_counts(&compiled.bytecode); + let bytecode_len = bytecode(&compiled).len(); + let (instruction_count, charged_op_count) = bytecode_op_counts(&bytecode(&compiled)); println!("sorting_network {bytecode_len} / {instruction_count} / {charged_op_count}"); assert_eq!( bytecode_len, 829, @@ -1065,23 +1118,23 @@ fn sorting_network_over_fixed_array_matches_rust_model_across_cases() { for values in cases { let [expected_a, expected_b, expected_c, expected_d, expected_e, expected_f, expected_g, expected_h] = sorted_expected(values); - let sigscript = compiled - .build_sig_script( - "main", - vec![ - Expr::inferred_array(values.into_iter().map(Expr::int).collect()).expect("non-empty fixed int array"), - Expr::int(expected_a), - Expr::int(expected_b), - Expr::int(expected_c), - Expr::int(expected_d), - Expr::int(expected_e), - Expr::int(expected_f), - Expr::int(expected_g), - Expr::int(expected_h), - ], - ) - .expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let sigscript = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Array(values.into_iter().map(ArtifactValue::Int).collect()), + ArtifactValue::Int(expected_a), + ArtifactValue::Int(expected_b), + ArtifactValue::Int(expected_c), + ArtifactValue::Int(expected_d), + ArtifactValue::Int(expected_e), + ArtifactValue::Int(expected_f), + ArtifactValue::Int(expected_g), + ArtifactValue::Int(expected_h), + ], + ) + .expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!(result.is_ok(), "sorting-network case {values:?} should match Rust model: {result:?}"); } } @@ -1095,7 +1148,7 @@ fn rejects_constructor_args_with_wrong_scalar_types() { } } "#; - let args = vec![Expr::bool(true), Expr::int(1), Expr::bytes(vec![1u8])]; + let args = vec![true.into(), 1.into(), vec![1u8].into()]; assert!(compile_contract(source, &args, CompileOptions::default()).is_err()); } @@ -1108,13 +1161,7 @@ fn rejects_constructor_args_with_wrong_byte_lengths() { } } "#; - let args = vec![ - Expr::bytes(vec![1u8; 2]), - Expr::bytes(vec![2u8; 3]), - Expr::bytes(vec![3u8; 31]), - Expr::bytes(vec![4u8; 63]), - Expr::bytes(vec![5u8; 66]), - ]; + let args = vec![vec![1u8; 2].into(), vec![2u8; 3].into(), vec![3u8; 31].into(), vec![4u8; 63].into(), vec![5u8; 66].into()]; assert!(compile_contract(source, &args, CompileOptions::default()).is_err()); } @@ -1147,7 +1194,7 @@ fn accepts_constructor_args_with_any_bytes_length() { } } "#; - let args = vec![Expr::dynamic_bytes(vec![9u8; 128])]; + let args = vec![vec![9u8; 128].into()]; compile_contract(source, &args, CompileOptions::default()).expect("compile succeeds"); } @@ -1161,8 +1208,8 @@ fn build_sig_script_builds_expected_script() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let args = vec![Expr::bytes(vec![1u8, 2, 3, 4]), Expr::int(7)]; - let sigscript = compiled.build_sig_script("spend", args).expect("sigscript builds"); + let args = vec![vec![1u8, 2, 3, 4].into(), 7.into()]; + let sigscript = encode_entry_sig_script(&compiled, "spend", &args).expect("sigscript builds"); let dispatch_tag = dispatch_tag_for(&compiled, "spend"); let mut builder = script_builder(); @@ -1204,10 +1251,11 @@ fn byte_variable_from_int_literal_uses_raw_byte_push() { .add_op(OpTrue) .unwrap() .drain(); - let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); + let dispatch_tag = entry_by_name(&compiled, "main").expect("entrypoint resolved").dispatch_tag.into_bytes(); + let expected = wrap_with_single_dispatch_tag(dispatch_tag, &[], &body); + assert_eq!(bytecode(&compiled), expected); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok(), "byte int literal script should execute"); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok(), "byte int literal script should execute"); } #[test] @@ -1237,8 +1285,8 @@ fn byte_equality_uses_op_equal_not_op_numequal() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte equality should compile"); - assert!(compiled.bytecode.iter().copied().any(|op| op == OpEqual), "byte equality should use OP_EQUAL"); - assert!(!compiled.bytecode.iter().copied().any(|op| op == OpNumEqual), "byte equality should not use OP_NUMEQUAL"); + assert!(bytecode(&compiled).iter().copied().any(|op| op == OpEqual), "byte equality should use OP_EQUAL"); + assert!(!bytecode(&compiled).iter().copied().any(|op| op == OpNumEqual), "byte equality should not use OP_NUMEQUAL"); } #[test] @@ -1269,10 +1317,14 @@ fn byte_equality_with_rhs_int_literal_uses_raw_byte_push() { .add_op(OpTrue) .unwrap() .drain(); - let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); + let dispatch_tag = entry_by_name(&compiled, "main").expect("entrypoint resolved").dispatch_tag.into_bytes(); + let expected = wrap_with_single_dispatch_tag(dispatch_tag, &[], &body); + assert_eq!(bytecode(&compiled), expected); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok(), "byte equality with rhs literal should execute"); + assert!( + run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok(), + "byte equality with rhs literal should execute" + ); } #[test] @@ -1369,10 +1421,10 @@ fn allows_arithmetic_after_signed_or_unsigned_byte_conversion() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("explicit byte conversions should allow arithmetic"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); assert_eq!(opcodes.matches("OpAdd").count(), 2, "converted byte arithmetic must emit OpAdd: {opcodes}"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok(), "converted byte arithmetic should execute"); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok(), "converted byte arithmetic should execute"); } #[test] @@ -1416,7 +1468,7 @@ fn allows_bitwise_operations_on_bytes() { let compiled = compile_contract(&source, &[], CompileOptions::default()) .unwrap_or_else(|err| panic!("byte operands for {operator} should compile: {err}")); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "byte operands for {operator} should execute: {result:?}"); } } @@ -1486,22 +1538,22 @@ fn allows_bitwise_operations_on_dynamic_byte_arrays_and_checks_size_at_runtime() let compiled = compile_contract(&source, &[], CompileOptions::default()) .unwrap_or_else(|err| panic!("dynamic byte arrays for {operator} should compile: {err}")); - let sigscript = compiled - .build_sig_script( - "main", - vec![Expr::dynamic_bytes(vec![0x12, 0x34]), Expr::dynamic_bytes(vec![0x4d, 0x0f]), Expr::dynamic_bytes(expected)], - ) - .expect("matching dynamic byte-array arguments should build"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let sigscript = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bytes(vec![0x12, 0x34]), ArtifactValue::Bytes(vec![0x4d, 0x0f]), ArtifactValue::Bytes(expected)], + ) + .expect("matching dynamic byte-array arguments should build"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!(result.is_ok(), "matching dynamic byte arrays for {operator} should execute: {result:?}"); - let sigscript = compiled - .build_sig_script( - "main", - vec![Expr::dynamic_bytes(vec![0x12, 0x34]), Expr::dynamic_bytes(vec![0x4d]), Expr::dynamic_bytes(vec![0x00, 0x00])], - ) - .expect("different-sized dynamic byte-array arguments should build"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let sigscript = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bytes(vec![0x12, 0x34]), ArtifactValue::Bytes(vec![0x4d]), ArtifactValue::Bytes(vec![0x00, 0x00])], + ) + .expect("different-sized dynamic byte-array arguments should build"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!(result.is_err(), "different-sized dynamic byte arrays for {operator} should fail at runtime"); } } @@ -1516,7 +1568,7 @@ fn build_sig_script_rejects_unknown_function() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let result = compiled.build_sig_script("missing", vec![Expr::int(1)]); + let result = encode_entry_sig_script(&compiled, "missing", &[ArtifactValue::Int(1)]); assert!(result.is_err()); } @@ -1537,7 +1589,7 @@ fn disallow_comparing_byte_array_to_byte_constant() { "#; assert!( - compile_contract(source, &[Expr::bytes(vec![1u8; 32]), Expr::byte(0)], CompileOptions::default()).is_err(), + compile_contract(source, &[ArtifactValue::Bytes(vec![1u8; 32]), ArtifactValue::Byte(0)], CompileOptions::default()).is_err(), "comparing byte[32] to byte should be rejected without cast" ); } @@ -1555,7 +1607,7 @@ fn disallow_comparing_dynamic_and_fixed_byte_arrays_without_cast_in_contract_sco "#; assert!( - compile_contract(source, &[Expr::dynamic_bytes(vec![0x12])], CompileOptions::default()).is_err(), + compile_contract(source, &[ArtifactValue::Bytes(vec![0x12])], CompileOptions::default()).is_err(), "comparing byte[] to byte[2] should be rejected without cast" ); } @@ -1572,7 +1624,7 @@ fn allow_comparing_dynamic_and_fixed_byte_arrays_with_cast_in_contract_scope() { } "#; - compile_contract(source, &[Expr::dynamic_bytes(vec![0x12])], CompileOptions::default()) + compile_contract(source, &[ArtifactValue::Bytes(vec![0x12])], CompileOptions::default()) .expect("comparing byte[] to byte[2] should be allowed with cast"); } @@ -1621,8 +1673,8 @@ fn script_pubkey_constructors_return_correct_fixed_dimension_types() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("script-pubkey constructors should compile"); - let sigscript = compiled.build_sig_script("main", vec![]).expect("signature script should build"); - run_bytecode_with_sigscript(compiled.bytecode, sigscript) + let sigscript = encode_single_entry_sig_script(&compiled, &[]).expect("signature script should build"); + run_bytecode_with_sigscript(bytecode(&compiled), sigscript) .expect("script-pubkey constructor results should have their declared lengths after conversion to byte[]"); } @@ -1655,8 +1707,8 @@ fn fixed_size_hash_builtins_return_their_declared_lengths() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("fixed-size hash builtins should compile"); - let sigscript = compiled.build_sig_script("main", vec![]).expect("signature script should build"); - run_bytecode_with_sigscript(compiled.bytecode, sigscript) + let sigscript = encode_single_entry_sig_script(&compiled, &[]).expect("signature script should build"); + run_bytecode_with_sigscript(bytecode(&compiled), sigscript) .expect("fixed-size hash builtin results should have their declared lengths after conversion to byte[]"); } @@ -1681,7 +1733,7 @@ fn introspection_fields_and_direct_lock_opcodes_emit_and_execute() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("all indexed introspection fields should compile"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode should stringify"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode should stringify"); for opcode in [ "OpTxInputAmount", "OpTxInputSpk", @@ -1699,7 +1751,7 @@ fn introspection_fields_and_direct_lock_opcodes_emit_and_execute() { let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); let (tx, mut entries) = build_basic_opcode_tx(sigscript); - entries[0].script_public_key = ScriptPublicKey::new(0, compiled.bytecode.clone().into()); + entries[0].script_public_key = ScriptPublicKey::new(0, bytecode(&compiled).clone().into()); let reused_values = SigHashReusedValuesUnsync::new(); let sig_cache = Cache::new(10_000); let populated = PopulatedTransaction::new(&tx, entries); @@ -2193,9 +2245,9 @@ fn byte_array_to_fixed_byte_array_cast_compiles_without_num2bin() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte[] to byte[32] cast should compile"); - assert!(!compiled.bytecode.iter().copied().any(|op| op == OpNum2Bin), "byte[] to byte[32] cast should not emit OpNum2Bin"); + assert!(!bytecode(&compiled).iter().copied().any(|op| op == OpNum2Bin), "byte[] to byte[32] cast should not emit OpNum2Bin"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok(), "byte[] to byte[32] cast should execute"); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok(), "byte[] to byte[32] cast should execute"); } #[test] @@ -2309,10 +2361,10 @@ fn bool_cast_accepts_only_a_singular_byte_as_its_source() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte-to-bool casts should compile"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); assert_eq!(opcodes.matches("OpIf").count(), 1, "byte-to-bool casts should not add branching beyond dispatch: {opcodes}"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "byte-to-bool casts should preserve VM truthiness: {result:?}"); } @@ -2358,7 +2410,7 @@ fn encodes_non_byte_array_literal_cast_in_contract_field() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("non-byte array literal cast should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); assert!( - run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok(), + run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok(), "encoded non-byte array literal cast should execute" ); } @@ -2523,7 +2575,7 @@ fn build_sig_script_rejects_wrong_argument_count() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let result = compiled.build_sig_script("spend", vec![Expr::int(1)]); + let result = encode_entry_sig_script(&compiled, "spend", &[ArtifactValue::Int(1)]); assert!(result.is_err()); } @@ -2537,7 +2589,7 @@ fn build_sig_script_rejects_wrong_argument_type() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let result = compiled.build_sig_script("spend", vec![Expr::bytes(vec![1u8; 3])]); + let result = encode_entry_sig_script(&compiled, "spend", &[ArtifactValue::Bytes(vec![1u8; 3])]); assert!(result.is_err()); } @@ -2554,14 +2606,13 @@ fn build_sig_script_for_covenant_decl_routes_to_hidden_auth_entrypoint() { } "#; - let compiled = compile_contract(source, &[Expr::int(7)], CompileOptions::default()).expect("compile succeeds"); - let args = vec![struct_object("State", vec![("value", Expr::int(8))])]; + let compiled = compile_contract(source, &[ArtifactValue::Int(7)], CompileOptions::default()).expect("compile succeeds"); + let args = vec![artifact_object([("value", 8.into())])]; - let actual = compiled - .build_sig_script_for_covenant_decl("step", args.clone(), CovenantDeclCallOptions { is_leader: false }) + let actual = build_sig_script_for_covenant_decl(&compiled, "step", args.clone(), CovenantDeclCallOptions { is_leader: false }) .expect("covenant sigscript builds"); - let expected = - compiled.build_sig_script(&generated_covenant_auth_entrypoint_name("step"), args).expect("hidden entrypoint sigscript builds"); + let expected = encode_entry_sig_script(&compiled, &generated_covenant_auth_entrypoint_name("step"), &args) + .expect("hidden entrypoint sigscript builds"); assert_eq!(actual, expected); } @@ -2579,20 +2630,19 @@ fn build_sig_script_for_covenant_decl_routes_to_hidden_cov_entrypoints() { } "#; - let compiled = compile_contract(source, &[Expr::int(7)], CompileOptions::default()).expect("compile succeeds"); - let leader_args = - vec![Expr::array(parse_type_ref("State[]").unwrap(), vec![struct_object("State", vec![("value", Expr::int(8))])])]; + let compiled = compile_contract(source, &[ArtifactValue::Int(7)], CompileOptions::default()).expect("compile succeeds"); + let leader_args = vec![ArtifactValue::Array(vec![artifact_object([("value", 8.into())])])]; - let leader = compiled - .build_sig_script_for_covenant_decl("rebalance", leader_args.clone(), CovenantDeclCallOptions { is_leader: true }) - .expect("leader sigscript builds"); - let expected_leader = compiled.build_sig_script("__leader_rebalance", leader_args).expect("hidden leader sigscript builds"); + let leader = + build_sig_script_for_covenant_decl(&compiled, "rebalance", leader_args.clone(), CovenantDeclCallOptions { is_leader: true }) + .expect("leader sigscript builds"); + let expected_leader = + encode_entry_sig_script(&compiled, "__leader_rebalance", &leader_args).expect("hidden leader sigscript builds"); assert_eq!(leader, expected_leader); - let delegate = compiled - .build_sig_script_for_covenant_decl("rebalance", vec![], CovenantDeclCallOptions { is_leader: false }) + let delegate = build_sig_script_for_covenant_decl(&compiled, "rebalance", vec![], CovenantDeclCallOptions { is_leader: false }) .expect("delegate sigscript builds"); - let expected_delegate = compiled.build_sig_script("__delegate", vec![]).expect("hidden delegate sigscript builds"); + let expected_delegate = encode_entry_sig_script(&compiled, "__delegate", &[]).expect("hidden delegate sigscript builds"); assert_eq!(delegate, expected_delegate); } @@ -2607,7 +2657,7 @@ fn build_sig_script_for_covenant_decl_rejects_unknown_declaration() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let result = compiled.build_sig_script_for_covenant_decl("missing", vec![], CovenantDeclCallOptions { is_leader: false }); + let result = build_sig_script_for_covenant_decl(&compiled, "missing", vec![], CovenantDeclCallOptions { is_leader: false }); assert!(result.is_err()); } @@ -2699,7 +2749,7 @@ fn rejects_external_call_without_entrypoint() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let result = compiled.build_sig_script("helper", vec![Expr::int(1)]); + let result = encode_entry_sig_script(&compiled, "helper", &[ArtifactValue::Int(1)]); assert!(result.is_err()); } @@ -2776,7 +2826,7 @@ fn build_sig_script_rejects_mismatched_bytes_length() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let result = compiled.build_sig_script("spend", vec![Expr::bytes(vec![1u8; 5])]); + let result = encode_entry_sig_script(&compiled, "spend", &[ArtifactValue::Bytes(vec![1u8; 5])]); assert!(result.is_err()); let source = r#" @@ -2787,7 +2837,7 @@ fn build_sig_script_rejects_mismatched_bytes_length() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let result = compiled.build_sig_script("spend", vec![Expr::bytes(vec![1u8; 4])]); + let result = encode_entry_sig_script(&compiled, "spend", &[ArtifactValue::Bytes(vec![1u8; 4])]); assert!(result.is_err()); } @@ -2801,12 +2851,19 @@ fn build_sig_script_appends_dispatch_tag_for_single_entrypoint() { } } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("spend", vec![1.into(), vec![2u8; 4].into()]).expect("sigscript builds"); - let dispatch_tag = compiled.entry_by_name("spend").expect("entrypoint resolved").dispatch_tag; + let artifact = compile_to_sil_abi_artifact(source, &[]).expect("compile succeeds"); + let sigscript = encode_single_entry_sig_script(&artifact, &[1.into(), vec![2u8; 4].into()]).expect("sigscript builds"); + let dispatch_tag = + artifact.contract("Single").and_then(|contract| contract.entry("spend")).expect("entrypoint resolved").dispatch_tag; - let expected = - script_builder().add_i64(1).unwrap().add_data_with_push_opcode(&[2u8; 4]).unwrap().add_data(&dispatch_tag).unwrap().drain(); + let expected = script_builder() + .add_i64(1) + .unwrap() + .add_data_with_push_opcode(&[2u8; 4]) + .unwrap() + .add_data(dispatch_tag.as_bytes()) + .unwrap() + .drain(); assert_eq!(sigscript, expected); } @@ -2834,7 +2891,7 @@ fn compiles_struct_sugar_for_locals_calls_and_field_access() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "script should execute successfully: {result:?}"); } @@ -2865,7 +2922,7 @@ fn compiles_struct_return_types_in_inline_calls() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "struct-return inline call should execute successfully: {result:?}"); } @@ -2886,8 +2943,8 @@ fn build_sig_script_supports_struct_entrypoint_arguments() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let arg = struct_object("S", vec![("a", Expr::int(0)), ("b", Expr::string("12345"))]); - let sigscript = compiled.build_sig_script("main", vec![arg]).expect("sigscript builds"); + let arg = artifact_object([("a", 0.into()), ("b", "12345".into())]); + let sigscript = encode_single_entry_sig_script(&compiled, &[arg]).expect("sigscript builds"); let expected = script_builder() .add_i64(0) @@ -2915,8 +2972,8 @@ fn build_sig_script_supports_state_entrypoint_arguments() { "#; let compiled = compile_contract(source, &[5.into(), vec![1u8, 2u8].into()], CompileOptions::default()).expect("compile succeeds"); - let arg = struct_object("State", vec![("x", Expr::int(9)), ("y", Expr::bytes(vec![0x34, 0x12]))]); - let sigscript = compiled.build_sig_script("main", vec![arg]).expect("sigscript builds"); + let arg = artifact_object([("x", 9.into()), ("y", vec![0x34u8, 0x12].into())]); + let sigscript = encode_single_entry_sig_script(&compiled, &[arg]).expect("sigscript builds"); let expected = script_builder() .add_i64(9) @@ -2942,12 +2999,9 @@ fn build_sig_script_supports_sig_array_arguments() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let sig_a = vec![0x11u8; 65]; let sig_b = vec![0x22u8; 65]; - let sigscript = compiled - .build_sig_script( - "main", - vec![Expr::array(parse_type_ref("sig[]").unwrap(), vec![Expr::bytes(sig_a.clone()), Expr::bytes(sig_b.clone())])], - ) - .expect("sigscript builds"); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Array(vec![sig_a.clone().into(), sig_b.clone().into()])]) + .expect("sigscript builds"); let mut encoded = sig_a; encoded.extend(sig_b); @@ -2956,65 +3010,35 @@ fn build_sig_script_supports_sig_array_arguments() { assert_eq!(sigscript, expected); } -fn struct_array_arg<'i>(values: Vec<(i64, Vec)>) -> Expr<'i> { - Expr::array( - parse_type_ref("S[]").unwrap(), - values.into_iter().map(|(a, b)| struct_object("S", vec![("a", Expr::int(a)), ("b", Expr::bytes(b))])).collect(), - ) +fn struct_array_arg(values: Vec<(i64, Vec)>) -> ArtifactValue { + ArtifactValue::Array(values.into_iter().map(|(a, b)| artifact_object([("a", a.into()), ("b", b.into())])).collect()) } -fn fixed_struct_array_arg<'i>(values: Vec<(i64, Vec)>) -> Expr<'i> { - let mut type_ref = parse_type_ref("S[]").unwrap(); - type_ref.array_dims[0] = silverscript_lang::ast::ArrayDim::Fixed(values.len()); - Expr::array( - type_ref, - values.into_iter().map(|(a, b)| struct_object("S", vec![("a", Expr::int(a)), ("b", Expr::bytes(b))])).collect(), - ) +fn fixed_struct_array_arg(values: Vec<(i64, Vec)>) -> ArtifactValue { + struct_array_arg(values) } -fn state_array_arg<'i>(values: Vec) -> Expr<'i> { - Expr::array( - parse_type_ref("State[]").unwrap(), - values.into_iter().map(|value| struct_object("State", vec![("value", Expr::int(value))])).collect(), - ) +fn state_array_arg(values: Vec) -> ArtifactValue { + ArtifactValue::Array(values.into_iter().map(|value| artifact_object([("value", value.into())])).collect()) } -fn state_array_arg_x<'i>(values: Vec) -> Expr<'i> { - Expr::array( - parse_type_ref("State[]").unwrap(), - values.into_iter().map(|value| struct_object("State", vec![("x", Expr::int(value))])).collect(), - ) +fn state_array_arg_x(values: Vec) -> ArtifactValue { + ArtifactValue::Array(values.into_iter().map(|value| artifact_object([("x", value.into())])).collect()) } -fn matrix_state_array_arg<'i>(values: Vec<(i64, Vec)>) -> Expr<'i> { - Expr::array( - parse_type_ref("State[]").unwrap(), - values - .into_iter() - .map(|(amount, owner)| struct_object("State", vec![("amount", Expr::int(amount)), ("owner", Expr::bytes(owner))])) - .collect(), +fn matrix_state_array_arg(values: Vec<(i64, Vec)>) -> ArtifactValue { + ArtifactValue::Array( + values.into_iter().map(|(amount, owner)| artifact_object([("amount", amount.into()), ("owner", owner.into())])).collect(), ) } fn replace_compiled_interface<'i>( compiled: &mut CompiledContract<'i>, source: &'i str, - entrypoint_name: &str, - inputs: &[(&str, &str)], + old_dispatch_tag: DispatchTag, + new_dispatch_tag: DispatchTag, ) { - let old_dispatch_tag = compiled.abi[0].dispatch_tag; compiled.ast = parse_contract_ast(source).expect("interface parses"); - // Do not rely on the dispatch tag in tests using this synthetic interface. - let dispatch_tag = [0u8; 4]; - compiled.abi = vec![FunctionAbiEntry { - name: entrypoint_name.to_string(), - inputs: inputs - .iter() - .map(|(name, type_name)| FunctionInputAbi { name: (*name).to_string(), type_name: (*type_name).to_string() }) - .collect(), - dispatch_tag, - }]; - let new_dispatch_tag = compiled.abi[0].dispatch_tag; let tag_offset = compiled .bytecode .windows(old_dispatch_tag.len()) @@ -3027,9 +3051,9 @@ fn replace_compiled_interface<'i>( fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { struct Case { source: &'static str, - constructor_args: Vec>, + constructor_args: Vec, function_name: &'static str, - args: Vec>, + args: Vec, options: CovenantDeclCallOptions, generated_covenant_entrypoint_name: &'static str, } @@ -3151,9 +3175,9 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(4)], + constructor_args: vec![ArtifactValue::Int(4)], function_name: "split", - args: vec![state_array_arg(vec![11]), Expr::int(3)], + args: vec![state_array_arg(vec![11]), ArtifactValue::Int(3)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__split", }, @@ -3168,9 +3192,9 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(3)], + constructor_args: vec![ArtifactValue::Int(2), ArtifactValue::Int(3)], function_name: "transition_ok", - args: vec![state_array_arg(vec![10, 11]), Expr::int(1)], + args: vec![state_array_arg(vec![10, 11]), ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: true }, generated_covenant_entrypoint_name: "__leader_transition_ok", }, @@ -3185,7 +3209,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(3)], + constructor_args: vec![ArtifactValue::Int(2), ArtifactValue::Int(3)], function_name: "transition_ok", args: vec![], options: CovenantDeclCallOptions { is_leader: false }, @@ -3202,9 +3226,9 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(7)], + constructor_args: vec![ArtifactValue::Int(7)], function_name: "bump", - args: vec![Expr::int(2)], + args: vec![ArtifactValue::Int(2)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__bump", }, @@ -3219,7 +3243,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(4), Expr::int(10)], + constructor_args: vec![ArtifactValue::Int(4), ArtifactValue::Int(10)], function_name: "fanout", args: vec![state_array_arg(vec![11, 12])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3236,7 +3260,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(10)], + constructor_args: vec![ArtifactValue::Int(10)], function_name: "bump_or_terminate", args: vec![state_array_arg(vec![13])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3254,9 +3278,9 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(4), ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", - args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), Expr::int(0)], + args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), ArtifactValue::Int(0)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__step", }, @@ -3272,7 +3296,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(4), ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3290,9 +3314,9 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(4), ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__step", }, @@ -3308,9 +3332,14 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "step", - args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), Expr::int(0)], + args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), ArtifactValue::Int(0)], options: CovenantDeclCallOptions { is_leader: true }, generated_covenant_entrypoint_name: "__leader_step", }, @@ -3326,7 +3355,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "step", args: vec![], options: CovenantDeclCallOptions { is_leader: false }, @@ -3345,9 +3379,14 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "step", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: true }, generated_covenant_entrypoint_name: "__leader_step", }, @@ -3364,7 +3403,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "step", args: vec![], options: CovenantDeclCallOptions { is_leader: false }, @@ -3382,7 +3426,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(4), ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3400,7 +3444,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "step", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: true }, @@ -3418,7 +3467,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "step", args: vec![], options: CovenantDeclCallOptions { is_leader: false }, @@ -3436,23 +3490,23 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { } } "#, - constructor_args: vec![Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__step", }, Case { source: matrix_singleton_transition_source, - constructor_args: vec![Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__step", }, Case { source: matrix_singleton_terminate_source, - constructor_args: vec![Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3460,7 +3514,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_fanout_verification_source, - constructor_args: vec![Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ArtifactValue::Int(4), ArtifactValue::Int(10), ArtifactValue::Bytes(owner.clone())], function_name: "step", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3468,15 +3522,25 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "auth_verification_multi", - args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), Expr::int(0)], + args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), ArtifactValue::Int(0)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__auth_verification_multi", }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "auth_verification_single", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3484,23 +3548,38 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "auth_transition", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__auth_transition", }, Case { source: matrix_cov_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "cov_verification", - args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), Expr::int(0)], + args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())]), ArtifactValue::Int(0)], options: CovenantDeclCallOptions { is_leader: true }, generated_covenant_entrypoint_name: "__leader_cov_verification", }, Case { source: matrix_cov_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "cov_verification", args: vec![], options: CovenantDeclCallOptions { is_leader: false }, @@ -3508,15 +3587,25 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_cov_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "cov_transition", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: true }, generated_covenant_entrypoint_name: "__leader_cov_transition", }, Case { source: matrix_cov_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "cov_transition", args: vec![], options: CovenantDeclCallOptions { is_leader: false }, @@ -3524,7 +3613,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "inferred_auth", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3532,7 +3626,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_cov_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "inferred_cov", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: true }, @@ -3540,7 +3639,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_cov_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "inferred_cov", args: vec![], options: CovenantDeclCallOptions { is_leader: false }, @@ -3548,23 +3652,38 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "inferred_transition", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__inferred_transition", }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "singleton_transition", - args: vec![Expr::int(1)], + args: vec![ArtifactValue::Int(1)], options: CovenantDeclCallOptions { is_leader: false }, generated_covenant_entrypoint_name: "__singleton_transition", }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "singleton_terminate", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3572,7 +3691,12 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { }, Case { source: matrix_auth_source, - constructor_args: vec![Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(owner.clone())], + constructor_args: vec![ + ArtifactValue::Int(2), + ArtifactValue::Int(4), + ArtifactValue::Int(10), + ArtifactValue::Bytes(owner.clone()), + ], function_name: "fanout_verification", args: vec![matrix_state_array_arg(vec![(11, next_owner.clone())])], options: CovenantDeclCallOptions { is_leader: false }, @@ -3582,8 +3706,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { for case in cases { let compiled = compile_contract(case.source, &case.constructor_args, CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled - .build_sig_script_for_covenant_decl(case.function_name, case.args.clone(), case.options) + let sigscript = build_sig_script_for_covenant_decl(&compiled, case.function_name, case.args.clone(), case.options) .expect("covenant declaration sigscript builds"); let generated_entrypoint_name = if case.generated_covenant_entrypoint_name.starts_with("__leader_") || case.generated_covenant_entrypoint_name == "__delegate" @@ -3593,7 +3716,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { generated_covenant_auth_entrypoint_name(case.function_name) }; let expected = - compiled.build_sig_script(&generated_entrypoint_name, case.args).expect("generated entrypoint sigscript builds"); + encode_entry_sig_script(&compiled, &generated_entrypoint_name, &case.args).expect("generated entrypoint sigscript builds"); assert_eq!(sigscript, expected, "covenant declaration sigscript should match generated entrypoint for {}", case.function_name); } } @@ -3618,8 +3741,9 @@ fn runtime_rejects_regular_struct_array_entrypoint_arguments_without_struct_sign } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let main_param_types: Vec = compiled + let lowered = compile_internal_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("portable artifact compiles"); + let main_param_types: Vec = lowered .ast .functions .iter() @@ -3631,8 +3755,7 @@ fn runtime_rejects_regular_struct_array_entrypoint_arguments_without_struct_sign .collect(); assert_eq!(main_param_types, vec!["int[]".to_string(), "byte[2][]".to_string()]); - let err = compiled - .build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) + let err = encode_entry_sig_script(&compiled, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) .expect_err("struct[] arguments should be rejected when the entrypoint signature is not struct-typed"); assert!(err.to_string().contains("expects 2 arguments"), "unexpected error: {err}"); } @@ -3674,8 +3797,12 @@ fn runtime_supports_regular_struct_array_entrypoint_arguments_with_struct_signat } "#; - let mut compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - replace_compiled_interface(&mut compiled, struct_signature_source, "main", &[("x", "S[]")]); + let mut compiled = compile_internal_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let original_artifact = compile_to_sil_abi_artifact(source, &[]).expect("original interface compiles"); + let old_dispatch_tag = single_contract(&original_artifact).entry("main").expect("main entry exists").dispatch_tag.into_bytes(); + let interface_artifact = compile_to_sil_abi_artifact(struct_signature_source, &[]).expect("struct interface compiles"); + let dispatch_tag = single_contract(&interface_artifact).entry("main").expect("main entry exists").dispatch_tag.into_bytes(); + replace_compiled_interface(&mut compiled, struct_signature_source, old_dispatch_tag, dispatch_tag); let main_param_types: Vec = compiled .ast @@ -3689,9 +3816,9 @@ fn runtime_supports_regular_struct_array_entrypoint_arguments_with_struct_signat .collect(); assert_eq!(main_param_types, vec!["S[]".to_string()]); - let sigscript = compiled - .build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) - .expect("sigscript builds"); + let sigscript = + encode_entry_sig_script(&interface_artifact, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) + .expect("sigscript builds"); let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); assert!(result.is_ok(), "regular struct[] entrypoint arg should execute successfully: {result:?}"); @@ -3716,8 +3843,9 @@ fn runtime_supports_direct_struct_array_entrypoint_signature() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let f_param_types: Vec = compiled + let lowered = compile_internal_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("portable artifact compiles"); + let f_param_types: Vec = lowered .ast .functions .iter() @@ -3729,10 +3857,9 @@ fn runtime_supports_direct_struct_array_entrypoint_signature() { .collect(); assert_eq!(f_param_types, vec!["S[]".to_string()]); - let sigscript = compiled - .build_sig_script("f", vec![struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) + let sigscript = encode_entry_sig_script(&compiled, "f", &[struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) .expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "direct struct[] entrypoint signature should execute successfully: {result:?}"); } @@ -3777,7 +3904,7 @@ fn runtime_rejects_mismatched_dynamic_struct_array_leaf_counts_before_append() { .unwrap() .drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_err(), "mismatched struct-array leaf counts must fail before the entrypoint body"); } @@ -3797,7 +3924,7 @@ fn codegen_reuses_dynamic_struct_array_leaf_sizes_for_cardinality_validation() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); assert_eq!(opcodes.matches("OpSize").count(), 2, "each flattened struct-array leaf should be sized once: {opcodes}"); } @@ -3844,10 +3971,10 @@ fn runtime_validates_cardinality_for_deeply_nested_struct_array_leaves() { .drain() }; - let valid = run_bytecode_with_sigscript(compiled.bytecode.clone(), build_sigscript(4)); + let valid = run_bytecode_with_sigscript(bytecode(&compiled).clone(), build_sigscript(4)); assert!(valid.is_ok(), "equal leaf cardinalities across nested fixed-width layouts should execute: {valid:?}"); - let malformed = run_bytecode_with_sigscript(compiled.bytecode, build_sigscript(8)); + let malformed = run_bytecode_with_sigscript(bytecode(&compiled), build_sigscript(8)); assert!(malformed.is_err(), "a surplus element in a deeply nested leaf must be rejected"); } @@ -3868,19 +3995,13 @@ fn runtime_keeps_cardinality_groups_separate_for_multiple_struct_array_params() "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let first = Expr::array( - parse_type_ref("Item[]").unwrap(), - vec![struct_object("Item", vec![("number", Expr::int(1)), ("tag", Expr::bytes(vec![1, 2]))])], - ); - let second = Expr::array( - parse_type_ref("Item[]").unwrap(), - vec![ - struct_object("Item", vec![("number", Expr::int(2)), ("tag", Expr::bytes(vec![3, 4]))]), - struct_object("Item", vec![("number", Expr::int(3)), ("tag", Expr::bytes(vec![5, 6]))]), - ], - ); - let sigscript = compiled.build_sig_script("main", vec![first, second]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let first = ArtifactValue::Array(vec![artifact_object([("number", 1.into()), ("tag", vec![1u8, 2].into())])]); + let second = ArtifactValue::Array(vec![ + artifact_object([("number", 2.into()), ("tag", vec![3u8, 4].into())]), + artifact_object([("number", 3.into()), ("tag", vec![5u8, 6].into())]), + ]); + let sigscript = encode_single_entry_sig_script(&compiled, &[first, second]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "separate struct-array parameters may have different lengths: {result:?}"); } @@ -3901,18 +4022,16 @@ fn build_sig_script_enforces_fixed_struct_array_length() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - compiled - .build_sig_script("main", vec![fixed_struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) + encode_entry_sig_script(&compiled, "main", &[fixed_struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) .expect("correctly sized struct array should encode"); - let err = compiled - .build_sig_script("main", vec![fixed_struct_array_arg(vec![(7, vec![0x01, 0x02])])]) + let err = encode_entry_sig_script(&compiled, "main", &[fixed_struct_array_arg(vec![(7, vec![0x01, 0x02])])]) .expect_err("wrongly sized struct array should be rejected"); - assert!(err.to_string().contains("size mismatch"), "unexpected error: {err}"); + assert!(err.to_string().contains("expects 2 bytes"), "unexpected error: {err}"); } #[test] -fn build_sig_script_rejects_structurally_identical_array_element_type() { +fn artifact_sigscript_accepts_structurally_identical_object_values() { let source = r#" contract C() { struct S { @@ -3932,14 +4051,10 @@ fn build_sig_script_rejects_structurally_identical_array_element_type() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let arg = Expr::array( - parse_type_ref("T[]").unwrap(), - vec![struct_object("T", vec![("a", Expr::int(7)), ("b", Expr::bytes(vec![0x01, 0x02]))])], - ); - let err = compiled - .build_sig_script("main", vec![arg]) - .expect_err("a structurally identical struct array must retain its nominal element type"); - assert!(err.to_string().contains("expected struct 'S', got 'T'"), "unexpected error: {err}"); + let arg = ArtifactValue::Array(vec![artifact_object([("a", 7.into()), ("b", vec![0x01u8, 0x02].into())])]); + let sigscript = encode_single_entry_sig_script(&compiled, &[arg]) + .expect("portable object values are structural and do not carry a source struct name"); + run_bytecode_with_sigscript(bytecode(&compiled), sigscript).expect("the structurally compatible object value executes"); } #[test] @@ -3959,8 +4074,9 @@ fn runtime_supports_struct_array_append_value_length_without_assignment() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02])])]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02])])]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "struct[] append result length should be usable without assignment: {result:?}"); } @@ -3991,8 +4107,9 @@ fn runtime_supports_struct_array_append_assignment_from_different_source() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02])])]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02])])]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "struct[] append assignment from a different source should execute successfully: {result:?}"); } @@ -4022,8 +4139,9 @@ fn runtime_supports_struct_array_append_value_expression() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02])])]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02])])]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "struct[] append value expression should execute successfully: {result:?}"); } @@ -4052,8 +4170,9 @@ fn runtime_rejects_regular_struct_array_non_entrypoint_arguments_without_struct_ } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let main_param_types: Vec = compiled + let lowered = compile_internal_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("portable artifact compiles"); + let main_param_types: Vec = lowered .ast .functions .iter() @@ -4065,7 +4184,7 @@ fn runtime_rejects_regular_struct_array_non_entrypoint_arguments_without_struct_ .collect(); assert_eq!(main_param_types, vec!["int[]".to_string(), "byte[2][]".to_string()]); - let verify_param_types: Vec = compiled + let verify_param_types: Vec = lowered .ast .functions .iter() @@ -4077,8 +4196,7 @@ fn runtime_rejects_regular_struct_array_non_entrypoint_arguments_without_struct_ .collect(); assert_eq!(verify_param_types, vec!["int[]".to_string(), "byte[2][]".to_string()]); - let err = compiled - .build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) + let err = encode_entry_sig_script(&compiled, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) .expect_err("struct[] arguments should be rejected when entrypoint and internal function signatures are not struct-typed"); assert!(err.to_string().contains("expects 2 arguments"), "unexpected error: {err}"); } @@ -4128,8 +4246,12 @@ fn runtime_supports_regular_struct_array_non_entrypoint_arguments_with_struct_si } "#; - let mut compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - replace_compiled_interface(&mut compiled, struct_signature_source, "main", &[("x", "S[]")]); + let mut compiled = compile_internal_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let original_artifact = compile_to_sil_abi_artifact(source, &[]).expect("original interface compiles"); + let old_dispatch_tag = single_contract(&original_artifact).entry("main").expect("main entry exists").dispatch_tag.into_bytes(); + let interface_artifact = compile_to_sil_abi_artifact(struct_signature_source, &[]).expect("struct interface compiles"); + let dispatch_tag = single_contract(&interface_artifact).entry("main").expect("main entry exists").dispatch_tag.into_bytes(); + replace_compiled_interface(&mut compiled, struct_signature_source, old_dispatch_tag, dispatch_tag); let main_param_types: Vec = compiled .ast @@ -4155,9 +4277,9 @@ fn runtime_supports_regular_struct_array_non_entrypoint_arguments_with_struct_si .collect(); assert_eq!(verify_param_types, vec!["S[]".to_string()]); - let sigscript = compiled - .build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) - .expect("sigscript builds"); + let sigscript = + encode_entry_sig_script(&interface_artifact, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) + .expect("sigscript builds"); let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); assert!(result.is_ok(), "regular struct[] arg should flow through non-entrypoint calls at runtime: {result:?}"); @@ -4211,8 +4333,9 @@ fn runtime_supports_direct_struct_array_non_entrypoint_signature() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let verify_param_types: Vec = compiled + let lowered = compile_internal_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("portable artifact compiles"); + let verify_param_types: Vec = lowered .ast .functions .iter() @@ -4224,10 +4347,10 @@ fn runtime_supports_direct_struct_array_non_entrypoint_signature() { .collect(); assert_eq!(verify_param_types, vec!["S[]".to_string()]); - let sigscript = compiled - .build_sig_script("main", vec![struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) - .expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[struct_array_arg(vec![(7, vec![0x01, 0x02]), (9, vec![0x03, 0x04])])]) + .expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "direct struct[] non-entrypoint signature should execute successfully: {result:?}"); } @@ -4433,8 +4556,8 @@ fn build_sig_script_rejects_struct_argument_with_wrong_field_type() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let arg = struct_object("S", vec![("a", Expr::string("hello")), ("b", Expr::string("world"))]); - let result = compiled.build_sig_script("main", vec![arg]); + let arg = artifact_object([("a", "hello".into()), ("b", "world".into())]); + let result = encode_single_entry_sig_script(&compiled, &[arg]); assert!(result.is_err()); } @@ -4458,7 +4581,7 @@ fn compiles_struct_destructuring_and_runs() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "struct destructuring runtime failed: {}", result.unwrap_err()); } @@ -4522,7 +4645,7 @@ fn compiles_function_call_assignment_and_verifies() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "array/loop/function-call example failed: {}", result.unwrap_err()); } @@ -4543,9 +4666,9 @@ fn function_call_statement_evaluates_and_drops_unused_return_expression() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let asm = script_to_str(&compiled.bytecode).expect("script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("script should stringify"); assert!(asm.contains("OpAdd OpDrop"), "unused inline return expression should be evaluated and dropped: {asm}"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -4669,7 +4792,7 @@ fn allows_calling_void_function() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "array/loop/function-call example failed: {}", result.unwrap_err()); } @@ -4716,8 +4839,8 @@ fn function_call_in_require_statement() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("expression-position helper call should compile"); - let sigscript = compiled.build_sig_script("main", vec![Expr::int(4)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(4)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "expression-position helper call should execute successfully: {}", result.unwrap_err()); } @@ -4736,8 +4859,8 @@ fn single_return_helper_call_can_participate_in_expression() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("single-return helper call should compile"); - let sigscript = compiled.build_sig_script("main", vec![Expr::int(4)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(4)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "single-return helper call should execute successfully: {}", result.unwrap_err()); } @@ -4777,7 +4900,7 @@ fn rejects_calling_later_defined_function() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("forward call should now compile"); let dispatch_tag = dispatch_tag_for(&compiled, "first"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "forward call should execute successfully: {}", result.unwrap_err()); } @@ -4857,7 +4980,7 @@ fn multi_return_helper_call_assignment_remains_valid() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("tuple call assignment should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "tuple call assignment should execute successfully: {}", result.unwrap_err()); } @@ -4878,7 +5001,7 @@ fn tuple_return_field_access_can_initialize_variable_and_run() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("tuple field access should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "tuple field access variable initializer should execute successfully: {}", result.unwrap_err()); } @@ -4898,7 +5021,7 @@ fn tuple_return_field_access_can_be_used_in_require_and_run() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("tuple field access in require should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "tuple field access in require should execute successfully: {}", result.unwrap_err()); } @@ -4918,7 +5041,7 @@ fn tuple_return_field_access_allows_parenthesized_single_return_type() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("f() : (int) should allow f().0"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "single-element tuple field access should execute successfully: {}", result.unwrap_err()); } @@ -5005,7 +5128,7 @@ fn allows_call_chain_with_earlier_defined_functions() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "array/loop/function-call example failed: {}", result.unwrap_err()); } @@ -5039,7 +5162,7 @@ fn allows_call_chain_with_later_defined_functions() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "array/loop/function-call example failed: {}", result.unwrap_err()); } @@ -5102,7 +5225,7 @@ fn allows_calling_void_function_fails() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_err()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_err()); } #[test] @@ -5171,7 +5294,7 @@ fn single_return_signature_without_parentheses_compiles_and_runs() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "single bare return type should execute successfully: {}", result.unwrap_err()); } @@ -5192,7 +5315,7 @@ fn single_return_signature_without_parentheses_supports_direct_variable_definiti let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "direct variable definition assignment should execute successfully: {}", result.unwrap_err()); } @@ -5213,7 +5336,7 @@ fn single_return_statement_without_parentheses_compiles_and_runs() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "single bare return statement should execute successfully: {}", result.unwrap_err()); } @@ -5257,7 +5380,7 @@ fn array_literal_codegen_uses_declared_element_type() { let compiled = compile_contract(source, &[], CompileOptions { record_debug_infos: true, ..CompileOptions::default() }) .expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "array literals should use their declared element type: {}", result.unwrap_err()); } @@ -5282,7 +5405,7 @@ fn bool_array_literal_normalizes_runtime_elements_to_one_byte() { .add_data(&dispatch_tag_for(&compiled, "main")) .unwrap() .drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "bool[] literal witness {witness:#04x} should normalize to 0x{expected}: {result:?}"); } } @@ -5303,8 +5426,8 @@ fn runtime_and_compile_time_false_have_identical_bool_array_encoding() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("check", vec![Expr::bool(true)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "check", &[ArtifactValue::Bool(true)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "runtime and compile-time false should have identical bool[] encoding: {result:?}"); } @@ -5323,7 +5446,7 @@ fn bool_array_append_normalizes_runtime_elements_to_one_byte() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let sigscript = script_builder().add_data_with_push_opcode(&[2]).unwrap().add_data(&dispatch_tag_for(&compiled, "main")).unwrap().drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "truthy bool[] append elements should be normalized to 0x01: {result:?}"); } @@ -5339,8 +5462,8 @@ fn int_array_literal_normalizes_runtime_elements_to_eight_bytes() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![Expr::int(1)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(1)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "int[] literal elements should each occupy eight bytes: {result:?}"); } @@ -5357,8 +5480,8 @@ fn int_array_append_normalizes_runtime_elements_to_eight_bytes() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![Expr::int(1)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(1)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "int[] append elements should each occupy eight bytes: {result:?}"); } @@ -5382,8 +5505,9 @@ fn struct_array_bool_and_int_leaves_use_fixed_width_encoding() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![Expr::bool(true), Expr::int(1)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bool(true), ArtifactValue::Int(1)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "flattened struct array leaves should retain bool/int element widths: {result:?}"); } @@ -5402,8 +5526,9 @@ fn nested_bool_and_int_array_literals_use_fixed_width_encoding() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![Expr::bool(true), Expr::int(1)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bool(true), ArtifactValue::Int(1)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "nested array literals should normalize their scalar elements: {result:?}"); } @@ -5427,7 +5552,7 @@ fn constant_and_contract_field_arrays_use_fixed_width_encoding() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "compile-time array encoders should use the canonical scalar widths: {result:?}"); } @@ -5443,10 +5568,10 @@ fn bool_and_int_array_sigscript_arguments_use_fixed_width_encoding() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let flags = Expr::array(parse_type_ref("bool[2]").expect("type parses"), vec![Expr::bool(true), Expr::bool(false)]); - let numbers = Expr::array(parse_type_ref("int[2]").expect("type parses"), vec![Expr::int(1), Expr::int(0)]); - let sigscript = compiled.build_sig_script("main", vec![flags, numbers]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let flags = ArtifactValue::Array(vec![true.into(), false.into()]); + let numbers = ArtifactValue::Array(vec![1.into(), 0.into()]); + let sigscript = encode_single_entry_sig_script(&compiled, &[flags, numbers]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "signature-script array encoding should use the canonical scalar widths: {result:?}"); } @@ -5491,7 +5616,7 @@ fn compiles_int_array_length_to_expected_script() { .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -5544,7 +5669,7 @@ fn compiles_int_array_append_to_expected_script() { .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -5662,30 +5787,30 @@ fn branchy_three_slot_splice_repro_matches_current_codegen_shape() { } "#; let args = vec![ - Expr::bytes(vec![0x11u8; 32]), - Expr::bytes({ + ArtifactValue::Bytes(vec![0x11u8; 32]), + ArtifactValue::Bytes({ let mut route_templates = Vec::with_capacity(32 * 9); for byte in 0x12u8..=0x1au8 { route_templates.extend_from_slice(&[byte; 32]); } route_templates }), - Expr::bytes(vec![0x21u8; 32]), - Expr::bytes(vec![0x22u8; 32]), - Expr::bytes(vec![0u8; 64]), - Expr::int(0), - Expr::int(0), - Expr::int(600), - Expr::bytes(vec![1u8; 4]), - Expr::int(-1), - Expr::int(12), - Expr::int(28), - Expr::int(0), - Expr::int(0), - Expr::int(3), + ArtifactValue::Bytes(vec![0x21u8; 32]), + ArtifactValue::Bytes(vec![0x22u8; 32]), + ArtifactValue::Bytes(vec![0u8; 64]), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(600), + ArtifactValue::Bytes(vec![1u8; 4]), + ArtifactValue::Int(-1), + ArtifactValue::Int(12), + ArtifactValue::Int(28), + ArtifactValue::Int(0), + ArtifactValue::Int(0), + ArtifactValue::Int(3), ]; let compiled = compile_contract(source, &args, CompileOptions::default()).expect("compile succeeds"); - let asm = script_to_str(&compiled.bytecode).expect("compiled bytecode should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("compiled bytecode should stringify"); // This is a reduced repro for the chess pawn blowup on the current branch. // This used to explode because branch-mutated splice @@ -5693,7 +5818,7 @@ fn branchy_three_slot_splice_repro_matches_current_codegen_shape() { // into a very large opcode shape. With array locals kept on the stack, the // same source should stay close to the old master-size range instead of // ballooning into thousands of bytes and OpPick instructions. - assert!(compiled.bytecode.len() < 1000, "script should stay compact, got {}", compiled.bytecode.len()); + assert!(bytecode(&compiled).len() < 1000, "script should stay compact, got {}", bytecode(&compiled).len()); assert!(asm.matches("OpPick").count() < 120, "OpPick count should stay bounded, got {}", asm.matches("OpPick").count()); assert!(asm.matches("OpSubstr").count() <= 24, "OpSubstr count should stay near master, got {}", asm.matches("OpSubstr").count()); assert!( @@ -5757,7 +5882,7 @@ fn compiles_int_array_index_to_expected_script() { .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -5779,7 +5904,7 @@ fn runs_array_append_runtime_examples() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "array append runtime example failed: {}", result.unwrap_err()); } @@ -5797,7 +5922,7 @@ fn runs_array_append_value_length_without_assignment() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "array append result length should be usable without assignment: {result:?}"); } @@ -5815,7 +5940,7 @@ fn runs_int_array_append_length_runtime_example() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "int[] append length runtime example failed: {}", result.unwrap_err()); } @@ -5833,7 +5958,7 @@ fn runs_slice_with_explicit_end_bounds() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "slice runtime should succeed: {}", result.unwrap_err()); } @@ -5857,7 +5982,7 @@ fn runs_slice_reconstruction_and_compare_runtime_example() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "slice reconstruction runtime should succeed: {}", result.unwrap_err()); } @@ -5951,7 +6076,7 @@ fn allows_concat_of_int_arrays_with_plus() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "int[] concatenation runtime failed: {}", result.unwrap_err()); } @@ -5973,7 +6098,7 @@ fn allows_concat_of_byte_arrays_with_plus() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "byte[] concatenation runtime failed: {}", result.unwrap_err()); } @@ -5990,7 +6115,7 @@ fn concatenated_byte_array_literal_has_element_length() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "byte[] literal concatenation should have two elements: {}", result.unwrap_err()); } @@ -6014,7 +6139,7 @@ fn allows_concat_of_fixed_size_byte_array_elements_with_plus() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "byte[N][] concatenation runtime failed: {}", result.unwrap_err()); } @@ -6033,10 +6158,10 @@ fn composite_array_index_uses_its_result_type_for_bytewise_operations() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - assert!(!compiled.bytecode.contains(&OpNum2Bin), "an indexed byte array element is already byte-encoded"); + assert!(!bytecode(&compiled).contains(&OpNum2Bin), "an indexed byte array element is already byte-encoded"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "composite array indexing should use bytewise operations: {}", result.unwrap_err()); } @@ -6061,7 +6186,7 @@ fn allows_concat_of_bool_arrays_with_plus() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "bool[] concatenation runtime failed: {}", result.unwrap_err()); } @@ -6087,7 +6212,7 @@ fn allows_concat_of_pubkey_arrays_with_plus() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "pubkey[] concatenation runtime failed: {}", result.unwrap_err()); } @@ -6143,7 +6268,7 @@ fn compiles_bytes20_array_append_without_num2bin() { .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -6163,7 +6288,7 @@ fn runs_bytes20_array_runtime_example() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "byte[20] array runtime example failed: {}", result.unwrap_err()); } @@ -6183,7 +6308,7 @@ fn allows_array_equality_comparison() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "array equality runtime failed: {}", result.unwrap_err()); } @@ -6203,7 +6328,7 @@ fn fails_array_equality_comparison() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_err()); } @@ -6271,7 +6396,7 @@ fn allows_array_inequality_with_different_sizes() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "array inequality runtime failed: {}", result.unwrap_err()); } @@ -6293,7 +6418,7 @@ fn runs_array_for_loop_example() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "array for-loop runtime failed: {}", result.unwrap_err()); } @@ -6314,9 +6439,9 @@ fn runs_array_for_loop_with_length_guard() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![vec![1i64, 2i64, 3i64, 4i64].into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "main", &[vec![1i64, 2i64, 3i64, 4i64].into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "array for-loop length-guard runtime failed: {}", result.unwrap_err()); } @@ -6347,7 +6472,7 @@ fn runs_array_loop_and_function_calls_example() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "array/loop/function-call example failed: {}", result.unwrap_err()); } @@ -6556,16 +6681,17 @@ fn runs_runtime_bounded_for_loop_example() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![2.into(), 4.into(), 2.into(), 3.into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[2.into(), 4.into(), 2.into(), 3.into()]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!(result.is_ok(), "runtime-bounded for-loop should honor end-exclusive bounds: {}", result.unwrap_err()); - let sigscript = compiled.build_sig_script("main", vec![5.into(), 8.into(), 3.into(), 7.into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[5.into(), 8.into(), 3.into(), 7.into()]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!(result.is_ok(), "runtime-bounded for-loop should allow ranges up to max iterations: {}", result.unwrap_err()); - let sigscript = compiled.build_sig_script("main", vec![4.into(), 2.into(), 0.into(), (-1).into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[4.into(), 2.into(), 0.into(), (-1).into()]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "runtime-bounded for-loop should skip iterations when start >= end: {}", result.unwrap_err()); } @@ -6591,8 +6717,8 @@ fn runtime_for_loop_snapshots_compound_bound_expressions_before_the_body() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![0.into(), 3.into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[0.into(), 3.into()]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "mutating variables used by bound expressions must not change the snapshotted range: {result:?}"); } @@ -6609,7 +6735,7 @@ fn runtime_for_loop_evaluates_each_bound_expression_once() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode should stringify"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode should stringify"); assert_eq!(opcodes.matches("OpTxInputCount").count(), 1, "the start expression must be evaluated once: {opcodes}"); assert_eq!(opcodes.matches("OpTxOutputCount").count(), 1, "the end expression must be evaluated once: {opcodes}"); } @@ -6627,8 +6753,8 @@ fn rejects_runtime_for_loop_range_above_max_iterations() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![2.into(), 6.into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[2.into(), 6.into()]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_err(), "runtime-bounded for-loop should fail when end - start exceeds max iterations"); } @@ -6647,7 +6773,7 @@ fn allows_array_assignment_with_compatible_types() { let options = CompileOptions::default(); let compiled = compile_contract(source, &[], options).expect("compile succeeds"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "array assignment runtime failed: {}", result.unwrap_err()); } @@ -6673,16 +6799,27 @@ fn inline_pubkey_param_reassignment_compiles_and_runs() { let a = vec![0x11u8; 32]; let b = vec![0x22u8; 32]; - let sigscript_take_b = compiled - .build_sig_script("main", vec![Expr::bytes(a.clone()), Expr::bytes(b.clone()), Expr::bytes(b.clone()), Expr::bool(true)]) - .expect("sigscript builds"); - let result_take_b = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_take_b); + let sigscript_take_b = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Bytes(a.clone()), + ArtifactValue::Bytes(b.clone()), + ArtifactValue::Bytes(b.clone()), + ArtifactValue::Bool(true), + ], + ) + .expect("sigscript builds"); + let result_take_b = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_take_b); assert!(result_take_b.is_ok(), "inline pubkey reassignment should allow taking the second value: {}", result_take_b.unwrap_err()); - let sigscript_keep_a = compiled - .build_sig_script("main", vec![Expr::bytes(a.clone()), Expr::bytes(b), Expr::bytes(a), Expr::bool(false)]) - .expect("sigscript builds"); - let result_keep_a = run_bytecode_with_sigscript(compiled.bytecode, sigscript_keep_a); + let sigscript_keep_a = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bytes(a.clone()), ArtifactValue::Bytes(b), ArtifactValue::Bytes(a), ArtifactValue::Bool(false)], + ) + .expect("sigscript builds"); + let result_keep_a = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_keep_a); assert!( result_keep_a.is_ok(), "inline pubkey reassignment should preserve the first value when branch is skipped: {}", @@ -6736,8 +6873,9 @@ fn locking_bytecode_p2pk_matches_pay_to_address_script() { expected.extend_from_slice(&spk.version().to_be_bytes()); expected.extend_from_slice(spk.script()); - let sigscript = compiled.build_sig_script("main", vec![pubkey.into(), Expr::dynamic_bytes(expected)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[pubkey.into(), ArtifactValue::Bytes(expected)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "p2pk locking bytecode mismatch: {}", result.unwrap_err()); } @@ -6760,8 +6898,9 @@ fn locking_bytecode_p2sh_matches_pay_to_address_script() { expected.extend_from_slice(&spk.version().to_be_bytes()); expected.extend_from_slice(spk.script()); - let sigscript = compiled.build_sig_script("main", vec![hash.into(), Expr::dynamic_bytes(expected)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = + encode_entry_sig_script(&compiled, "main", &[hash.into(), ArtifactValue::Bytes(expected)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "p2sh locking bytecode mismatch: {}", result.unwrap_err()); } @@ -6783,10 +6922,9 @@ fn locking_bytecode_p2sh_from_redeem_script_matches_pay_to_script_hash_script() expected.extend_from_slice(&spk.version().to_be_bytes()); expected.extend_from_slice(spk.script()); - let sigscript = compiled - .build_sig_script("main", vec![Expr::dynamic_bytes(redeem_script), Expr::dynamic_bytes(expected)]) + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bytes(redeem_script), ArtifactValue::Bytes(expected)]) .expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "p2sh-from-redeem-script locking bytecode mismatch: {}", result.unwrap_err()); } @@ -6883,8 +7021,8 @@ fn build_covenant_opcode_tx(sigscript: Vec, covenant_id_a: Hash, covenant_id (tx, entries) } -fn dispatch_tag_for(compiled: &CompiledContract<'_>, function_name: &str) -> DispatchTag { - compiled.entry_by_name(function_name).expect("entrypoint resolved").dispatch_tag +fn dispatch_tag_for(compiled: &silverscript_abi::SilAbiArtifact, function_name: &str) -> DispatchTag { + entry_by_name(compiled, function_name).expect("entrypoint resolved").dispatch_tag.into_bytes() } fn dispatch_tag_for_preimage(preimage: &str) -> DispatchTag { @@ -6892,15 +7030,19 @@ fn dispatch_tag_for_preimage(preimage: &str) -> DispatchTag { hash.as_bytes()[..4].try_into().expect("a BLAKE3 hash contains a four-byte dispatch tag") } -fn wrap_with_single_dispatch(compiled: &CompiledContract<'_>, body: Vec) -> Vec { +fn wrap_with_single_dispatch(compiled: &silverscript_abi::SilAbiArtifact, body: Vec) -> Vec { wrap_with_single_dispatch_and_state(compiled, &[], &body) } -fn wrap_with_single_dispatch_and_state(compiled: &CompiledContract<'_>, state: &[u8], body: &[u8]) -> Vec { - let [entrypoint] = compiled.abi.as_slice() else { +fn wrap_with_single_dispatch_and_state(compiled: &silverscript_abi::SilAbiArtifact, state: &[u8], body: &[u8]) -> Vec { + let entries = &single_contract(compiled).entries; + let Some(entrypoint) = entries.values().next().filter(|_| entries.len() == 1) else { panic!("single-dispatch wrapper requires exactly one ABI entrypoint"); }; - let dispatch_tag = entrypoint.dispatch_tag; + wrap_with_single_dispatch_tag(entrypoint.dispatch_tag.into_bytes(), state, body) +} + +fn wrap_with_single_dispatch_tag(dispatch_tag: DispatchTag, state: &[u8], body: &[u8]) -> Vec { let mut builder = script_builder(); if !state.is_empty() { builder.add_op(OpToAltStack).unwrap(); @@ -6919,7 +7061,7 @@ fn wrap_with_single_dispatch_and_state(compiled: &CompiledContract<'_>, state: & builder.drain() } -fn stateless_single_dispatch_body_opcodes(compiled: &CompiledContract<'_>, function_name: &str) -> Vec { +fn stateless_single_dispatch_body_opcodes(compiled: &silverscript_abi::SilAbiArtifact, function_name: &str) -> Vec { let dispatch_tag = dispatch_tag_for(compiled, function_name); let prefix = script_builder() .add_op(OpDup) @@ -6933,7 +7075,8 @@ fn stateless_single_dispatch_body_opcodes(compiled: &CompiledContract<'_>, funct .add_op(OpDrop) .unwrap() .drain(); - let body = compiled.bytecode.strip_prefix(prefix.as_slice()).expect("single-dispatch prefix should be present"); + let bytecode = bytecode(compiled); + let body = bytecode.strip_prefix(prefix.as_slice()).expect("single-dispatch prefix should be present"); let body = body.strip_suffix(&[OpElse, OpReturn, OpEndIf]).expect("single-dispatch suffix should be present"); parse_script::, SigHashReusedValuesUnsync>(body) @@ -6951,8 +7094,7 @@ fn compiles_with_kcc1_dispatch_tag_for_single_entrypoint() { } "#; - let contract = parse_contract_ast(source).expect("ast parsed"); - let compiled = compile_contract_ast(&contract, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let body = script_builder() .add_i64(1) @@ -6970,13 +7112,14 @@ fn compiles_with_kcc1_dispatch_tag_for_single_entrypoint() { .add_op(OpTrue) .unwrap() .drain(); - let expected = wrap_with_single_dispatch(&compiled, body); + let dispatch_tag = entry_by_name(&compiled, "main").expect("entrypoint resolved").dispatch_tag.into_bytes(); + let expected = wrap_with_single_dispatch_tag(dispatch_tag, &[], &body); - assert_eq!(compiled.bytecode, expected); - assert_eq!(compiled.state_layout.start, 0); - assert_eq!(compiled.state_layout.len, 0); - assert!(!compiled.bytecode.contains(&OpToAltStack)); - assert!(!compiled.bytecode.contains(&OpFromAltStack)); + assert_eq!(bytecode(&compiled), expected); + assert_eq!(state_layout(&compiled).start, 0); + assert_eq!(state_layout(&compiled).len, 0); + assert!(!bytecode(&compiled).contains(&OpToAltStack)); + assert!(!bytecode(&compiled).contains(&OpFromAltStack)); } #[test] @@ -6988,10 +7131,9 @@ fn compiles_stateless_multiple_entrypoints_with_kcc1_dispatch_tags() { } "#; - let contract = parse_contract_ast(source).expect("ast parsed"); - let compiled = compile_contract_ast(&contract, &[], CompileOptions::default()).expect("compile succeeds"); - let dispatch_tag_a = compiled.entry_by_name("a").expect("entrypoint resolved").dispatch_tag; - let dispatch_tag_b = compiled.entry_by_name("b").expect("entrypoint resolved").dispatch_tag; + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let dispatch_tag_a = entry_by_name(&compiled, "a").expect("entrypoint resolved").dispatch_tag.into_bytes(); + let dispatch_tag_b = entry_by_name(&compiled, "b").expect("entrypoint resolved").dispatch_tag.into_bytes(); let body_a = script_builder() .add_i64(1) @@ -7054,16 +7196,16 @@ fn compiles_stateless_multiple_entrypoints_with_kcc1_dispatch_tags() { .unwrap() .drain(); - assert_eq!(compiled.bytecode, expected_bytecode); - assert_eq!(compiled.state_layout.start, 0); - assert_eq!(compiled.state_layout.len, 0); - assert!(!compiled.bytecode.contains(&OpToAltStack)); - assert!(!compiled.bytecode.contains(&OpFromAltStack)); + assert_eq!(bytecode(&compiled), expected_bytecode); + assert_eq!(state_layout(&compiled).start, 0); + assert_eq!(state_layout(&compiled).len, 0); + assert!(!bytecode(&compiled).contains(&OpToAltStack)); + assert!(!bytecode(&compiled).contains(&OpFromAltStack)); - let sigscript = compiled.build_sig_script("a", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "a", &[]).expect("sigscript builds"); let expected = script_builder().add_data(&dispatch_tag_a).unwrap().drain(); assert_eq!(sigscript, expected); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript).is_ok()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled), sigscript).is_ok()); } #[test] @@ -7082,16 +7224,17 @@ fn dispatch_tag_and_argument_encoding_match_kcc1_vector() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let step = compiled.entry_by_name("step").expect("step entrypoint exists"); - assert_eq!(step.inputs[1].type_name, "byte[4]"); - assert_eq!(step.dispatch_tag, [0x2c, 0x49, 0xed, 0x65]); + let artifact = compile_to_sil_abi_artifact(source, &[]).expect("compile succeeds"); + let contract = artifact.contract("Test").expect("contract exists"); + let step = contract.entry("step").expect("step entrypoint exists"); + assert_eq!(step.params[1].ty, TypeArtifact::FixedBytes { len: 4 }); + assert_eq!(step.dispatch_tag.into_bytes(), [0x2c, 0x49, 0xed, 0x65]); - let sigscript = compiled - .build_sig_script("step", vec![Expr::int(17), Expr::bytes(vec![1, 2, 3, 4]), Expr::bool(true), Expr::byte(1)]) + let sigscript = encode_entry_sig_script(&artifact, "step", &[17.into(), vec![1u8, 2, 3, 4].into(), true.into(), 1u8.into()]) .expect("KCC-01 vector sigscript builds"); assert!(sigscript.ends_with(&[0x04, 0x2c, 0x49, 0xed, 0x65])); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript).is_ok()); + let bytecode = contract.compiled.bytecode.clone(); + assert!(run_bytecode_with_sigscript(bytecode, sigscript).is_ok()); } #[test] @@ -7112,13 +7255,17 @@ fn record_dispatch_tag_matches_kcc1_structural_type_vector() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let dispense = compiled.entry_by_name("dispense").expect("dispense entrypoint exists"); - assert_eq!(dispense.inputs[0].type_name, "CoffeeOrder[]"); - assert_eq!(dispense.dispatch_tag, [0x67, 0x6b, 0x1a, 0x86]); + let artifact = compile_to_sil_abi_artifact(source, &[]).expect("compile succeeds"); + let dispense = + artifact.contract("CoffeeMachine").and_then(|contract| contract.entry("dispense")).expect("dispense entrypoint exists"); + assert_eq!( + dispense.params[0].ty, + TypeArtifact::DynamicArray { item: Box::new(TypeArtifact::Struct { name: "CoffeeOrder".to_string() }) } + ); + assert_eq!(dispense.dispatch_tag.into_bytes(), [0x67, 0x6b, 0x1a, 0x86]); let json = serde_json::to_string(dispense).expect("ABI entry serializes"); - assert_eq!(serde_json::from_str::(&json).unwrap()["dispatch_tag"], serde_json::json!([103, 107, 26, 134])); + assert_eq!(serde_json::from_str::(&json).unwrap()["dispatch_tag"], "676b1a86"); } #[test] @@ -7143,7 +7290,8 @@ fn nested_record_dispatch_tags_hash_structural_type_preimages() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let artifact = compile_to_sil_abi_artifact(source, &[]).expect("compile succeeds"); + let contract = artifact.contract("Test").expect("contract exists"); let vectors = [ ("scalar", "scalar({{int,byte[3]},bool[2]})"), ("dynamic", "dynamic({{int,byte[3]},bool[2]}[])"), @@ -7151,10 +7299,133 @@ fn nested_record_dispatch_tags_hash_structural_type_preimages() { ]; for (entrypoint, preimage) in vectors { - assert_eq!(dispatch_tag_for(&compiled, entrypoint), dispatch_tag_for_preimage(preimage), "preimage: {preimage}"); + assert_eq!( + contract.entry(entrypoint).expect("entrypoint exists").dispatch_tag.into_bytes(), + dispatch_tag_for_preimage(preimage), + "preimage: {preimage}" + ); } } +#[test] +fn silverscript_abi_encodes_and_runs_nested_struct_entry_arguments() { + let source = r#" + contract AbiStructs(int expectedAmount) { + struct Coordinates { + int x; + int y; + } + + struct Item { + int amount; + Coordinates location; + byte[4] code; + bool active; + } + + entry main(Item selected, Item[] items, Item[2] fixedItems) { + Coordinates selectedLocation = selected.location; + require(selected.amount == expectedAmount); + require(selectedLocation.x == 1); + require(selectedLocation.y == 2); + require(selected.code == byte[4](0x01020304)); + require(selected.active); + + require(items.length == 2); + require(items[0].amount == 10); + require(items[0].code == byte[4](0x11121314)); + require(!items[0].active); + require(items[1].amount == 20); + require(items[1].code == byte[4](0x21222324)); + require(items[1].active); + + require(fixedItems[0].amount == 30); + require(fixedItems[0].code == byte[4](0x31323334)); + require(fixedItems[0].active); + require(fixedItems[1].amount == 40); + require(fixedItems[1].code == byte[4](0x41424344)); + require(!fixedItems[1].active); + } + } + "#; + + let artifact_constructor_args = [7.into()]; + let abi = + compile_to_sil_abi_artifact(source, &artifact_constructor_args).expect("source compiles to a complete portable ABI artifact"); + abi.verify().expect("portable ABI matches the compiled contract"); + + let coordinates = |x, y| { + ArtifactValue::Object(BTreeMap::from([("x".to_string(), ArtifactValue::Int(x)), ("y".to_string(), ArtifactValue::Int(y))])) + }; + let item = |amount, x, y, code: [u8; 4], active| { + ArtifactValue::Object(BTreeMap::from([ + ("amount".to_string(), ArtifactValue::Int(amount)), + ("location".to_string(), coordinates(x, y)), + ("code".to_string(), ArtifactValue::Bytes(code.to_vec())), + ("active".to_string(), ArtifactValue::Bool(active)), + ])) + }; + let args = vec![ + item(7, 1, 2, [1, 2, 3, 4], true), + ArtifactValue::Array(vec![ + item(10, 11, 12, [0x11, 0x12, 0x13, 0x14], false), + item(20, 21, 22, [0x21, 0x22, 0x23, 0x24], true), + ]), + ArtifactValue::Array(vec![ + item(30, 31, 32, [0x31, 0x32, 0x33, 0x34], true), + item(40, 41, 42, [0x41, 0x42, 0x43, 0x44], false), + ]), + ]; + let sigscript = encode_single_entry_sig_script(&abi, &args).expect("ABI encodes sigscript"); + let bytecode = abi.contract("AbiStructs").expect("contract exists").compiled.bytecode.clone(); + + run_bytecode_with_sigscript(bytecode, sigscript).expect("ABI-generated sigscript executes"); +} + +#[test] +fn artifact_values_compile_nested_constructor_arguments() { + let source = r#" + contract ArtifactConstructors(Config config, int[] values, byte[] payload, string label) { + struct Flags { + bool enabled; + } + + struct Config { + int count; + Flags flags; + } + + entry main() { + require(config.count == 7); + require(config.flags.enabled); + require(values.length == 2); + require(values[0] == 11); + require(values[1] == 12); + require(payload.length == 2); + require(payload[0] == 0xaa); + require(payload[1] == 0xbb); + require(label == "ready"); + } + } + "#; + let args = vec![ + ArtifactValue::Object(BTreeMap::from([ + ("count".to_string(), ArtifactValue::Int(7)), + ("flags".to_string(), ArtifactValue::Object(BTreeMap::from([("enabled".to_string(), ArtifactValue::Bool(true))]))), + ])), + ArtifactValue::Array(vec![ArtifactValue::Int(11), ArtifactValue::Int(12)]), + ArtifactValue::Bytes(vec![0xaa, 0xbb]), + ArtifactValue::Text("ready".to_string()), + ]; + + let abi = compile_to_sil_abi_artifact(source, &args).expect("portable ABI constructor values compile"); + abi.verify().expect("portable ABI verifies"); + let contract = abi.contract("ArtifactConstructors").expect("contract exists"); + let bytecode = contract.compiled.bytecode.clone(); + let dispatch_tag = contract.entry("main").expect("entry exists").dispatch_tag.into_bytes(); + run_bytecode_with_dispatch_tag(bytecode, dispatch_tag).expect("compiled constructor values execute"); +} + #[test] fn dispatch_tag_hashes_exact_utf8_signature_bytes() { // KCC identifiers are currently ASCII-only. Mutating parsed ASTs still pins the @@ -7178,7 +7449,7 @@ fn dispatch_tag_hashes_exact_utf8_signature_bytes() { let compiled = compile_contract_ast(&contract, &[], CompileOptions::default()).expect("contract compiles"); assert_eq!(&blake3::hash(utf8_signature).as_bytes()[..4], expected_tag); - assert_eq!(compiled.entry_by_name(name).expect("entrypoint exists").dispatch_tag, expected_tag); + assert_eq!(compiled.dispatch_tags.get(name).copied(), Some(expected_tag)); } } @@ -7240,8 +7511,8 @@ fn compiles_basic_arithmetic_and_verifies() { let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert_eq!(bytecode(&compiled), expected); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -7274,8 +7545,8 @@ fn compiles_contract_constants_and_verifies() { let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert_eq!(bytecode(&compiled), expected); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -7316,7 +7587,7 @@ fn compiles_contract_fields_as_script_prolog() { .drain(); let expected = wrap_with_single_dispatch_and_state(&compiled, &state, &body); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -7335,7 +7606,7 @@ fn runs_contract_with_fields_prolog() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -7357,18 +7628,18 @@ fn runs_dispatch_tag_dispatch_with_contract_fields() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - assert_eq!(compiled.state_layout.start, 1); - assert!(compiled.state_layout.len > 0); - assert_eq!(compiled.bytecode.first().copied(), Some(OpToAltStack)); - assert!(compiled.bytecode.contains(&OpFromAltStack)); + assert_eq!(state_layout(&compiled).start, 1); + assert!(state_layout(&compiled).len > 0); + assert_eq!(bytecode(&compiled).first().copied(), Some(OpToAltStack)); + assert!(bytecode(&compiled).contains(&OpFromAltStack)); - let sigscript_a = compiled.build_sig_script("a", vec![]).expect("sigscript a builds"); - let sigscript_b = compiled.build_sig_script("b", vec![]).expect("sigscript b builds"); + let sigscript_a = encode_entry_sig_script(&compiled, "a", &[]).expect("sigscript a builds"); + let sigscript_b = encode_entry_sig_script(&compiled, "b", &[]).expect("sigscript b builds"); - let result_a = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_a); + let result_a = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_a); assert!(result_a.is_ok(), "entrypoint a runtime failed: {}", result_a.unwrap_err()); - let result_b = run_bytecode_with_sigscript(compiled.bytecode, sigscript_b); + let result_b = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_b); assert!(result_b.is_ok(), "entrypoint b runtime failed: {}", result_b.unwrap_err()); } @@ -7448,13 +7719,13 @@ fn compiles_validate_output_state_to_expected_script() { .unwrap() .add_op(OpTxInputScriptSigLen) .unwrap() - .add_i64(compiled.bytecode.len() as i64) + .add_i64(bytecode(&compiled).len() as i64) .unwrap() .add_op(OpSub) .unwrap() .add_op(OpDup) .unwrap() - .add_i64(compiled.state_layout.start as i64) + .add_i64(state_layout(&compiled).start as i64) .unwrap() .add_op(OpAdd) .unwrap() @@ -7480,7 +7751,7 @@ fn compiles_validate_output_state_to_expected_script() { .unwrap() // Precompute state_end - bytecode_size, where // state_end = dispatch prefix + len() = 13. - .add_i64(13 - compiled.bytecode.len() as i64) + .add_i64(13 - bytecode(&compiled).len() as i64) .unwrap() // start offset of REST_OF_SCRIPT inside sigscript .add_op(OpAdd) @@ -7557,11 +7828,11 @@ fn compiles_validate_output_state_to_expected_script() { .unwrap() .drain(); - let (state, body) = expected.split_at(compiled.state_layout.len); + let (state, body) = expected.split_at(state_layout(&compiled).len); let expected = wrap_with_single_dispatch_and_state(&compiled, state, body); - let actual_ops = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); - assert_eq!(compiled.bytecode, expected, "actual opcodes: {actual_ops}"); + let actual_ops = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); + assert_eq!(bytecode(&compiled), expected, "actual opcodes: {actual_ops}"); } #[test] @@ -7580,14 +7851,14 @@ fn runs_validate_output_state() { let input_compiled = compile_contract(source, &[5.into(), vec![1u8, 2u8].into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); let output_compiled = compile_contract(source, &[6.into(), vec![0x34u8, 0x12u8].into()], CompileOptions::default()).expect("compile succeeds"); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -7608,7 +7879,8 @@ fn validate_output_state_normalizes_runtime_bool_fields() { } "#; - let input_compiled = compile_contract(source, &[Expr::bool(false)], CompileOptions::default()).expect("input contract compiles"); + let input_compiled = + compile_contract(source, &[ArtifactValue::Bool(false)], CompileOptions::default()).expect("input contract compiles"); let raw_truthy_arg = script_builder() .add_data_with_push_opcode(&[2]) .unwrap() @@ -7616,13 +7888,14 @@ fn validate_output_state_normalizes_runtime_bool_fields() { .unwrap() .drain(); let signature_script = - pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), raw_truthy_arg).expect("P2SH signature script builds"); + pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), raw_truthy_arg).expect("P2SH signature script builds"); let input = test_input(0, signature_script); - let output_compiled = compile_contract(source, &[Expr::bool(true)], CompileOptions::default()).expect("output contract compiles"); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); + let output_compiled = + compile_contract(source, &[ArtifactValue::Bool(true)], CompileOptions::default()).expect("output contract compiles"); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); let output = - TransactionOutput { value: 1000, script_public_key: pay_to_script_hash_script(&output_compiled.bytecode), covenant: None }; + TransactionOutput { value: 1000, script_public_key: pay_to_script_hash_script(&bytecode(&output_compiled)), covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -7647,14 +7920,14 @@ fn runs_validate_output_state_with_state_variable() { let input_compiled = compile_contract(source, &[5.into(), vec![1u8, 2u8].into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); let output_compiled = compile_contract(source, &[6.into(), vec![0x34u8, 0x12u8].into()], CompileOptions::default()).expect("compile succeeds"); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -7665,31 +7938,31 @@ fn runs_validate_output_state_with_state_variable() { fn run_read_input_state_with_template_case( reader_source: &str, - reader_constructor_args: &[Expr<'static>], - target_input_compiled: &CompiledContract<'_>, + reader_constructor_args: &[ArtifactValue], + target_input_compiled: &silverscript_abi::SilAbiArtifact, ) -> Result<(), kaspa_txscript_errors::TxScriptError> { run_read_input_state_with_template_case_with_input_spk( reader_source, reader_constructor_args, target_input_compiled, - pay_to_script_hash_script(&target_input_compiled.bytecode), + pay_to_script_hash_script(&bytecode(target_input_compiled)), ) } fn run_read_input_state_with_template_case_with_input_spk( reader_source: &str, - reader_constructor_args: &[Expr<'static>], - target_input_compiled: &CompiledContract<'_>, + reader_constructor_args: &[ArtifactValue], + target_input_compiled: &silverscript_abi::SilAbiArtifact, input1_spk: ScriptPublicKey, ) -> Result<(), kaspa_txscript_errors::TxScriptError> { let reader_compiled = compile_contract(reader_source, reader_constructor_args, CompileOptions::default()).expect("compile reader succeeds"); let input0 = test_input(0, dispatch_tag_sigscript(dispatch_tag_for(&reader_compiled, "main"))); - let input1 = test_input(1, sigscript_push_bytecode(&target_input_compiled.bytecode)); + let input1 = test_input(1, sigscript_push_bytecode(&bytecode(target_input_compiled))); let output = TransactionOutput { value: 1000, - script_public_key: ScriptPublicKey::new(0, reader_compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&reader_compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input0.clone(), input1], vec![output.clone()], 0, Default::default(), 0, vec![]); @@ -7703,7 +7976,7 @@ fn run_validate_output_state_with_template_case( template_prefix: Vec, template_suffix: Vec, expected_template_hash: Vec, - output_compiled: &CompiledContract, + output_compiled: &silverscript_abi::SilAbiArtifact, ) -> Result<(), kaspa_txscript_errors::TxScriptError> { let mux_source = format!( r#" @@ -7737,12 +8010,12 @@ fn run_validate_output_state_with_template_case( ) .expect("compile mux succeeds"); - let sigscript = mux_input_compiled.build_sig_script("routeToA", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(mux_input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&mux_input_compiled, "routeToA", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&mux_input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&mux_input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&mux_input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -7769,7 +8042,7 @@ fn runs_validate_output_state_with_template() { let target_a0 = compile_contract( target_source, - &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), Expr::int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], + &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), ArtifactValue::Int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], CompileOptions::default(), ) .expect("compile target succeeds"); @@ -7814,12 +8087,12 @@ fn runs_validate_output_state_with_template() { ) .expect("compile mux succeeds"); - let sigscript = mux_input_compiled.build_sig_script("routeToA", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(mux_input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&mux_input_compiled, "routeToA", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&mux_input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&mux_input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&target_output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&mux_input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&target_output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -7842,9 +8115,9 @@ fn template_hash_matches_all_template_builtins() { let target_input = compile_contract(target_source, &[7.into()], CompileOptions::default()).expect("compile target input succeeds"); let target_output = compile_contract(target_source, &[8.into()], CompileOptions::default()).expect("compile target output succeeds"); - let layout = target_input.state_layout; - let prefix = &target_input.bytecode[..layout.start]; - let suffix = &target_input.bytecode[layout.start + layout.len..]; + let layout = state_layout(&target_input); + let prefix = &bytecode(&target_input)[..layout.start]; + let suffix = &bytecode(&target_input)[layout.start + layout.len..]; let prefix_hex = prefix.iter().map(|byte| format!("{byte:02x}")).collect::(); let suffix_hex = suffix.iter().map(|byte| format!("{byte:02x}")).collect::(); @@ -7892,16 +8165,16 @@ fn template_hash_matches_all_template_builtins() { suffix.len(), ); let verifier = compile_contract(&verifier_source, &[], CompileOptions::default()).expect("compile verifier succeeds"); - let verifier_sigscript = verifier.build_sig_script("main", vec![]).expect("verifier sigscript builds"); - let verifier_sigscript = pay_to_script_hash_signature_script(verifier.bytecode.clone(), verifier_sigscript).unwrap(); + let verifier_sigscript = encode_single_entry_sig_script(&verifier, &[]).expect("verifier sigscript builds"); + let verifier_sigscript = pay_to_script_hash_signature_script(bytecode(&verifier).clone(), verifier_sigscript).unwrap(); let verifier_input = test_input(0, verifier_sigscript); - let target_input_tx = test_input(1, sigscript_push_bytecode(&target_input.bytecode)); + let target_input_tx = test_input(1, sigscript_push_bytecode(&bytecode(&target_input))); let output = - TransactionOutput { value: 1000, script_public_key: pay_to_script_hash_script(&target_output.bytecode), covenant: None }; + TransactionOutput { value: 1000, script_public_key: pay_to_script_hash_script(&bytecode(&target_output)), covenant: None }; let tx = Transaction::new(1, vec![verifier_input, target_input_tx], vec![output.clone()], 0, Default::default(), 0, vec![]); - let verifier_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&verifier.bytecode), 0, tx.is_coinbase(), None); - let target_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&target_input.bytecode), 0, tx.is_coinbase(), None); + let verifier_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&bytecode(&verifier)), 0, tx.is_coinbase(), None); + let target_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&bytecode(&target_input)), 0, tx.is_coinbase(), None); let result = execute_input(tx, vec![verifier_utxo, target_utxo], 0); assert!(result.is_ok(), "templateHash should match all state template builtins: {}", result.unwrap_err()); @@ -7931,15 +8204,16 @@ fn template_hash_matches_all_template_builtins() { ); let invalid_verifier = compile_contract(&invalid_verifier_source, &[], CompileOptions::default()).expect("compile invalid verifier succeeds"); - let invalid_sigscript = invalid_verifier.build_sig_script("main", vec![]).expect("invalid verifier sigscript builds"); - let invalid_sigscript = pay_to_script_hash_signature_script(invalid_verifier.bytecode.clone(), invalid_sigscript).unwrap(); + let invalid_sigscript = encode_single_entry_sig_script(&invalid_verifier, &[]).expect("invalid verifier sigscript builds"); + let invalid_sigscript = pay_to_script_hash_signature_script(bytecode(&invalid_verifier).clone(), invalid_sigscript).unwrap(); let invalid_input = test_input(0, invalid_sigscript); - let target_input_tx = test_input(1, sigscript_push_bytecode(&target_input.bytecode)); + let target_input_tx = test_input(1, sigscript_push_bytecode(&bytecode(&target_input))); let output = - TransactionOutput { value: 1000, script_public_key: pay_to_script_hash_script(&target_output.bytecode), covenant: None }; + TransactionOutput { value: 1000, script_public_key: pay_to_script_hash_script(&bytecode(&target_output)), covenant: None }; let tx = Transaction::new(1, vec![invalid_input, target_input_tx], vec![output.clone()], 0, Default::default(), 0, vec![]); - let invalid_verifier_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&invalid_verifier.bytecode), 0, tx.is_coinbase(), None); - let target_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&target_input.bytecode), 0, tx.is_coinbase(), None); + let invalid_verifier_utxo = + UtxoEntry::new(1000, pay_to_script_hash_script(&bytecode(&invalid_verifier)), 0, tx.is_coinbase(), None); + let target_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&bytecode(&target_input)), 0, tx.is_coinbase(), None); assert!(execute_input(tx, vec![invalid_verifier_utxo, target_utxo], 0).is_err(), "incorrect template lengths must fail"); } @@ -7986,7 +8260,7 @@ fn runs_validate_output_state_with_template_using_passed_struct_layout() { let target_a0 = compile_contract( &target_source, - &[vec![0x55u8, 0x66u8].into(), Expr::int(0x1111_1111_1111_1111), vec![0x33u8; 32].into()], + &[vec![0x55u8, 0x66u8].into(), ArtifactValue::Int(0x1111_1111_1111_1111), vec![0x33u8; 32].into()], CompileOptions::default(), ) .expect("compile target succeeds"); @@ -8035,12 +8309,13 @@ fn runs_validate_output_state_with_template_using_passed_struct_layout() { let mux_input_compiled = compile_contract(&mux_source, &[5.into(), vec![0x10u8, 0x20u8].into()], CompileOptions::default()) .expect("compile mux succeeds"); - let sigscript = mux_input_compiled.build_sig_script("routeToA", vec![target_hash_value.clone().into()]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(mux_input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = + encode_entry_sig_script(&mux_input_compiled, "routeToA", &[target_hash_value.clone().into()]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&mux_input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&mux_input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&target_output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&mux_input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&target_output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8052,12 +8327,12 @@ fn runs_validate_output_state_with_template_using_passed_struct_layout() { result.unwrap_err() ); - let a_sigscript = target_output_compiled.build_sig_script("noop", vec![]).expect("A sigscript builds"); - let a_sigscript = pay_to_script_hash_signature_script(target_output_compiled.bytecode.clone(), a_sigscript).unwrap(); + let a_sigscript = encode_entry_sig_script(&target_output_compiled, "noop", &[]).expect("A sigscript builds"); + let a_sigscript = pay_to_script_hash_signature_script(bytecode(&target_output_compiled).clone(), a_sigscript).unwrap(); let a_input = test_input(0, a_sigscript); let a_output = TransactionOutput { value: 1000, script_public_key: ScriptPublicKey::new(0, vec![OpTrue].into()), covenant: None }; let a_tx = Transaction::new(1, vec![a_input], vec![a_output], 0, Default::default(), 0, vec![]); - let a_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&target_output_compiled.bytecode), 0, a_tx.is_coinbase(), None); + let a_utxo = UtxoEntry::new(1000, pay_to_script_hash_script(&bytecode(&target_output_compiled)), 0, a_tx.is_coinbase(), None); let a_result = execute_input(a_tx, vec![a_utxo], 0); assert!( a_result.is_ok(), @@ -8083,7 +8358,7 @@ fn validate_output_state_with_template_rejects_wrong_template_hash() { let target = compile_contract( target_source, - &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), Expr::int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], + &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), ArtifactValue::Int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], CompileOptions::default(), ) .expect("compile target succeeds"); @@ -8119,7 +8394,7 @@ fn validate_output_state_with_template_rejects_wrong_template_parts() { let target = compile_contract( target_source, - &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), Expr::int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], + &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), ArtifactValue::Int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], CompileOptions::default(), ) .expect("compile target succeeds"); @@ -8154,7 +8429,7 @@ fn validate_output_state_with_template_rejects_wrong_output_script() { let target = compile_contract( target_source, - &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), Expr::int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], + &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), ArtifactValue::Int(0x1111_1111_1111_1111), vec![0x55u8, 0x66u8].into()], CompileOptions::default(), ) .expect("compile target succeeds"); @@ -8187,7 +8462,7 @@ fn validate_output_state_with_template_rejects_different_target_state_layout() { let target = compile_contract( target_source, - &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), Expr::int(0x1111_1111_1111_1111)], + &[vec![0x11u8; 32].into(), vec![0x33u8; 32].into(), ArtifactValue::Int(0x1111_1111_1111_1111)], CompileOptions::default(), ) .expect("compile different-layout target succeeds"); @@ -8226,9 +8501,9 @@ contract Sweep(int BOUND, byte[64] init_board) { let bounds = [4i64, 8i64, 12i64]; let mut lens = Vec::new(); for b in bounds { - let args = [Expr::int(b), Expr::bytes(vec![0u8; 64])]; + let args = [b.into(), vec![0u8; 64].into()]; let compiled = compile_contract(SOURCE, &args, CompileOptions::default()).expect("compile succeeds"); - lens.push(compiled.bytecode.len()); + lens.push(bytecode(&compiled).len()); } // Monotonic growth, and no doubling behavior in this range. @@ -8255,12 +8530,12 @@ fn validate_output_state_accepts_state_value_from_array_index() { "#; let input_compiled = compile_contract(source, &[5.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![state_array_arg_x(vec![6])]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[state_array_arg_x(vec![6])]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[6.into()], CompileOptions::default()).expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8288,12 +8563,12 @@ fn validate_output_state_accepts_state_value_from_inline_returned_array() { "#; let input_compiled = compile_contract(source, &[5.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![state_array_arg_x(vec![6])]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[state_array_arg_x(vec![6])]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[6.into()], CompileOptions::default()).expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8320,12 +8595,12 @@ fn read_input_state_accepts_self_state_under_dispatch_tag_dispatch() { "#; let input_compiled = compile_contract(source, &[5.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[5.into()], CompileOptions::default()).expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8349,10 +8624,10 @@ fn read_input_state_int_addition_uses_numeric_semantics() { "#; let compiled = compile_contract(source, &[5.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(compiled.bytecode.clone(), sigscript).expect("p2sh sigscript wraps"); + let sigscript = encode_single_entry_sig_script(&compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&compiled).clone(), sigscript).expect("p2sh sigscript wraps"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&compiled)); let output = TransactionOutput { value: 1000, script_public_key: input_spk.clone(), covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8385,14 +8660,14 @@ fn read_input_state_accepts_three_field_state_under_dispatch_tag_dispatch() { let input_compiled = compile_contract(source, &[5.into(), vec![0x34u8, 0x12u8].into(), vec![1u8; 32].into()], CompileOptions::default()) .expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[5.into(), vec![0x34u8, 0x12u8].into(), vec![1u8; 32].into()], CompileOptions::default()) .expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8422,13 +8697,13 @@ fn read_input_state_accepts_pubkey_and_bool_fields_under_dispatch_tag_dispatch() let input_compiled = compile_contract(source, &[true.into(), vec![2u8; 32].into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[true.into(), vec![2u8; 32].into()], CompileOptions::default()).expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8439,12 +8714,12 @@ fn read_input_state_accepts_pubkey_and_bool_fields_under_dispatch_tag_dispatch() #[test] fn read_input_state_runtime_preserves_supported_field_types_across_contract_shapes() { - let run_case = |source: &str, args: Vec>, label: &str| { + let run_case = |source: &str, args: Vec, label: &str| { let compiled = compile_contract(source, &args, CompileOptions::default()).unwrap_or_else(|err| panic!("{label}: {err:?}")); - let sigscript = compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(compiled.bytecode.clone(), sigscript).expect("p2sh sigscript wraps"); + let sigscript = encode_entry_sig_script(&compiled, "main", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&compiled).clone(), sigscript).expect("p2sh sigscript wraps"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&compiled)); let output = TransactionOutput { value: 1000, script_public_key: input_spk.clone(), covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8489,7 +8764,7 @@ fn read_input_state_runtime_preserves_supported_field_types_across_contract_shap } } "#, - vec![Expr::try_from(vec![Expr::int(1), Expr::int(2)]).unwrap()], + vec![ArtifactValue::Array(vec![1.into(), 2.into()])], "int[2] fields should preserve array indexing semantics", ); @@ -8529,7 +8804,7 @@ fn read_input_state_runtime_preserves_supported_field_types_across_contract_shap } } "#, - vec![Expr::try_from(vec![Expr::bool(true), Expr::bool(false)]).unwrap()], + vec![ArtifactValue::Array(vec![true.into(), false.into()])], "bool[2] fields should preserve array indexing semantics", ); @@ -8625,12 +8900,12 @@ fn read_input_state_runtime_preserves_supported_field_types_across_contract_shap #[test] fn read_input_state_runtime_preserves_supported_field_types_with_single_entrypoint_dispatch() { - let run_case = |source: &str, args: Vec>, label: &str| { + let run_case = |source: &str, args: Vec, label: &str| { let compiled = compile_contract(source, &args, CompileOptions::default()).unwrap_or_else(|err| panic!("{label}: {err:?}")); - let sigscript = compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(compiled.bytecode.clone(), sigscript).expect("p2sh sigscript wraps"); + let sigscript = encode_single_entry_sig_script(&compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&compiled).clone(), sigscript).expect("p2sh sigscript wraps"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&compiled)); let output = TransactionOutput { value: 1000, script_public_key: input_spk.clone(), covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8714,12 +8989,12 @@ fn read_input_state_scalar_byte_round_trips_at_runtime() { } "#; - let compiled = - compile_contract(source, &[Expr::byte(7), vec![2u8; 32].into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(compiled.bytecode.clone(), sigscript).expect("p2sh sigscript wraps"); + let compiled = compile_contract(source, &[ArtifactValue::Byte(7), vec![2u8; 32].into()], CompileOptions::default()) + .expect("compile succeeds"); + let sigscript = encode_entry_sig_script(&compiled, "main", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&compiled).clone(), sigscript).expect("p2sh sigscript wraps"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&compiled)); let output = TransactionOutput { value: 1000, script_public_key: input_spk.clone(), covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8745,13 +9020,12 @@ fn validate_output_state_accepts_state_under_dispatch_tag_dispatch() { "#; let input_compiled = compile_contract(source, &[5.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = - input_compiled.build_sig_script("main", vec![struct_object("State", vec![("x", Expr::int(6))])]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[artifact_object([("x", 6.into())])]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[6.into()], CompileOptions::default()).expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8781,22 +9055,19 @@ fn validate_output_state_accepts_three_field_state_under_dispatch_tag_dispatch() let input_compiled = compile_contract(source, &[5.into(), vec![0x34u8, 0x12u8].into(), vec![1u8; 32].into()], CompileOptions::default()) .expect("compile succeeds"); - let sigscript = input_compiled - .build_sig_script( - "main", - vec![struct_object( - "State", - vec![("amount", Expr::int(6)), ("code", Expr::bytes(vec![0xabu8, 0xcdu8])), ("owner", Expr::bytes(vec![2u8; 32]))], - )], - ) - .expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script( + &input_compiled, + "main", + &[artifact_object([("amount", 6.into()), ("code", vec![0xabu8, 0xcdu8].into()), ("owner", vec![2u8; 32].into())])], + ) + .expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[6.into(), vec![0xabu8, 0xcdu8].into(), vec![2u8; 32].into()], CompileOptions::default()) .expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8834,11 +9105,11 @@ fn debug_validate_output_state_accepts_current_byte32_fields() { ) .expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_single_entry_sig_script(&input_compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8864,14 +9135,13 @@ fn validate_output_state_accepts_pubkey_field_under_dispatch_tag_dispatch() { "#; let input_compiled = compile_contract(source, &[vec![1u8; 32].into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled - .build_sig_script("main", vec![struct_object("State", vec![("owner", Expr::bytes(vec![2u8; 32]))])]) + let sigscript = encode_entry_sig_script(&input_compiled, "main", &[artifact_object([("owner", vec![2u8; 32].into())])]) .expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let output_compiled = compile_contract(source, &[vec![2u8; 32].into()], CompileOptions::default()).expect("compile succeeds"); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -8923,7 +9193,7 @@ fn runs_state_variable_and_internal_function_argument() { let compiled = compile_contract(source, &[5.into(), vec![1u8, 2u8].into()], CompileOptions::default()).expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "script should execute successfully: {result:?}"); } @@ -8965,7 +9235,7 @@ fn byte_hex_literal_is_a_scalar_numeral() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("scalar byte hex literal should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -8985,7 +9255,7 @@ fn hex_literals_are_numerals_for_int_and_byte() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("hex numerals should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -9015,7 +9285,7 @@ fn hex_literal_over_eight_bytes_requires_an_immediate_byte_array_cast() { let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("immediately cast nine-byte literal should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -9040,7 +9310,7 @@ fn fixed_byte_sequence_types_accept_immediate_hex_literals() { let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("direct fixed byte-sequence casts should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -9115,7 +9385,7 @@ fn compiles_read_input_state_to_expected_script() { .add_op(OpTxInputScriptSigLen) .unwrap() // this.bytecodeSize - .add_i64(compiled.bytecode.len() as i64) + .add_i64(bytecode(&compiled).len() as i64) .unwrap() // base = sig_len - bytecode_size .add_op(OpSub) @@ -9134,7 +9404,7 @@ fn compiles_read_input_state_to_expected_script() { .add_op(OpTxInputScriptSigLen) .unwrap() // this.bytecodeSize - .add_i64(compiled.bytecode.len() as i64) + .add_i64(bytecode(&compiled).len() as i64) .unwrap() // base = sig_len - bytecode_size .add_op(OpSub) @@ -9175,7 +9445,7 @@ fn compiles_read_input_state_to_expected_script() { .add_op(OpTxInputScriptSigLen) .unwrap() // this.bytecodeSize - .add_i64(compiled.bytecode.len() as i64) + .add_i64(bytecode(&compiled).len() as i64) .unwrap() // base = sig_len - bytecode_size .add_op(OpSub) @@ -9194,7 +9464,7 @@ fn compiles_read_input_state_to_expected_script() { .add_op(OpTxInputScriptSigLen) .unwrap() // this.bytecodeSize - .add_i64(compiled.bytecode.len() as i64) + .add_i64(bytecode(&compiled).len() as i64) .unwrap() // base = sig_len - bytecode_size .add_op(OpSub) @@ -9235,12 +9505,12 @@ fn compiles_read_input_state_to_expected_script() { .unwrap() .drain(); - let asm = script_to_str(&compiled.bytecode).expect("stringifies"); + let asm = script_to_str(&bytecode(&compiled)).expect("stringifies"); assert_eq!(asm.matches("OpTxInputScriptSigSubstr").count(), 2, "should read two state fields"); assert_eq!(asm.matches("OpGreaterThan").count(), 1, "should compare x numerically"); assert_eq!(asm.matches("OpEqual").count(), 2, "should compare y bytewise in addition to dispatch"); assert!( - compiled.bytecode.ends_with(&[OpDrop, OpDrop, OpTrue, OpElse, OpReturn, OpEndIf]), + bytecode(&compiled).ends_with(&[OpDrop, OpDrop, OpTrue, OpElse, OpReturn, OpEndIf]), "expected stack cleanup for active state before the dispatch epilogue" ); } @@ -9266,11 +9536,11 @@ fn runs_read_input_state() { compile_contract(source, &[8.into(), vec![0x34u8, 0x12u8].into()], CompileOptions::default()).expect("compile succeeds"); let input0 = test_input(0, dispatch_tag_sigscript(dispatch_tag_for(&active_compiled, "main"))); - let input1 = test_input(1, sigscript_push_bytecode(&input1_compiled.bytecode)); + let input1 = test_input(1, sigscript_push_bytecode(&bytecode(&input1_compiled))); let output = TransactionOutput { value: 1000, - script_public_key: ScriptPublicKey::new(0, active_compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&active_compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input0.clone(), input1], vec![output.clone()], 0, Default::default(), 0, vec![]); @@ -9301,11 +9571,11 @@ fn runs_read_input_state_into_state_variable() { compile_contract(source, &[8.into(), vec![0x34u8, 0x12u8].into()], CompileOptions::default()).expect("compile succeeds"); let input0 = test_input(0, dispatch_tag_sigscript(dispatch_tag_for(&active_compiled, "main"))); - let input1 = test_input(1, sigscript_push_bytecode(&input1_compiled.bytecode)); + let input1 = test_input(1, sigscript_push_bytecode(&bytecode(&input1_compiled))); let output = TransactionOutput { value: 1000, - script_public_key: ScriptPublicKey::new(0, active_compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&active_compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input0.clone(), input1], vec![output.clone()], 0, Default::default(), 0, vec![]); @@ -9339,10 +9609,10 @@ fn runs_read_input_state_as_internal_function_argument() { compile_contract(source, &[8.into(), vec![0x34u8, 0x12u8].into()], CompileOptions::default()).expect("compile succeeds"); let input0 = test_input(0, dispatch_tag_sigscript(dispatch_tag_for(&active_compiled, "main"))); - let input1 = test_input(1, sigscript_push_bytecode(&input1_compiled.bytecode)); + let input1 = test_input(1, sigscript_push_bytecode(&bytecode(&input1_compiled))); let output = TransactionOutput { value: 1000, - script_public_key: ScriptPublicKey::new(0, active_compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&active_compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input0, input1], vec![output.clone()], 0, Default::default(), 0, vec![]); @@ -9818,11 +10088,11 @@ fn validate_output_state_lowers_nested_state_literal_in_state_field_order() { compile_contract(source, &[5.into(), 3.into(), 4.into()], CompileOptions::default()).expect("compile succeeds"); let output_compiled = compile_contract(source, &[6.into(), 7.into(), 8.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_single_entry_sig_script(&input_compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -9854,11 +10124,11 @@ fn validate_output_state_with_state_identifier() { compile_contract(source, &[5.into(), 3.into(), 4.into()], CompileOptions::default()).expect("compile succeeds"); let output_compiled = compile_contract(source, &[6.into(), 7.into(), 8.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_single_entry_sig_script(&input_compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -9967,13 +10237,13 @@ fn fails_validate_output_state_with_wrong_output_index() { let expected_output_state = compile_contract(source, &[6.into(), vec![0x34u8, 0x12u8].into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_single_entry_sig_script(&input_compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let matching_spk = pay_to_script_hash_script(&expected_output_state.bytecode); - let wrong_spk = pay_to_script_hash_script(&input_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let matching_spk = pay_to_script_hash_script(&bytecode(&expected_output_state)); + let wrong_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); let output0 = TransactionOutput { value: 1000, script_public_key: wrong_spk, covenant: None }; let output1 = TransactionOutput { value: 1000, script_public_key: matching_spk, covenant: None }; @@ -10002,12 +10272,12 @@ fn fails_validate_output_state_with_mismatched_next_state_fields() { let wrong_output_state = compile_contract(source, &[7.into(), vec![0x34u8, 0x12u8].into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_single_entry_sig_script(&input_compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let wrong_output_spk = pay_to_script_hash_script(&wrong_output_state.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let wrong_output_spk = pay_to_script_hash_script(&bytecode(&wrong_output_state)); let output = TransactionOutput { value: 1000, script_public_key: wrong_output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(1000, input_spk, 0, tx.is_coinbase(), None); @@ -10073,7 +10343,7 @@ fn rejects_validate_output_state_with_unknown_state_field() { fn assert_compiled_body(source: &str, body: Vec) { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -10116,7 +10386,7 @@ fn check_sig_ecdsa_lowers_to_matching_opcode() { .unwrap() .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -10153,7 +10423,7 @@ fn checksigfromstack_lowers_to_matching_opcode() { .unwrap() .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -10190,7 +10460,7 @@ fn check_msg_sig_ecdsa_lowers_to_matching_opcode() { .unwrap() .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -10319,8 +10589,8 @@ fn g16_verify_lowers_to_groth16_precompile() { let compiled = compile_contract( source, &[ - Expr::dynamic_bytes(verifying_key.clone()), - Expr::dynamic_bytes(proof.clone()), + ArtifactValue::Bytes(verifying_key.clone()), + ArtifactValue::Bytes(proof.clone()), public_inputs[0].clone().into(), public_inputs[1].clone().into(), ], @@ -10348,7 +10618,7 @@ fn g16_verify_lowers_to_groth16_precompile() { .add_op(OpTrue) .unwrap() .drain(); - assert_eq!(compiled.bytecode, wrap_with_single_dispatch(&compiled, body)); + assert_eq!(bytecode(&compiled), wrap_with_single_dispatch(&compiled, body)); } #[test] @@ -10443,19 +10713,19 @@ fn g16_verify_executes_with_fixture_and_rejects_tampered_input() { "#; let (verifying_key, proof, public_inputs) = kaspa_txscript::zk_precompiles::tests::helpers::load_groth_fields(); let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let build_args = |inputs: &[Vec]| -> Vec> { - let mut args = vec![Expr::dynamic_bytes(verifying_key.clone()), Expr::dynamic_bytes(proof.clone())]; + let build_args = |inputs: &[Vec]| -> Vec { + let mut args = vec![verifying_key.clone().into(), proof.clone().into()]; args.extend(inputs.iter().cloned().map(Into::into)); args }; - let sigscript = compiled.build_sig_script("verify", build_args(&public_inputs)).expect("sigscript builds"); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript).is_ok(), "valid Groth16 proof should pass"); + let sigscript = encode_entry_sig_script(&compiled, "verify", &build_args(&public_inputs)).expect("sigscript builds"); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript).is_ok(), "valid Groth16 proof should pass"); let mut tampered_inputs = public_inputs; tampered_inputs[0][0] ^= 0x01; - let sigscript = compiled.build_sig_script("verify", build_args(&tampered_inputs)).expect("sigscript builds"); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript).is_err(), "tampered public input should fail"); + let sigscript = encode_entry_sig_script(&compiled, "verify", &build_args(&tampered_inputs)).expect("sigscript builds"); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript).is_err(), "tampered public input should fail"); } #[test] @@ -10494,8 +10764,8 @@ fn r0_succinct_verify_lowers_hash_aliases_to_zk_precompile() { control_id.clone().into(), claim.clone().into(), control_index.clone().into(), - Expr::dynamic_bytes(control_digests.clone()), - Expr::dynamic_bytes(seal.clone()), + ArtifactValue::Bytes(control_digests.clone()), + ArtifactValue::Bytes(seal.clone()), journal.clone().into(), ], CompileOptions::default(), @@ -10529,9 +10799,9 @@ fn r0_succinct_verify_lowers_hash_aliases_to_zk_precompile() { .unwrap() .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - let asm = script_to_str(&compiled.bytecode).expect("R0 succinct script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("R0 succinct script should stringify"); assert!(asm.contains("OpZkPrecompile OpDrop"), "void verifier result should be dropped: {asm}"); - assert_eq!(compiled.bytecode, expected, "{call_name} lowered unexpectedly"); + assert_eq!(bytecode(&compiled), expected, "{call_name} lowered unexpectedly"); } } @@ -10572,7 +10842,7 @@ fn r0_g16_verify_lowers_with_sdk_verifier_fragment() { let image_id = [0x33u8; 32]; let compiled = compile_contract( source, - &[journal_hash.clone().into(), Expr::dynamic_bytes(proof.clone()), image_id.to_vec().into()], + &[journal_hash.clone().into(), ArtifactValue::Bytes(proof.clone()), image_id.to_vec().into()], CompileOptions::default(), ) .expect("compile succeeds"); @@ -10586,9 +10856,9 @@ fn r0_g16_verify_lowers_with_sdk_verifier_fragment() { expected_builder.add_op(OpTrue).unwrap(); let expected = wrap_with_single_dispatch(&compiled, expected_builder.drain()); - let asm = script_to_str(&compiled.bytecode).expect("R0 Groth16 script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("R0 Groth16 script should stringify"); assert!(asm.contains("OpZkPrecompile OpDrop"), "void verifier result should be dropped: {asm}"); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -10633,7 +10903,7 @@ fn value_returning_builtin_statement_discards_result() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("value-returning builtin statement should compile"); - let asm = script_to_str(&compiled.bytecode).expect("builtin statement script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("builtin statement script should stringify"); assert!( asm.ends_with("OpSHA256 OpDrop OpTrue OpElse OpReturn OpEndIf"), "builtin statement result should be discarded before the dispatch epilogue: {asm}" @@ -10656,7 +10926,7 @@ fn discarded_helper_return_expressions_are_evaluated_and_dropped() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("discarded helper returns should compile"); - let asm = script_to_str(&compiled.bytecode).expect("script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("script should stringify"); assert_eq!(asm.matches("OpSHA256").count(), 2, "both return expressions must be evaluated: {asm}"); assert_eq!(asm.matches("OpDrop").count(), 3, "both discarded return values plus the dispatch tag must be dropped: {asm}"); } @@ -10682,7 +10952,7 @@ fn discarded_nested_helper_return_expression_is_evaluated() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("nested discarded return should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_err(), "the nested discarded division by zero must execute"); } @@ -10852,19 +11122,19 @@ fn r0_succinct_verify_runtime_checks_each_hash_with_fixture() { let compiled = compile_contract(&source, &[image_id.clone().into(), control_id.clone().into()], CompileOptions::default()) .expect("compile succeeds"); - let sigscript = compiled - .build_sig_script( - "main", - vec![ - claim.clone().into(), - control_index.clone().into(), - Expr::dynamic_bytes(control_digests.clone()), - Expr::dynamic_bytes(seal.clone()), - journal.clone().into(), - ], - ) - .expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script( + &compiled, + "main", + &[ + claim.clone().into(), + control_index.clone().into(), + ArtifactValue::Bytes(control_digests.clone()), + ArtifactValue::Bytes(seal.clone()), + journal.clone().into(), + ], + ) + .expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "{call_name} should execute successfully: {result:?}"); } } @@ -10889,11 +11159,10 @@ fn r0_g16_verify_executes_with_fixture() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled - .build_sig_script("main", vec![journal_hash.into(), Expr::dynamic_bytes(proof), image_id.into()]) + let sigscript = encode_entry_sig_script(&compiled, "main", &[journal_hash.into(), ArtifactValue::Bytes(proof), image_id.into()]) .expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "R0 Groth16 verifier should execute successfully: {result:?}"); } @@ -10991,7 +11260,7 @@ fn checksigfromstack_executes_schnorr_signature_verification() { ) .expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag) + run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag) }; assert!(run(valid_signature.clone()).is_ok(), "valid Schnorr data signature should pass"); @@ -11017,10 +11286,13 @@ fn checksigfromstack_false_result_can_be_asserted() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); let run = |signature: Vec| { - let sigscript = compiled - .build_sig_script("main", vec![signature.into(), digest.as_bytes().to_vec().into(), public_key.clone().into()]) - .expect("sigscript builds"); - run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript) + let sigscript = encode_entry_sig_script( + &compiled, + "main", + &[signature.into(), digest.as_bytes().to_vec().into(), public_key.clone().into()], + ) + .expect("sigscript builds"); + run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript) }; let valid_result = run(valid_signature); @@ -11052,7 +11324,7 @@ fn check_msg_sig_ecdsa_executes_ecdsa_signature_verification() { ) .expect("compile succeeds"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag) + run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag) }; assert!(run(valid_signature.clone()).is_ok(), "valid ECDSA data signature should pass"); @@ -11967,7 +12239,7 @@ fn executes_opcode_builtins_basic() { let dispatch_tag = dispatch_tag_for(&compiled, "main"); let sigscript = dispatch_tag_sigscript(dispatch_tag); let (tx, entries) = build_basic_opcode_tx(sigscript); - let result = run_bytecode_with_tx_and_covenants(compiled.bytecode, tx, entries, None); + let result = run_bytecode_with_tx_and_covenants(bytecode(&compiled), tx, entries, None); assert!(result.is_ok(), "opcode builtin {name} failed: {}", result.unwrap_err()); } } @@ -12008,7 +12280,7 @@ fn template_hash_matches_canonical_rust_and_sil_vectors() { let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("templateHash should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "templateHash should match canonical vector {expected_hex}: {result:?}"); } } @@ -12025,7 +12297,7 @@ fn template_hash_binds_prefix_suffix_boundary() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("templateHash should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "templateHash should commit to the prefix/suffix boundary: {result:?}"); } @@ -12061,7 +12333,7 @@ fn executes_opcode_builtins_covenants() { let covenant_id_b = Hash::from_bytes(*b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); let (tx, entries) = build_covenant_opcode_tx(sigscript, covenant_id_a, covenant_id_b); - let result = run_bytecode_with_tx_and_covenants(compiled.bytecode, tx, entries, None); + let result = run_bytecode_with_tx_and_covenants(bytecode(&compiled), tx, entries, None); assert!(result.is_ok(), "opcode builtins covenants failed: {}", result.unwrap_err()); } @@ -12101,7 +12373,7 @@ fn executes_opcode_chainblock_seq_commit() { let block = Hash::from_bytes(*b"0123456789abcdef0123456789abcdef"); let commitment = Hash::from_bytes(*b"fedcba9876543210fedcba9876543210"); let accessor = MockSeqCommitAccessor { block, commitment }; - let result = run_bytecode_with_tx_and_covenants(compiled.bytecode, tx, entries, Some(&accessor)); + let result = run_bytecode_with_tx_and_covenants(bytecode(&compiled), tx, entries, Some(&accessor)); assert!(result.is_ok(), "chainblock seq commit failed: {}", result.unwrap_err()); } @@ -12149,8 +12421,8 @@ fn compiles_if_else_and_verifies() { let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert_eq!(bytecode(&compiled), expected); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -12186,8 +12458,8 @@ fn compiles_require_age_daa_to_csv_and_verifies() { .drain(); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert!(run_bytecode_with_tx(compiled.bytecode, dispatch_tag, 0, 20).is_ok()); + assert_eq!(bytecode(&compiled), expected); + assert!(run_bytecode_with_tx(bytecode(&compiled), dispatch_tag, 0, 20).is_ok()); } #[test] @@ -12222,8 +12494,8 @@ fn compiles_require_tx_daa_to_bounded_cltv_and_verifies() { .drain(); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert!(run_bytecode_with_tx(compiled.bytecode, dispatch_tag, 10, 0).is_ok()); + assert_eq!(bytecode(&compiled), expected); + assert!(run_bytecode_with_tx(bytecode(&compiled), dispatch_tag, 10, 0).is_ok()); } #[test] @@ -12259,8 +12531,8 @@ fn compiles_require_tx_time_to_lower_bounded_cltv_and_verifies() { .drain(); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert!(run_bytecode_with_tx(compiled.bytecode, dispatch_tag, threshold as u64, 0).is_ok()); + assert_eq!(bytecode(&compiled), expected); + assert!(run_bytecode_with_tx(bytecode(&compiled), dispatch_tag, threshold as u64, 0).is_ok()); } #[test] @@ -12278,10 +12550,13 @@ fn signed_arithmetic_and_comparisons_match_rust_for_small_values() { if matches!(operator, "/" | "%") && b == 0 { continue; } - let sigscript = compiled - .build_sig_script("main", vec![Expr::int(a), Expr::int(b), Expr::int(oracle(a, b))]) - .expect("arithmetic signature script builds"); - run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript) + let sigscript = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Int(a), ArtifactValue::Int(b), ArtifactValue::Int(oracle(a, b))], + ) + .expect("arithmetic signature script builds"); + run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript) .unwrap_or_else(|error| panic!("operator={operator} a={a} b={b}: {error:?}")); } } @@ -12299,9 +12574,9 @@ fn signed_arithmetic_and_comparisons_match_rust_for_small_values() { let expected = oracle(-7, 3); let source = format!("contract C() {{ entry main(int a, int b) {{ require((a {operator} b) == {expected}); }} }}"); let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("comparison contract compiles"); - let sigscript = - compiled.build_sig_script("main", vec![Expr::int(-7), Expr::int(3)]).expect("comparison signature script builds"); - run_bytecode_with_sigscript(compiled.bytecode, sigscript).expect("comparison agrees with Rust"); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(-7), ArtifactValue::Int(3)]) + .expect("comparison signature script builds"); + run_bytecode_with_sigscript(bytecode(&compiled), sigscript).expect("comparison agrees with Rust"); } } @@ -12330,7 +12605,7 @@ fn boolean_operators_use_vm_truthiness_for_noncanonical_values() { .add_data(&dispatch_tag_for(&compiled, "main")) .unwrap() .drain(); - run_bytecode_with_sigscript(compiled.bytecode, sigscript).unwrap_or_else(|error| { + run_bytecode_with_sigscript(bytecode(&compiled), sigscript).unwrap_or_else(|error| { panic!("operator={operator} left={left:?} right={right:?} expected={expected}: {error:?}") }); } @@ -12348,19 +12623,19 @@ fn relative_age_rejects_out_of_range_static_and_dynamic_values() { let largest_in_range = "contract C() { entry main() { require(this.ageDaa >= 4294967295); } }"; let compiled = compile_contract(largest_in_range, &[], CompileOptions::default()).expect("2^32 - 1 remains valid"); - let sigscript = compiled.build_sig_script("main", vec![]).expect("signature script builds"); - run_bytecode_with_sigscript_and_time(compiled.bytecode, sigscript, 0, 0) + let sigscript = encode_single_entry_sig_script(&compiled, &[]).expect("signature script builds"); + run_bytecode_with_sigscript_and_time(bytecode(&compiled), sigscript, 0, 0) .expect_err("the largest 32-bit requirement must reject sequence zero"); let dynamic_source = "contract C() { entry main(int age) { require(this.ageDaa >= age); } }"; let compiled = compile_contract(dynamic_source, &[], CompileOptions::default()).expect("dynamic age contract compiles"); for invalid in [-1, 1_i64 << 32] { - let sigscript = compiled.build_sig_script("main", vec![Expr::int(invalid)]).expect("signature script builds"); - run_bytecode_with_sigscript_and_time(compiled.bytecode.clone(), sigscript, 0, 0) + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(invalid)]).expect("signature script builds"); + run_bytecode_with_sigscript_and_time(bytecode(&compiled).clone(), sigscript, 0, 0) .expect_err("out-of-range runtime age must be rejected before CSV"); } - let sigscript = compiled.build_sig_script("main", vec![Expr::int(0)]).expect("signature script builds"); - run_bytecode_with_sigscript_and_time(compiled.bytecode, sigscript, 0, 0).expect("zero age must remain valid"); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(0)]).expect("signature script builds"); + run_bytecode_with_sigscript_and_time(bytecode(&compiled), sigscript, 0, 0).expect("zero age must remain valid"); } #[test] @@ -12387,21 +12662,23 @@ fn absolute_daa_and_time_locks_enforce_consensus_domains() { compile_contract("contract C() { entry main(int value) { require(tx.daa >= value); } }", &[], CompileOptions::default()) .expect("dynamic DAA contract compiles"); for invalid in [-1, threshold] { - let sigscript = dynamic_daa.build_sig_script("main", vec![Expr::int(invalid)]).expect("DAA sigscript builds"); - run_bytecode_with_sigscript_and_time(dynamic_daa.bytecode.clone(), sigscript, threshold as u64 - 1, 0) + let sigscript = encode_entry_sig_script(&dynamic_daa, "main", &[ArtifactValue::Int(invalid)]).expect("DAA sigscript builds"); + run_bytecode_with_sigscript_and_time(bytecode(&dynamic_daa).clone(), sigscript, threshold as u64 - 1, 0) .expect_err("runtime DAA domain guard must reject the value"); } - let sigscript = dynamic_daa.build_sig_script("main", vec![Expr::int(42)]).expect("DAA sigscript builds"); - run_bytecode_with_sigscript_and_time(dynamic_daa.bytecode, sigscript, 42, 0).expect("valid DAA lock must satisfy CLTV"); + let sigscript = encode_entry_sig_script(&dynamic_daa, "main", &[ArtifactValue::Int(42)]).expect("DAA sigscript builds"); + run_bytecode_with_sigscript_and_time(bytecode(&dynamic_daa), sigscript, 42, 0).expect("valid DAA lock must satisfy CLTV"); let dynamic_time = compile_contract("contract C() { entry main(temporal value) { require(tx.time >= value); } }", &[], CompileOptions::default()) .expect("dynamic time contract compiles"); - let invalid_sigscript = dynamic_time.build_sig_script("main", vec![Expr::temporal(threshold - 1)]).expect("time sigscript builds"); - run_bytecode_with_sigscript_and_time(dynamic_time.bytecode.clone(), invalid_sigscript, threshold as u64, 0) + let invalid_sigscript = + encode_entry_sig_script(&dynamic_time, "main", &[ArtifactValue::Int(threshold - 1)]).expect("time sigscript builds"); + run_bytecode_with_sigscript_and_time(bytecode(&dynamic_time).clone(), invalid_sigscript, threshold as u64, 0) .expect_err("runtime timestamp domain guard must reject a DAA-domain value"); - let valid_sigscript = dynamic_time.build_sig_script("main", vec![Expr::temporal(threshold)]).expect("time sigscript builds"); - run_bytecode_with_sigscript_and_time(dynamic_time.bytecode, valid_sigscript, threshold as u64, 0) + let valid_sigscript = + encode_entry_sig_script(&dynamic_time, "main", &[ArtifactValue::Int(threshold)]).expect("time sigscript builds"); + run_bytecode_with_sigscript_and_time(bytecode(&dynamic_time), valid_sigscript, threshold as u64, 0) .expect("valid timestamp lock must satisfy CLTV"); } @@ -12432,8 +12709,8 @@ fn temporal_literals_use_milliseconds_and_are_separate_from_daa_age() { let unix_milliseconds = 1_893_456_000_000; assert!(unix_milliseconds >= 500_000_000_000, "the consensus threshold must classify this as a timestamp"); let compiled = compile_contract(date_source, &[], CompileOptions::default()).expect("date contract compiles"); - let sigscript = compiled.build_sig_script("main", vec![]).expect("signature script builds"); - run_bytecode_with_sigscript_and_time(compiled.bytecode, sigscript, unix_milliseconds, 0) + let sigscript = encode_single_entry_sig_script(&compiled, &[]).expect("signature script builds"); + run_bytecode_with_sigscript_and_time(bytecode(&compiled), sigscript, unix_milliseconds, 0) .expect("the millisecond timestamp must satisfy CLTV"); } @@ -12549,8 +12826,8 @@ fn compiles_reused_variables_and_verifies() { let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert_eq!(bytecode(&compiled), expected); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -12593,7 +12870,7 @@ fn return_reused_local_is_stored_once_and_reused() { .drain(); let expected = wrap_with_single_dispatch(&compiled, expected); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -12614,7 +12891,7 @@ fn compiles_sigscript_inputs_and_verifies() { builder.add_data(&dispatch_tag).unwrap(); let sigscript = builder.drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "sigscript test failed: {}", result.unwrap_err()); } @@ -12645,10 +12922,10 @@ fn compiles_bytecode_size_and_runs_sum_array() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let expected_size = compiled.bytecode.len() as i64; - let sigscript = compiled.build_sig_script("main", vec![Expr::int(expected_size)]).expect("sigscript builds"); + let expected_size = bytecode(&compiled).len() as i64; + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(expected_size)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "script size contract failed: {}", result.unwrap_err()); } @@ -12672,10 +12949,10 @@ fn compiles_bytecode_size_data_prefix_small_script() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let expected_prefix = data_prefix_for_size(compiled.bytecode.len()); - let sigscript = compiled.build_sig_script("main", vec![Expr::dynamic_bytes(expected_prefix)]).expect("sigscript builds"); + let expected_prefix = data_prefix_for_size(bytecode(&compiled).len()); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bytes(expected_prefix)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "bytecodeSizeDataPrefix small failed: {}", result.unwrap_err()); } @@ -12693,10 +12970,10 @@ fn compiles_bytecode_size_data_prefix_medium_script() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let expected_prefix = data_prefix_for_size(compiled.bytecode.len()); - let sigscript = compiled.build_sig_script("main", vec![Expr::dynamic_bytes(expected_prefix)]).expect("sigscript builds"); + let expected_prefix = data_prefix_for_size(bytecode(&compiled).len()); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bytes(expected_prefix)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "bytecodeSizeDataPrefix medium failed: {}", result.unwrap_err()); } @@ -12714,10 +12991,10 @@ fn compiles_bytecode_size_data_prefix_large_script() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let expected_prefix = data_prefix_for_size(compiled.bytecode.len()); - let sigscript = compiled.build_sig_script("main", vec![Expr::dynamic_bytes(expected_prefix)]).expect("sigscript builds"); + let expected_prefix = data_prefix_for_size(bytecode(&compiled).len()); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bytes(expected_prefix)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "bytecodeSizeDataPrefix large failed: {}", result.unwrap_err()); } @@ -12738,7 +13015,7 @@ fn compiles_sigscript_reused_inputs_and_verifies() { builder.add_data(&dispatch_tag).unwrap(); let sigscript = builder.drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "sigscript reuse test failed: {}", result.unwrap_err()); } @@ -12760,7 +13037,7 @@ fn compiles_sigscript_inputs_and_fails_on_wrong_sum() { builder.add_data(&dispatch_tag).unwrap(); let sigscript = builder.drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_err()); } @@ -12781,7 +13058,7 @@ fn compiles_sigscript_reused_inputs_and_fails_on_wrong_value() { builder.add_data(&dispatch_tag).unwrap(); let sigscript = builder.drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_err()); } @@ -12810,13 +13087,13 @@ fn entrypoints_validate_fixed_array_argument_sizes_at_runtime() { builder.drain() }; - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript("bytes", &[1, 2, 3])).is_ok()); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript("bytes", &[1, 2])).is_err()); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript("bytes", &[1, 2, 3, 4])).is_err()); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript("ints", &[0; 16])).is_ok()); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript("ints", &[0; 8])).is_err()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript("bytes", &[1, 2, 3])).is_ok()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript("bytes", &[1, 2])).is_err()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript("bytes", &[1, 2, 3, 4])).is_err()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript("ints", &[0; 16])).is_ok()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript("ints", &[0; 8])).is_err()); - let asm = script_to_str(&compiled.bytecode).expect("stringifies"); + let asm = script_to_str(&bytecode(&compiled)).expect("stringifies"); assert_eq!(asm.matches("OpSize").count(), 2, "each entrypoint should validate its fixed-array argument: {asm}"); } @@ -12857,27 +13134,28 @@ fn entrypoints_validate_fixed_width_scalar_argument_sizes_at_runtime() { .drain(); let dispatch_tag = dispatch_tag_for(&compiled, "main"); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected, "unexpected ABI validation bytecode for {type_name}"); + assert_eq!(bytecode(&compiled), expected, "unexpected ABI validation bytecode for {type_name}"); let sigscript = |size: usize| script_builder().add_data_with_push_opcode(&vec![1; size]).unwrap().add_data(&dispatch_tag).unwrap().drain(); assert!( - run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript(expected_size)).is_ok(), + run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript(expected_size)).is_ok(), "{type_name} should accept exactly {expected_size} bytes" ); if type_name == "byte" { - let zero_sigscript = compiled.build_sig_script("main", vec![Expr::byte(0)]).expect("zero byte sigscript builds"); + let zero_sigscript = + encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Byte(0)]).expect("zero byte sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode.clone(), zero_sigscript).is_ok(), + run_bytecode_with_sigscript(bytecode(&compiled).clone(), zero_sigscript).is_ok(), "the typed builder must preserve byte(0) as a one-byte stack item" ); } assert!( - run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript(expected_size - 1)).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript(expected_size - 1)).is_err(), "{type_name} should reject a short value" ); assert!( - run_bytecode_with_sigscript(compiled.bytecode, sigscript(expected_size + 1)).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled), sigscript(expected_size + 1)).is_err(), "{type_name} should reject a long value" ); } @@ -12917,13 +13195,13 @@ fn entrypoint_int_argument_accepts_below_nine_bytes_and_rejects_nine() { .drain(); let dispatch_tag = dispatch_tag_for(&compiled, "main"); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); let sigscript = |size: usize| script_builder().add_data_with_push_opcode(&vec![1; size]).unwrap().add_data(&dispatch_tag).unwrap().drain(); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript(0)).is_ok()); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript(8)).is_ok()); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript(9)).is_err()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript(0)).is_ok()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript(8)).is_ok()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled), sigscript(9)).is_err()); } #[test] @@ -12960,14 +13238,14 @@ fn entrypoint_bool_argument_accepts_at_most_one_byte() { .drain(); let dispatch_tag = dispatch_tag_for(&compiled, "main"); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); let sigscript = |size: usize| script_builder().add_data_with_push_opcode(&vec![1; size]).unwrap().add_data(&dispatch_tag).unwrap().drain(); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript(0)).is_ok()); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript(1)).is_ok()); - assert!(run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript(2)).is_err()); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript(9)).is_err()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript(0)).is_ok()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript(1)).is_ok()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript(2)).is_err()); + assert!(run_bytecode_with_sigscript(bytecode(&compiled), sigscript(9)).is_err()); } #[test] @@ -12982,7 +13260,7 @@ fn compile_time_length_for_fixed_size_int_array() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let asm = script_to_str(&compiled.bytecode).expect("stringifies"); + let asm = script_to_str(&bytecode(&compiled)).expect("stringifies"); assert!(!asm.contains("OpSize"), "fixed-size array length should be compile-time, got asm: {asm}"); assert!(asm.contains("Op5 Op5 OpNumEqual OpVerify"), "expected compile-time length comparison, got asm: {asm}"); } @@ -12999,7 +13277,7 @@ fn compile_time_length_for_fixed_size_byte_array() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let asm = script_to_str(&compiled.bytecode).expect("stringifies"); + let asm = script_to_str(&bytecode(&compiled)).expect("stringifies"); assert!(!asm.contains("OpSize"), "fixed-size byte-array length should be compile-time, got asm: {asm}"); assert!(asm.contains("Op3 Op3 OpNumEqual OpVerify"), "expected compile-time length comparison, got asm: {asm}"); } @@ -13018,7 +13296,7 @@ fn compile_time_length_for_inferred_array_sizes() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let asm = script_to_str(&compiled.bytecode).expect("stringifies"); + let asm = script_to_str(&bytecode(&compiled)).expect("stringifies"); assert!(!asm.contains("OpSize"), "inferred fixed-array lengths should be compile-time, got asm: {asm}"); assert!(asm.contains("Op4 Op4 OpNumEqual OpVerify"), "expected byte-array compile-time length, got asm: {asm}"); assert!(asm.contains("Op3 Op3 OpNumEqual OpVerify"), "expected int-array compile-time length, got asm: {asm}"); @@ -13136,7 +13414,7 @@ fn accepts_well_typed_constant_dependencies_on_constructor_params_and_constants( let compiled = compile_contract(source, &[3.into()], CompileOptions::default()).expect("well-typed constant dependencies should compile"); let sigscript = dispatch_tag_sigscript(dispatch_tag_for(&compiled, "main")); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "constant dependencies should retain their runtime value: {result:?}"); } @@ -13207,7 +13485,7 @@ fn compile_time_length_with_constant_size() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let asm = script_to_str(&compiled.bytecode).expect("stringifies"); + let asm = script_to_str(&bytecode(&compiled)).expect("stringifies"); assert!(!asm.contains("OpSize"), "constant-sized array length should be compile-time, got asm: {asm}"); assert!(asm.contains("Op5 Op5 OpNumEqual OpVerify"), "expected compile-time length comparison, got asm: {asm}"); } @@ -13306,12 +13584,12 @@ fn bool_as_int_normalizes_vm_truthiness() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("bool as int compiles"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); assert_eq!(opcodes.matches("Op0NotEqual").count(), 1, "bool as int must normalize VM truthiness exactly once: {opcodes}"); let raw_truthy_arg = script_builder().add_data_with_push_opcode(&[2]).unwrap().add_data(&dispatch_tag_for(&compiled, "main")).unwrap().drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, raw_truthy_arg); + let result = run_bytecode_with_sigscript(bytecode(&compiled), raw_truthy_arg); assert!(result.is_ok(), "truthy 0x02 must normalize to integer 1: {result:?}"); } @@ -13351,9 +13629,9 @@ fn int_as_fixed_bytes_has_a_fixed_result_type_and_uses_num2bin() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("fixed-size integer conversions compile"); - assert_eq!(compiled.bytecode.iter().filter(|&&op| op == OpNum2Bin).count(), 4); + assert_eq!(bytecode(&compiled).iter().filter(|&&op| op == OpNum2Bin).count(), 4); let sigscript = script_builder().add_i64(42).unwrap().add_data(&dispatch_tag_for(&compiled, "test")).unwrap().drain(); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript).is_ok(), "fixed-size integer conversions should execute"); + assert!(run_bytecode_with_sigscript(bytecode(&compiled), sigscript).is_ok(), "fixed-size integer conversions should execute"); } #[test] @@ -13368,11 +13646,11 @@ fn int_as_byte_uses_num2bin_and_executes() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("int as byte compiles"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); assert_eq!(opcodes.matches("OpNum2Bin").count(), 1, "int as byte must emit one OpNum2Bin: {opcodes}"); - let sigscript = compiled.build_sig_script("test", vec![Expr::int(42)]).expect("int argument encodes"); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript).is_ok(), "one-byte numeric conversion should execute"); + let sigscript = encode_entry_sig_script(&compiled, "test", &[ArtifactValue::Int(42)]).expect("int argument encodes"); + assert!(run_bytecode_with_sigscript(bytecode(&compiled), sigscript).is_ok(), "one-byte numeric conversion should execute"); } #[test] @@ -13387,8 +13665,8 @@ fn int_as_byte_fails_at_runtime_when_value_does_not_fit() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("int as byte compiles"); - let sigscript = compiled.build_sig_script("test", vec![Expr::int(128)]).expect("int argument encodes"); - let err = run_bytecode_with_sigscript(compiled.bytecode, sigscript).expect_err("128 needs two script-number bytes"); + let sigscript = encode_entry_sig_script(&compiled, "test", &[ArtifactValue::Int(128)]).expect("int argument encodes"); + let err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript).expect_err("128 needs two script-number bytes"); assert_eq!( err, kaspa_txscript_errors::TxScriptError::Serialization(kaspa_txscript_errors::SerializationError::NumberTooLong(128, 1)) @@ -13459,7 +13737,7 @@ fn blake2b_builtins_require_dynamic_byte_array_arguments() { } "#; let compiled = compile_contract(valid_source, &[], CompileOptions::default()).expect("byte[] arguments should compile"); - let asm = script_to_str(&compiled.bytecode).expect("Blake2b script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("Blake2b script should stringify"); assert!(asm.contains("OpBlake2b"), "expected OpBlake2b in generated script: {asm}"); assert!(asm.contains("OpBlake2bWithKey"), "expected OpBlake2bWithKey in generated script: {asm}"); } @@ -13517,10 +13795,10 @@ fn fixed_byte_array_up_to_eight_bytes_casts_to_int_without_extra_opcodes() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte[2] to int cast compiles"); - assert!(!compiled.bytecode.contains(&OpBin2Num), "int(data) must not emit OpBin2Num"); + assert!(!bytecode(&compiled).contains(&OpBin2Num), "int(data) must not emit OpBin2Num"); - let sigscript = compiled.build_sig_script("test", vec![vec![42u8, 0].into()]).expect("sigscript builds"); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript).is_ok(), "int(byte[2]) should produce a usable integer value"); + let sigscript = encode_entry_sig_script(&compiled, "test", &[vec![42u8, 0].into()]).expect("sigscript builds"); + assert!(run_bytecode_with_sigscript(bytecode(&compiled), sigscript).is_ok(), "int(byte[2]) should produce a usable integer value"); } #[test] @@ -13608,7 +13886,7 @@ fn scalar_byte_cast_cannot_escape_validate_output_state_push() { } "#; - compile_contract(source, &[Expr::byte(0)], CompileOptions::default()) + compile_contract(source, &[ArtifactValue::Byte(0)], CompileOptions::default()) .expect_err("a string must not cast to a scalar byte and escape the generated one-byte state push"); } @@ -13649,12 +13927,12 @@ fn signed_byte_cast_is_a_passthrough_with_signed_numeric_semantics() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("signed(byte) compiles"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); assert!(!opcodes.contains("OpCat"), "signed(byte) must be a passthrough: {opcodes}"); assert!(!opcodes.contains("OpBin2Num"), "signed(byte) must not normalize its operand: {opcodes}"); - let sigscript = compiled.build_sig_script("test", vec![Expr::byte(255)]).expect("byte argument encodes"); - assert!(run_bytecode_with_sigscript(compiled.bytecode, sigscript).is_ok(), "0xff must have signed value -127"); + let sigscript = encode_entry_sig_script(&compiled, "test", &[ArtifactValue::Byte(255)]).expect("byte argument encodes"); + assert!(run_bytecode_with_sigscript(bytecode(&compiled), sigscript).is_ok(), "0xff must have signed value -127"); } #[test] @@ -13670,11 +13948,11 @@ fn unsigned_byte_cast_appends_zero_and_preserves_255() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("unsigned(byte) compiles"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode stringifies"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode stringifies"); assert_eq!(opcodes.matches("OpCat").count(), 1, "unsigned(byte) must append one zero byte: {opcodes}"); assert!(!opcodes.contains("OpBin2Num"), "unsigned(byte) must use concatenation rather than normalization: {opcodes}"); let dispatch_tag = dispatch_tag_for(&compiled, "test"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok(), "unsigned(0xff) must equal 255"); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok(), "unsigned(0xff) must equal 255"); } #[test] @@ -13703,7 +13981,7 @@ fn empty_array_statement_expr_evaluation_compiles_to_empty_array_data() { .drain(); let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); + assert_eq!(bytecode(&compiled), expected); } #[test] @@ -13721,14 +13999,14 @@ fn function_param_shadows_constructor_constant_with_same_name() { "#; // Constructor fee=2, param fee=3 => local = 3+1 = 4 => pass - let compiled = compile_contract(source, &[Expr::int(2)], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![Expr::int(3)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let compiled = compile_contract(source, &[ArtifactValue::Int(2)], CompileOptions::default()).expect("compile succeeds"); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(3)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!(result.is_ok(), "function param should shadow constructor constant: {}", result.unwrap_err()); // Constructor fee=2, param fee=2 => local = 2+1 = 3 != 4 => fail (proves it's not always the constant) - let sigscript_wrong = compiled.build_sig_script("main", vec![Expr::int(2)]).expect("sigscript builds"); - let result_wrong = run_bytecode_with_sigscript(compiled.bytecode, sigscript_wrong); + let sigscript_wrong = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(2)]).expect("sigscript builds"); + let result_wrong = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_wrong); assert!(result_wrong.is_err(), "require(3==4) should fail, proving the param value matters"); } @@ -13756,7 +14034,7 @@ fn allows_same_variable_name_in_different_functions() { let compiled = compile_contract(source, &[], CompileOptions::default()) .expect("separate functions should have independent variable namespaces"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -13794,7 +14072,7 @@ fn allows_same_variable_name_in_non_overlapping_sibling_scopes() { let compiled = compile_contract(source, &[], CompileOptions::default()) .expect("non-overlapping sibling scopes should have independent variable namespaces"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - assert!(run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).is_ok()); + assert!(run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).is_ok()); } #[test] @@ -13833,7 +14111,7 @@ fn rejects_contract_constant_that_shadows_constructor_parameter_used_as_array_si } "#; - let err = compile_contract(source, &[Expr::int(4)], CompileOptions::default()) + let err = compile_contract(source, &[ArtifactValue::Int(4)], CompileOptions::default()) .expect_err("a contract constant must not shadow a constructor parameter"); assert!(err.to_string().contains("variable 'A' is already defined"), "unexpected error: {err}"); let span = err.span().expect("the conflicting constant should be identified"); @@ -14047,16 +14325,19 @@ fn ternary_expression_executes_selected_branch() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("ternary contract should compile"); - let sigscript_then = compiled.build_sig_script("main", vec![Expr::int(1), Expr::int(7)]).expect("sigscript builds"); - let result_then = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_then); + let sigscript_then = + encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(1), ArtifactValue::Int(7)]).expect("sigscript builds"); + let result_then = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_then); assert!(result_then.is_ok(), "then branch should execute successfully: {}", result_then.unwrap_err()); - let sigscript_else = compiled.build_sig_script("main", vec![Expr::int(0), Expr::int(11)]).expect("sigscript builds"); - let result_else = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_else); + let sigscript_else = + encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(0), ArtifactValue::Int(11)]).expect("sigscript builds"); + let result_else = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_else); assert!(result_else.is_ok(), "else branch should execute successfully: {}", result_else.unwrap_err()); - let sigscript_wrong = compiled.build_sig_script("main", vec![Expr::int(0), Expr::int(7)]).expect("sigscript builds"); - let result_wrong = run_bytecode_with_sigscript(compiled.bytecode, sigscript_wrong); + let sigscript_wrong = + encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(0), ArtifactValue::Int(7)]).expect("sigscript builds"); + let result_wrong = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_wrong); assert!(result_wrong.is_err(), "else branch should not produce the then value"); } @@ -14079,7 +14360,7 @@ fn ternary_expression_does_not_execute_unselected_branch() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("ternary contract should compile"); - let asm = script_to_str(&compiled.bytecode).expect("ternary script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("ternary script should stringify"); let if_index = asm.find("OpIf").expect("ternary should emit OpIf"); let else_index = asm.find("OpElse").expect("ternary should emit OpElse"); let end_if_index = asm.find("OpEndIf").expect("ternary should emit OpEndIf"); @@ -14090,31 +14371,71 @@ fn ternary_expression_does_not_execute_unselected_branch() { "divisions should remain inside their respective conditional branches: {asm}" ); - let select_then = compiled - .build_sig_script("main", vec![Expr::bool(true), Expr::int(10), Expr::int(2), Expr::int(20), Expr::int(0), Expr::int(5)]) - .expect("then-branch sigscript builds"); - let then_result = run_bytecode_with_sigscript(compiled.bytecode.clone(), select_then); + let select_then = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Bool(true), + ArtifactValue::Int(10), + ArtifactValue::Int(2), + ArtifactValue::Int(20), + ArtifactValue::Int(0), + ArtifactValue::Int(5), + ], + ) + .expect("then-branch sigscript builds"); + let then_result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), select_then); assert!(then_result.is_ok(), "zero divisor in the unselected else branch must not execute: {}", then_result.unwrap_err()); - let select_else = compiled - .build_sig_script("main", vec![Expr::bool(false), Expr::int(10), Expr::int(0), Expr::int(20), Expr::int(4), Expr::int(5)]) - .expect("else-branch sigscript builds"); - let else_result = run_bytecode_with_sigscript(compiled.bytecode.clone(), select_else); + let select_else = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Bool(false), + ArtifactValue::Int(10), + ArtifactValue::Int(0), + ArtifactValue::Int(20), + ArtifactValue::Int(4), + ArtifactValue::Int(5), + ], + ) + .expect("else-branch sigscript builds"); + let else_result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), select_else); assert!(else_result.is_ok(), "zero divisor in the unselected then branch must not execute: {}", else_result.unwrap_err()); - let failing_then = compiled - .build_sig_script("main", vec![Expr::bool(true), Expr::int(10), Expr::int(0), Expr::int(20), Expr::int(4), Expr::int(5)]) - .expect("failing then-branch sigscript builds"); + let failing_then = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Bool(true), + ArtifactValue::Int(10), + ArtifactValue::Int(0), + ArtifactValue::Int(20), + ArtifactValue::Int(4), + ArtifactValue::Int(5), + ], + ) + .expect("failing then-branch sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode.clone(), failing_then).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled).clone(), failing_then).is_err(), "zero divisor in the selected then branch should execute and fail" ); - let failing_else = compiled - .build_sig_script("main", vec![Expr::bool(false), Expr::int(10), Expr::int(2), Expr::int(20), Expr::int(0), Expr::int(5)]) - .expect("failing else-branch sigscript builds"); + let failing_else = encode_entry_sig_script( + &compiled, + "main", + &[ + ArtifactValue::Bool(false), + ArtifactValue::Int(10), + ArtifactValue::Int(2), + ArtifactValue::Int(20), + ArtifactValue::Int(0), + ArtifactValue::Int(5), + ], + ) + .expect("failing else-branch sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode, failing_else).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled), failing_else).is_err(), "zero divisor in the selected else branch should execute and fail" ); } @@ -14137,8 +14458,9 @@ fn ternary_does_not_read_input_state_in_unselected_then_branch() { } "#; - let compiled = compile_contract(source, &[Expr::int(7)], CompileOptions::default()).expect("ternary contract should compile"); - let asm = script_to_str(&compiled.bytecode).expect("ternary script should stringify"); + let compiled = + compile_contract(source, &[ArtifactValue::Int(7)], CompileOptions::default()).expect("ternary contract should compile"); + let asm = script_to_str(&bytecode(&compiled)).expect("ternary script should stringify"); let if_index = asm.find("OpIf").expect("ternary should emit OpIf"); let read_index = asm.find("OpTxInputScriptSigLen").expect("selected branch should contain the input-state read"); let else_index = asm.find("OpElse").expect("ternary should emit OpElse"); @@ -14147,13 +14469,13 @@ fn ternary_does_not_read_input_state_in_unselected_then_branch() { "input-state access should remain inside the ternary's then branch: {asm}" ); - let select_local = compiled.build_sig_script("main", vec![Expr::bool(false)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), select_local); + let select_local = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bool(false)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), select_local); assert!(result.is_ok(), "input 9 in the unselected branch must not be read: {}", result.unwrap_err()); - let select_remote = compiled.build_sig_script("main", vec![Expr::bool(true)]).expect("sigscript builds"); + let select_remote = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bool(true)]).expect("sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode, select_remote).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled), select_remote).is_err(), "input 9 in the selected branch should be read and fail" ); } @@ -14175,7 +14497,7 @@ fn ternary_expression_does_not_execute_function_call_in_unselected_else_branch() "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("ternary contract should compile"); - let asm = script_to_str(&compiled.bytecode).expect("ternary script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("ternary script should stringify"); let if_index = asm.find("OpIf").expect("ternary should emit OpIf"); let else_index = asm.find("OpElse").expect("ternary should emit OpElse"); let fail_index = asm.find("OpFalse OpVerify").expect("else-branch helper should emit require(false)"); @@ -14185,17 +14507,23 @@ fn ternary_expression_does_not_execute_function_call_in_unselected_else_branch() "require(false) should remain inside the ternary's else branch: {asm}" ); - let select_then = compiled - .build_sig_script("main", vec![Expr::bool(true), Expr::int(7), Expr::int(11), Expr::int(7)]) - .expect("then-branch sigscript builds"); - let then_result = run_bytecode_with_sigscript(compiled.bytecode.clone(), select_then); + let select_then = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(true), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(7)], + ) + .expect("then-branch sigscript builds"); + let then_result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), select_then); assert!(then_result.is_ok(), "require(false) in the unselected else-branch call must not execute: {}", then_result.unwrap_err()); - let select_else = compiled - .build_sig_script("main", vec![Expr::bool(false), Expr::int(7), Expr::int(11), Expr::int(11)]) - .expect("else-branch sigscript builds"); + let select_else = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(false), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(11)], + ) + .expect("else-branch sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode, select_else).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled), select_else).is_err(), "require(false) in the selected else-branch call should execute and fail" ); } @@ -14217,7 +14545,7 @@ fn ternary_expression_does_not_execute_function_call_in_unselected_then_branch() "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("ternary contract should compile"); - let asm = script_to_str(&compiled.bytecode).expect("ternary script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("ternary script should stringify"); let if_index = asm.find("OpIf").expect("ternary should emit OpIf"); let fail_index = asm.find("OpFalse OpVerify").expect("then-branch helper should emit require(false)"); let else_index = asm.find("OpElse").expect("ternary should emit OpElse"); @@ -14227,17 +14555,23 @@ fn ternary_expression_does_not_execute_function_call_in_unselected_then_branch() "require(false) should remain inside the ternary's then branch: {asm}" ); - let select_else = compiled - .build_sig_script("main", vec![Expr::bool(false), Expr::int(7), Expr::int(11), Expr::int(11)]) - .expect("else-branch sigscript builds"); - let else_result = run_bytecode_with_sigscript(compiled.bytecode.clone(), select_else); + let select_else = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(false), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(11)], + ) + .expect("else-branch sigscript builds"); + let else_result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), select_else); assert!(else_result.is_ok(), "require(false) in the unselected then-branch call must not execute: {}", else_result.unwrap_err()); - let select_then = compiled - .build_sig_script("main", vec![Expr::bool(true), Expr::int(7), Expr::int(11), Expr::int(7)]) - .expect("then-branch sigscript builds"); + let select_then = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(true), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(7)], + ) + .expect("then-branch sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode, select_then).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled), select_then).is_err(), "require(false) in the selected then-branch call should execute and fail" ); } @@ -14260,21 +14594,27 @@ fn nested_ternary_function_call_remains_in_selected_branch() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("nested ternary contract should compile"); - let select_then = compiled - .build_sig_script("main", vec![Expr::bool(true), Expr::int(7), Expr::int(11), Expr::int(8)]) - .expect("then-branch sigscript builds"); - let then_result = run_bytecode_with_sigscript(compiled.bytecode.clone(), select_then); + let select_then = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(true), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(8)], + ) + .expect("then-branch sigscript builds"); + let then_result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), select_then); assert!( then_result.is_ok(), "require(false) in a nested unselected else-branch call must not execute: {}", then_result.unwrap_err() ); - let select_else = compiled - .build_sig_script("main", vec![Expr::bool(false), Expr::int(7), Expr::int(11), Expr::int(12)]) - .expect("else-branch sigscript builds"); + let select_else = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(false), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(12)], + ) + .expect("else-branch sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode, select_else).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled), select_else).is_err(), "require(false) in a nested selected else-branch call should execute and fail" ); } @@ -14317,7 +14657,13 @@ fn ternary_lowering_initializes_generated_results_for_supported_types() { compile_contract( source, - &[Expr::int(2), Expr::bytes(vec![1, 2]), Expr::bytes(vec![3; 32]), Expr::bytes(vec![4; 65]), Expr::bytes(vec![5; 64])], + &[ + ArtifactValue::Int(2), + ArtifactValue::Bytes(vec![1, 2]), + ArtifactValue::Bytes(vec![3; 32]), + ArtifactValue::Bytes(vec![4; 65]), + ArtifactValue::Bytes(vec![5; 64]), + ], CompileOptions::default(), ) .expect("ternary defaults should compile for every supported value type"); @@ -14345,7 +14691,7 @@ fn if_else_does_not_execute_function_call_in_unselected_else_branch() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("if/else contract should compile"); - let asm = script_to_str(&compiled.bytecode).expect("if/else script should stringify"); + let asm = script_to_str(&bytecode(&compiled)).expect("if/else script should stringify"); let if_index = asm.find("OpIf").expect("if/else should emit OpIf"); let else_index = asm.find("OpElse").expect("if/else should emit OpElse"); let fail_index = asm.find("OpFalse OpVerify").expect("else-branch helper should emit require(false)"); @@ -14355,17 +14701,23 @@ fn if_else_does_not_execute_function_call_in_unselected_else_branch() { "require(false) should remain inside the else branch: {asm}" ); - let select_then = compiled - .build_sig_script("main", vec![Expr::bool(true), Expr::int(7), Expr::int(11), Expr::int(7)]) - .expect("then-branch sigscript builds"); - let then_result = run_bytecode_with_sigscript(compiled.bytecode.clone(), select_then); + let select_then = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(true), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(7)], + ) + .expect("then-branch sigscript builds"); + let then_result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), select_then); assert!(then_result.is_ok(), "require(false) in the unselected else-branch call must not execute: {}", then_result.unwrap_err()); - let select_else = compiled - .build_sig_script("main", vec![Expr::bool(false), Expr::int(7), Expr::int(11), Expr::int(11)]) - .expect("else-branch sigscript builds"); + let select_else = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Bool(false), ArtifactValue::Int(7), ArtifactValue::Int(11), ArtifactValue::Int(11)], + ) + .expect("else-branch sigscript builds"); assert!( - run_bytecode_with_sigscript(compiled.bytecode, select_else).is_err(), + run_bytecode_with_sigscript(bytecode(&compiled), select_else).is_err(), "require(false) in the selected else-branch call should execute and fail" ); } @@ -14448,8 +14800,8 @@ fn nested_inline_calls_with_args_compile_and_execute() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("nested inline calls should compile"); - let sigscript = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "nested inline calls should execute correctly: {}", result.unwrap_err()); } @@ -14472,17 +14824,17 @@ fn inline_local_binding_is_stored_once_and_reused() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("inline helper should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpAdd).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpAdd).count(), 1, "x + 1 should be computed once and stored for both require statements" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "stored inline local should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(10)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(10)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "stored inline local should still enforce the second require"); } @@ -14504,17 +14856,17 @@ fn inline_function_argument_expression_is_stored_once_and_reused() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("inline call should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpAdd).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpAdd).count(), 1, "x + 1 should be computed once and reused for both require statements in the inline callee" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "stored inline argument should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(10)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(10)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "stored inline argument should still enforce the second require"); } @@ -14574,18 +14926,18 @@ fn inline_argument_alias_reuses_existing_local_without_extra_snapshot() { let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpDup).count(), 3); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpOver).count(), 1); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpPick).count(), 0); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpMul).count(), 1); + assert_eq!(bytecode(&compiled), expected); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpDup).count(), 3); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpOver).count(), 1); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpPick).count(), 0); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpMul).count(), 1); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(2)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(2)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "reused local should satisfy both inline requires: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(4)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(4)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "reused local should still fail the second inline require"); } @@ -14636,18 +14988,18 @@ fn inline_argument_alias_snapshots_entrypoint_param_once_per_inlined_call() { let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpDup).count(), 2); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpOver).count(), 0); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpPick).count(), 0); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpDrop).count(), 1); + assert_eq!(bytecode(&compiled), expected); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpDup).count(), 2); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpOver).count(), 0); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpPick).count(), 0); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpDrop).count(), 1); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(2)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(2)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "entrypoint param alias should satisfy both inline requires: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(10)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(10)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "entrypoint param alias should still fail the second inline require"); } @@ -14700,18 +15052,18 @@ fn local_alias_snapshots_existing_stack_value_once() { let expected = wrap_with_single_dispatch(&compiled, body); - assert_eq!(compiled.bytecode, expected); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpMul).count(), 1); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpDup).count(), 3); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpOver).count(), 1); - assert_eq!(compiled.bytecode.iter().copied().filter(|op| *op == OpPick).count(), 0); + assert_eq!(bytecode(&compiled), expected); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpMul).count(), 1); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpDup).count(), 3); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpOver).count(), 1); + assert_eq!(bytecode(&compiled).iter().copied().filter(|op| *op == OpPick).count(), 0); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(2)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(2)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "local alias should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(1)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(1)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "local alias should still enforce the requires"); } @@ -14731,12 +15083,12 @@ fn local_alias_reassignment_from_alias_passes_for_x_5() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("local alias reassignment should compile"); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "x=5 should pass after z is incremented past y: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(1)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(1)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "x=1 should still fail the initial require(y > 1)"); } @@ -14755,17 +15107,17 @@ fn local_bool_expression_is_stored_once_and_reused() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("bool local should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpAdd).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpAdd).count(), 1, "x + 1 should be computed once for the stored bool expression" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "stored bool local should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(0)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(0)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "stored bool local should still enforce the false branch"); } @@ -14784,22 +15136,22 @@ fn local_nested_expression_is_stored_once_and_reused() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("nested local should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpAdd).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpAdd).count(), 2, "the nested local expression should compute each addition once before storing the result" ); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpMul).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpMul).count(), 1, "the nested local expression should multiply once before storing the result" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "stored nested local should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(10)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(10)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "stored nested local should still enforce the second require"); } @@ -14846,8 +15198,8 @@ fn runs_branch_local_shadowing_and_preserves_outer_scope() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("branch-local shadowing should compile"); for cond in [true, false] { - let sigscript = compiled.build_sig_script("main", vec![Expr::bool(cond)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bool(cond)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript); assert!(result.is_ok(), "branch-local shadowing should execute successfully for cond={cond}: {}", result.unwrap_err()); } } @@ -14870,7 +15222,7 @@ fn runs_for_loop_local_shadowing_and_preserves_outer_scope() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("loop-local shadowing should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "loop-local shadowing should execute successfully: {}", result.unwrap_err()); } @@ -14892,7 +15244,7 @@ fn runs_standalone_block_local_shadowing_and_preserves_outer_scope() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("block-local shadowing should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "block-local shadowing should execute successfully: {}", result.unwrap_err()); } @@ -14909,8 +15261,8 @@ fn runs_function_parameter_shadowing() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("parameter shadowing should compile"); - let sigscript = compiled.build_sig_script("main", vec![Expr::int(9)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(9)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "parameter shadowing should execute successfully: {}", result.unwrap_err()); } @@ -14933,7 +15285,7 @@ fn runs_inlined_function_parameter_shadowing() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("inlined function parameter shadowing should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "inlined function parameter shadowing should execute successfully: {}", result.unwrap_err()); } @@ -14957,7 +15309,7 @@ fn runs_standalone_block_tuple_binding_shadowing() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("tuple binding shadowing should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "tuple binding shadowing should execute successfully: {}", result.unwrap_err()); } @@ -14980,7 +15332,7 @@ fn runs_split_on_non_byte_array() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("split on int[] should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "split on int[] should execute successfully: {}", result.unwrap_err()); } @@ -15001,8 +15353,9 @@ fn runtime_split_index_produces_dynamic_array_parts() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("runtime split index should produce dynamic parts"); - let sigscript = compiled.build_sig_script("main", vec![vec![10i64, 20, 30, 40].into(), Expr::int(2)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[vec![10i64, 20, 30, 40].into(), ArtifactValue::Int(2)]) + .expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "runtime-index split should execute successfully: {result:?}"); } @@ -15026,8 +15379,8 @@ fn constant_split_index_produces_dynamic_parts_for_dynamic_source() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("constant split index should preserve dynamic parts"); - let sigscript = compiled.build_sig_script("main", vec![vec![10i64, 20, 30, 40].into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[vec![10i64, 20, 30, 40].into()]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "constant-index split of a dynamic source should execute successfully: {result:?}"); } @@ -15053,7 +15406,7 @@ fn constant_split_index_produces_dynamic_parts_for_fixed_source() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("fixed source split should return dynamic parts"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "constant-index split of a fixed source should execute successfully: {result:?}"); } @@ -15165,7 +15518,7 @@ fn runs_slice_on_non_byte_array() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("slice on int[] should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "slice on int[] should execute successfully: {}", result.unwrap_err()); } @@ -15207,7 +15560,7 @@ fn runs_split_and_slice_on_struct_array() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("struct array sequence operations should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "struct array sequence operations should execute successfully: {}", result.unwrap_err()); } @@ -15280,7 +15633,7 @@ fn scalar_struct_values_can_be_compared_directly() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("scalar struct comparisons should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "scalar struct comparisons should execute successfully: {result:?}"); } @@ -15405,7 +15758,7 @@ fn runs_standalone_block_function_result_binding_shadowing() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("function result binding shadowing should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "function result binding shadowing should execute successfully: {}", result.unwrap_err()); } @@ -15428,11 +15781,11 @@ fn runs_standalone_block_state_binding_shadowing() { "#; let input_compiled = - compile_contract(source, &[Expr::int(7)], CompileOptions::default()).expect("state binding shadowing should compile"); - let sigscript = input_compiled.build_sig_script("main", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + compile_contract(source, &[ArtifactValue::Int(7)], CompileOptions::default()).expect("state binding shadowing should compile"); + let sigscript = encode_single_entry_sig_script(&input_compiled, &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); let output = TransactionOutput { value: 1000, script_public_key: input_spk.clone(), covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -15465,7 +15818,7 @@ fn runs_standalone_block_struct_destructure_binding_shadowing() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("struct destructure binding shadowing should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "struct destructure binding shadowing should execute successfully: {}", result.unwrap_err()); } @@ -15487,8 +15840,8 @@ fn branch_shadowing_initializer_reads_outer_binding() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("branch shadowing initializer should read the outer binding"); - let sigscript = compiled.build_sig_script("main", vec![Expr::bool(true)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bool(true)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "branch shadowing initializer should execute successfully: {}", result.unwrap_err()); } @@ -15511,8 +15864,8 @@ fn branch_reference_before_shadowing_declaration_reads_outer_binding() { let compiled = compile_contract(source, &[], CompileOptions::default()) .expect("a branch reference before a shadowing declaration should read the outer binding"); - let sigscript = compiled.build_sig_script("main", vec![Expr::bool(true)]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Bool(true)]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "branch reference before shadowing should execute successfully: {}", result.unwrap_err()); } @@ -15535,7 +15888,7 @@ fn for_loop_shadowing_initializer_reads_outer_binding() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("loop shadowing initializer should read the outer binding"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "loop shadowing initializer should execute successfully: {}", result.unwrap_err()); } @@ -15559,7 +15912,7 @@ fn for_loop_reference_before_shadowing_declaration_reads_outer_binding() { let compiled = compile_contract(source, &[], CompileOptions::default()) .expect("a loop reference before a shadowing declaration should read the outer binding"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "loop reference before shadowing should execute successfully: {}", result.unwrap_err()); } @@ -15599,12 +15952,12 @@ fn runs_standalone_block_and_preserves_outer_scope() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "standalone block should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(8)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(8)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_ok(), "outer scope should remain valid after the block: {}", result_err.unwrap_err()); } @@ -15626,22 +15979,22 @@ fn inline_nested_argument_expression_is_stored_once_and_reused() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("inline nested arg should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpAdd).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpAdd).count(), 2, "the inline nested argument should compute each addition once and reuse the stored result" ); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpMul).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpMul).count(), 1, "the inline nested argument should multiply once and reuse the stored result" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(5)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "stored inline nested argument should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(10)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(10)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "stored inline nested argument should still enforce the second require"); } @@ -15672,22 +16025,22 @@ fn function_call_assignment_result_is_stored_once_and_reused() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("function-call assignment should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpSub).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpSub).count(), 1, "the nested g(x) return calculation should be computed once and the assigned local reused" ); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpMul).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpMul).count(), 1, "the extra arithmetic in f(x) should be computed once and the assigned local reused" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(19)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(19)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "stored function-call assignment result should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(29)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(29)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "stored function-call assignment result should still enforce the second require"); } @@ -15720,22 +16073,22 @@ fn struct_return_field_is_stored_once_and_reused() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("struct-return local should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpAdd).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpAdd).count(), 1, "s.a should be computed once and reused across both require statements" ); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpMul).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpMul).count(), 1, "s.b should be computed once and reused across both require statements" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(3)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(3)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "stored struct fields should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(10)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(10)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "stored struct fields should still enforce the require conditions"); } @@ -15838,7 +16191,7 @@ fn struct_reassignment_snapshots_all_fields_before_rebinding() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("struct field swap should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "struct field swap should execute atomically: {result:?}"); } @@ -15861,7 +16214,7 @@ fn nested_struct_reassignment_snapshots_all_leaves_before_rebinding() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("nested struct rotation should compile"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "nested struct rotation should execute atomically: {result:?}"); } @@ -15881,12 +16234,9 @@ fn struct_array_self_append_snapshots_all_leaf_expressions_before_rebinding() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("struct array append should compile"); - let argument = Expr::array( - parse_type_ref("S[]").expect("array type parses"), - vec![struct_object("S", vec![("a", Expr::int(7)), ("b", Expr::int(8))])], - ); - let sigscript = compiled.build_sig_script("main", vec![argument]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let argument = ArtifactValue::Array(vec![artifact_object([("a", 7.into()), ("b", 8.into())])]); + let sigscript = encode_single_entry_sig_script(&compiled, &[argument]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "struct array leaf expressions should observe the pre-append value: {result:?}"); } @@ -15910,21 +16260,21 @@ fn partially_reassigned_struct_field_does_not_recompute_unchanged_fields() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("partial struct reassignment should compile"); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpMul).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpMul).count(), 1, "the unchanged field should keep using its original expression instead of being copied into a new stack slot" ); assert_eq!( - compiled.bytecode.iter().copied().filter(|op| *op == OpAdd).count(), + bytecode(&compiled).iter().copied().filter(|op| *op == OpAdd).count(), 2, "only the initial `s.a = x + 1` and the reassigned `s.a = s.a + 1` should emit additions" ); - let sigscript_ok = compiled.build_sig_script("main", vec![Expr::int(2)]).expect("sigscript builds"); - let result_ok = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_ok); + let sigscript_ok = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(2)]).expect("sigscript builds"); + let result_ok = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_ok); assert!(result_ok.is_ok(), "partial struct reassignment should execute successfully: {}", result_ok.unwrap_err()); - let sigscript_err = compiled.build_sig_script("main", vec![Expr::int(0)]).expect("sigscript builds"); - let result_err = run_bytecode_with_sigscript(compiled.bytecode, sigscript_err); + let sigscript_err = encode_single_entry_sig_script(&compiled, &[ArtifactValue::Int(0)]).expect("sigscript builds"); + let result_err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_err); assert!(result_err.is_err(), "partial struct reassignment should still enforce the updated field checks"); } @@ -15948,14 +16298,22 @@ fn if_branch_reassignment_drops_hidden_shadow_bindings() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("if branch reassignment should compile"); - let sigscript_then = - compiled.build_sig_script("main", vec![Expr::int(1), Expr::int(1), Expr::int(1), Expr::int(3)]).expect("sigscript builds"); - let result_then = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_then); + let sigscript_then = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Int(1), ArtifactValue::Int(1), ArtifactValue::Int(1), ArtifactValue::Int(3)], + ) + .expect("sigscript builds"); + let result_then = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_then); assert!(result_then.is_ok(), "then-branch reassignment should leave a clean stack: {}", result_then.unwrap_err()); - let sigscript_else = - compiled.build_sig_script("main", vec![Expr::int(0), Expr::int(1), Expr::int(1), Expr::int(2)]).expect("sigscript builds"); - let result_else = run_bytecode_with_sigscript(compiled.bytecode, sigscript_else); + let sigscript_else = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Int(0), ArtifactValue::Int(1), ArtifactValue::Int(1), ArtifactValue::Int(2)], + ) + .expect("sigscript builds"); + let result_else = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_else); assert!(result_else.is_ok(), "else-branch reassignment should leave a clean stack: {}", result_else.unwrap_err()); } @@ -15986,7 +16344,8 @@ fn struct_if_reassignment_preserves_types_after_merge() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("post-if struct type merge should compile"); + let compiled = + compile_internal_contract(source, &[], CompileOptions::default()).expect("post-if struct type merge should compile"); let normalized = format_contract_ast(&compiled.ast); assert!(normalized.contains("S t = s;"), "merged struct type should still allow assignment after the if: {normalized}"); } @@ -16018,7 +16377,8 @@ fn partial_struct_if_reassignment_preserves_types_after_merge() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("post-if partial struct type merge should compile"); + let compiled = + compile_internal_contract(source, &[], CompileOptions::default()).expect("post-if partial struct type merge should compile"); let normalized = format_contract_ast(&compiled.ast); assert!(normalized.contains("S t = s;"), "merged struct type should still allow assignment after the if: {normalized}"); } @@ -16049,16 +16409,22 @@ fn struct_if_branch_reassignment_drops_hidden_shadow_bindings() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("struct branch cleanup should compile"); - let sigscript_then = compiled - .build_sig_script("main", vec![Expr::int(1), Expr::int(2), Expr::int(3), Expr::int(6), Expr::int(7)]) - .expect("sigscript builds"); - let result_then = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_then); + let sigscript_then = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Int(1), ArtifactValue::Int(2), ArtifactValue::Int(3), ArtifactValue::Int(6), ArtifactValue::Int(7)], + ) + .expect("sigscript builds"); + let result_then = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_then); assert!(result_then.is_ok(), "then-branch struct cleanup should leave a clean stack: {}", result_then.unwrap_err()); - let sigscript_else = compiled - .build_sig_script("main", vec![Expr::int(0), Expr::int(2), Expr::int(3), Expr::int(5), Expr::int(7)]) - .expect("sigscript builds"); - let result_else = run_bytecode_with_sigscript(compiled.bytecode, sigscript_else); + let sigscript_else = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Int(0), ArtifactValue::Int(2), ArtifactValue::Int(3), ArtifactValue::Int(5), ArtifactValue::Int(7)], + ) + .expect("sigscript builds"); + let result_else = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_else); assert!(result_else.is_ok(), "else-branch struct cleanup should leave a clean stack: {}", result_else.unwrap_err()); } @@ -16088,16 +16454,22 @@ fn partial_struct_if_branch_reassignment_drops_hidden_shadow_bindings() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("partial struct branch cleanup should compile"); - let sigscript_then = compiled - .build_sig_script("main", vec![Expr::int(1), Expr::int(2), Expr::int(3), Expr::int(6), Expr::int(3)]) - .expect("sigscript builds"); - let result_then = run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript_then); + let sigscript_then = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Int(1), ArtifactValue::Int(2), ArtifactValue::Int(3), ArtifactValue::Int(6), ArtifactValue::Int(3)], + ) + .expect("sigscript builds"); + let result_then = run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript_then); assert!(result_then.is_ok(), "then-branch partial struct cleanup should leave a clean stack: {}", result_then.unwrap_err()); - let sigscript_else = compiled - .build_sig_script("main", vec![Expr::int(0), Expr::int(2), Expr::int(3), Expr::int(2), Expr::int(8)]) - .expect("sigscript builds"); - let result_else = run_bytecode_with_sigscript(compiled.bytecode, sigscript_else); + let sigscript_else = encode_entry_sig_script( + &compiled, + "main", + &[ArtifactValue::Int(0), ArtifactValue::Int(2), ArtifactValue::Int(3), ArtifactValue::Int(2), ArtifactValue::Int(8)], + ) + .expect("sigscript builds"); + let result_else = run_bytecode_with_sigscript(bytecode(&compiled), sigscript_else); assert!(result_else.is_ok(), "else-branch partial struct cleanup should leave a clean stack: {}", result_else.unwrap_err()); } @@ -16122,9 +16494,9 @@ contract CounterLoop(int BOUND) { let bounds = [4i64, 8i64, 12i64]; let mut lens = Vec::new(); for b in bounds { - let args = [Expr::int(b)]; + let args = [b.into()]; let compiled = compile_contract(SOURCE, &args, CompileOptions::default()).expect("compile succeeds"); - lens.push(compiled.bytecode.len()); + lens.push(bytecode(&compiled).len()); } assert!(lens[0] < lens[1] && lens[1] < lens[2], "expected monotonic growth, got {lens:?}"); @@ -16162,9 +16534,9 @@ contract StructCounterLoop(int BOUND) { let bounds = [4i64, 8i64, 12i64]; let mut lens = Vec::new(); for b in bounds { - let args = [Expr::int(b)]; + let args = [b.into()]; let compiled = compile_contract(SOURCE, &args, CompileOptions::default()).expect("compile succeeds"); - lens.push(compiled.bytecode.len()); + lens.push(bytecode(&compiled).len()); } assert!(lens[0] < lens[1] && lens[1] < lens[2], "expected monotonic growth, got {lens:?}"); @@ -16202,11 +16574,11 @@ fn validate_output_state_preserves_nested_struct_field_paths() { let input_compiled = compile_contract(source, &[1.into(), 2.into()], CompileOptions::default()).expect("compile succeeds"); let output_compiled = compile_contract(source, &[3.into(), 4.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = input_compiled.build_sig_script("route", vec![]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "route", &[]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&output_compiled.bytecode); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); @@ -16301,17 +16673,17 @@ fn validate_output_state_with_template_preserves_nested_struct_field_paths() { ); let input_compiled = compile_contract(&source, &[], CompileOptions::default()).expect("compile router succeeds"); - let sigscript = input_compiled.build_sig_script("route", vec![target_hash.into()]).expect("sigscript builds"); - let sigscript = pay_to_script_hash_signature_script(input_compiled.bytecode.clone(), sigscript).unwrap(); + let sigscript = encode_entry_sig_script(&input_compiled, "route", &[target_hash.into()]).expect("sigscript builds"); + let sigscript = pay_to_script_hash_signature_script(bytecode(&input_compiled).clone(), sigscript).unwrap(); let input = test_input(0, sigscript); - let template_input = test_input(1, sigscript_push_bytecode(&target_template_compiled.bytecode)); - let input_spk = pay_to_script_hash_script(&input_compiled.bytecode); - let output_spk = pay_to_script_hash_script(&target_output_compiled.bytecode); + let template_input = test_input(1, sigscript_push_bytecode(&bytecode(&target_template_compiled))); + let input_spk = pay_to_script_hash_script(&bytecode(&input_compiled)); + let output_spk = pay_to_script_hash_script(&bytecode(&target_output_compiled)); let output = TransactionOutput { value: 1000, script_public_key: output_spk, covenant: None }; let tx = Transaction::new(1, vec![input, template_input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = UtxoEntry::new(output.value, input_spk, 0, tx.is_coinbase(), None); let template_utxo = - UtxoEntry::new(output.value, pay_to_script_hash_script(&target_template_compiled.bytecode), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, pay_to_script_hash_script(&bytecode(&target_template_compiled)), 0, tx.is_coinbase(), None); let result = execute_input(tx, vec![utxo_entry, template_utxo], 0); assert!(result.is_ok(), "nested struct fields with the same leaf name should remain distinct by path: {result:?}"); @@ -16337,10 +16709,10 @@ fn blake2b_builtins_lower_and_execute_correctly() { ); let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("Blake2b builtins compile"); - assert!(compiled.bytecode.contains(&OpBlake2b)); - assert!(compiled.bytecode.contains(&OpBlake2bWithKey)); + assert!(bytecode(&compiled).contains(&OpBlake2b)); + assert!(bytecode(&compiled).contains(&OpBlake2bWithKey)); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "Blake2b builtins should execute correctly: {result:?}"); } @@ -16365,10 +16737,10 @@ fn blake3_builtins_lower_and_execute_correctly() { ); let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("Blake3 builtins compile"); - assert!(compiled.bytecode.contains(&OpBlake3)); - assert!(compiled.bytecode.contains(&OpBlake3WithKey)); + assert!(bytecode(&compiled).contains(&OpBlake3)); + assert!(bytecode(&compiled).contains(&OpBlake3WithKey)); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "Blake3 builtins should call the engine correctly: {result:?}"); } @@ -16414,14 +16786,14 @@ fn rejects_misaligned_dynamic_array_entrypoint_payload() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("dynamic int array should compile"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode should stringify"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode should stringify"); assert!(opcodes.contains("OpMod"), "dynamic array validation should check payload alignment: {opcodes}"); // Bypass build_sig_script to model an untrusted spender pushing one byte for // an int[] whose elements require eight bytes each. let sigscript = script_builder().add_data_with_push_opcode(&[1]).unwrap().add_data(&dispatch_tag_for(&compiled, "main")).unwrap().drain(); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_err(), "a dynamic int array payload must contain a whole number of elements"); } @@ -16435,8 +16807,8 @@ fn derived_dynamic_array_length_counts_elements() { } "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("dynamic int array slice should compile"); - let sigscript = compiled.build_sig_script("main", vec![vec![10i64, 20i64].into()]).expect("sigscript builds"); - let result = run_bytecode_with_sigscript(compiled.bytecode, sigscript); + let sigscript = encode_entry_sig_script(&compiled, "main", &[vec![10i64, 20i64].into()]).expect("sigscript builds"); + let result = run_bytecode_with_sigscript(bytecode(&compiled), sigscript); assert!(result.is_ok(), "slice length should be measured in int elements, not encoded bytes: {result:?}"); } @@ -16468,10 +16840,10 @@ fn allows_fixed_array_cast_with_compatible_encoded_size() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()) .expect("fixed arrays with equal encoded sizes should be cast-compatible"); - let opcodes = script_to_str(&compiled.bytecode).expect("compiled bytecode should stringify"); + let opcodes = script_to_str(&bytecode(&compiled)).expect("compiled bytecode should stringify"); assert!(!opcodes.contains("OpNum2Bin"), "an equal-size array cast should remain a passthrough: {opcodes}"); let dispatch_tag = dispatch_tag_for(&compiled, "main"); - let result = run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag); + let result = run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag); assert!(result.is_ok(), "the reinterpreted byte array should preserve the int payload bytes: {result:?}"); } @@ -16597,7 +16969,7 @@ fn rejects_user_function_with_builtin_name() { } "#; - let err = compile_contract(source, &[Expr::int(0)], CompileOptions::default()) + let err = compile_contract(source, &[ArtifactValue::Int(0)], CompileOptions::default()) .expect_err("user-defined functions must not use builtin names"); assert!(err.to_string().contains("function name 'validateOutputState' is reserved for a builtin"), "unexpected error: {err}"); let span = err.span().expect("the reserved function name should be identified"); @@ -16616,7 +16988,7 @@ fn rejects_builtin_names_for_variables() { ]; for source in cases { - let constructor_args = if source.contains("int sha256") { vec![Expr::int(0)] } else { vec![] }; + let constructor_args = if source.contains("int sha256") { vec![0.into()] } else { vec![] }; let err = compile_contract(source, &constructor_args, CompileOptions::default()).expect_err("variables must not use builtin names"); assert!(err.to_string().contains("is reserved for a builtin"), "unexpected error for `{source}`: {err}"); @@ -16628,6 +17000,7 @@ fn rejects_duplicate_declaration_names() { let cases = [ ( "contract DuplicateCtor(int value, int value) { entry spend() { require(true); } }", + vec![1.into(), 2.into()], vec![Expr::int(1), Expr::int(2)], "value", "duplicate contract parameter name 'value'", @@ -16635,24 +17008,27 @@ fn rejects_duplicate_declaration_names() { ( "contract DuplicateEntry() { entry spend(int value, int value) { require(value == value); } }", vec![], + vec![], "value", "duplicate parameter name 'value' in function 'spend'", ), ( "contract DuplicateHelper() { function helper(int value, int value) { require(true); } entry spend() { require(true); } }", vec![], + vec![], "value", "duplicate parameter name 'value' in function 'helper'", ), ( "contract DuplicateConstant() { int constant VALUE = 1; int constant VALUE = 2; entry spend() { require(true); } }", vec![], + vec![], "VALUE", "duplicate constant name 'VALUE'", ), ]; - for (source, constructor_args, duplicate_name, expected_error) in cases { + for (source, constructor_args, ast_constructor_args, duplicate_name, expected_error) in cases { let source_error = compile_contract(source, &constructor_args, CompileOptions::default()) .expect_err("source compilation must reject duplicate declarations"); assert_eq!(source_error.root().to_string(), format!("unsupported feature: {expected_error}")); @@ -16660,7 +17036,7 @@ fn rejects_duplicate_declaration_names() { assert_eq!(&source[span.start..span.end], duplicate_name); let ast = parse_contract_ast(source).expect("duplicate declarations remain representable in the public AST"); - let ast_error = compile_contract_ast(&ast, &constructor_args, CompileOptions::default()) + let ast_error = compile_contract_ast(&ast, &ast_constructor_args, CompileOptions::default()) .expect_err("public AST compilation must reject duplicate declarations"); assert_eq!(ast_error.root().to_string(), source_error.root().to_string()); } @@ -16774,7 +17150,7 @@ fn compile_and_execute_conformance_assertion(assertion: &str) { let source = format!("contract Generated() {{ entry spend() {{ require({assertion}); }} }}"); let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("generated well-typed program compiles"); let dispatch_tag = dispatch_tag_for(&compiled, "spend"); - run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).expect("reference result agrees with local VM"); + run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).expect("reference result agrees with local VM"); } #[test] @@ -16801,7 +17177,7 @@ fn bounded_metamorphic_variants_preserve_behavior() { let source = format!("contract Meta() {{ {helper} entry spend() {{ {body} }} }}"); let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("metamorphic variant compiles"); let dispatch_tag = dispatch_tag_for(&compiled, "spend"); - run_bytecode_with_dispatch_tag(compiled.bytecode, dispatch_tag).expect("metamorphic variant executes"); + run_bytecode_with_dispatch_tag(bytecode(&compiled), dispatch_tag).expect("metamorphic variant executes"); } } @@ -16809,14 +17185,17 @@ fn bounded_metamorphic_variants_preserve_behavior() { fn formatting_and_ast_round_trip_preserve_artifact() { let source = "contract RoundTrip(int seed) { int state = seed; entry spend() { require(state == 4); } }"; let args = [Expr::int(4)]; - let original = compile_contract(source, &args, CompileOptions::default()).expect("source compiles"); + let original = compile_contract(source, &[4.into()], CompileOptions::default()).expect("source compiles"); let ast = parse_contract_ast(source).expect("source parses"); let formatted = format_contract_ast(&ast); let reparsed = parse_contract_ast(&formatted).expect("formatted source parses"); let from_ast = compile_contract_ast(&reparsed, &args, CompileOptions::default()).expect("public AST path compiles"); - assert_eq!(original.bytecode, from_ast.bytecode); - assert_eq!(original.abi, from_ast.abi); - assert_eq!(original.state_layout, from_ast.state_layout); + let from_ast_artifact = sil_abi_artifact_from_compiled(&from_ast, &args).expect("AST portable artifact builds"); + assert_eq!(bytecode(&original), from_ast.bytecode); + let original_entries = &single_contract(&original).entries; + let ast_entries = &single_contract(&from_ast_artifact).entries; + assert_eq!(original_entries, ast_entries); + assert_eq!(state_layout(&original), from_ast.state_layout); } #[test] @@ -16825,14 +17204,18 @@ fn debug_recording_does_not_change_executable_artifact() { let plain = compile_contract(source, &[], CompileOptions::default()).expect("plain compile"); let debug = compile_contract(source, &[], CompileOptions { record_debug_infos: true, ..CompileOptions::default() }) .expect("debug compile"); - assert_eq!(plain.abi, debug.abi); - assert_eq!(plain.state_layout, debug.state_layout); - assert!(plain.debug_info.is_none()); - assert!(debug.debug_info.is_some()); + assert_eq!(single_contract(&plain).entries, single_contract(&debug).entries); + assert_eq!(state_layout(&plain), state_layout(&debug)); + let plain_internal = compile_internal_contract(source, &[], CompileOptions::default()).expect("plain internal compile"); + let debug_internal = + compile_internal_contract(source, &[], CompileOptions { record_debug_infos: true, ..CompileOptions::default() }) + .expect("debug internal compile"); + assert!(plain_internal.debug_info.is_none()); + assert!(debug_internal.debug_info.is_some()); let dispatch_tag = dispatch_tag_for(&plain, "spend"); - run_bytecode_with_dispatch_tag(plain.bytecode, dispatch_tag).expect("plain artifact executes"); + run_bytecode_with_dispatch_tag(bytecode(&plain), dispatch_tag).expect("plain artifact executes"); let dispatch_tag = dispatch_tag_for(&debug, "spend"); - run_bytecode_with_dispatch_tag(debug.bytecode, dispatch_tag).expect("debug artifact executes with equivalent semantics"); + run_bytecode_with_dispatch_tag(bytecode(&debug), dispatch_tag).expect("debug artifact executes with equivalent semantics"); } #[test] @@ -16933,7 +17316,7 @@ fn dynamic_array_abi_rejects_zero_width_elements() { } } "#; - let err = compile_contract(constructor_source, &[Expr::bytes(Vec::new())], CompileOptions::default()) + let err = compile_contract(constructor_source, &[ArtifactValue::Bytes(Vec::new())], CompileOptions::default()) .expect_err("constructor parameter dimensions must be greater than zero"); assert!(err.to_string().contains("must be greater than zero"), "unexpected error: {err}"); } @@ -16966,7 +17349,7 @@ fn artifact_state_resolution_supports_constructor_sized_arrays() { } "#; let constructor_args = [Expr::int(2), Expr::bytes(vec![0xaa, 0xbb])]; - let compiled = compile_contract(source, &constructor_args, CompileOptions::default()).expect("contract compiles"); + let compiled = compile_internal_contract(source, &constructor_args, CompileOptions::default()).expect("contract compiles"); let state = compiled .ast @@ -16989,19 +17372,20 @@ fn artifact_sigscript_builder_supports_constructor_sized_struct_array_fields() { } } "#; - let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("contract compiles"); - let input = &compiled.entry_by_name("main").expect("entrypoint exists").inputs[0]; - assert_eq!(input.type_name, "Item[]"); - assert_eq!(compiled.ast.structs[0].fields[0].type_ref, parse_type_ref("byte[3]").expect("resolved field type parses")); + let artifact = compile_to_sil_abi_artifact(source, &[3.into()]).expect("contract compiles"); + let input = &artifact.contract("C").and_then(|contract| contract.entry("main")).expect("entrypoint exists").params[0]; + assert_eq!(input.ty, TypeArtifact::DynamicArray { item: Box::new(TypeArtifact::Struct { name: "Item".to_string() }) }); + assert_eq!(artifact.structs["Item"].fields[0].ty, TypeArtifact::FixedBytes { len: 3 }); - let json = serde_json::to_string(&compiled).expect("compiled artifact serializes"); - let compiled: CompiledContract<'_> = serde_json::from_str(&json).expect("compiled artifact deserializes"); - assert_eq!(compiled.ast.structs[0].fields[0].type_ref, parse_type_ref("byte[3]").expect("resolved field type survives JSON")); - let item = struct_object("Item", vec![("data", Expr::bytes(vec![0xaa, 0xbb, 0xcc]))]); - let items = Expr::array(parse_type_ref("Item[]").expect("array type parses"), vec![item]); - let sigscript = compiled.build_sig_script("main", vec![items]).expect("resolved ABI encodes the valid argument"); + let json = serde_json::to_string(&artifact).expect("portable artifact serializes"); + let artifact: SilAbiArtifact = serde_json::from_str(&json).expect("portable artifact deserializes"); + assert_eq!(artifact.structs["Item"].fields[0].ty, TypeArtifact::FixedBytes { len: 3 }); + let items = + ArtifactValue::Array(vec![ArtifactValue::Object(BTreeMap::from([("data".to_string(), vec![0xaau8, 0xbb, 0xcc].into())]))]); + let sigscript = encode_single_entry_sig_script(&artifact, &[items]).expect("resolved ABI encodes the valid argument"); + let bytecode = artifact.contract("C").expect("contract exists").compiled.bytecode.clone(); - run_bytecode_with_sigscript(compiled.bytecode, sigscript).expect("artifact-built invocation executes"); + run_bytecode_with_sigscript(bytecode, sigscript).expect("artifact-built invocation executes"); } #[test] @@ -17017,14 +17401,13 @@ fn artifact_sigscript_builder_rejects_wrong_constructor_sized_struct_fields() { } } "#; - let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("contract compiles"); - let valid = struct_object("Item", vec![("data", Expr::bytes(vec![0xaa, 0xbb, 0xcc]))]); - compiled.build_sig_script("main", vec![valid]).expect("the valid constructor-sized struct argument encodes"); + let artifact = compile_to_sil_abi_artifact(source, &[3.into()]).expect("contract compiles"); + let item = |data: Vec| ArtifactValue::Object(BTreeMap::from([("data".to_string(), ArtifactValue::from(data))])); + encode_single_entry_sig_script(&artifact, &[item(vec![0xaa, 0xbb, 0xcc])]) + .expect("the valid constructor-sized struct argument encodes"); for data in [vec![0xaa, 0xbb], vec![0xaa, 0xbb, 0xcc, 0xdd]] { - let malformed = struct_object("Item", vec![("data", Expr::bytes(data))]); - compiled - .build_sig_script("main", vec![malformed]) + encode_single_entry_sig_script(&artifact, &[item(data)]) .expect_err("the resolved ABI must reject an incorrectly sized nested field"); } } @@ -17078,11 +17461,11 @@ fn append_accepts_a_nested_array_element_as_its_array_source() { } fn execute_handcrafted_p2sh( - compiled: &CompiledContract<'_>, + compiled: &silverscript_abi::SilAbiArtifact, unlocking_prefix: Vec, ) -> Result<(), kaspa_txscript_errors::TxScriptError> { let flags = EngineFlags { covenants_enabled: true, ..Default::default() }; - let signature_script = pay_to_script_hash_signature_script_with_flags(compiled.bytecode.clone(), unlocking_prefix, flags) + let signature_script = pay_to_script_hash_signature_script_with_flags(bytecode(compiled).clone(), unlocking_prefix, flags) .expect("redeem script push should build"); let input = TransactionInput::new( TransactionOutpoint { transaction_id: TransactionId::from_bytes([1; 32]), index: 0 }, @@ -17091,7 +17474,7 @@ fn execute_handcrafted_p2sh( 0, ); let spent_output = - TransactionOutput { value: 1_000, script_public_key: pay_to_script_hash_script(&compiled.bytecode), covenant: None }; + TransactionOutput { value: 1_000, script_public_key: pay_to_script_hash_script(&bytecode(compiled)), covenant: None }; let tx = Transaction::new(1, vec![input.clone()], vec![spent_output.clone()], 0, SubnetworkId::default(), 0, vec![]); let utxo = UtxoEntry::new(spent_output.value, spent_output.script_public_key, 0, tx.is_coinbase(), None); let populated = PopulatedTransaction::new(&tx, vec![utxo.clone()]); @@ -17108,7 +17491,7 @@ fn execute_handcrafted_p2sh( vm.execute() } -fn compile_sigscript_boundary_contract() -> CompiledContract<'static> { +fn compile_sigscript_boundary_contract() -> silverscript_abi::SilAbiArtifact { let source = r#" contract Boundary(int committed) { int stored = committed; @@ -17120,10 +17503,10 @@ fn compile_sigscript_boundary_contract() -> CompiledContract<'static> { } } "#; - compile_contract(source, &[Expr::int(9)], CompileOptions::default()).expect("boundary contract compiles") + compile_contract(source, &[ArtifactValue::Int(9)], CompileOptions::default()).expect("boundary contract compiles") } -fn valid_sigscript_boundary_prefix(compiled: &CompiledContract<'_>) -> Vec { +fn valid_sigscript_boundary_prefix(compiled: &silverscript_abi::SilAbiArtifact) -> Vec { script_builder().add_i64(11).unwrap().add_i64(22).unwrap().add_data(&dispatch_tag_for(compiled, "main")).unwrap().drain() } @@ -17242,10 +17625,10 @@ fn runtime_empty_loop_with_extreme_reversed_bounds_matches_constant_lowering() { } "#; let compiled = compile_contract(runtime_source, &[], CompileOptions::default()).expect("runtime-bound contract compiles"); - let sigscript = - compiled.build_sig_script("main", vec![Expr::int(i64::MAX), Expr::int(-i64::MAX)]).expect("runtime sigscript builds"); - let err = - run_bytecode_with_sigscript(compiled.bytecode, sigscript).expect_err("the equivalent runtime range subtraction must overflow"); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(i64::MAX), ArtifactValue::Int(-i64::MAX)]) + .expect("runtime sigscript builds"); + let err = run_bytecode_with_sigscript(bytecode(&compiled), sigscript) + .expect_err("the equivalent runtime range subtraction must overflow"); assert!(matches!(err, kaspa_txscript_errors::TxScriptError::NumberTooBig(_)), "unexpected runtime error: {err:?}"); } @@ -17342,21 +17725,20 @@ fn signature_script_builder_requires_explicit_byte_values() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte contract compiles"); for value in [0, 0x80, 0xff] { - let err = compiled - .build_sig_script("main", vec![Expr::int(value)]) + let err = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Int(value)]) .expect_err("integer AST values must not be reinterpreted as ABI bytes"); - assert!(err.to_string().contains("expects byte"), "unexpected error for {value:#04x}: {err}"); + assert!(err.to_string().contains("expected byte"), "unexpected error for {value:#04x}: {err}"); } - let sigscript = compiled.build_sig_script("main", vec![Expr::byte(0xff)]).expect("an explicit byte value builds"); - run_bytecode_with_sigscript(compiled.bytecode.clone(), sigscript).expect("the explicit byte invocation executes"); + let sigscript = encode_entry_sig_script(&compiled, "main", &[ArtifactValue::Byte(0xff)]).expect("an explicit byte value builds"); + run_bytecode_with_sigscript(bytecode(&compiled).clone(), sigscript).expect("the explicit byte invocation executes"); - let contextual_array = Expr::array(parse_type_ref("byte[]").expect("byte array type parses"), vec![Expr::int(1)]); - let err = compiled - .build_sig_script("array", vec![contextual_array]) - .expect_err("integer AST elements must not be reinterpreted as ABI bytes"); - assert!(err.to_string().contains("expects byte[]"), "unexpected array error: {err}"); + let contextual_array = ArtifactValue::Array(vec![1.into()]); + let err = encode_entry_sig_script(&compiled, "array", &[contextual_array]) + .expect_err("integer artifact values must not be reinterpreted as ABI bytes"); + assert!(err.to_string().contains("expected bytes"), "unexpected array error: {err}"); - let sigscript = compiled.build_sig_script("array", vec![Expr::dynamic_bytes(vec![1])]).expect("explicit byte elements build"); - run_bytecode_with_sigscript(compiled.bytecode, sigscript).expect("the explicit byte-array invocation executes"); + let sigscript = + encode_entry_sig_script(&compiled, "array", &[ArtifactValue::Bytes(vec![1])]).expect("explicit byte elements build"); + run_bytecode_with_sigscript(bytecode(&compiled), sigscript).expect("the explicit byte-array invocation executes"); } diff --git a/silverscript-lang/tests/consensus_time_tests.rs b/silverscript-lang/tests/consensus_time_tests.rs index 3b8bbdb8..b8d10b32 100644 --- a/silverscript-lang/tests/consensus_time_tests.rs +++ b/silverscript-lang/tests/consensus_time_tests.rs @@ -1,3 +1,5 @@ +mod common; + use kaspa_consensus::config::ConfigBuilder; use kaspa_consensus::consensus::test_consensus::TestConsensus; use kaspa_consensus::params::{DEVNET_PARAMS, ForkActivation}; @@ -14,8 +16,10 @@ use kaspa_consensus_core::tx::{ UtxoEntry, }; use kaspa_muhash::MuHash; -use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::compiler::{CompileOptions, compile_to_sil_abi_artifact_with_options}; + +use common::{bytecode, encode_entry_sig_script, encode_single_entry_sig_script}; const FUNDING_AMOUNT: u64 = 10_000_000_000; const AGE_OUTPUT_AMOUNT: u64 = 1_900_000_000; @@ -57,11 +61,11 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { // Compile one contract for each lock domain. The tests below spend their // outputs in real consensus blocks rather than inspecting emitted opcodes. let age_source = "contract Age() { entry main(int age) { require(this.ageDaa >= age); } }"; - let age = compile_contract(age_source, &[], CompileOptions::default()).expect("age contract compiles"); + let age = compile_to_sil_abi_artifact_with_options(age_source, &[], CompileOptions::default()).expect("age contract compiles"); let time_source = "contract TimeLock() { entry main(temporal timestamp) { require(tx.time >= timestamp); } }"; - let time = compile_contract(time_source, &[], CompileOptions::default()).expect("time contract compiles"); + let time = compile_to_sil_abi_artifact_with_options(time_source, &[], CompileOptions::default()).expect("time contract compiles"); let daa_source = "contract DaaLock() { entry main(int daa) { require(tx.daa >= daa); } }"; - let daa = compile_contract(daa_source, &[], CompileOptions::default()).expect("DAA contract compiles"); + let daa = compile_to_sil_abi_artifact_with_options(daa_source, &[], CompileOptions::default()).expect("DAA contract compiles"); let funding_outpoint = TransactionOutpoint::new(TransactionId::from_bytes([1; 32]), 0); let age_overflow_outpoint = TransactionOutpoint::new(TransactionId::from_bytes([2; 32]), 0); @@ -72,13 +76,16 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { // invalid candidate could make a later assertion depend on that candidate. let mut initial_utxos = vec![ (funding_outpoint, UtxoEntry::new(FUNDING_AMOUNT, ScriptPublicKey::from_vec(0, vec![0x51]), 0, false, None)), - (age_overflow_outpoint, UtxoEntry::new(FUNDING_AMOUNT, ScriptPublicKey::new(0, age.bytecode.clone().into()), 0, false, None)), + ( + age_overflow_outpoint, + UtxoEntry::new(FUNDING_AMOUNT, ScriptPublicKey::new(0, bytecode(&age).clone().into()), 0, false, None), + ), ]; initial_utxos.extend(daa_outpoints.iter().copied().map(|outpoint| { - (outpoint, UtxoEntry::new(FUNDING_AMOUNT, ScriptPublicKey::new(0, daa.bytecode.clone().into()), 0, false, None)) + (outpoint, UtxoEntry::new(FUNDING_AMOUNT, ScriptPublicKey::new(0, bytecode(&daa).clone().into()), 0, false, None)) })); initial_utxos.extend(time_outpoints.iter().copied().map(|outpoint| { - (outpoint, UtxoEntry::new(FUNDING_AMOUNT, ScriptPublicKey::new(0, time.bytecode.clone().into()), 0, false, None)) + (outpoint, UtxoEntry::new(FUNDING_AMOUNT, ScriptPublicKey::new(0, bytecode(&time).clone().into()), 0, false, None)) })); let config = ConfigBuilder::new(DEVNET_PARAMS) @@ -112,7 +119,7 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { let mut funding_tx = Transaction::new( 0, vec![TransactionInput::new(funding_outpoint, vec![], 0, 0)], - (0..5).map(|_| TransactionOutput::new(AGE_OUTPUT_AMOUNT, ScriptPublicKey::new(0, age.bytecode.clone().into()))).collect(), + (0..5).map(|_| TransactionOutput::new(AGE_OUTPUT_AMOUNT, ScriptPublicKey::new(0, bytecode(&age).clone().into()))).collect(), 0, SUBNETWORK_ID_NATIVE, 0, @@ -129,7 +136,7 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { let funding_daa_score = consensus.get_header(funding_block_hash).unwrap().daa_score; assert_eq!(funding_daa_score, config.genesis.daa_score + 4, "the funding transaction must be in the fifth post-genesis block"); - let age_sigscript = age.build_sig_script("main", vec![Expr::int(4)]).expect("age sigscript builds"); + let age_sigscript = encode_single_entry_sig_script(&age, &[ArtifactValue::Int(4)]).expect("age sigscript builds"); let miner_data = MinerData::new(ScriptPublicKey::from_vec(0, vec![]), vec![]); // At ages 0, 1, 2, and 3, append the spend to an otherwise valid candidate @@ -189,7 +196,7 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { let daa_start = consensus.get_header(tip).unwrap().daa_score; let daa_target = daa_start + 4; assert!(daa_target < kaspa_txscript::LOCK_TIME_THRESHOLD); - let daa_sigscript = daa.build_sig_script("main", vec![Expr::int(daa_target as i64)]).expect("DAA sigscript builds"); + let daa_sigscript = encode_entry_sig_script(&daa, "main", &[ArtifactValue::Int(daa_target as i64)]).expect("DAA sigscript builds"); // Candidate blocks at target - 4 through target - 1 must all fail. Add one // valid empty block after each attempt to advance the chain one DAA step. @@ -224,8 +231,8 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { // Equality remains locked because finality requires lock_time < median time. let locked_time_millis = consensus.get_virtual_past_median_time(); assert!(locked_time_millis >= kaspa_txscript::LOCK_TIME_THRESHOLD); - let locked_time_sigscript = - time.build_sig_script("main", vec![Expr::temporal(locked_time_millis as i64)]).expect("locked time sigscript builds"); + let locked_time_sigscript = encode_entry_sig_script(&time, "main", &[ArtifactValue::Int(locked_time_millis as i64)]) + .expect("locked time sigscript builds"); let premature_time_tx = spending_transaction(time_outpoints[0], locked_time_sigscript, 0, locked_time_millis); let mut premature_time_block = consensus.build_utxo_valid_block_with_parents(300.into(), vec![tip], miner_data.clone(), vec![]); premature_time_block.transactions.push(premature_time_tx); @@ -238,8 +245,8 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { // millisecond—not second—granularity end to end. let unlocked_time_millis = locked_time_millis - 1; assert_eq!(locked_time_millis - unlocked_time_millis, 1, "the acceptance boundary must be one millisecond wide"); - let unlocked_time_sigscript = - time.build_sig_script("main", vec![Expr::temporal(unlocked_time_millis as i64)]).expect("unlocked time sigscript builds"); + let unlocked_time_sigscript = encode_entry_sig_script(&time, "main", &[ArtifactValue::Int(unlocked_time_millis as i64)]) + .expect("unlocked time sigscript builds"); let time_tx = spending_transaction(time_outpoints[1], unlocked_time_sigscript, 0, unlocked_time_millis); let mut time_tx = MutableTransaction::from_tx(time_tx); consensus.validate_mempool_transaction(&mut time_tx, &TransactionValidationArgs::default()).unwrap(); @@ -250,7 +257,7 @@ async fn compiled_lock_domains_are_enforced_in_actual_consensus_blocks() { // Finally, prove the compiled this.ageDaa runtime guard also survives the // full consensus path: 2^32 is rejected even when supplied dynamically. - let overflow_sigscript = age.build_sig_script("main", vec![Expr::int(1_i64 << 32)]).expect("age sigscript builds"); + let overflow_sigscript = encode_entry_sig_script(&age, "main", &[ArtifactValue::Int(1_i64 << 32)]).expect("age sigscript builds"); let mut overflow_tx = MutableTransaction::from_tx(spending_transaction(age_overflow_outpoint, overflow_sigscript, 0, 0)); let _ = consensus.validate_mempool_transaction(&mut overflow_tx, &TransactionValidationArgs::default()); let overflow_tx = (*overflow_tx.tx).clone(); diff --git a/silverscript-lang/tests/covenant_compiler_tests.rs b/silverscript-lang/tests/covenant_compiler_tests.rs index d8a9fc1e..212beec1 100644 --- a/silverscript-lang/tests/covenant_compiler_tests.rs +++ b/silverscript-lang/tests/covenant_compiler_tests.rs @@ -1,6 +1,14 @@ +mod common; + use kaspa_txscript::opcodes::codes::{OpAuthOutputCount, OpCovInputCount, OpCovInputIdx, OpCovOutputCount, OpInputCovenantId}; +use silverscript_abi::{ArtifactValue, SilAbiArtifact, TypeArtifact}; use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, compile_contract, generated_covenant_auth_entrypoint_name}; +use silverscript_lang::compiler::{ + CompileOptions, compile_contract, compile_to_sil_abi_artifact, compile_to_sil_abi_artifact_with_options, + generated_covenant_auth_entrypoint_name, +}; + +use common::{build_sig_script_for_covenant_decl, bytecode, encode_entry_sig_script, single_contract}; #[test] fn lowers_auth_covenant_declaration_to_hidden_entrypoint_name() { @@ -14,11 +22,13 @@ fn lowers_auth_covenant_declaration_to_hidden_entrypoint_name() { "#; let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); - assert_eq!(compiled.abi.len(), 1); - assert_eq!(compiled.abi[0].name, generated_covenant_auth_entrypoint_name("spend")); + let abi = compile_to_sil_abi_artifact(source, &[3.into()]).expect("portable ABI compiles"); + let artifact = single_contract(&abi); + assert_eq!(artifact.entries.len(), 1); + assert!(artifact.entries.contains_key(&generated_covenant_auth_entrypoint_name("spend"))); assert!(compiled.ast.functions.iter().any(|f| f.name == "__covenant_policy_spend" && !f.entrypoint)); assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("spend") && f.entrypoint)); - assert!(compiled.bytecode.contains(&OpAuthOutputCount)); + assert!(compiled.bytecode.clone().contains(&OpAuthOutputCount)); } #[test] @@ -33,11 +43,13 @@ fn infers_auth_binding_from_from_equal_one_when_binding_omitted() { "#; let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); - assert_eq!(compiled.abi.len(), 1); - assert_eq!(compiled.abi[0].name, generated_covenant_auth_entrypoint_name("spend")); + let abi = compile_to_sil_abi_artifact(source, &[3.into()]).expect("portable ABI compiles"); + let artifact = single_contract(&abi); + assert_eq!(artifact.entries.len(), 1); + assert!(artifact.entries.contains_key(&generated_covenant_auth_entrypoint_name("spend"))); assert!(compiled.ast.functions.iter().any(|f| f.name == "__covenant_policy_spend" && !f.entrypoint)); assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("spend") && f.entrypoint)); - assert!(compiled.bytecode.contains(&OpAuthOutputCount)); + assert!(compiled.bytecode.clone().contains(&OpAuthOutputCount)); } #[test] @@ -52,12 +64,14 @@ fn lowers_cov_covenant_to_leader_and_delegate_entrypoints() { "#; let compiled = compile_contract(source, &[Expr::int(2), Expr::int(4)], CompileOptions::default()).expect("compile succeeds"); - let abi_names: Vec<&str> = compiled.abi.iter().map(|entry| entry.name.as_str()).collect(); - assert_eq!(abi_names, vec!["__leader_transition_ok", "__delegate"]); + let abi = compile_to_sil_abi_artifact(source, &[2.into(), 4.into()]).expect("portable ABI compiles"); + let artifact = single_contract(&abi); + let abi_names: Vec<&str> = artifact.entries.keys().map(String::as_str).collect(); + assert_eq!(abi_names, vec!["__delegate", "__leader_transition_ok"]); assert!(compiled.ast.functions.iter().any(|f| f.name == "__covenant_policy_transition_ok" && !f.entrypoint)); - assert!(compiled.bytecode.contains(&OpCovInputCount)); - assert!(compiled.bytecode.contains(&OpCovOutputCount)); - assert!(compiled.bytecode.contains(&OpCovInputIdx)); + assert!(compiled.bytecode.clone().contains(&OpCovInputCount)); + assert!(compiled.bytecode.clone().contains(&OpCovOutputCount)); + assert!(compiled.bytecode.clone().contains(&OpCovInputIdx)); } #[test] @@ -72,12 +86,14 @@ fn infers_cov_binding_from_from_greater_than_one_when_binding_omitted() { "#; let compiled = compile_contract(source, &[Expr::int(2), Expr::int(4)], CompileOptions::default()).expect("compile succeeds"); - let abi_names: Vec<&str> = compiled.abi.iter().map(|entry| entry.name.as_str()).collect(); - assert_eq!(abi_names, vec!["__leader_transition_ok", "__delegate"]); + let abi = compile_to_sil_abi_artifact(source, &[2.into(), 4.into()]).expect("portable ABI compiles"); + let artifact = single_contract(&abi); + let abi_names: Vec<&str> = artifact.entries.keys().map(String::as_str).collect(); + assert_eq!(abi_names, vec!["__delegate", "__leader_transition_ok"]); assert!(compiled.ast.functions.iter().any(|f| f.name == "__covenant_policy_transition_ok" && !f.entrypoint)); - assert!(compiled.bytecode.contains(&OpCovInputCount)); - assert!(compiled.bytecode.contains(&OpCovOutputCount)); - assert!(compiled.bytecode.contains(&OpCovInputIdx)); + assert!(compiled.bytecode.clone().contains(&OpCovInputCount)); + assert!(compiled.bytecode.clone().contains(&OpCovOutputCount)); + assert!(compiled.bytecode.clone().contains(&OpCovInputIdx)); } #[test] @@ -93,7 +109,7 @@ fn rejects_cov_verification_without_prev_new_field_arrays() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("cov verification with state fields should require prev/new field arrays"); assert!(err.to_string().contains("expects parameters '(State[] prev_states, State[] new_states, ...)'")); } @@ -111,7 +127,7 @@ fn rejects_cov_transition_without_prev_field_arrays() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("cov transition with state fields should require prev-state field arrays"); assert!(err.to_string().contains("expects parameters '(State[] prev_states, ...)'")); } @@ -129,7 +145,7 @@ fn rejects_auth_verification_without_prev_new_state_shape() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("auth verification with state fields should require prev/new state params"); assert!(err.to_string().contains("mode=verification with binding=auth")); } @@ -147,7 +163,7 @@ fn rejects_auth_transition_without_prev_state_shape() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("auth transition with state fields should require prev-state params"); assert!(err.to_string().contains("mode=transition with binding=auth")); } @@ -163,7 +179,7 @@ fn rejects_auth_transition_when_contract_state_is_empty() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("auth transition should be unsupported when contract state is empty"); assert!(err.to_string().contains("mode=tranisition is not supported when contract state is empty")); } @@ -179,7 +195,7 @@ fn rejects_cov_transition_when_contract_state_is_empty() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("cov transition should be unsupported when contract state is empty"); assert!(err.to_string().contains("mode=tranisition is not supported when contract state is empty")); } @@ -198,7 +214,7 @@ fn rejects_old_per_field_covenant_state_syntax() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("old per-field covenant syntax should be rejected for stateful contracts"); assert!(err.to_string().contains("expects parameters '(State prev_state, State[] new_states, ...)'")); } @@ -216,7 +232,7 @@ fn rejects_canonical_one_to_one_auth_verification_with_scalar_new_state() { } "#; - let err = compile_contract(source, &[Expr::int(7)], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(7)], CompileOptions::default()) .expect_err("canonical one-to-one auth verification should require State[] new_states"); assert!(err.to_string().contains( "mode=verification with binding=auth on function 'step' expects parameters '(State prev_state, State[] new_states, ...)'" @@ -234,9 +250,10 @@ fn lowers_singleton_sugar_to_auth_one_to_one_defaults() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - assert_eq!(compiled.abi[0].name, generated_covenant_auth_entrypoint_name("spend")); - assert!(compiled.bytecode.contains(&OpAuthOutputCount)); + let compiled = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect("compile succeeds"); + let artifact = single_contract(&compiled); + assert!(artifact.entries.contains_key(&generated_covenant_auth_entrypoint_name("spend"))); + assert!(bytecode(&compiled).contains(&OpAuthOutputCount)); } #[test] @@ -250,9 +267,11 @@ fn lowers_fanout_sugar_to_auth_with_to_bound() { } "#; - let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); - assert_eq!(compiled.abi[0].name, generated_covenant_auth_entrypoint_name("split")); - assert!(compiled.bytecode.contains(&OpAuthOutputCount)); + let compiled = compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(3)], CompileOptions::default()) + .expect("compile succeeds"); + let artifact = single_contract(&compiled); + assert!(artifact.entries.contains_key(&generated_covenant_auth_entrypoint_name("split"))); + assert!(bytecode(&compiled).contains(&OpAuthOutputCount)); } #[test] @@ -266,7 +285,7 @@ fn rejects_fanout_sugar_without_to_argument() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("fanout sugar requires to"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect_err("fanout sugar requires to"); assert!(err.to_string().contains("missing covenant attribute argument 'to'")); } @@ -281,7 +300,8 @@ fn rejects_singleton_sugar_with_from_or_to_arguments() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("singleton sugar should reject from/to"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("singleton sugar should reject from/to"); assert!(err.to_string().contains("covenant.singleton is sugar and does not accept 'from' or 'to' arguments")); } @@ -296,7 +316,8 @@ fn rejects_auth_covenant_with_from_not_equal_one() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("auth binding must require from=1"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("auth binding must require from=1"); assert!(err.to_string().contains("binding=auth requires from = 1")); } @@ -311,7 +332,8 @@ fn rejects_cov_covenant_groups_multiple_for_now() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("cov groups=multiple should be rejected"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("cov groups=multiple should be rejected"); assert!(err.to_string().contains("binding=cov with groups=multiple is not supported yet")); } @@ -364,8 +386,12 @@ fn rejects_auth_transition_single_state_return_when_to_is_not_literal_one() { } "#; - let err = compile_contract(source, &[Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])], CompileOptions::default()) - .expect_err("auth transition returning one State must not accept dynamic to bounds"); + let err = compile_to_sil_abi_artifact_with_options( + source, + &[ArtifactValue::Int(4), ArtifactValue::Int(10), ArtifactValue::Bytes(vec![7u8; 32])], + CompileOptions::default(), + ) + .expect_err("auth transition returning one State must not accept dynamic to bounds"); assert!(err.to_string().contains("may return a single State only when 'to' is the literal 1 or omitted")); } @@ -383,7 +409,7 @@ fn rejects_auth_transition_single_state_return_when_to_is_constant_one() { } "#; - let err = compile_contract(source, &[Expr::int(3)], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(3)], CompileOptions::default()) .expect_err("auth transition returning one State should require literal to=1"); assert!(err.to_string().contains("may return a single State only when 'to' is the literal 1 or omitted")); } @@ -436,7 +462,7 @@ fn rejects_omitted_to_for_auth_transition_array_state_return() { } "#; - let err = compile_contract(source, &[Expr::int(3)], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(3)], CompileOptions::default()) .expect_err("omitted to should only infer literal 1 for single State returns"); assert!(err.to_string().contains("missing covenant attribute argument 'to'")); } @@ -454,7 +480,7 @@ fn rejects_singleton_transition_array_returns_without_termination_allowed() { } "#; - let err = compile_contract(source, &[Expr::int(3)], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(3)], CompileOptions::default()) .expect_err("singleton transition arrays should require termination=allowed"); assert!(err.to_string().contains("arrays are not allowed unless termination=allowed")); } @@ -489,8 +515,9 @@ fn rejects_termination_allowed_for_non_singleton() { } "#; - let err = compile_contract(source, &[Expr::int(3), Expr::int(10)], CompileOptions::default()) - .expect_err("termination=allowed should be singleton-only"); + let err = + compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(3), ArtifactValue::Int(10)], CompileOptions::default()) + .expect_err("termination=allowed should be singleton-only"); assert!(err.to_string().contains("termination is only supported for singleton covenants")); } @@ -507,8 +534,9 @@ fn rejects_termination_disallowed_for_non_singleton() { } "#; - let err = compile_contract(source, &[Expr::int(3), Expr::int(10)], CompileOptions::default()) - .expect_err("termination arg should be singleton-only regardless of value"); + let err = + compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(3), ArtifactValue::Int(10)], CompileOptions::default()) + .expect_err("termination arg should be singleton-only regardless of value"); assert!(err.to_string().contains("termination is only supported for singleton covenants")); } @@ -540,7 +568,8 @@ fn rejects_transition_mode_without_return_values() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("transition policy must return values"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("transition policy must return values"); assert!(err.to_string().contains("transition mode policy functions must declare return values")); } @@ -555,7 +584,8 @@ fn rejects_verification_mode_with_return_values() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("verification policy must not return values"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("verification policy must not return values"); assert!(err.to_string().contains("verification mode policy functions must not declare return values")); } @@ -570,10 +600,10 @@ fn auth_covenant_groups_single_injects_shared_count_check() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - assert!(compiled.bytecode.contains(&OpInputCovenantId)); - assert!(compiled.bytecode.contains(&OpCovOutputCount)); - assert!(compiled.bytecode.contains(&OpAuthOutputCount)); + let compiled = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect("compile succeeds"); + assert!(bytecode(&compiled).contains(&OpInputCovenantId)); + assert!(bytecode(&compiled).contains(&OpCovOutputCount)); + assert!(bytecode(&compiled).contains(&OpAuthOutputCount)); } #[test] @@ -592,8 +622,9 @@ fn rejects_mixed_auth_and_cov_covenant_declarations() { } "#; - let err = compile_contract(source, &[Expr::int(2), Expr::int(4)], CompileOptions::default()) - .expect_err("auth-bound declarations must not coexist with generated delegates"); + let err = + compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(2), ArtifactValue::Int(4)], CompileOptions::default()) + .expect_err("auth-bound declarations must not coexist with generated delegates"); let message = err.to_string(); assert!(message.contains("binding=cov"), "unexpected error: {message}"); assert!(message.contains("merge"), "unexpected error: {message}"); @@ -617,8 +648,9 @@ fn rejects_mixed_inferred_auth_and_cov_covenant_declarations() { } "#; - let err = compile_contract(source, &[Expr::int(2), Expr::int(4)], CompileOptions::default()) - .expect_err("inferred auth and cov declarations must not coexist"); + let err = + compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(2), ArtifactValue::Int(4)], CompileOptions::default()) + .expect_err("inferred auth and cov declarations must not coexist"); assert!(err.to_string().contains("cannot use binding=auth"), "unexpected error: {err}"); } @@ -637,8 +669,9 @@ fn leader_contract_rejects_unacknowledged_manual_entrypoint() { } "#; - let err = compile_contract(source, &[Expr::int(2), Expr::int(4)], CompileOptions::default()) - .expect_err("manual entrypoints in leader contracts require acknowledgment"); + let err = + compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(2), ArtifactValue::Int(4)], CompileOptions::default()) + .expect_err("manual entrypoints in leader contracts require acknowledgment"); let message = err.to_string(); assert!(message.contains("manual entrypoint 'recover'"), "unexpected error: {message}"); assert!(message.contains("manual_entrypoint_in_leader_contract"), "unexpected error: {message}"); @@ -663,7 +696,9 @@ fn leader_contract_allows_acknowledged_manual_entrypoint() { let compiled = compile_contract(source, &[Expr::int(2), Expr::int(4)], CompileOptions::default()) .expect("acknowledged manual entrypoint compiles"); - assert!(compiled.entry_by_name("recover").is_some()); + let abi = compile_to_sil_abi_artifact(source, &[2.into(), 4.into()]).expect("portable ABI compiles"); + let artifact = single_contract(&abi); + assert!(artifact.entry("recover").is_some()); let recover = compiled.ast.functions.iter().find(|function| function.name == "recover").expect("recover remains an entrypoint"); assert!(recover.attributes.is_empty(), "allow acknowledgment must not survive lowering"); } @@ -684,24 +719,53 @@ fn allows_multiple_cov_covenant_declarations() { } "#; - let compiled = compile_contract(source, &[Expr::int(2), Expr::int(4)], CompileOptions::default()) + let compiled = compile_to_sil_abi_artifact_with_options(source, &[2.into(), 4.into()], CompileOptions::default()) .expect("multiple cov-bound declarations compile"); - let abi_names: Vec<&str> = compiled.abi.iter().map(|entry| entry.name.as_str()).collect(); - assert_eq!(abi_names, vec!["__leader_merge", "__leader_rebalance", "__delegate"]); - assert_eq!(compiled.cov_decl_to_abi.get("merge"), compiled.entry_by_name("__leader_merge")); - assert_eq!(compiled.cov_decl_to_abi.get("rebalance"), compiled.entry_by_name("__leader_rebalance")); - assert_eq!(compiled.delegate_entry_abi.as_ref(), compiled.entry_by_name("__delegate")); - - let merge_delegate = - compiled.build_sig_script_for_covenant_decl("merge", vec![], Default::default()).expect("merge routes to the shared delegate"); - let rebalance_delegate = compiled - .build_sig_script_for_covenant_decl("rebalance", vec![], Default::default()) + let abi = compile_to_sil_abi_artifact(source, &[2.into(), 4.into()]).expect("portable ABI compiles"); + let artifact = single_contract(&abi); + let abi_names: Vec<&str> = artifact.entries.keys().map(String::as_str).collect(); + assert_eq!(abi_names, vec!["__delegate", "__leader_merge", "__leader_rebalance"]); + assert_eq!(artifact.cov_binding_leader_decl_entry("merge"), artifact.entry("__leader_merge")); + assert_eq!(artifact.cov_binding_leader_decl_entry("rebalance"), artifact.entry("__leader_rebalance")); + assert_eq!(artifact.delegate_entry_abi.as_deref(), Some("__delegate")); + + let merge_delegate = build_sig_script_for_covenant_decl(&compiled, "merge", vec![], Default::default()) + .expect("merge routes to the shared delegate"); + let rebalance_delegate = build_sig_script_for_covenant_decl(&compiled, "rebalance", vec![], Default::default()) .expect("rebalance routes to the shared delegate"); - let shared_delegate = compiled.build_sig_script("__delegate", vec![]).expect("shared delegate sigscript builds"); + let shared_delegate = encode_entry_sig_script(&compiled, "__delegate", &[]).expect("shared delegate sigscript builds"); assert_eq!(merge_delegate, shared_delegate); assert_eq!(rebalance_delegate, shared_delegate); } +#[test] +fn portable_abi_preserves_covenant_declaration_and_delegate_entries() { + let source = r#" + contract Decls(int max_ins, int max_outs) { + #[covenant(binding = cov, from = max_ins, to = max_outs)] + function merge(int nonce) { + require(nonce >= 0); + } + + #[covenant(binding = cov, from = max_ins, to = max_outs)] + function rebalance(int nonce) { + require(nonce >= 0); + } + } + "#; + + let artifact = compile_to_sil_abi_artifact(source, &[2.into(), 4.into()]).expect("portable covenant ABI compiles"); + let json = serde_json::to_string(&artifact).expect("portable covenant ABI serializes"); + let decoded: SilAbiArtifact = serde_json::from_str(&json).expect("portable covenant ABI deserializes"); + let contract = decoded.contract("Decls").expect("contract exists"); + + assert_eq!(contract.cov_decl_to_abi["merge"], "__leader_merge"); + assert_eq!(contract.cov_decl_to_abi["rebalance"], "__leader_rebalance"); + assert_eq!(contract.delegate_entry_abi.as_deref(), Some("__delegate")); + assert_eq!(contract.cov_binding_leader_decl_entry("merge"), contract.entry("__leader_merge")); + assert_eq!(contract.covenant_decl_entry("merge", false), contract.entry("__delegate")); +} + #[test] fn lowers_kcc20_shaped_public_names_and_shared_delegate_body() { let source = r#" @@ -735,30 +799,41 @@ fn lowers_kcc20_shaped_public_names_and_shared_delegate_body() { } "#; - let compiled = compile_contract(source, &[Expr::bytes(vec![7]), Expr::int(10)], CompileOptions::default()) + let compiled = compile_to_sil_abi_artifact_with_options(source, &[vec![7u8].into(), 10.into()], CompileOptions::default()) .expect("KCC20-shaped declaration compiles"); + let abi_artifact = compile_to_sil_abi_artifact(source, &[vec![7u8].into(), 10.into()]).expect("portable ABI compiles"); + let artifact = single_contract(&abi_artifact); - let abi = compiled - .abi + let abi = artifact + .entries .iter() - .map(|entry| (entry.name.as_str(), entry.inputs.iter().map(|input| input.type_name.as_str()).collect::>())) + .map(|(name, entry)| (name.as_str(), entry.params.iter().map(|input| &input.ty).collect::>())) .collect::>(); - assert_eq!(abi, vec![("transfer", vec!["State[]", "byte[]"]), ("transfer_delegator", vec!["byte[]"]),]); - - let transfer = compiled.entry_by_name("transfer").expect("public transfer exists"); + assert_eq!( + abi, + vec![ + ( + "transfer", + vec![ + &TypeArtifact::DynamicArray { item: Box::new(TypeArtifact::Struct { name: "State".to_string() }) }, + &TypeArtifact::Bytes, + ], + ), + ("transfer_delegator", vec![&TypeArtifact::Bytes]), + ] + ); + + let transfer = artifact.entry("transfer").expect("public transfer exists"); let expected_hash = blake3::hash(b"transfer({byte[1],int}[],byte[])"); - assert_eq!(transfer.dispatch_tag, expected_hash.as_bytes()[..4]); + assert_eq!(transfer.dispatch_tag.as_bytes(), &expected_hash.as_bytes()[..4]); - let delegate_args = vec![Expr::dynamic_bytes(vec![7])]; - let routed = compiled - .build_sig_script_for_covenant_decl("transferPolicy", delegate_args.clone(), Default::default()) + let delegate_args = vec![vec![7u8].into()]; + let routed = build_sig_script_for_covenant_decl(&compiled, "transferPolicy", delegate_args.clone(), Default::default()) .expect("declaration helper resolves the overridden delegate name"); - let direct = compiled.build_sig_script("transfer_delegator", delegate_args).expect("public delegate sigscript builds"); + let direct = encode_entry_sig_script(&compiled, "transfer_delegator", &delegate_args).expect("public delegate sigscript builds"); assert_eq!(routed, direct); - assert_eq!(compiled.cov_decl_to_abi.get("transferPolicy"), compiled.entry_by_name("transfer")); - assert_eq!(compiled.delegate_entry_abi.as_ref(), compiled.entry_by_name("transfer_delegator")); - assert_eq!(compiled.covenant_decl_entrypoint_name("transferPolicy", true), Some("transfer")); - assert_eq!(compiled.covenant_decl_entrypoint_name("transferPolicy", false), Some("transfer_delegator")); + assert_eq!(artifact.cov_binding_leader_decl_entry("transferPolicy"), artifact.entry("transfer")); + assert_eq!(artifact.delegate_entry_abi.as_deref(), Some("transfer_delegator")); } #[test] @@ -772,11 +847,11 @@ fn supports_public_name_override_for_auth_bound_declaration() { } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - assert_eq!(compiled.abi[0].name, "spend"); - assert_eq!(compiled.cov_decl_to_abi.get("spendPolicy"), compiled.entry_by_name("spend")); - assert_eq!(compiled.delegate_entry_abi, None); - assert_eq!(compiled.covenant_decl_entrypoint_name("spendPolicy", false), Some("spend")); + let artifact = compile_to_sil_abi_artifact(source, &[]).expect("portable auth ABI compiles"); + let contract = artifact.contract("Decls").expect("contract exists"); + assert!(contract.entries.contains_key("spend")); + assert_eq!(contract.auth_decl_entry("spendPolicy"), contract.entry("spend")); + assert_eq!(contract.delegate_entry_abi, None); } #[test] @@ -794,7 +869,8 @@ fn rejects_duplicate_delegate_bodies() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("duplicate delegate bodies must fail"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("duplicate delegate bodies must fail"); assert!(err.to_string().contains("more than one #[covenant.delegate] body"), "unexpected error: {err}"); } @@ -807,7 +883,8 @@ fn rejects_delegate_body_without_cov_bound_declaration() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("unused delegate body must fail"); + let err = + compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect_err("unused delegate body must fail"); assert!(err.to_string().contains("requires at least one binding=cov"), "unexpected error: {err}"); } @@ -844,7 +921,8 @@ fn rejects_direct_calls_to_the_delegate_body() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("delegate bodies are compiler hooks"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("delegate bodies are compiler hooks"); let message = err.to_string(); assert!(message.contains("covenant-annotated function 'authorizeDelegate' cannot be called directly")); assert!(message.contains("extract shared logic into an unannotated helper function")); @@ -866,7 +944,8 @@ fn rejects_direct_calls_to_a_covenant_policy() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("covenant policies are compiler hooks"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("covenant policies are compiler hooks"); let message = err.to_string(); assert!(message.contains("covenant-annotated function 'transferPolicy' cannot be called directly")); assert!(message.contains("extract shared logic into an unannotated helper function")); @@ -884,7 +963,7 @@ fn rejects_conflicting_shared_delegate_names() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("delegate names must agree"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect_err("delegate names must agree"); assert!(err.to_string().contains("conflicting shared delegate entrypoint names"), "unexpected error: {err}"); } @@ -918,7 +997,8 @@ fn rejects_invalid_delegate_body_signatures() { ]; for source in cases { - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("invalid delegate signature must fail"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("invalid delegate signature must fail"); assert!(err.to_string().contains("#[covenant.delegate]"), "unexpected error: {err}"); } } @@ -938,7 +1018,8 @@ fn rejects_generated_public_name_collision() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("generated public name collision must fail"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("generated public name collision must fail"); assert!(err.to_string().contains("duplicate function name 'transfer'"), "unexpected error: {err}"); } @@ -964,7 +1045,7 @@ fn rejects_generated_public_name_collisions_with_helpers() { ]; for source in cases { - let err = compile_contract(source, &[], CompileOptions::default()) + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) .expect_err("generated public names must not collide with helper functions"); assert!(err.to_string().contains("duplicate function name 'spend'"), "unexpected error: {err}"); } @@ -984,6 +1065,7 @@ fn rejects_per_leader_delegate_policy_selection() { } "#; - let err = compile_contract(source, &[], CompileOptions::default()).expect_err("leaders cannot select delegate policies"); + let err = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("leaders cannot select delegate policies"); assert!(err.to_string().contains("unknown covenant attribute argument 'delegate_policy'"), "unexpected error: {err}"); } diff --git a/silverscript-lang/tests/covenant_declaration_security_tests.rs b/silverscript-lang/tests/covenant_declaration_security_tests.rs index aea58688..76ee5b7e 100644 --- a/silverscript-lang/tests/covenant_declaration_security_tests.rs +++ b/silverscript-lang/tests/covenant_declaration_security_tests.rs @@ -1,17 +1,20 @@ +use std::collections::BTreeMap; + use kaspa_consensus_core::Hash; use kaspa_consensus_core::tx::{Transaction, TransactionOutput, UtxoEntry}; use kaspa_txscript_errors::TxScriptError; -use silverscript_lang::ast::{Expr, parse_type_ref}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::ast::Expr; use silverscript_lang::compiler::{ - CompileOptions, CompiledContract, CovenantDeclCallOptions, compile_contract, generated_covenant_auth_entrypoint_name, - struct_object, + CompileOptions, CompiledContract, CovenantDeclCallOptions, compile_contract, compile_to_sil_abi_artifact_with_options, + generated_covenant_auth_entrypoint_name, }; mod common; use common::{ - assert_verify_like_error, covenant_decl_sigscript, covenant_output, covenant_utxo, execute_input_with_covenants, - plain_covenant_output, plain_utxo, push_redeem_script, tx_input, + assert_verify_like_error, build_sig_script_for_covenant_decl, bytecode, covenant_decl_sigscript, covenant_output, covenant_utxo, + execute_input_with_covenants, plain_covenant_output, plain_utxo, push_redeem_script, tx_input, }; const COV_A: Hash = Hash::from_bytes(*b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); @@ -152,8 +155,9 @@ const AUTH_VERIFICATION_CARDINALITY_SOURCE: &str = r#" } "#; -fn compile_state(source: &'static str, value: i64) -> CompiledContract<'static> { - compile_contract(source, &[Expr::int(value)], CompileOptions::default()).expect("compile succeeds") +fn compile_state(source: &'static str, value: i64) -> silverscript_abi::SilAbiArtifact { + compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(value)], CompileOptions::default()) + .expect("compile succeeds") } fn function_param_type_names(compiled: &CompiledContract<'_>, function_name: &str) -> Vec { @@ -169,32 +173,33 @@ fn function_param_type_names(compiled: &CompiledContract<'_>, function_name: &st .collect() } -fn state_array_arg(values: Vec) -> Expr<'static> { - Expr::array( - parse_type_ref("State[]").unwrap(), - values.into_iter().map(|value| struct_object("State", vec![("value", Expr::int(value))])).collect(), - ) +fn state_array_arg(values: Vec) -> ArtifactValue { + ArtifactValue::Array(values.into_iter().map(state_arg).collect()) } -fn state_arg(value: i64) -> Expr<'static> { - struct_object("State", vec![("value", Expr::int(value))]) +fn state_arg(value: i64) -> ArtifactValue { + BTreeMap::from([("value".to_string(), value.into())]).into() } -fn compile_kcc20_state(owner: u8, amount: i64) -> CompiledContract<'static> { - compile_contract(KCC20_SHAPED_SOURCE, &[Expr::bytes(vec![owner]), Expr::int(amount)], CompileOptions::default()) - .expect("KCC20-shaped contract compiles") +fn compile_kcc20_state(owner: u8, amount: i64) -> silverscript_abi::SilAbiArtifact { + compile_to_sil_abi_artifact_with_options( + KCC20_SHAPED_SOURCE, + &[ArtifactValue::Bytes(vec![owner]), ArtifactValue::Int(amount)], + CompileOptions::default(), + ) + .expect("KCC20-shaped contract compiles") } -fn kcc20_state_arg(owner: u8, amount: i64) -> Expr<'static> { - struct_object("State", vec![("owner", Expr::bytes(vec![owner])), ("amount", Expr::int(amount))]) +fn kcc20_state_arg(owner: u8, amount: i64) -> ArtifactValue { + BTreeMap::from([("owner".to_string(), vec![owner].into()), ("amount".to_string(), amount.into())]).into() } -fn cov_decl_nm_leader_sigscript(compiled: &CompiledContract<'_>, next_values: Vec) -> Vec { +fn cov_decl_nm_leader_sigscript(compiled: &silverscript_abi::SilAbiArtifact, next_values: Vec) -> Vec { covenant_decl_sigscript(compiled, "rebalance", vec![state_array_arg(next_values)], true) } -fn redeem_only_sigscript(compiled: &CompiledContract<'_>) -> Vec { - push_redeem_script(&compiled.bytecode) +fn redeem_only_sigscript(compiled: &silverscript_abi::SilAbiArtifact) -> Vec { + push_redeem_script(&bytecode(compiled)) } #[test] @@ -231,7 +236,7 @@ fn singleton_transition_allows_correct_state_update() { let active = compile_state(AUTH_SINGLETON_TRANSITION_SOURCE, 10); let out = compile_state(AUTH_SINGLETON_TRANSITION_SOURCE, 13); - let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![Expr::int(3)], false)); + let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![ArtifactValue::Int(3)], false)); let outputs = vec![covenant_output(&out, 0, COV_A)]; let tx = Transaction::new(1, vec![input0], outputs, 0, Default::default(), 0, vec![]); let entries = vec![covenant_utxo(&active, COV_A)]; @@ -245,7 +250,7 @@ fn singleton_transition_rejects_mismatched_output_state() { let active = compile_state(AUTH_SINGLETON_TRANSITION_SOURCE, 10); let wrong_out = compile_state(AUTH_SINGLETON_TRANSITION_SOURCE, 12); - let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![Expr::int(3)], false)); + let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![ArtifactValue::Int(3)], false)); let outputs = vec![covenant_output(&wrong_out, 0, COV_A)]; let tx = Transaction::new(1, vec![input0], outputs, 0, Default::default(), 0, vec![]); let entries = vec![covenant_utxo(&active, COV_A)]; @@ -260,7 +265,7 @@ fn singleton_transition_rejects_two_authorized_outputs() { let out0 = compile_state(AUTH_SINGLETON_TRANSITION_SOURCE, 13); let out1 = compile_state(AUTH_SINGLETON_TRANSITION_SOURCE, 13); - let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![Expr::int(3)], false)); + let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![ArtifactValue::Int(3)], false)); let outputs = vec![covenant_output(&out0, 0, COV_A), covenant_output(&out1, 0, COV_A)]; let tx = Transaction::new(1, vec![input0], outputs, 0, Default::default(), 0, vec![]); let entries = vec![covenant_utxo(&active, COV_A)]; @@ -273,7 +278,7 @@ fn singleton_transition_rejects_two_authorized_outputs() { fn singleton_transition_rejects_missing_authorized_output() { let active = compile_state(AUTH_SINGLETON_TRANSITION_SOURCE, 10); - let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![Expr::int(3)], false)); + let input0 = tx_input(0, covenant_decl_sigscript(&active, "bump", vec![ArtifactValue::Int(3)], false)); let tx = Transaction::new(1, vec![input0], vec![], 0, Default::default(), 0, vec![]); let entries = vec![covenant_utxo(&active, COV_A)]; @@ -433,8 +438,8 @@ fn many_to_many_happy_path_succeeds() { let in1 = compile_state(COV_N_TO_M_SOURCE, 7); let out0 = compile_state(COV_N_TO_M_SOURCE, 10); let out1 = compile_state(COV_N_TO_M_SOURCE, 10); - assert_eq!(in0.bytecode, out0.bytecode, "leader input and output[0] script should match"); - assert_eq!(in0.bytecode, out1.bytecode, "leader input and output[1] script should match"); + assert_eq!(bytecode(&in0), bytecode(&out0), "leader input and output[0] script should match"); + assert_eq!(bytecode(&in0), bytecode(&out1), "leader input and output[1] script should match"); // Intended valid shape: two covenant inputs in the same id, two covenant outputs in the same id, // leader path on input 0 and delegate path on input 1. @@ -453,11 +458,10 @@ fn shared_delegate_body_authenticates_each_inputs_local_state() { let in1 = compile_kcc20_state(2, 7); let out0 = compile_kcc20_state(3, 8); let out1 = compile_kcc20_state(4, 9); - let next_states = - Expr::array(parse_type_ref("State[]").expect("State[] parses"), vec![kcc20_state_arg(3, 8), kcc20_state_arg(4, 9)]); + let next_states = ArtifactValue::Array(vec![kcc20_state_arg(3, 8), kcc20_state_arg(4, 9)]); - let leader_sigscript = covenant_decl_sigscript(&in0, "transferPolicy", vec![next_states, Expr::dynamic_bytes(vec![1])], true); - let delegate_sigscript = covenant_decl_sigscript(&in1, "transferPolicy", vec![Expr::dynamic_bytes(vec![2])], false); + let leader_sigscript = covenant_decl_sigscript(&in0, "transferPolicy", vec![next_states, ArtifactValue::Bytes(vec![1])], true); + let delegate_sigscript = covenant_decl_sigscript(&in1, "transferPolicy", vec![ArtifactValue::Bytes(vec![2])], false); let outputs = vec![covenant_output(&out0, 0, COV_A), covenant_output(&out1, 1, COV_A)]; let tx = Transaction::new( 1, @@ -473,7 +477,7 @@ fn shared_delegate_body_authenticates_each_inputs_local_state() { execute_input_with_covenants(tx.clone(), entries.clone(), 0).expect("leader authenticates its local owner"); execute_input_with_covenants(tx, entries.clone(), 1).expect("delegate authenticates its own local owner"); - let wrong_delegate_sigscript = covenant_decl_sigscript(&in1, "transferPolicy", vec![Expr::dynamic_bytes(vec![1])], false); + let wrong_delegate_sigscript = covenant_decl_sigscript(&in1, "transferPolicy", vec![ArtifactValue::Bytes(vec![1])], false); let wrong_tx = Transaction::new( 1, vec![tx_input(0, redeem_only_sigscript(&in0)), tx_input(1, wrong_delegate_sigscript)], @@ -561,18 +565,17 @@ fn many_to_many_leader_rejects_cov_output_with_different_script() { #[test] fn many_to_many_transition_leader_rejects_spoofed_prev_states() { let in0 = compile_state(COV_N_TO_M_TRANSITION_SOURCE, 10); - let honest = in0 - .build_sig_script_for_covenant_decl("carry_forward", vec![], CovenantDeclCallOptions { is_leader: true }) + let honest = build_sig_script_for_covenant_decl(&in0, "carry_forward", vec![], CovenantDeclCallOptions { is_leader: true }) .expect("leader transition call should succeed without caller-supplied prev_states"); assert!(!honest.is_empty(), "leader transition sigscript should not be empty"); - let err = in0 - .build_sig_script_for_covenant_decl( - "carry_forward", - vec![state_array_arg(vec![42, 43])], - CovenantDeclCallOptions { is_leader: true }, - ) - .expect_err("spoofed prev_states should no longer be accepted through the leader ABI"); + let err = build_sig_script_for_covenant_decl( + &in0, + "carry_forward", + vec![state_array_arg(vec![42, 43])], + CovenantDeclCallOptions { is_leader: true }, + ) + .expect_err("spoofed prev_states should no longer be accepted through the leader ABI"); assert!(matches!(err, silverscript_lang::compiler::CompilerError::Unsupported(_)), "unexpected error: {err:?}"); } @@ -610,12 +613,14 @@ fn runtime_accepts_state_entrypoint_argument_for_generated_wrapper() { fn runtime_passes_state_into_generated_policy_function() { let active = compile_state(AUTH_SINGLETON_ARRAY_RUNTIME_SOURCE, 10); let out = compile_state(AUTH_SINGLETON_ARRAY_RUNTIME_SOURCE, 11); + let lowered = compile_contract(AUTH_SINGLETON_ARRAY_RUNTIME_SOURCE, &[Expr::int(10)], CompileOptions::default()) + .expect("lowered AST compiles"); let wrapper_name = generated_covenant_auth_entrypoint_name("step"); - let wrapper_param_types = function_param_type_names(&active, &wrapper_name); + let wrapper_param_types = function_param_type_names(&lowered, &wrapper_name); assert_eq!(wrapper_param_types, vec!["State".to_string()]); - let policy = active + let policy = lowered .ast .functions .iter() diff --git a/silverscript-lang/tests/examples_tests.rs b/silverscript-lang/tests/examples_tests.rs index c4c9e2fb..6a6e4401 100644 --- a/silverscript-lang/tests/examples_tests.rs +++ b/silverscript-lang/tests/examples_tests.rs @@ -1,3 +1,5 @@ +mod common; + use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::hashing::sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash}; use kaspa_consensus_core::hashing::sighash_type::SIG_HASH_ALL; @@ -13,10 +15,12 @@ use kaspa_txscript::script_builder::ScriptBuilder; use kaspa_txscript::{EngineCtx, EngineFlags, TxScriptEngine, pay_to_script_hash_script}; use rand::{RngCore, thread_rng}; use secp256k1::{Keypair, Secp256k1, SecretKey}; -use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::compiler::{CompileOptions, compile_to_sil_abi_artifact_with_options}; use std::fs; +use common::{bytecode, encode_entry_sig_script, encode_single_entry_sig_script}; + fn build_null_data_script(tag: i64, message: &str) -> Vec { ScriptBuilder::new().add_op(OpReturn).unwrap().add_i64(tag).unwrap().add_data(message.as_bytes()).unwrap().drain() } @@ -171,18 +175,18 @@ fn r0_groth16_fixture() -> (Vec, Vec, Vec) { fn compiles_announcement_example_and_verifies() { let source = load_example_source("announcement.sil"); - let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[], CompileOptions::default()).expect("compile succeeds"); let message = "A contract may not injure a human being or, through inaction, allow a human being to come to harm."; let announcement_script = build_null_data_script(27906, message); // Test announce() with changeAmount >= minerFee (else branch). - let sigscript = compiled.build_sig_script("announce", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "announce", &[]).expect("sigscript builds"); let input_value = 3000u64; let output1_value = input_value - 1000; let result = run_contract_with_tx( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), announcement_script.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), input_value, 0, output1_value, @@ -192,13 +196,13 @@ fn compiles_announcement_example_and_verifies() { assert!(result.is_ok(), "announcement example failed: {}", result.unwrap_err()); // Test announce() with changeAmount < minerFee (if branch). - let sigscript = compiled.build_sig_script("announce", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "announce", &[]).expect("sigscript builds"); let input_value = 1500u64; let output1_value = 1u64; let result = run_contract_with_tx( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), announcement_script, - compiled.bytecode, + bytecode(&compiled), input_value, 0, output1_value, @@ -212,19 +216,19 @@ fn compiles_announcement_example_and_verifies() { fn compiles_constant_budget_example_and_verifies() { let source = load_example_source("constant_budget.sil"); - let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[], CompileOptions::default()).expect("compile succeeds"); let recipient0 = [2u8; 32]; let recipient1 = [3u8; 32]; let output0_script = build_p2pk_script(&recipient0); let output1_script = build_p2pk_script(&recipient1); // Test spend() with output1 >= MIN_CHANGE (if branch). - let sigscript = compiled.build_sig_script("spend", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "spend", &[]).expect("sigscript builds"); let input_value = 4000u64; let output0_value = 1500u64; let output1_value = 1200u64; let result = run_contract_with_tx( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), output0_script.clone(), output1_script.clone(), input_value, @@ -236,12 +240,12 @@ fn compiles_constant_budget_example_and_verifies() { assert!(result.is_ok(), "constant_budget if branch failed: {}", result.unwrap_err()); // Test spend() with output1 < MIN_CHANGE (else branch). - let sigscript = compiled.build_sig_script("spend", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "spend", &[]).expect("sigscript builds"); let input_value = 3000u64; let output0_value = 1300u64; let output1_value = 500u64; let result = run_contract_with_tx( - compiled.bytecode, + bytecode(&compiled), output0_script, output1_script, input_value, @@ -257,7 +261,7 @@ fn compiles_constant_budget_example_and_verifies() { fn compiles_for_loop_example_and_verifies() { let source = load_example_source("for_loop.sil"); - let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("compile succeeds"); + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[], CompileOptions::default()).expect("compile succeeds"); let recipient0 = [5u8; 32]; let recipient1 = [6u8; 32]; let recipient2 = [7u8; 32]; @@ -268,7 +272,7 @@ fn compiles_for_loop_example_and_verifies() { let output3_script = build_p2pk_script(&recipient3); // Test check() with loop bounds START..END. - let sigscript = compiled.build_sig_script("check", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "check", &[]).expect("sigscript builds"); let input_value = 10_000u64; let outputs = vec![ (1000u64, output0_script.clone()), @@ -276,11 +280,11 @@ fn compiles_for_loop_example_and_verifies() { (1002u64, output2_script.clone()), (1003u64, output3_script.clone()), ]; - let result = run_contract_with_outputs(compiled.bytecode.clone(), outputs, input_value, sigscript, 0); + let result = run_contract_with_outputs(bytecode(&compiled).clone(), outputs, input_value, sigscript, 0); assert!(result.is_ok(), "for_loop example failed: {}", result.unwrap_err()); // Test check() failure when require fails in the loop. - let sigscript = compiled.build_sig_script("check", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "check", &[]).expect("sigscript builds"); let input_value = 10_000u64; let outputs = vec![ (1000u64, output0_script.clone()), @@ -288,14 +292,14 @@ fn compiles_for_loop_example_and_verifies() { (999u64, output2_script.clone()), (1003u64, output3_script.clone()), ]; - let result = run_contract_with_outputs(compiled.bytecode.clone(), outputs, input_value, sigscript, 0); + let result = run_contract_with_outputs(bytecode(&compiled).clone(), outputs, input_value, sigscript, 0); assert!(result.is_err(), "for_loop require failure should error"); // Test check() failure when there are fewer than 4 outputs. - let sigscript = compiled.build_sig_script("check", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "check", &[]).expect("sigscript builds"); let input_value = 10_000u64; let outputs = vec![(1000u64, output0_script), (1001u64, output1_script), (1002u64, output2_script)]; - let result = run_contract_with_outputs(compiled.bytecode, outputs, input_value, sigscript, 0); + let result = run_contract_with_outputs(bytecode(&compiled), outputs, input_value, sigscript, 0); assert!(result.is_err(), "for_loop with too few outputs should error"); } @@ -304,7 +308,8 @@ fn compiles_for_loop_ctor_example_with_constructor_bounds() { let source = load_example_source("for_loop_ctor.sil"); let constructor_args = [(0).into(), (4).into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let recipient0 = [5u8; 32]; let recipient1 = [6u8; 32]; let recipient2 = [7u8; 32]; @@ -314,7 +319,7 @@ fn compiles_for_loop_ctor_example_with_constructor_bounds() { let output2_script = build_p2pk_script(&recipient2); let output3_script = build_p2pk_script(&recipient3); - let sigscript = compiled.build_sig_script("check", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "check", &[]).expect("sigscript builds"); let input_value = 10_000u64; let outputs = vec![ (1000u64, output0_script.clone()), @@ -322,7 +327,7 @@ fn compiles_for_loop_ctor_example_with_constructor_bounds() { (1002u64, output2_script.clone()), (1003u64, output3_script.clone()), ]; - let result = run_contract_with_outputs(compiled.bytecode, outputs, input_value, sigscript, 0); + let result = run_contract_with_outputs(bytecode(&compiled), outputs, input_value, sigscript, 0); assert!(result.is_ok(), "for_loop_ctor example failed: {}", result.unwrap_err()); } @@ -331,15 +336,15 @@ fn compiles_return_basic_example_file_and_verifies() { let source = load_example_source("return_basic.sil"); let options = CompileOptions { allow_entrypoint_return: true, ..CompileOptions::default() }; - let compiled = compile_contract(&source, &[], options).expect("compile succeeds"); - let script = bytecode_with_return_checks(compiled.bytecode.clone(), &[12, 8]); + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[], options).expect("compile succeeds"); + let script = bytecode_with_return_checks(bytecode(&compiled).clone(), &[12, 8]); let recipient0 = [9u8; 32]; let recipient1 = [10u8; 32]; let output0_script = build_p2pk_script(&recipient0); let output1_script = build_p2pk_script(&recipient1); // Test main(b=8) returns [12, 8] on stack. - let sigscript = compiled.build_sig_script("main", vec![8.into()]).expect("sigscript builds"); + let sigscript = encode_single_entry_sig_script(&compiled, &[8.into()]).expect("sigscript builds"); let result = run_contract_with_tx(script, output0_script, output1_script, 2000, 500, 500, sigscript, 0); assert!(result.is_ok(), "return basic failed: {}", result.unwrap_err()); } @@ -349,15 +354,15 @@ fn compiles_return_loop_example_file_and_verifies() { let source = load_example_source("return_loop.sil"); let options = CompileOptions { allow_entrypoint_return: true, ..CompileOptions::default() }; - let compiled = compile_contract(&source, &[], options).expect("compile succeeds"); - let script = bytecode_with_return_checks(compiled.bytecode.clone(), &[10]); + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[], options).expect("compile succeeds"); + let script = bytecode_with_return_checks(bytecode(&compiled).clone(), &[10]); let recipient0 = [11u8; 32]; let recipient1 = [12u8; 32]; let output0_script = build_p2pk_script(&recipient0); let output1_script = build_p2pk_script(&recipient1); // Test main() returns the loop total on stack. - let sigscript = compiled.build_sig_script("main", vec![]).expect("sigscript builds"); + let sigscript = encode_single_entry_sig_script(&compiled, &[]).expect("sigscript builds"); let result = run_contract_with_tx(script, output0_script, output1_script, 2000, 500, 500, sigscript, 0); assert!(result.is_ok(), "return loop failed: {}", result.unwrap_err()); } @@ -366,14 +371,15 @@ fn compiles_return_loop_example_file_and_verifies() { fn compiles_r0_g16_example_and_verifies() { let source = load_example_source("r0_g16.sil"); let (journal_hash, proof, image_id) = r0_groth16_fixture(); - let compiled = compile_contract(&source, &[image_id.into()], CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &[image_id.into()], CompileOptions::default()).expect("compile succeeds"); let sigscript = - compiled.build_sig_script("verify", vec![journal_hash.into(), Expr::dynamic_bytes(proof)]).expect("sigscript builds"); + encode_entry_sig_script(&compiled, "verify", &[journal_hash.into(), ArtifactValue::Bytes(proof)]).expect("sigscript builds"); let result = run_contract_with_tx( - compiled.bytecode.clone(), - compiled.bytecode.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), 2000, 500, 500, @@ -387,15 +393,15 @@ fn compiles_r0_g16_example_and_verifies() { fn compiles_g16_verify_example_and_verifies() { let source = load_example_source("g16_verify.sil"); let (verifying_key, proof, public_inputs) = kaspa_txscript::zk_precompiles::tests::helpers::load_groth_fields(); - let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("compile succeeds"); - let mut args: Vec> = vec![Expr::dynamic_bytes(verifying_key), Expr::dynamic_bytes(proof)]; + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[], CompileOptions::default()).expect("compile succeeds"); + let mut args = vec![ArtifactValue::Bytes(verifying_key), ArtifactValue::Bytes(proof)]; args.extend(public_inputs.into_iter().map(Into::into)); - let sigscript = compiled.build_sig_script("verify", args).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "verify", &args).expect("sigscript builds"); let result = run_contract_with_tx( - compiled.bytecode.clone(), - compiled.bytecode.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), 2000, 500, 500, @@ -411,19 +417,19 @@ fn compiles_r0_succinct_example_and_verifies() { let (control_id, seal, claim, hashfn, control_index, control_digests, journal, image_id) = kaspa_txscript::zk_precompiles::tests::helpers::load_stark_fields(); assert_eq!(hashfn, vec![1u8], "fixture should use Poseidon2 hash function id"); - let compiled = - compile_contract(&source, &[image_id.into(), control_id.into()], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled - .build_sig_script( - "verify", - vec![claim.into(), control_index.into(), Expr::dynamic_bytes(control_digests), Expr::dynamic_bytes(seal), journal.into()], - ) - .expect("sigscript builds"); + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[image_id.into(), control_id.into()], CompileOptions::default()) + .expect("compile succeeds"); + let sigscript = encode_entry_sig_script( + &compiled, + "verify", + &[claim.into(), control_index.into(), ArtifactValue::Bytes(control_digests), ArtifactValue::Bytes(seal), journal.into()], + ) + .expect("sigscript builds"); let result = run_contract_with_tx( - compiled.bytecode.clone(), - compiled.bytecode.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), 2000, 500, 500, @@ -439,8 +445,8 @@ fn r0_succinct_sha256_example_is_reserved_for_future_use() { let image_id = vec![0x11u8; 32]; let control_id = vec![0x22u8; 32]; - let err = - compile_contract(&source, &[image_id.into(), control_id.into()], CompileOptions::default()).expect_err("compile should fail"); + let err = compile_to_sil_abi_artifact_with_options(&source, &[image_id.into(), control_id.into()], CompileOptions::default()) + .expect_err("compile should fail"); assert!(err.to_string().contains("only Poseidon2 R0 Succinct verification is currently supported"), "unexpected error: {err}"); } @@ -455,14 +461,14 @@ fn compiles_return_basic_example_and_verifies() { "#; let options = CompileOptions { allow_entrypoint_return: true, ..CompileOptions::default() }; - let compiled = compile_contract(source, &[], options).expect("compile succeeds"); - let script = bytecode_with_return_checks(compiled.bytecode.clone(), &[2, 5]); + let compiled = compile_to_sil_abi_artifact_with_options(source, &[], options).expect("compile succeeds"); + let script = bytecode_with_return_checks(bytecode(&compiled).clone(), &[2, 5]); let recipient0 = [13u8; 32]; let recipient1 = [14u8; 32]; let output0_script = build_p2pk_script(&recipient0); let output1_script = build_p2pk_script(&recipient1); - let sigscript = compiled.build_sig_script("main", vec![1.into(), 3.into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "main", &[1.into(), 3.into()]).expect("sigscript builds"); let result = run_contract_with_tx(script, output0_script, output1_script, 2000, 500, 500, sigscript, 0); assert!(result.is_ok(), "return basic failed: {}", result.unwrap_err()); } @@ -479,7 +485,8 @@ fn runs_everything_example_and_verifies() { let owner_pk = owner.x_only_public_key().0.serialize(); let constructor_args = [7.into(), String::from("hello").into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([23u8; 32]), index: 0 }, @@ -489,13 +496,13 @@ fn runs_everything_example_and_verifies() { }; let output = TransactionOutput { value: 5_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -507,7 +514,7 @@ fn runs_everything_example_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); let sigscript = - compiled.build_sig_script("hello", vec![owner_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + encode_entry_sig_script(&compiled, "hello", &[owner_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -533,12 +540,13 @@ fn runs_sum_series_example_with_multiple_inputs() { for (max_iterations, n, should_pass) in cases { let constructor_args = [max_iterations.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![n.into()]).expect("sigscript builds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let sigscript = encode_single_entry_sig_script(&compiled, &[n.into()]).expect("sigscript builds"); let result = run_contract_with_tx( - compiled.bytecode.clone(), - compiled.bytecode.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), 2000, 500, 500, @@ -562,12 +570,13 @@ fn runs_complex_assignments_example_and_verifies() { for (limit, n) in cases { let constructor_args = [limit.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("main", vec![n.into()]).expect("sigscript builds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let sigscript = encode_single_entry_sig_script(&compiled, &[n.into()]).expect("sigscript builds"); let result = run_contract_with_tx( - compiled.bytecode.clone(), - compiled.bytecode.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), 2000, 500, 500, @@ -597,7 +606,8 @@ fn compiles_hodl_vault_example_and_verifies() { let oracle_sig = oracle.sign_schnorr(oracle_signed).as_ref().to_vec(); let constructor_args = vec![owner_pk.to_vec().into(), oracle_pk.to_vec().into(), min_block.into(), price_target.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([7u8; 32]), index: 0 }, @@ -607,13 +617,13 @@ fn compiles_hodl_vault_example_and_verifies() { }; let output = TransactionOutput { value: 5000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], block_height as u64, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -625,9 +635,12 @@ fn compiles_hodl_vault_example_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test spend() function call (build sigscript for spend()). - let sigscript = compiled - .build_sig_script("spend", vec![signature.clone().into(), oracle_sig.into(), Expr::dynamic_bytes(oracle_message.clone())]) - .expect("sigscript builds"); + let sigscript = encode_entry_sig_script( + &compiled, + "spend", + &[signature.clone().into(), oracle_sig.into(), ArtifactValue::Bytes(oracle_message.clone())], + ) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -657,19 +670,20 @@ fn compiles_mecenas_example_and_verifies() { let pledge = 2000i64; let constructor_args = vec![recipient.to_vec().into(), funder_hash.clone().into(), pledge.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); // Test receive() with changeValue > pledge + minerFee (else branch). - let sigscript = compiled.build_sig_script("receive", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "receive", &[]).expect("sigscript builds"); let input_value = 10000u64; let output0_value = pledge as u64; let output1_value = input_value - pledge as u64 - 1000; let output0_script = build_p2pk_script(&recipient); let result = run_contract_with_tx( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), output0_script, - compiled.bytecode.clone(), + bytecode(&compiled).clone(), input_value, output0_value, output1_value, @@ -679,7 +693,7 @@ fn compiles_mecenas_example_and_verifies() { assert!(result.is_ok(), "mecenas example failed: {}", result.unwrap_err()); // Test receive() with changeValue <= pledge + minerFee (if branch). - let sigscript = compiled.build_sig_script("receive", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "receive", &[]).expect("sigscript builds"); let input_value = 6000u64; let output0_value = input_value - 1000; @@ -687,9 +701,9 @@ fn compiles_mecenas_example_and_verifies() { let output0_script = build_p2pk_script(&recipient); let result = run_contract_with_tx( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), output0_script, - compiled.bytecode.clone(), + bytecode(&compiled).clone(), input_value, output0_value, output1_value, @@ -706,13 +720,13 @@ fn compiles_mecenas_example_and_verifies() { }; let output = TransactionOutput { value: 5000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -724,8 +738,8 @@ fn compiles_mecenas_example_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test reclaim() function call (build sigscript for reclaim()). - let sigscript = - compiled.build_sig_script("reclaim", vec![funder_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "reclaim", &[funder_pk.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -762,14 +776,15 @@ fn compiles_mecenas_locktime_example_and_verifies() { initial_block.to_le_bytes().to_vec().into(), ]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let passed_blocks = lock_time - initial_block; let pledge = passed_blocks as i64 * pledge_per_block; let output0_script = build_p2pk_script(&recipient); - let mut active_bytecode = Vec::with_capacity(2 + compiled.bytecode.len()); + let mut active_bytecode = Vec::with_capacity(2 + bytecode(&compiled).len()); active_bytecode.extend_from_slice(&0u16.to_be_bytes()); - active_bytecode.extend_from_slice(&compiled.bytecode); + active_bytecode.extend_from_slice(&bytecode(&compiled)); let mut bc_value = Vec::new(); bc_value.push(8u8); bc_value.extend_from_slice(&lock_time.to_le_bytes()); @@ -777,13 +792,13 @@ fn compiles_mecenas_locktime_example_and_verifies() { let output1_script = pay_to_script_hash_script(&bc_value).script().to_vec(); // Test receive() with changeValue > pledgePerBlock + minerFee (else branch). - let sigscript = compiled.build_sig_script("receive", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "receive", &[]).expect("sigscript builds"); let input_value = 20000u64; let output0_value = pledge as u64; let output1_value = input_value - pledge as u64 - 1000; let result = run_contract_with_tx( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), output0_script.clone(), output1_script, input_value, @@ -795,16 +810,16 @@ fn compiles_mecenas_locktime_example_and_verifies() { assert!(result.is_ok(), "mecenas_locktime example failed: {}", result.unwrap_err()); // Test receive() with changeValue <= pledgePerBlock + minerFee (if branch). - let sigscript = compiled.build_sig_script("receive", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "receive", &[]).expect("sigscript builds"); let input_value = 11000u64; let output0_value = input_value - 1000; let output1_value = 0u64; let result = run_contract_with_tx( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), output0_script, - compiled.bytecode.clone(), + bytecode(&compiled).clone(), input_value, output0_value, output1_value, @@ -821,13 +836,13 @@ fn compiles_mecenas_locktime_example_and_verifies() { }; let output = TransactionOutput { value: 6000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -839,8 +854,8 @@ fn compiles_mecenas_locktime_example_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test reclaim() function call (build sigscript for reclaim()). - let sigscript = - compiled.build_sig_script("reclaim", vec![funder_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "reclaim", &[funder_pk.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -867,7 +882,8 @@ fn compiles_p2pkh_example_and_verifies() { let pkh = blake2b_simd::Params::new().hash_length(32).to_state().update(pubkey_bytes.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = [pkh.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([5u8; 32]), index: 0 }, @@ -877,13 +893,13 @@ fn compiles_p2pkh_example_and_verifies() { }; let output = TransactionOutput { value: 7000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -896,7 +912,7 @@ fn compiles_p2pkh_example_and_verifies() { let mut run = |signature: Vec| { tx.tx.inputs[0].signature_script = - compiled.build_sig_script("spend", vec![pubkey_bytes.to_vec().into(), signature.into()]).expect("sigscript builds"); + encode_entry_sig_script(&compiled, "spend", &[pubkey_bytes.to_vec().into(), signature.into()]).expect("sigscript builds"); let verifiable_tx = tx.as_verifiable(); let sig_cache = Cache::new(100); @@ -925,7 +941,8 @@ fn compiles_p2pkh_ecdsa_example_and_verifies() { let pkh = blake2b_simd::Params::new().hash_length(32).to_state().update(pubkey_bytes.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = [pkh.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([5u8; 32]), index: 0 }, @@ -935,13 +952,13 @@ fn compiles_p2pkh_ecdsa_example_and_verifies() { }; let output = TransactionOutput { value: 7000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -953,7 +970,7 @@ fn compiles_p2pkh_ecdsa_example_and_verifies() { let mut run = |signature: Vec| { tx.tx.inputs[0].signature_script = - compiled.build_sig_script("spend", vec![pubkey_bytes.to_vec().into(), signature.into()]).expect("sigscript builds"); + encode_entry_sig_script(&compiled, "spend", &[pubkey_bytes.to_vec().into(), signature.into()]).expect("sigscript builds"); let verifiable_tx = tx.as_verifiable(); let sig_cache = Cache::new(100); @@ -982,9 +999,10 @@ fn compiles_transfer_with_timeout_and_verifies() { let sender_pk = sender.x_only_public_key().0.serialize(); let recipient_pk = recipient.x_only_public_key().0.serialize(); let timeout = kaspa_txscript::LOCK_TIME_THRESHOLD as i64; - let constructor_args = vec![sender_pk.to_vec().into(), recipient_pk.to_vec().into(), Expr::temporal(timeout)]; + let constructor_args = vec![sender_pk.to_vec().into(), recipient_pk.to_vec().into(), timeout.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([6u8; 32]), index: 0 }, @@ -994,13 +1012,13 @@ fn compiles_transfer_with_timeout_and_verifies() { }; let output = TransactionOutput { value: 8_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1012,7 +1030,7 @@ fn compiles_transfer_with_timeout_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test transfer() function call (build sigscript for transfer()). - let sigscript = compiled.build_sig_script("transfer", vec![signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "transfer", &[signature.clone().into()]).expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1038,13 +1056,13 @@ fn compiles_transfer_with_timeout_and_verifies() { }; let output = TransactionOutput { value: 9_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], lock_time, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1056,7 +1074,7 @@ fn compiles_transfer_with_timeout_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test timeout() function call (build sigscript for timeout()). - let sigscript = compiled.build_sig_script("timeout", vec![signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "timeout", &[signature.clone().into()]).expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1086,7 +1104,8 @@ fn compiles_covenant_escrow_example_and_verifies() { let seller = [11u8; 32]; let constructor_args = vec![arbiter_hash.clone().into(), buyer.to_vec().into(), seller.to_vec().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input_value = 12_000u64; let output0_value = input_value - 1000; @@ -1102,7 +1121,8 @@ fn compiles_covenant_escrow_example_and_verifies() { TransactionOutput { value: output0_value, script_public_key: ScriptPublicKey::new(0, output0_script.into()), covenant: None }; let tx = Transaction::new(1, vec![input.clone()], vec![output0.clone()], 0, Default::default(), 0, vec![]); - let utxo_entry = UtxoEntry::new(input_value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + let utxo_entry = + UtxoEntry::new(input_value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1114,8 +1134,8 @@ fn compiles_covenant_escrow_example_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test spend() function call (build sigscript for spend()). - let sigscript = - compiled.build_sig_script("spend", vec![arbiter_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "spend", &[arbiter_pk.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1150,7 +1170,8 @@ fn compiles_covenant_last_will_and_verifies() { let hot_hash = blake2b_simd::Params::new().hash_length(32).to_state().update(hot_pk.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = vec![inheritor_hash.clone().into(), cold_hash.clone().into(), hot_hash.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([12u8; 32]), index: 0 }, @@ -1160,13 +1181,13 @@ fn compiles_covenant_last_will_and_verifies() { }; let output = TransactionOutput { value: 5_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1178,8 +1199,8 @@ fn compiles_covenant_last_will_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test inherit() function call (build sigscript for inherit()). - let sigscript = - compiled.build_sig_script("inherit", vec![inheritor_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "inherit", &[inheritor_pk.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1204,13 +1225,13 @@ fn compiles_covenant_last_will_and_verifies() { }; let output = TransactionOutput { value: 4_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1223,7 +1244,7 @@ fn compiles_covenant_last_will_and_verifies() { // Test cold() function call (build sigscript for cold()). let sigscript = - compiled.build_sig_script("cold", vec![cold_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + encode_entry_sig_script(&compiled, "cold", &[cold_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1251,12 +1272,13 @@ fn compiles_covenant_last_will_and_verifies() { }; let output0 = TransactionOutput { value: output0_value, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output0.clone()], 0, Default::default(), 0, vec![]); - let utxo_entry = UtxoEntry::new(input_value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + let utxo_entry = + UtxoEntry::new(input_value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1269,7 +1291,7 @@ fn compiles_covenant_last_will_and_verifies() { // Test refresh() function call (build sigscript for refresh()). let sigscript = - compiled.build_sig_script("refresh", vec![hot_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + encode_entry_sig_script(&compiled, "refresh", &[hot_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1300,10 +1322,11 @@ fn compiles_covenant_mecenas_example_and_verifies() { let period = 10i64; let constructor_args = vec![recipient.to_vec().into(), funder_hash.clone().into(), pledge.into(), period.into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); // Test receive() with changeValue > pledge + minerFee (else branch). - let sigscript = compiled.build_sig_script("receive", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "receive", &[]).expect("sigscript builds"); let input_value = 10000u64; let output0_value = pledge as u64; @@ -1311,9 +1334,9 @@ fn compiles_covenant_mecenas_example_and_verifies() { let output0_script = build_p2pk_script(&recipient); let result = run_contract_with_tx_sequence( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), output0_script, - compiled.bytecode.clone(), + bytecode(&compiled).clone(), input_value, output0_value, output1_value, @@ -1323,7 +1346,7 @@ fn compiles_covenant_mecenas_example_and_verifies() { ); assert!(result.is_ok(), "covenant mecenas example failed: {}", result.unwrap_err()); // Test receive() with changeValue <= pledge + minerFee (if branch). - let sigscript = compiled.build_sig_script("receive", vec![]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "receive", &[]).expect("sigscript builds"); let input_value = 6000u64; let output0_value = input_value - 1000; @@ -1331,9 +1354,9 @@ fn compiles_covenant_mecenas_example_and_verifies() { let output0_script = build_p2pk_script(&recipient); let result = run_contract_with_tx_sequence( - compiled.bytecode.clone(), + bytecode(&compiled).clone(), output0_script, - compiled.bytecode.clone(), + bytecode(&compiled).clone(), input_value, output0_value, output1_value, @@ -1351,13 +1374,13 @@ fn compiles_covenant_mecenas_example_and_verifies() { }; let output = TransactionOutput { value: 7_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1369,8 +1392,8 @@ fn compiles_covenant_mecenas_example_and_verifies() { signature.push(SIG_HASH_ALL.to_u8()); // Test reclaim() function call (build sigscript for reclaim()). - let sigscript = - compiled.build_sig_script("reclaim", vec![funder_pk.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "reclaim", &[funder_pk.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1398,21 +1421,35 @@ fn compiles_covenant_id_example_and_verifies() { let other_covenant_id = kaspa_consensus_core::Hash::from_bytes(*b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); let execute_case = |out0_amount: i64, out1_amount: i64| { - let active_compiled = - compile_contract(&source, &[max_ins.into(), max_outs.into(), 1_000i64.into()], CompileOptions::default()) - .expect("compile succeeds"); - let input1_compiled = compile_contract(&source, &[max_ins.into(), max_outs.into(), 600i64.into()], CompileOptions::default()) - .expect("compile succeeds"); - let output0_compiled = - compile_contract(&source, &[max_ins.into(), max_outs.into(), out0_amount.into()], CompileOptions::default()) - .expect("compile succeeds"); - let output1_compiled = - compile_contract(&source, &[max_ins.into(), max_outs.into(), out1_amount.into()], CompileOptions::default()) - .expect("compile succeeds"); + let active_compiled = compile_to_sil_abi_artifact_with_options( + &source, + &[max_ins.into(), max_outs.into(), 1_000i64.into()], + CompileOptions::default(), + ) + .expect("compile succeeds"); + let input1_compiled = compile_to_sil_abi_artifact_with_options( + &source, + &[max_ins.into(), max_outs.into(), 600i64.into()], + CompileOptions::default(), + ) + .expect("compile succeeds"); + let output0_compiled = compile_to_sil_abi_artifact_with_options( + &source, + &[max_ins.into(), max_outs.into(), out0_amount.into()], + CompileOptions::default(), + ) + .expect("compile succeeds"); + let output1_compiled = compile_to_sil_abi_artifact_with_options( + &source, + &[max_ins.into(), max_outs.into(), out1_amount.into()], + CompileOptions::default(), + ) + .expect("compile succeeds"); let mut active_sigscript = - active_compiled.build_sig_script("main", vec![vec![out0_amount, out1_amount].into()]).expect("sigscript builds"); - active_sigscript.extend_from_slice(&sigscript_push_bytecode(&active_compiled.bytecode)); + encode_entry_sig_script(&active_compiled, "main", &[ArtifactValue::Array(vec![out0_amount.into(), out1_amount.into()])]) + .expect("sigscript builds"); + active_sigscript.extend_from_slice(&sigscript_push_bytecode(&bytecode(&active_compiled))); let input0 = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([24u8; 32]), index: 0 }, @@ -1422,7 +1459,7 @@ fn compiles_covenant_id_example_and_verifies() { }; let input1 = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([25u8; 32]), index: 1 }, - signature_script: sigscript_push_bytecode(&input1_compiled.bytecode), + signature_script: sigscript_push_bytecode(&bytecode(&input1_compiled)), sequence: 0, compute_commit: SigopCount(0).into(), }; @@ -1435,7 +1472,7 @@ fn compiles_covenant_id_example_and_verifies() { let output0 = TransactionOutput { value: 1, - script_public_key: pay_to_script_hash_script(&output0_compiled.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&output0_compiled)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id }), }; @@ -1447,7 +1484,7 @@ fn compiles_covenant_id_example_and_verifies() { let output2 = TransactionOutput { value: 1, - script_public_key: pay_to_script_hash_script(&output1_compiled.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&output1_compiled)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id }), }; @@ -1462,8 +1499,9 @@ fn compiles_covenant_id_example_and_verifies() { ); let utxo0 = - UtxoEntry::new(1_600, pay_to_script_hash_script(&active_compiled.bytecode), 0, tx.is_coinbase(), Some(covenant_id)); - let utxo1 = UtxoEntry::new(700, pay_to_script_hash_script(&input1_compiled.bytecode), 0, tx.is_coinbase(), Some(covenant_id)); + UtxoEntry::new(1_600, pay_to_script_hash_script(&bytecode(&active_compiled)), 0, tx.is_coinbase(), Some(covenant_id)); + let utxo1 = + UtxoEntry::new(700, pay_to_script_hash_script(&bytecode(&input1_compiled)), 0, tx.is_coinbase(), Some(covenant_id)); let utxo2 = UtxoEntry::new(300, ScriptPublicKey::new(0, vec![OpTrue].into()), 0, tx.is_coinbase(), Some(other_covenant_id)); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1499,7 +1537,8 @@ fn compiles_bar_example_and_verifies() { let pkh = blake2b_simd::Params::new().hash_length(32).to_state().update(pubkey_bytes.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = [pkh.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([18u8; 32]), index: 0 }, @@ -1509,13 +1548,13 @@ fn compiles_bar_example_and_verifies() { }; let output = TransactionOutput { value: 7_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1526,8 +1565,8 @@ fn compiles_bar_example_and_verifies() { signature.extend_from_slice(sig.as_ref().as_slice()); signature.push(SIG_HASH_ALL.to_u8()); - let sigscript = - compiled.build_sig_script("execute", vec![pubkey_bytes.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "execute", &[pubkey_bytes.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1554,7 +1593,8 @@ fn compiles_foo_example_and_verifies() { let pkh = blake2b_simd::Params::new().hash_length(32).to_state().update(pubkey_bytes.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = [pkh.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([19u8; 32]), index: 0 }, @@ -1564,13 +1604,13 @@ fn compiles_foo_example_and_verifies() { }; let output = TransactionOutput { value: 7_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1581,8 +1621,8 @@ fn compiles_foo_example_and_verifies() { signature.extend_from_slice(sig.as_ref().as_slice()); signature.push(SIG_HASH_ALL.to_u8()); - let sigscript = - compiled.build_sig_script("execute", vec![pubkey_bytes.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "execute", &[pubkey_bytes.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1604,12 +1644,12 @@ fn compiles_foo_example_and_verifies() { fn compiles_bounded_bytes_example_and_verifies() { let source = load_example_source("bounded_bytes.sil"); - let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("spend", vec![vec![0u8; 4].into(), 0.into()]).expect("sigscript builds"); + let compiled = compile_to_sil_abi_artifact_with_options(&source, &[], CompileOptions::default()).expect("compile succeeds"); + let sigscript = encode_entry_sig_script(&compiled, "spend", &[vec![0u8; 4].into(), 0.into()]).expect("sigscript builds"); let result = run_contract_with_tx( - compiled.bytecode.clone(), - compiled.bytecode.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), 2000, 500, 500, @@ -1618,11 +1658,11 @@ fn compiles_bounded_bytes_example_and_verifies() { ); assert!(result.is_ok(), "bounded_bytes example failed: {}", result.unwrap_err()); - let sigscript = compiled.build_sig_script("spend", vec![vec![0u8; 4].into(), 1.into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "spend", &[vec![0u8; 4].into(), 1.into()]).expect("sigscript builds"); let result = run_contract_with_tx( - compiled.bytecode.clone(), - compiled.bytecode.clone(), - compiled.bytecode.clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), + bytecode(&compiled).clone(), 2000, 500, 500, @@ -1641,7 +1681,8 @@ fn compiles_p2pkh_invalid_example_and_fails() { let pkh = blake2b_simd::Params::new().hash_length(20).to_state().update(pubkey_bytes.as_slice()).finalize().as_bytes().to_vec(); let constructor_args = [pkh.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); let input = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([20u8; 32]), index: 0 }, @@ -1651,13 +1692,13 @@ fn compiles_p2pkh_invalid_example_and_fails() { }; let output = TransactionOutput { value: 7_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo_entry = - UtxoEntry::new(output.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + UtxoEntry::new(output.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let mut tx = MutableTransaction::with_entries(tx, vec![utxo_entry.clone()]); let reused_values = SigHashReusedValuesUnsync::new(); @@ -1668,8 +1709,8 @@ fn compiles_p2pkh_invalid_example_and_fails() { signature.extend_from_slice(sig.as_ref().as_slice()); signature.push(SIG_HASH_ALL.to_u8()); - let sigscript = - compiled.build_sig_script("spend", vec![pubkey_bytes.to_vec().into(), signature.clone().into()]).expect("sigscript builds"); + let sigscript = encode_entry_sig_script(&compiled, "spend", &[pubkey_bytes.to_vec().into(), signature.clone().into()]) + .expect("sigscript builds"); tx.tx.inputs[0].signature_script = sigscript; let tx = tx.as_verifiable(); @@ -1695,10 +1736,11 @@ fn compiles_sibling_introspection_example_and_verifies() { let mut expected_locking_bytecode = Vec::new(); expected_locking_bytecode.extend_from_slice(&0u16.to_be_bytes()); expected_locking_bytecode.extend_from_slice(&expected_script); - let constructor_args = [Expr::dynamic_bytes(expected_locking_bytecode.clone())]; + let constructor_args = [expected_locking_bytecode.clone().into()]; - let compiled = compile_contract(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); - let sigscript = compiled.build_sig_script("spend", vec![]).expect("sigscript builds"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()).expect("compile succeeds"); + let sigscript = encode_entry_sig_script(&compiled, "spend", &[]).expect("sigscript builds"); let input0 = TransactionInput { previous_outpoint: TransactionOutpoint { transaction_id: TransactionId::from_bytes([21u8; 32]), index: 0 }, signature_script: sigscript, @@ -1714,7 +1756,7 @@ fn compiles_sibling_introspection_example_and_verifies() { let output0 = TransactionOutput { value: 1_000, - script_public_key: ScriptPublicKey::new(0, compiled.bytecode.clone().into()), + script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), covenant: None, }; let output1 = TransactionOutput { @@ -1732,7 +1774,7 @@ fn compiles_sibling_introspection_example_and_verifies() { 0, vec![], ); - let utxo0 = UtxoEntry::new(output0.value, ScriptPublicKey::new(0, compiled.bytecode.clone().into()), 0, tx.is_coinbase(), None); + let utxo0 = UtxoEntry::new(output0.value, ScriptPublicKey::new(0, bytecode(&compiled).clone().into()), 0, tx.is_coinbase(), None); let utxo1 = UtxoEntry::new( output1.value, ScriptPublicKey::new(0, expected_locking_bytecode[2..].to_vec().into()), @@ -1761,11 +1803,12 @@ fn compiles_sibling_introspection_example_and_verifies() { fn compiles_many_assignments_example_under_500_bytes() { let source = load_example_source("many_assignments.sil"); - let compiled = compile_contract(&source, &[], CompileOptions::default()).expect("long example should compile"); + let compiled = + compile_to_sil_abi_artifact_with_options(&source, &[], CompileOptions::default()).expect("long example should compile"); // This example chains many assignments like `a_n = a_(n-1) * a_(n-1)`. // We check the final bytecode stays small to prove the compiler is not // re-expanding earlier expressions exponentially. Instead, each interim // variable should be stored on the stack once and reused by later steps. - assert!(compiled.bytecode.len() < 500, "long.sil should compile to less than 500 bytes, got {}", compiled.bytecode.len()); + assert!(bytecode(&compiled).len() < 500, "long.sil should compile to less than 500 bytes, got {}", bytecode(&compiled).len()); } diff --git a/silverscript-lang/tests/kcc20_tests.rs b/silverscript-lang/tests/kcc20_tests.rs index bf1e51e9..438a2958 100644 --- a/silverscript-lang/tests/kcc20_tests.rs +++ b/silverscript-lang/tests/kcc20_tests.rs @@ -13,15 +13,16 @@ use kaspa_txscript::pay_to_script_hash_script; use kaspa_txscript::standard::multisig_redeem_script; use rand::{RngCore, thread_rng}; use secp256k1::{Keypair, Secp256k1, SecretKey}; -use silverscript_lang::ast::{Expr, parse_type_ref}; -use silverscript_lang::compiler::{CompileOptions, CompiledContract, compile_contract, struct_object}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::compiler::{CompileOptions, compile_to_sil_abi_artifact_with_options}; +use std::collections::BTreeMap; use std::fs; mod common; use common::{ - COV_A, COV_B, assert_verify_like_error, compiled_template_parts_and_hash, covenant_decl_sigscript, covenant_output, covenant_utxo, - execute_input_with_covenants, + COV_A, COV_B, assert_verify_like_error, bytecode, compiled_template_parts_and_hash, covenant_decl_sigscript, covenant_output, + covenant_utxo, execute_input_with_covenants, }; fn load_example_source(name: &str) -> String { @@ -41,91 +42,94 @@ fn random_keypair() -> Keypair { } } -fn kcc20_state_array_arg<'i>(values: Vec<(Vec, i64)>) -> Expr<'i> { +fn kcc20_state_array_arg(values: Vec<(Vec, i64)>) -> ArtifactValue { kcc20_state_array_arg_with_minter(values.into_iter().map(|(owner_identifier, amount)| (owner_identifier, amount, false)).collect()) } -fn kcc20_state_array_arg_full<'i>(values: Vec<(Vec, u8, i64, bool)>) -> Expr<'i> { - Expr::array( - parse_type_ref("State[]").unwrap(), +fn kcc20_state_array_arg_full(values: Vec<(Vec, u8, i64, bool)>) -> ArtifactValue { + ArtifactValue::Array( values .into_iter() .map(|(owner_identifier, identifier_type, amount, is_minter)| { - struct_object( - "State", - vec![ - ("ownerIdentifier", Expr::bytes(owner_identifier)), - ("identifierType", Expr::byte(identifier_type)), - ("amount", Expr::int(amount)), - ("isMinter", Expr::bool(is_minter)), - ], - ) + BTreeMap::from([ + ("ownerIdentifier".to_string(), owner_identifier.into()), + ("identifierType".to_string(), identifier_type.into()), + ("amount".to_string(), amount.into()), + ("isMinter".to_string(), is_minter.into()), + ]) + .into() }) .collect(), ) } -fn kcc20_state_array_arg_with_minter<'i>(values: Vec<(Vec, i64, bool)>) -> Expr<'i> { +fn kcc20_state_array_arg_with_minter(values: Vec<(Vec, i64, bool)>) -> ArtifactValue { kcc20_state_array_arg_full( values.into_iter().map(|(owner_identifier, amount, is_minter)| (owner_identifier, 0, amount, is_minter)).collect(), ) } -fn kcc20_state_arg<'i>(owner_identifier: Vec, identifier_type: u8, amount: i64, is_minter: bool) -> Expr<'i> { - struct_object( - "KCC20State", - vec![ - ("ownerIdentifier", Expr::bytes(owner_identifier)), - ("identifierType", Expr::byte(identifier_type)), - ("amount", Expr::int(amount)), - ("isMinter", Expr::bool(is_minter)), - ], - ) +fn kcc20_state_arg(owner_identifier: Vec, identifier_type: u8, amount: i64, is_minter: bool) -> ArtifactValue { + BTreeMap::from([ + ("ownerIdentifier".to_string(), owner_identifier.into()), + ("identifierType".to_string(), identifier_type.into()), + ("amount".to_string(), amount.into()), + ("isMinter".to_string(), is_minter.into()), + ]) + .into() } -fn kcc20_minter_state_arg<'i>(kcc20_covid: Vec, amount: i64, initialized: bool) -> Expr<'i> { - struct_object( - "State", - vec![("kcc20Covid", Expr::bytes(kcc20_covid)), ("amount", Expr::int(amount)), ("initialized", Expr::bool(initialized))], - ) +fn kcc20_minter_state_arg(kcc20_covid: Vec, amount: i64, initialized: bool) -> ArtifactValue { + BTreeMap::from([ + ("kcc20Covid".to_string(), kcc20_covid.into()), + ("amount".to_string(), amount.into()), + ("initialized".to_string(), initialized.into()), + ]) + .into() } -fn compile_kcc20_state<'a>(source: &'a str, owner: Vec, amount: i64, max_cov_ins: i64, max_cov_outs: i64) -> CompiledContract<'a> { +fn compile_kcc20_state( + source: &str, + owner: Vec, + amount: i64, + max_cov_ins: i64, + max_cov_outs: i64, +) -> silverscript_abi::SilAbiArtifact { compile_kcc20_state_with_minter(source, owner, amount, false, max_cov_ins, max_cov_outs) } -fn compile_kcc20_state_full<'a>( - source: &'a str, +fn compile_kcc20_state_full( + source: &str, owner: Vec, amount: i64, identifier_type: u8, is_minter: bool, max_cov_ins: i64, max_cov_outs: i64, -) -> CompiledContract<'a> { - compile_contract( +) -> silverscript_abi::SilAbiArtifact { + compile_to_sil_abi_artifact_with_options( source, &[ - Expr::bytes(owner), - Expr::int(amount), - Expr::byte(identifier_type), - Expr::bool(is_minter), - Expr::int(max_cov_ins), - Expr::int(max_cov_outs), + ArtifactValue::Bytes(owner), + ArtifactValue::Int(amount), + ArtifactValue::Byte(identifier_type), + ArtifactValue::Bool(is_minter), + ArtifactValue::Int(max_cov_ins), + ArtifactValue::Int(max_cov_outs), ], CompileOptions::default(), ) .expect("compile succeeds") } -fn compile_kcc20_state_with_minter<'a>( - source: &'a str, +fn compile_kcc20_state_with_minter( + source: &str, owner: Vec, amount: i64, is_minter: bool, max_cov_ins: i64, max_cov_outs: i64, -) -> CompiledContract<'a> { +) -> silverscript_abi::SilAbiArtifact { compile_kcc20_state_full(source, owner, amount, 0, is_minter, max_cov_ins, max_cov_outs) } @@ -171,7 +175,7 @@ fn kcc20_can_split_then_merge_tokens_with_two_way_fanout() { let handoff_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&handoff.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&handoff)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let handoff_entries = vec![covenant_utxo(&genesis, COV_A)]; @@ -188,7 +192,11 @@ fn kcc20_can_split_then_merge_tokens_with_two_way_fanout() { let handoff_sigscript = covenant_decl_sigscript( &genesis, "transfer", - vec![kcc20_state_array_arg(vec![(handoff_owner_bytes.clone(), 1_000)]), Expr::bytes(handoff_sig), Expr::byte(0)], + vec![ + kcc20_state_array_arg(vec![(handoff_owner_bytes.clone(), 1_000)]), + ArtifactValue::Bytes(handoff_sig), + ArtifactValue::Byte(0), + ], true, ); let handoff_tx = Transaction::new( @@ -209,12 +217,12 @@ fn kcc20_can_split_then_merge_tokens_with_two_way_fanout() { let split_outputs = vec![ TransactionOutput { value: 700, - script_public_key: pay_to_script_hash_script(&split_a.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_a)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, TransactionOutput { value: 700, - script_public_key: pay_to_script_hash_script(&split_b.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_b)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, ]; @@ -240,8 +248,8 @@ fn kcc20_can_split_then_merge_tokens_with_two_way_fanout() { "transfer", vec![ kcc20_state_array_arg(vec![(split_owner_a_bytes.clone(), 400), (split_owner_b_bytes.clone(), 600)]), - Expr::bytes(split_sig), - Expr::byte(0), + ArtifactValue::Bytes(split_sig), + ArtifactValue::Byte(0), ], true, ); @@ -260,12 +268,12 @@ fn kcc20_can_split_then_merge_tokens_with_two_way_fanout() { let merged = compile_kcc20_state(&source, merged_owner_bytes.clone(), 1_000, 2, 2); let merge_outputs = vec![TransactionOutput { value: 2_000, - script_public_key: pay_to_script_hash_script(&merged.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&merged)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let merge_entries = vec![ - UtxoEntry::new(700, pay_to_script_hash_script(&split_a.bytecode), 0, split_tx.is_coinbase(), Some(COV_A)), - UtxoEntry::new(700, pay_to_script_hash_script(&split_b.bytecode), 0, split_tx.is_coinbase(), Some(COV_A)), + UtxoEntry::new(700, pay_to_script_hash_script(&bytecode(&split_a)), 0, split_tx.is_coinbase(), Some(COV_A)), + UtxoEntry::new(700, pay_to_script_hash_script(&bytecode(&split_b)), 0, split_tx.is_coinbase(), Some(COV_A)), ]; let merge_unsigned_tx = Transaction::new( 1, @@ -284,10 +292,11 @@ fn kcc20_can_split_then_merge_tokens_with_two_way_fanout() { let merge_leader_sigscript = covenant_decl_sigscript( &split_a, "transfer", - vec![kcc20_state_array_arg(vec![(merged_owner_bytes, 1_000)]), Expr::bytes(merge_sig_a), Expr::byte(0)], + vec![kcc20_state_array_arg(vec![(merged_owner_bytes, 1_000)]), ArtifactValue::Bytes(merge_sig_a), ArtifactValue::Byte(0)], true, ); - let merge_delegate_sigscript = covenant_decl_sigscript(&split_b, "transfer", vec![Expr::bytes(merge_sig_b), Expr::byte(1)], false); + let merge_delegate_sigscript = + covenant_decl_sigscript(&split_b, "transfer", vec![ArtifactValue::Bytes(merge_sig_b), ArtifactValue::Byte(1)], false); let merge_tx = Transaction::new( 1, vec![ @@ -330,7 +339,7 @@ fn kcc20_rejects_merge_when_one_signature_is_wrong() { let handoff_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&handoff.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&handoff)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let handoff_entries = vec![covenant_utxo(&genesis, COV_A)]; @@ -347,7 +356,11 @@ fn kcc20_rejects_merge_when_one_signature_is_wrong() { let handoff_sigscript = covenant_decl_sigscript( &genesis, "transfer", - vec![kcc20_state_array_arg(vec![(handoff_owner_bytes.clone(), 1_000)]), Expr::bytes(handoff_sig), Expr::byte(0)], + vec![ + kcc20_state_array_arg(vec![(handoff_owner_bytes.clone(), 1_000)]), + ArtifactValue::Bytes(handoff_sig), + ArtifactValue::Byte(0), + ], true, ); let handoff_tx = Transaction::new( @@ -368,12 +381,12 @@ fn kcc20_rejects_merge_when_one_signature_is_wrong() { let split_outputs = vec![ TransactionOutput { value: 700, - script_public_key: pay_to_script_hash_script(&split_a.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_a)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, TransactionOutput { value: 700, - script_public_key: pay_to_script_hash_script(&split_b.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_b)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, ]; @@ -399,8 +412,8 @@ fn kcc20_rejects_merge_when_one_signature_is_wrong() { "transfer", vec![ kcc20_state_array_arg(vec![(split_owner_a_bytes.clone(), 400), (split_owner_b_bytes.clone(), 600)]), - Expr::bytes(split_sig), - Expr::byte(0), + ArtifactValue::Bytes(split_sig), + ArtifactValue::Byte(0), ], true, ); @@ -418,12 +431,12 @@ fn kcc20_rejects_merge_when_one_signature_is_wrong() { let merge_outputs = vec![TransactionOutput { value: 2_000, - script_public_key: pay_to_script_hash_script(&merged.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&merged)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let merge_entries = vec![ - UtxoEntry::new(700, pay_to_script_hash_script(&split_a.bytecode), 0, split_tx.is_coinbase(), Some(COV_A)), - UtxoEntry::new(700, pay_to_script_hash_script(&split_b.bytecode), 0, split_tx.is_coinbase(), Some(COV_A)), + UtxoEntry::new(700, pay_to_script_hash_script(&bytecode(&split_a)), 0, split_tx.is_coinbase(), Some(COV_A)), + UtxoEntry::new(700, pay_to_script_hash_script(&bytecode(&split_b)), 0, split_tx.is_coinbase(), Some(COV_A)), ]; let merge_unsigned_tx = Transaction::new( 1, @@ -442,10 +455,11 @@ fn kcc20_rejects_merge_when_one_signature_is_wrong() { let merge_leader_sigscript = covenant_decl_sigscript( &split_a, "transfer", - vec![kcc20_state_array_arg(vec![(merged_owner_bytes, 1_000)]), Expr::bytes(merge_sig_a), Expr::byte(0)], + vec![kcc20_state_array_arg(vec![(merged_owner_bytes, 1_000)]), ArtifactValue::Bytes(merge_sig_a), ArtifactValue::Byte(0)], true, ); - let merge_delegate_sigscript = covenant_decl_sigscript(&split_b, "transfer", vec![Expr::bytes(wrong_sig_b), Expr::byte(1)], false); + let merge_delegate_sigscript = + covenant_decl_sigscript(&split_b, "transfer", vec![ArtifactValue::Bytes(wrong_sig_b), ArtifactValue::Byte(1)], false); let merge_tx = Transaction::new( 1, vec![ @@ -487,7 +501,7 @@ fn kcc20_rejects_split_when_amounts_do_not_match() { let handoff_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&handoff.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&handoff)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let handoff_entries = vec![covenant_utxo(&genesis, COV_A)]; @@ -504,7 +518,11 @@ fn kcc20_rejects_split_when_amounts_do_not_match() { let handoff_sigscript = covenant_decl_sigscript( &genesis, "transfer", - vec![kcc20_state_array_arg(vec![(handoff_owner_bytes.clone(), 1_000)]), Expr::bytes(handoff_sig), Expr::byte(0)], + vec![ + kcc20_state_array_arg(vec![(handoff_owner_bytes.clone(), 1_000)]), + ArtifactValue::Bytes(handoff_sig), + ArtifactValue::Byte(0), + ], true, ); let handoff_tx = Transaction::new( @@ -525,12 +543,12 @@ fn kcc20_rejects_split_when_amounts_do_not_match() { let split_outputs = vec![ TransactionOutput { value: 700, - script_public_key: pay_to_script_hash_script(&split_a.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_a)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, TransactionOutput { value: 700, - script_public_key: pay_to_script_hash_script(&split_b.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_b)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, ]; @@ -556,8 +574,8 @@ fn kcc20_rejects_split_when_amounts_do_not_match() { "transfer", vec![ kcc20_state_array_arg(vec![(split_owner_a_bytes, 400), (split_owner_b_bytes, 500)]), - Expr::bytes(split_sig), - Expr::byte(0), + ArtifactValue::Bytes(split_sig), + ArtifactValue::Byte(0), ], true, ); @@ -597,12 +615,12 @@ fn kcc20_minter_can_split_then_mint_then_burn() { let split_outputs = vec![ TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&split_minter.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_minter)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&split_other.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&split_other)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }, ]; @@ -622,8 +640,8 @@ fn kcc20_minter_can_split_then_mint_then_burn() { "transfer", vec![ kcc20_state_array_arg_with_minter(vec![(minter_owner_bytes.clone(), 400, true), (other_owner_bytes.clone(), 600, false)]), - Expr::bytes(split_sig), - Expr::byte(0), + ArtifactValue::Bytes(split_sig), + ArtifactValue::Byte(0), ], true, ); @@ -645,11 +663,11 @@ fn kcc20_minter_can_split_then_mint_then_burn() { let forged_other = compile_kcc20_state(&source, other_owner_bytes.clone(), 700, 2, 2); let forged_other_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&forged_other.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&forged_other)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let forged_other_entries = - vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&split_other.bytecode), 0, split_tx.is_coinbase(), Some(COV_A))]; + vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&bytecode(&split_other)), 0, split_tx.is_coinbase(), Some(COV_A))]; let forged_other_unsigned_tx = Transaction::new( 1, vec![tx_input_from_outpoint_v1(TransactionOutpoint { transaction_id: split_tx.id(), index: 1 }, vec![])], @@ -665,8 +683,8 @@ fn kcc20_minter_can_split_then_mint_then_burn() { "transfer", vec![ kcc20_state_array_arg_with_minter(vec![(other_owner_bytes.clone(), 700, false)]), - Expr::bytes(forged_other_sig), - Expr::byte(0), + ArtifactValue::Bytes(forged_other_sig), + ArtifactValue::Byte(0), ], true, ); @@ -687,11 +705,11 @@ fn kcc20_minter_can_split_then_mint_then_burn() { let forged_other_minter = compile_kcc20_state_with_minter(&source, other_owner_bytes.clone(), 600, true, 2, 2); let forged_other_minter_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&forged_other_minter.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&forged_other_minter)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let forged_other_minter_entries = - vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&split_other.bytecode), 0, split_tx.is_coinbase(), Some(COV_A))]; + vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&bytecode(&split_other)), 0, split_tx.is_coinbase(), Some(COV_A))]; let forged_other_minter_unsigned_tx = Transaction::new( 1, vec![tx_input_from_outpoint_v1(TransactionOutpoint { transaction_id: split_tx.id(), index: 1 }, vec![])], @@ -707,8 +725,8 @@ fn kcc20_minter_can_split_then_mint_then_burn() { "transfer", vec![ kcc20_state_array_arg_with_minter(vec![(other_owner_bytes.clone(), 600, true)]), - Expr::bytes(forged_other_minter_sig), - Expr::byte(0), + ArtifactValue::Bytes(forged_other_minter_sig), + ArtifactValue::Byte(0), ], true, ); @@ -731,11 +749,11 @@ fn kcc20_minter_can_split_then_mint_then_burn() { let mint_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&minted_minter.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&minted_minter)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let mint_entries = - vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&split_minter.bytecode), 0, split_tx.is_coinbase(), Some(COV_A))]; + vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&bytecode(&split_minter)), 0, split_tx.is_coinbase(), Some(COV_A))]; let mint_unsigned_tx = Transaction::new( 1, vec![tx_input_from_outpoint_v1(TransactionOutpoint { transaction_id: split_tx.id(), index: 0 }, vec![])], @@ -749,7 +767,11 @@ fn kcc20_minter_can_split_then_mint_then_burn() { let mint_sigscript = covenant_decl_sigscript( &split_minter, "transfer", - vec![kcc20_state_array_arg_with_minter(vec![(minter_owner_bytes.clone(), 900, true)]), Expr::bytes(mint_sig), Expr::byte(0)], + vec![ + kcc20_state_array_arg_with_minter(vec![(minter_owner_bytes.clone(), 900, true)]), + ArtifactValue::Bytes(mint_sig), + ArtifactValue::Byte(0), + ], true, ); let mint_tx = Transaction::new( @@ -766,11 +788,11 @@ fn kcc20_minter_can_split_then_mint_then_burn() { let burn_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&burned_minter.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&burned_minter)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let burn_entries = - vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&minted_minter.bytecode), 0, mint_tx.is_coinbase(), Some(COV_A))]; + vec![UtxoEntry::new(1_000, pay_to_script_hash_script(&bytecode(&minted_minter)), 0, mint_tx.is_coinbase(), Some(COV_A))]; let burn_unsigned_tx = Transaction::new( 1, vec![tx_input_from_outpoint_v1(TransactionOutpoint { transaction_id: mint_tx.id(), index: 0 }, vec![])], @@ -784,7 +806,11 @@ fn kcc20_minter_can_split_then_mint_then_burn() { let burn_sigscript = covenant_decl_sigscript( &minted_minter, "transfer", - vec![kcc20_state_array_arg_with_minter(vec![(minter_owner_bytes, 500, true)]), Expr::bytes(burn_sig), Expr::byte(0)], + vec![ + kcc20_state_array_arg_with_minter(vec![(minter_owner_bytes, 500, true)]), + ArtifactValue::Bytes(burn_sig), + ArtifactValue::Byte(0), + ], true, ); let burn_tx = Transaction::new( @@ -812,7 +838,7 @@ fn kcc20_minter_can_mint_in_single_transaction() { let mint_outputs = vec![TransactionOutput { value: 1_000, - script_public_key: pay_to_script_hash_script(&minted.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(&minted)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }]; let mint_entries = vec![covenant_utxo(&genesis, COV_A)]; @@ -829,7 +855,11 @@ fn kcc20_minter_can_mint_in_single_transaction() { let mint_sigscript = covenant_decl_sigscript( &genesis, "transfer", - vec![kcc20_state_array_arg_with_minter(vec![(genesis_owner_bytes, 1_500, true)]), Expr::bytes(mint_sig), Expr::byte(0)], + vec![ + kcc20_state_array_arg_with_minter(vec![(genesis_owner_bytes, 1_500, true)]), + ArtifactValue::Bytes(mint_sig), + ArtifactValue::Byte(0), + ], true, ); let mint_tx = Transaction::new( @@ -890,16 +920,16 @@ fn kcc20_covenant_minter() { compiled_template_parts_and_hash(&kcc20_template_probe) }; let compile_minter = |kcc20_covid: Hash, amount: i64, initialized: bool| { - compile_contract( + compile_to_sil_abi_artifact_with_options( &kcc20_minter_source, &[ - Expr::bytes(owner_bytes.clone()), // owner - Expr::bytes(kcc20_covid.as_bytes().to_vec()), // initKCC20Covid - Expr::int(amount), // initAmount - Expr::bool(initialized), // initInitialized - Expr::int(template_prefix.len() as i64), // templatePrefixLen - Expr::int(template_suffix.len() as i64), // templateSuffixLen - Expr::bytes(expected_template_hash.clone()), // expectedTemplateHash + ArtifactValue::Bytes(owner_bytes.clone()), // owner + ArtifactValue::Bytes(kcc20_covid.as_bytes().to_vec()), // initKCC20Covid + ArtifactValue::Int(amount), // initAmount + ArtifactValue::Bool(initialized), // initInitialized + ArtifactValue::Int(template_prefix.len() as i64), // templatePrefixLen + ArtifactValue::Int(template_suffix.len() as i64), // templateSuffixLen + ArtifactValue::Bytes(expected_template_hash.clone()), // expectedTemplateHash ], CompileOptions::default(), ) @@ -929,7 +959,7 @@ fn kcc20_covenant_minter() { let minter_genesis_input = tx_input_from_outpoint_v1(minter_genesis_outpoint, vec![]); let minter_genesis_utxo = UtxoEntry::new(1_500, funding_spk.clone(), 0, false, None); let minter_genesis_output_without_covenant = - TransactionOutput { value: 1_000, script_public_key: pay_to_script_hash_script(&pre_init.bytecode), covenant: None }; + TransactionOutput { value: 1_000, script_public_key: pay_to_script_hash_script(&bytecode(&pre_init)), covenant: None }; let minter_cov_id = hashing::covenant_id::covenant_id(minter_genesis_outpoint, std::iter::once((0, &minter_genesis_output_without_covenant))); let minter_genesis_outputs = vec![TransactionOutput { @@ -972,11 +1002,11 @@ fn kcc20_covenant_minter() { // mint tx builder: spend A and C together // ============================================================ let build_mint_tx = |prev_tx: &TestTx, - prev_kcc20: &CompiledContract<'_>, - prev_minter: &CompiledContract<'_>, - next_minter_kcc20: &CompiledContract<'_>, - next_recipient_kcc20: &CompiledContract<'_>, - next_minter: &CompiledContract<'_>, + prev_kcc20: &silverscript_abi::SilAbiArtifact, + prev_minter: &silverscript_abi::SilAbiArtifact, + next_minter_kcc20: &silverscript_abi::SilAbiArtifact, + next_recipient_kcc20: &silverscript_abi::SilAbiArtifact, + next_minter: &silverscript_abi::SilAbiArtifact, minted_amount: i64, next_minter_amount: i64| { let outputs = vec![ @@ -1005,8 +1035,8 @@ fn kcc20_covenant_minter() { (minter_cov_id.as_bytes().to_vec(), IDENTIFIER_COVENANT_ID, 0, true), (owner_bytes.clone(), 0, minted_amount, false), ]), - Expr::bytes(vec![0; 65]), - Expr::byte(1), + ArtifactValue::Bytes(vec![0; 65]), + ArtifactValue::Byte(1), ], true, ); @@ -1015,7 +1045,7 @@ fn kcc20_covenant_minter() { "mint", vec![ kcc20_minter_state_arg(kcc20_covenant_id.as_bytes().to_vec(), next_minter_amount, true), - Expr::bytes(minter_sig), + ArtifactValue::Bytes(minter_sig), kcc20_state_arg(minter_cov_id.as_bytes().to_vec(), IDENTIFIER_COVENANT_ID, 0, true), kcc20_state_arg(owner_bytes.clone(), 0, minted_amount, false), ], @@ -1049,7 +1079,7 @@ fn kcc20_covenant_minter() { "init", vec![ kcc20_minter_state_arg(kcc20_covenant_id.as_bytes().to_vec(), MINTER_AMOUNT, true), // newState - Expr::bytes(asset_genesis_sig), // s + ArtifactValue::Bytes(asset_genesis_sig), // s ], true, ); @@ -1104,8 +1134,8 @@ fn kcc20_covenant_minter() { "transfer", vec![ kcc20_state_array_arg(vec![(alternate_owner_bytes.clone(), FIRST_MINTED_AMOUNT)]), - Expr::bytes(recipient_transfer_sig), - Expr::byte(0), + ArtifactValue::Bytes(recipient_transfer_sig), + ArtifactValue::Byte(0), ], true, ); @@ -1230,7 +1260,7 @@ fn kcc20_non_minter_can_spend_script_hash_and_covenant_id_owned_outputs() { .iter() .map(|state| TransactionOutput { value: 150, - script_public_key: pay_to_script_hash_script(&state.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(state)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }) .collect(); @@ -1253,8 +1283,8 @@ fn kcc20_non_minter_can_spend_script_hash_and_covenant_id_owned_outputs() { (multisig_script_hash.clone(), IDENTIFIER_SCRIPT_HASH, 400, false), (covenant_owner_bytes.clone(), IDENTIFIER_COVENANT_ID, 600, false), ]), - Expr::bytes(split_sig), - Expr::byte(0), + ArtifactValue::Bytes(split_sig), + ArtifactValue::Byte(0), ], true, ); @@ -1273,10 +1303,10 @@ fn kcc20_non_minter_can_spend_script_hash_and_covenant_id_owned_outputs() { execute_input_with_covenants(split_tx.clone(), split_entries, 0).expect("KCC20 non-minter split should succeed"); - let build_single_output = |state: &CompiledContract<'_>| { + let build_single_output = |state: &silverscript_abi::SilAbiArtifact| { vec![TransactionOutput { value: 150, - script_public_key: pay_to_script_hash_script(&state.bytecode), + script_public_key: pay_to_script_hash_script(&bytecode(state)), covenant: Some(CovenantBinding { authorizing_input: 0, covenant_id: COV_A }), }] }; @@ -1289,11 +1319,15 @@ fn kcc20_non_minter_can_spend_script_hash_and_covenant_id_owned_outputs() { } Transaction::new(1, inputs, outputs, 0, Default::default(), 0, vec![]) }; - let build_kcc20_sigscript = |state: &CompiledContract<'_>, destination_owner: Vec, amount: i64, witness: u8| { + let build_kcc20_sigscript = |state: &silverscript_abi::SilAbiArtifact, destination_owner: Vec, amount: i64, witness: u8| { covenant_decl_sigscript( state, "transfer", - vec![kcc20_state_array_arg(vec![(destination_owner, amount)]), Expr::bytes(vec![0; 65]), Expr::byte(witness)], + vec![ + kcc20_state_array_arg(vec![(destination_owner, amount)]), + ArtifactValue::Bytes(vec![0; 65]), + ArtifactValue::Byte(witness), + ], true, ) }; @@ -1301,7 +1335,7 @@ fn kcc20_non_minter_can_spend_script_hash_and_covenant_id_owned_outputs() { let script_hash_spent = compile_kcc20_state(&source, multisig_spend_destination_owner_bytes.clone(), 400, 2, 2); let script_hash_spend_outputs = build_single_output(&script_hash_spent); let script_hash_spend_entries = vec![ - UtxoEntry::new(150, pay_to_script_hash_script(&split_states[0].bytecode), 0, split_tx.is_coinbase(), Some(COV_A)), + UtxoEntry::new(150, pay_to_script_hash_script(&bytecode(&split_states[0])), 0, split_tx.is_coinbase(), Some(COV_A)), UtxoEntry::new(500, pay_to_script_hash_script(&multisig_redeem_script), 0, false, None), ]; let script_hash_auxiliary_outpoint = TransactionOutpoint { transaction_id: TransactionId::from_bytes([2; 32]), index: 0 }; @@ -1364,7 +1398,7 @@ fn kcc20_non_minter_can_spend_script_hash_and_covenant_id_owned_outputs() { let covenant_id_spent = compile_kcc20_state(&source, covenant_spend_destination_owner_bytes.clone(), 600, 2, 2); let covenant_id_spend_outputs = build_single_output(&covenant_id_spent); let covenant_id_spend_entries = vec![ - UtxoEntry::new(150, pay_to_script_hash_script(&split_states[1].bytecode), 0, split_tx.is_coinbase(), Some(COV_A)), + UtxoEntry::new(150, pay_to_script_hash_script(&bytecode(&split_states[1])), 0, split_tx.is_coinbase(), Some(COV_A)), UtxoEntry::new(500, pay_to_script_hash_script(&[0x51]), 0, false, Some(covenant_owner)), ]; let covenant_id_auxiliary_outpoint = TransactionOutpoint { transaction_id: TransactionId::from_bytes([3; 32]), index: 0 }; diff --git a/silverscript-lang/tests/silverc-test-files/with_ctor_args.json b/silverscript-lang/tests/silverc-test-files/with_ctor_args.json index 85511bb3..b583c0c1 100644 --- a/silverscript-lang/tests/silverc-test-files/with_ctor_args.json +++ b/silverscript-lang/tests/silverc-test-files/with_ctor_args.json @@ -1 +1 @@ -[{"kind":"int","data":7}] +[{"kind":"int","value":7}] diff --git a/silverscript-lang/tests/silverc_tests.rs b/silverscript-lang/tests/silverc_tests.rs index 61ed5433..c8983e89 100644 --- a/silverscript-lang/tests/silverc_tests.rs +++ b/silverscript-lang/tests/silverc_tests.rs @@ -12,8 +12,9 @@ use kaspa_txscript::caches::Cache; use kaspa_txscript::script_builder::ScriptBuilder; use kaspa_txscript::{EngineCtx, EngineFlags, TxScriptEngine}; use rand::RngCore; +use silverscript_abi::SilAbiArtifact; use silverscript_lang::ast::ContractAst; -use silverscript_lang::compiler::{COMPILER_VERSION, CompiledContract, DispatchTag}; +use silverscript_lang::compiler::{COMPILER_VERSION, DispatchTag}; fn contract_fixture(name: &str) -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("silverc-test-files").join(name) @@ -80,13 +81,18 @@ fn silverc_defaults_output_path_and_empty_ctor_args() { let out_path = dir.join("basic.json"); let json = fs::read_to_string(&out_path).expect("read output"); - let artifact: serde_json::Value = serde_json::from_str(&json).expect("parse compiled artifact JSON"); - assert!(artifact.get("cov_decl_to_abi").is_none()); - assert!(artifact.get("delegate_entry_abi").is_none()); - let compiled: CompiledContract = serde_json::from_str(&json).expect("parse compiled contract"); - assert_eq!(compiled.contract_name, "Basic"); - assert_eq!(compiled.compiler_version, COMPILER_VERSION); - assert_eq!(artifact["abi"][0]["dispatch_tag"], serde_json::json!(compiled.abi[0].dispatch_tag)); + let json_value: serde_json::Value = serde_json::from_str(&json).expect("parse portable ABI artifact JSON"); + let artifact: SilAbiArtifact = serde_json::from_str(&json).expect("parse portable ABI artifact"); + artifact.verify().expect("portable ABI verifies"); + assert_eq!(artifact.contracts.len(), 1); + assert!(artifact.contracts.contains_key("Basic")); + assert_eq!(json_value["compiler_version"], COMPILER_VERSION); + assert!(json_value["contracts"]["Basic"].get("cov_decl_to_abi").is_none()); + assert!(json_value["contracts"]["Basic"].get("delegate_entry_abi").is_none()); + assert_eq!( + json_value["contracts"]["Basic"]["entries"]["main"]["dispatch_tag"], + serde_json::json!(artifact.contract("Basic").unwrap().entry("main").unwrap().dispatch_tag) + ); } #[test] @@ -124,11 +130,12 @@ fn silverc_accepts_constructor_args_and_output_flag() { assert!(status.success()); let json = fs::read_to_string(&out_path).expect("read output"); - let compiled: CompiledContract = serde_json::from_str(&json).expect("parse compiled contract"); - assert_eq!(compiled.contract_name, "WithCtor"); - assert_eq!(compiled.compiler_version, COMPILER_VERSION); - let selector = compiled.entry_by_name("main").expect("entrypoint resolved").dispatch_tag; - assert!(run_bytecode_with_selector(compiled.bytecode, selector).is_ok()); + let artifact: SilAbiArtifact = serde_json::from_str(&json).expect("parse portable ABI artifact"); + artifact.verify().expect("portable ABI verifies"); + let contract = artifact.contract("WithCtor").expect("contract resolved"); + let selector = contract.entry("main").expect("entrypoint resolved").dispatch_tag.into_bytes(); + let bytecode = contract.compiled.bytecode.clone(); + assert!(run_bytecode_with_selector(bytecode, selector).is_ok()); } #[test] diff --git a/silverscript-lang/tests/temporal_type_tests.rs b/silverscript-lang/tests/temporal_type_tests.rs index 465496a7..20385c57 100644 --- a/silverscript-lang/tests/temporal_type_tests.rs +++ b/silverscript-lang/tests/temporal_type_tests.rs @@ -1,3 +1,5 @@ +mod common; + use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; use kaspa_consensus_core::tx::{ PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput, @@ -5,11 +7,13 @@ use kaspa_consensus_core::tx::{ }; use kaspa_txscript::caches::Cache; use kaspa_txscript::{EngineCtx, EngineFlags, TxScriptEngine}; -use silverscript_lang::ast::{Expr, parse_type_ref}; -use silverscript_lang::compiler::{CompileOptions, CompiledContract, compile_contract}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::compiler::{CompileOptions, compile_to_sil_abi_artifact_with_options}; + +use common::{bytecode, encode_single_entry_sig_script}; -fn execute(compiled: CompiledContract<'_>, args: Vec>) { - let signature_script = compiled.build_sig_script("main", args).expect("signature script builds"); +fn execute(compiled: silverscript_abi::SilAbiArtifact, args: &[ArtifactValue]) { + let signature_script = encode_single_entry_sig_script(&compiled, args).expect("signature script builds"); let input = TransactionInput::new( TransactionOutpoint { transaction_id: TransactionId::from_bytes([9; 32]), index: 0 }, signature_script, @@ -17,7 +21,7 @@ fn execute(compiled: CompiledContract<'_>, args: Vec>) { 0, ); let output = - TransactionOutput { value: 1_000, script_public_key: ScriptPublicKey::new(0, compiled.bytecode.into()), covenant: None }; + TransactionOutput { value: 1_000, script_public_key: ScriptPublicKey::new(0, bytecode(&compiled).into()), covenant: None }; let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); let utxo = UtxoEntry::new(output.value, output.script_public_key, 0, false, None); let populated = PopulatedTransaction::new(&tx, vec![utxo.clone()]); @@ -51,8 +55,9 @@ fn temporal_supports_int_operations_with_temporal_operands() { } } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("temporal operations compile"); - execute(compiled, vec![Expr::temporal(20), Expr::temporal(3)]); + let compiled = + compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect("temporal operations compile"); + execute(compiled, &[20.into(), 3.into()]); } #[test] @@ -67,8 +72,9 @@ fn int_and_temporal_conversions_are_runtime_no_ops() { } } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("explicit conversions compile"); - execute(compiled, vec![Expr::temporal(1234), Expr::int(5678)]); + let compiled = + compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect("explicit conversions compile"); + execute(compiled, &[1234.into(), 5678.into()]); } #[test] @@ -84,8 +90,9 @@ fn temporal_fields_arrays_and_millisecond_units_round_trip() { } } "#; - let compiled = compile_contract(source, &[Expr::temporal(1_000)], CompileOptions::default()).expect("temporal storage compiles"); - execute(compiled, vec![Expr::temporal(3_000), Expr::temporal(3_000)]); + let compiled = compile_to_sil_abi_artifact_with_options(source, &[ArtifactValue::Int(1_000)], CompileOptions::default()) + .expect("temporal storage compiles"); + execute(compiled, &[3_000.into(), 3_000.into()]); } #[test] @@ -100,12 +107,10 @@ fn temporal_array_entrypoint_arguments_round_trip() { } } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("temporal array argument compiles"); - let points = Expr::array( - parse_type_ref("temporal[3]").expect("temporal array type parses"), - vec![Expr::temporal(1_000), Expr::temporal(2_000), Expr::temporal(62_000)], - ); - execute(compiled, vec![points]); + let compiled = + compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect("temporal array argument compiles"); + let points = ArtifactValue::Array(vec![1_000.into(), 2_000.into(), 62_000.into()]); + execute(compiled, &[points]); } #[test] @@ -125,8 +130,9 @@ fn temporal_array_size_inference_and_append_execute() { } } "#; - let compiled = compile_contract(source, &[], CompileOptions::default()).expect("temporal array operations compile"); - execute(compiled, vec![Expr::temporal(2_000)]); + let compiled = + compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).expect("temporal array operations compile"); + execute(compiled, &[2_000.into()]); } #[test] @@ -137,7 +143,7 @@ fn temporal_arrays_reject_int_elements_without_conversion() { "contract C() { entry main() { int[] values = temporal[]{temporal(1)}; } }", ] { assert!( - compile_contract(source, &[], CompileOptions::default()).is_err(), + compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).is_err(), "mixed int/temporal array must be rejected: {source}" ); } @@ -156,7 +162,7 @@ fn int_and_temporal_require_explicit_conversion() { "contract C() { entry main() { require(this.age >= 1); } }", ] { assert!( - compile_contract(source, &[], CompileOptions::default()).is_err(), + compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()).is_err(), "mixed or obsolete temporal expression must be rejected: {source}" ); } @@ -170,21 +176,24 @@ fn known_relative_age_must_fit_u32() { entry main() { require(this.ageDaa >= TOO_OLD); } } "#; - let error = compile_contract(source, &[], CompileOptions::default()).expect_err("known 2^32 value must be rejected"); + let error = compile_to_sil_abi_artifact_with_options(source, &[], CompileOptions::default()) + .expect_err("known 2^32 value must be rejected"); assert!(error.to_string().contains("0 <= value < 2^32"), "unexpected error: {error}"); let negative = "contract C() { entry main() { require(this.ageDaa >= -1); } }"; - let error = compile_contract(negative, &[], CompileOptions::default()).expect_err("known negative age must be rejected"); + let error = compile_to_sil_abi_artifact_with_options(negative, &[], CompileOptions::default()) + .expect_err("known negative age must be rejected"); assert!(error.to_string().contains("0 <= value < 2^32"), "unexpected error: {error}"); let zero = "contract C() { entry main() { require(this.ageDaa >= 0); } }"; - compile_contract(zero, &[], CompileOptions::default()).expect("zero remains valid"); + compile_to_sil_abi_artifact_with_options(zero, &[], CompileOptions::default()).expect("zero remains valid"); let max = "contract C() { entry main() { require(this.ageDaa >= 4294967295); } }"; - compile_contract(max, &[], CompileOptions::default()).expect("2^32 - 1 remains valid"); + compile_to_sil_abi_artifact_with_options(max, &[], CompileOptions::default()).expect("2^32 - 1 remains valid"); let constructor_known = "contract C(int age) { entry main() { require(this.ageDaa >= age); } }"; - let error = compile_contract(constructor_known, &[Expr::int(1_i64 << 32)], CompileOptions::default()) - .expect_err("known constructor age must be rejected"); + let error = + compile_to_sil_abi_artifact_with_options(constructor_known, &[ArtifactValue::Int(1_i64 << 32)], CompileOptions::default()) + .expect_err("known constructor age must be rejected"); assert!(error.to_string().contains("0 <= value < 2^32"), "unexpected error: {error}"); } diff --git a/silverscript-lang/tests/tutorial_examples_tests.rs b/silverscript-lang/tests/tutorial_examples_tests.rs index eb22fad9..4ad19ec1 100644 --- a/silverscript-lang/tests/tutorial_examples_tests.rs +++ b/silverscript-lang/tests/tutorial_examples_tests.rs @@ -1,5 +1,6 @@ -use silverscript_lang::ast::{ArrayDim, Expr, TypeBase, TypeRef, parse_contract_ast}; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +use silverscript_abi::ArtifactValue; +use silverscript_lang::ast::{ArrayDim, TypeBase, TypeRef, parse_contract_ast}; +use silverscript_lang::compiler::{CompileOptions, compile_to_sil_abi_artifact_with_options}; #[test] fn tutorial_contract_examples_parse() { @@ -32,13 +33,13 @@ fn tutorial_examples_compile() { .map(|param| dummy_value(¶m.type_ref)) .collect::, _>>() .unwrap_or_else(|err| panic!("tutorial example #{index} constructor arguments could not be generated: {err}")); - if let Err(err) = compile_contract(&source, &constructor_args, CompileOptions::default()) { + if let Err(err) = compile_to_sil_abi_artifact_with_options(&source, &constructor_args, CompileOptions::default()) { panic!("tutorial example #{index} failed to compile: {err}\n--- snippet ---\n{snippet}\n--- wrapped source ---\n{source}"); } } } -fn dummy_value(type_ref: &TypeRef) -> Result, String> { +fn dummy_value(type_ref: &TypeRef) -> Result { if type_ref.is_array() { let length = match type_ref.array_size() { Some(ArrayDim::Fixed(length)) => *length, @@ -46,20 +47,23 @@ fn dummy_value(type_ref: &TypeRef) -> Result, String> { Some(ArrayDim::Constant(name)) => return Err(format!("array size constant '{name}' is unsupported in tutorial tests")), Some(ArrayDim::Inferred) | None => return Err(format!("cannot generate a value for {}", type_ref.type_name())), }; + if type_ref.base == TypeBase::Byte && type_ref.array_dims.len() == 1 { + return Ok(vec![0u8; length].into()); + } let element_type = type_ref.array_element_type().ok_or_else(|| format!("invalid array type {}", type_ref.type_name()))?; let values = (0..length).map(|_| dummy_value(&element_type)).collect::, _>>()?; - return Ok(Expr::array(type_ref.clone(), values)); + return Ok(values.into()); } Ok(match &type_ref.base { - TypeBase::Int => Expr::int(0), - TypeBase::Temporal => Expr::temporal(kaspa_txscript::LOCK_TIME_THRESHOLD as i64), - TypeBase::Bool => Expr::bool(false), - TypeBase::Byte => Expr::byte(0), - TypeBase::String => Expr::string(String::new()), - TypeBase::Pubkey => Expr::bytes(vec![0; 32]), - TypeBase::Sig => Expr::bytes(vec![0; 65]), - TypeBase::Datasig => Expr::bytes(vec![0; 64]), + TypeBase::Int => 0.into(), + TypeBase::Temporal => (kaspa_txscript::LOCK_TIME_THRESHOLD as i64).into(), + TypeBase::Bool => false.into(), + TypeBase::Byte => 0u8.into(), + TypeBase::String => String::new().into(), + TypeBase::Pubkey => vec![0u8; 32].into(), + TypeBase::Sig => vec![0u8; 65].into(), + TypeBase::Datasig => vec![0u8; 64].into(), TypeBase::Tuple(_) | TypeBase::Custom(_) => return Err(format!("cannot generate a value for {}", type_ref.type_name())), }) } @@ -238,3 +242,4 @@ fn indent(text: &str, spaces: usize) -> String { let padding = " ".repeat(spaces); text.lines().map(|line| if line.is_empty() { line.to_string() } else { format!("{padding}{line}") }).collect::>().join("\n") } +mod common; diff --git a/silverscript-lang/tests/tutorial_rust_examples_tests.rs b/silverscript-lang/tests/tutorial_rust_examples_tests.rs index c5f40b9e..72bd992c 100644 --- a/silverscript-lang/tests/tutorial_rust_examples_tests.rs +++ b/silverscript-lang/tests/tutorial_rust_examples_tests.rs @@ -1,5 +1,8 @@ -use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +mod common; + +use silverscript_lang::compiler::compile_to_sil_abi_artifact; + +use common::encode_entry_sig_script; #[test] fn tutorial_rust_programmatic_compilation_example() { @@ -13,14 +16,12 @@ fn tutorial_rust_programmatic_compilation_example() { } "#; - let constructor_args = vec![Expr::int(100)]; - let compiled = compile_contract(source, &constructor_args, CompileOptions::default()) - .expect("programmatic compilation example should compile"); + let artifact = compile_to_sil_abi_artifact(source, &[100.into()]).expect("programmatic compilation example should compile"); + let contract = artifact.contract("MyContract").expect("contract exists"); - assert_eq!(compiled.contract_name, "MyContract"); - assert!(!compiled.bytecode.is_empty()); - assert_eq!(compiled.abi.len(), 1); - assert_eq!(compiled.abi[0].name, "spend"); + assert!(!contract.compiled.bytecode.is_empty()); + assert_eq!(contract.entries.len(), 1); + assert!(contract.entries.contains_key("spend")); } #[test] @@ -43,13 +44,13 @@ fn tutorial_rust_build_sigscript_multiple_entrypoints_example() { let sender_pk = vec![3u8; 32]; let recipient_pk = vec![4u8; 32]; let timeout = 1_640_000_000_000i64; - let compiled = - compile_contract(source, &[sender_pk.into(), recipient_pk.into(), Expr::temporal(timeout)], CompileOptions::default()) - .expect("multi-entrypoint example should compile"); + let artifact = compile_to_sil_abi_artifact(source, &[sender_pk.into(), recipient_pk.into(), timeout.into()]) + .expect("multi-entrypoint example should compile"); let sig = vec![5u8; 65]; - let transfer_sigscript = compiled.build_sig_script("transfer", vec![sig.clone().into()]).expect("transfer sigscript should build"); - let reclaim_sigscript = compiled.build_sig_script("reclaim", vec![sig.into()]).expect("reclaim sigscript should build"); + let transfer_sigscript = + encode_entry_sig_script(&artifact, "transfer", &[sig.clone().into()]).expect("transfer sigscript should build"); + let reclaim_sigscript = encode_entry_sig_script(&artifact, "reclaim", &[sig.into()]).expect("reclaim sigscript should build"); assert!(!transfer_sigscript.is_empty()); assert!(!reclaim_sigscript.is_empty());