From d1a2752bde1d6e532757240954ca1f70278620a7 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 11 May 2026 16:53:08 +0300 Subject: [PATCH 01/13] Add a more distinct prefix for cov decl generated auth entrypoint --- debugger/session/src/covenant.rs | 21 +++++------------- .../src/compiler/covenant_declarations.rs | 2 +- silverscript-lang/src/compiler/mod.rs | 11 +++++----- silverscript-lang/tests/compiler_tests.rs | 17 +++++++++----- .../tests/covenant_compiler_tests.rs | 22 +++++++++---------- .../tests/covenant_declaration_ast_tests.rs | 5 ++++- .../covenant_declaration_security_tests.rs | 11 +++++----- 7 files changed, 45 insertions(+), 44 deletions(-) diff --git a/debugger/session/src/covenant.rs b/debugger/session/src/covenant.rs index 22bf23c..07279b3 100644 --- a/debugger/session/src/covenant.rs +++ b/debugger/session/src/covenant.rs @@ -1,7 +1,10 @@ use std::collections::HashSet; use silverscript_lang::ast::{ContractAst, FunctionAst}; -use silverscript_lang::compiler::CompiledContract; +use silverscript_lang::compiler::{ + CompiledContract, generated_covenant_auth_entrypoint_name, generated_covenant_delegate_entrypoint_name, + generated_covenant_leader_entrypoint_name, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CovenantBinding { @@ -43,7 +46,7 @@ impl ResolvedCovenantCallTarget { pub fn generated_entrypoint_name_for(&self, is_leader: bool) -> String { match self.binding { - CovenantBinding::Auth => generated_covenant_entrypoint_name(&self.source_name), + CovenantBinding::Auth => generated_covenant_auth_entrypoint_name(&self.source_name), CovenantBinding::Cov => { if is_leader { generated_covenant_leader_entrypoint_name(&self.source_name) @@ -63,7 +66,7 @@ pub fn resolve_covenant_call_target<'i>( let function = contract.functions.iter().find(|function| function.name == function_name && is_covenant_source_function(function))?; - let auth_entrypoint_name = generated_covenant_entrypoint_name(function_name); + let auth_entrypoint_name = generated_covenant_auth_entrypoint_name(function_name); let leader_entrypoint_name = generated_covenant_leader_entrypoint_name(function_name); let has_auth_entrypoint = abi_contains_function(compiled, &auth_entrypoint_name); let has_leader_entrypoint = abi_contains_function(compiled, &leader_entrypoint_name); @@ -119,15 +122,3 @@ fn is_covenant_source_function(function: &FunctionAst<'_>) -> bool { fn generated_covenant_policy_name(function_name: &str) -> String { format!("__covenant_policy_{function_name}") } - -fn generated_covenant_entrypoint_name(function_name: &str) -> String { - format!("__{function_name}") -} - -fn generated_covenant_leader_entrypoint_name(function_name: &str) -> String { - format!("__leader_{function_name}") -} - -fn generated_covenant_delegate_entrypoint_name(function_name: &str) -> String { - format!("__delegate_{function_name}") -} diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 3b51bec..0145ae0 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -71,7 +71,7 @@ pub(super) fn lower_covenant_declarations<'i>( match declaration.binding { CovenantBinding::Auth => { - let entrypoint_name = generated_covenant_entrypoint_name(&function.name); + let entrypoint_name = generated_covenant_auth_entrypoint_name(&function.name); let mut wrapper = build_auth_wrapper(&policy, &policy_name, declaration.clone(), entrypoint_name, &contract.fields)?; wrapper.params = preserved_entrypoint_params(function, declaration, true, &contract.fields); lowered.push(wrapper); diff --git a/silverscript-lang/src/compiler/mod.rs b/silverscript-lang/src/compiler/mod.rs index eb6fd9d..90884b9 100644 --- a/silverscript-lang/src/compiler/mod.rs +++ b/silverscript-lang/src/compiler/mod.rs @@ -43,6 +43,7 @@ pub(super) use structs::{ pub const SYNTHETIC_ARG_PREFIX: &str = "__arg"; pub const COMPILER_VERSION: &str = "0.1.0"; const COVENANT_POLICY_PREFIX: &str = "__covenant_policy"; +pub const COVENANT_ENTRYPOINT_AUTH_PREFIX: &str = "__covenant_entrypoint_auth"; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct CovenantDeclCallOptions { @@ -53,15 +54,15 @@ fn generated_covenant_policy_name(function_name: &str) -> String { format!("{COVENANT_POLICY_PREFIX}_{function_name}") } -fn generated_covenant_entrypoint_name(function_name: &str) -> String { - format!("__{function_name}") +pub fn generated_covenant_auth_entrypoint_name(function_name: &str) -> String { + format!("{COVENANT_ENTRYPOINT_AUTH_PREFIX}_{function_name}") } -fn generated_covenant_leader_entrypoint_name(function_name: &str) -> String { +pub fn generated_covenant_leader_entrypoint_name(function_name: &str) -> String { format!("__leader_{function_name}") } -fn generated_covenant_delegate_entrypoint_name(function_name: &str) -> String { +pub fn generated_covenant_delegate_entrypoint_name(function_name: &str) -> String { format!("__delegate_{function_name}") } @@ -210,7 +211,7 @@ impl<'i> CompiledContract<'i> { args: Vec>, options: CovenantDeclCallOptions, ) -> Result, CompilerError> { - let auth_entrypoint = generated_covenant_entrypoint_name(function_name); + let auth_entrypoint = generated_covenant_auth_entrypoint_name(function_name); if self.abi.iter().any(|entry| entry.name == auth_entrypoint) { return self.build_sig_script(&auth_entrypoint, args); } diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index eee139f..c957304 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -19,7 +19,7 @@ use kaspa_txscript::{ use silverscript_lang::ast::{Expr, ExprKind, Statement, format_contract_ast, parse_contract_ast}; use silverscript_lang::compiler::{ CompileOptions, CompiledContract, CovenantDeclCallOptions, FunctionAbiEntry, FunctionInputAbi, compile_contract, - compile_contract_ast, function_branch_index, struct_object, + compile_contract_ast, function_branch_index, generated_covenant_auth_entrypoint_name, struct_object, }; use silverscript_lang::debug_info::StepKind; @@ -1570,7 +1570,8 @@ fn build_sig_script_for_covenant_decl_routes_to_hidden_auth_entrypoint() { let actual = compiled .build_sig_script_for_covenant_decl("step", args.clone(), CovenantDeclCallOptions { is_leader: false }) .expect("covenant sigscript builds"); - let expected = compiled.build_sig_script("__step", args).expect("hidden entrypoint sigscript builds"); + let expected = + compiled.build_sig_script(&generated_covenant_auth_entrypoint_name("step"), args).expect("hidden entrypoint sigscript builds"); assert_eq!(actual, expected); } @@ -2488,9 +2489,15 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { let sigscript = compiled .build_sig_script_for_covenant_decl(case.function_name, case.args.clone(), case.options) .expect("covenant declaration sigscript builds"); - let expected = compiled - .build_sig_script(case.generated_covenant_entrypoint_name, case.args) - .expect("generated entrypoint sigscript builds"); + let generated_entrypoint_name = if case.generated_covenant_entrypoint_name.starts_with("__leader_") + || case.generated_covenant_entrypoint_name.starts_with("__delegate_") + { + case.generated_covenant_entrypoint_name.to_string() + } else { + generated_covenant_auth_entrypoint_name(case.function_name) + }; + let expected = + compiled.build_sig_script(&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); } } diff --git a/silverscript-lang/tests/covenant_compiler_tests.rs b/silverscript-lang/tests/covenant_compiler_tests.rs index f290c87..d0f28da 100644 --- a/silverscript-lang/tests/covenant_compiler_tests.rs +++ b/silverscript-lang/tests/covenant_compiler_tests.rs @@ -1,6 +1,6 @@ use kaspa_txscript::opcodes::codes::{OpAuthOutputCount, OpCovInputCount, OpCovInputIdx, OpCovOutputCount, OpInputCovenantId}; use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +use silverscript_lang::compiler::{CompileOptions, compile_contract, generated_covenant_auth_entrypoint_name}; #[test] fn lowers_auth_covenant_declaration_to_hidden_entrypoint_name() { @@ -16,9 +16,9 @@ fn lowers_auth_covenant_declaration_to_hidden_entrypoint_name() { let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); assert!(compiled.without_selector); assert_eq!(compiled.abi.len(), 1); - assert_eq!(compiled.abi[0].name, "__spend"); + assert_eq!(compiled.abi[0].name, 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 == "__spend" && f.entrypoint)); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("spend") && f.entrypoint)); assert!(compiled.script.contains(&OpAuthOutputCount)); } @@ -36,9 +36,9 @@ 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!(compiled.without_selector); assert_eq!(compiled.abi.len(), 1); - assert_eq!(compiled.abi[0].name, "__spend"); + assert_eq!(compiled.abi[0].name, 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 == "__spend" && f.entrypoint)); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("spend") && f.entrypoint)); assert!(compiled.script.contains(&OpAuthOutputCount)); } @@ -206,7 +206,7 @@ fn lowers_singleton_sugar_to_auth_one_to_one_defaults() { let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); assert!(compiled.without_selector); - assert_eq!(compiled.abi[0].name, "__spend"); + assert_eq!(compiled.abi[0].name, generated_covenant_auth_entrypoint_name("spend")); assert!(compiled.script.contains(&OpAuthOutputCount)); } @@ -223,7 +223,7 @@ fn lowers_fanout_sugar_to_auth_with_to_bound() { let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); assert!(compiled.without_selector); - assert_eq!(compiled.abi[0].name, "__split"); + assert_eq!(compiled.abi[0].name, generated_covenant_auth_entrypoint_name("split")); assert!(compiled.script.contains(&OpAuthOutputCount)); } @@ -299,7 +299,7 @@ fn infers_verification_mode_when_mode_omitted_and_no_returns() { "#; let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); - assert!(compiled.ast.functions.iter().any(|f| f.name == "__check" && f.entrypoint)); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("check") && f.entrypoint)); } #[test] @@ -316,7 +316,7 @@ fn infers_transition_mode_when_mode_omitted_and_has_returns() { "#; let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); - assert!(compiled.ast.functions.iter().any(|f| f.name == "__roll" && f.entrypoint)); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("roll") && f.entrypoint)); } #[test] @@ -351,7 +351,7 @@ fn allows_singleton_transition_array_returns_with_termination_allowed() { "#; let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); - assert!(compiled.ast.functions.iter().any(|f| f.name == "__roll" && f.entrypoint)); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("roll") && f.entrypoint)); } #[test] @@ -404,7 +404,7 @@ fn allows_termination_in_singleton_verification_mode() { "#; let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); - assert!(compiled.ast.functions.iter().any(|f| f.name == "__check" && f.entrypoint)); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("check") && f.entrypoint)); } #[test] diff --git a/silverscript-lang/tests/covenant_declaration_ast_tests.rs b/silverscript-lang/tests/covenant_declaration_ast_tests.rs index c80d086..2f7b0eb 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_tests.rs +++ b/silverscript-lang/tests/covenant_declaration_ast_tests.rs @@ -1,6 +1,6 @@ use silverscript_lang::ast::visit::{AstVisitorMut, NameKind, visit_contract_mut}; use silverscript_lang::ast::{ContractAst, Expr, FunctionAst, parse_contract_ast}; -use silverscript_lang::compiler::{CompileOptions, compile_contract}; +use silverscript_lang::compiler::{COVENANT_ENTRYPOINT_AUTH_PREFIX, CompileOptions, compile_contract}; use silverscript_lang::span::Span; use std::collections::HashSet; @@ -8,6 +8,9 @@ fn canonicalize_generated_name(name: &str) -> String { if let Some(rest) = name.strip_prefix("__covenant_policy_") { return format!("covenant_policy_{rest}"); } + if let Some(rest) = name.strip_prefix(&format!("{COVENANT_ENTRYPOINT_AUTH_PREFIX}_")) { + return rest.to_string(); + } if let Some(rest) = name.strip_prefix("__cov_") { return format!("cov_{rest}"); } diff --git a/silverscript-lang/tests/covenant_declaration_security_tests.rs b/silverscript-lang/tests/covenant_declaration_security_tests.rs index a5036db..12b5579 100644 --- a/silverscript-lang/tests/covenant_declaration_security_tests.rs +++ b/silverscript-lang/tests/covenant_declaration_security_tests.rs @@ -2,7 +2,10 @@ use kaspa_consensus_core::Hash; use kaspa_consensus_core::tx::{Transaction, TransactionOutput, UtxoEntry}; use kaspa_txscript_errors::TxScriptError; use silverscript_lang::ast::Expr; -use silverscript_lang::compiler::{CompileOptions, CompiledContract, CovenantDeclCallOptions, compile_contract, struct_object}; +use silverscript_lang::compiler::{ + CompileOptions, CompiledContract, CovenantDeclCallOptions, compile_contract, generated_covenant_auth_entrypoint_name, + struct_object, +}; mod common; @@ -135,10 +138,6 @@ fn function_param_type_names(compiled: &CompiledContract<'_>, function_name: &st .collect() } -fn generated_auth_entrypoint_name(function_name: &str) -> String { - format!("__{function_name}") -} - fn state_array_arg(values: Vec) -> Expr<'static> { values.into_iter().map(|value| struct_object(vec![("value", Expr::int(value))])).collect::>().into() } @@ -529,7 +528,7 @@ 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 wrapper_name = generated_auth_entrypoint_name("step"); + let wrapper_name = generated_covenant_auth_entrypoint_name("step"); let wrapper_param_types = function_param_type_names(&active, &wrapper_name); assert_eq!(wrapper_param_types, vec!["State".to_string()]); From 9a8befe49bb243bfe3f6d811b5ef20aab338b981 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 18 May 2026 17:44:40 +0300 Subject: [PATCH 02/13] Validate auth transition State returns against singleton 'to' bounds --- .../src/compiler/covenant_declarations.rs | 48 ++++++---- silverscript-lang/tests/compiler_tests.rs | 4 +- .../tests/covenant_compiler_tests.rs | 94 +++++++++++++++++++ ...lowers_auth_transition_two_field_state.sil | 6 +- ..._covenant_declarations_in_one_contract.sil | 2 +- .../tests/covenant_declaration_ast_tests.rs | 4 +- 6 files changed, 133 insertions(+), 25 deletions(-) diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 0145ae0..5cccec3 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -152,12 +152,7 @@ fn parse_covenant_declaration<'i>( .copied() .ok_or_else(|| CompilerError::Unsupported("missing covenant attribute argument 'from'".to_string()))? .clone(); - let to_expr = args_by_name - .get("to") - .copied() - .ok_or_else(|| CompilerError::Unsupported("missing covenant attribute argument 'to'".to_string()))? - .clone(); - (from_expr, to_expr) + (from_expr, args_by_name.get("to").copied().cloned()) } CovenantSyntax::Singleton => { if args_by_name.contains_key("from") || args_by_name.contains_key("to") { @@ -165,7 +160,7 @@ fn parse_covenant_declaration<'i>( "covenant.singleton is sugar and does not accept 'from' or 'to' arguments".to_string(), )); } - (Expr::int(1), Expr::int(1)) + (Expr::int(1), Some(Expr::int(1))) } CovenantSyntax::Fanout => { if args_by_name.contains_key("from") { @@ -178,20 +173,15 @@ fn parse_covenant_declaration<'i>( .copied() .ok_or_else(|| CompilerError::Unsupported("missing covenant attribute argument 'to'".to_string()))? .clone(); - (Expr::int(1), to_expr) + (Expr::int(1), Some(to_expr)) } }; let from_value = eval_const_int(&from_expr, constants) .map_err(|_| CompilerError::Unsupported("covenant 'from' must be a compile-time integer".to_string()))?; - let to_value = eval_const_int(&to_expr, constants) - .map_err(|_| CompilerError::Unsupported("covenant 'to' must be a compile-time integer".to_string()))?; if from_value < 1 { return Err(CompilerError::Unsupported("covenant 'from' must be >= 1".to_string())); } - if to_value < 1 { - return Err(CompilerError::Unsupported("covenant 'to' must be >= 1".to_string())); - } let default_binding = if from_value == 1 { CovenantBinding::Auth } else { CovenantBinding::Cov }; let binding = match args_by_name.get("binding").copied() { @@ -275,10 +265,6 @@ fn parse_covenant_declaration<'i>( return Err(CompilerError::Unsupported("binding=cov with groups=multiple is not supported yet".to_string())); } - if args_by_name.contains_key("termination") && !(from_value == 1 && to_value == 1) { - return Err(CompilerError::Unsupported("termination is only supported for singleton covenants (from=1, to=1)".to_string())); - } - if mode == CovenantMode::Verification && !function.return_types.is_empty() { return Err(CompilerError::Unsupported("verification mode policy functions must not declare return values".to_string())); } @@ -286,6 +272,23 @@ fn parse_covenant_declaration<'i>( return Err(CompilerError::Unsupported("transition mode policy functions must declare return values".to_string())); } + let infers_single_state_transition_to_one = + mode == CovenantMode::Transition && function.return_types.len() == 1 && is_state_type_ref(&function.return_types[0]); + let to_expr = match to_expr { + Some(to_expr) => to_expr, + None if infers_single_state_transition_to_one => Expr::int(1), + None => return Err(CompilerError::Unsupported("missing covenant attribute argument 'to'".to_string())), + }; + let to_value = eval_const_int(&to_expr, constants) + .map_err(|_| CompilerError::Unsupported("covenant 'to' must be a compile-time integer".to_string()))?; + if to_value < 1 { + return Err(CompilerError::Unsupported("covenant 'to' must be >= 1".to_string())); + } + + if args_by_name.contains_key("termination") && !(from_value == 1 && to_value == 1) { + return Err(CompilerError::Unsupported("termination is only supported for singleton covenants (from=1, to=1)".to_string())); + } + Ok(CovenantDeclaration { binding, mode, @@ -387,6 +390,13 @@ fn validate_covenant_policy_state_shape<'i>( policy.name ))); } + + if is_state_type_ref(return_type) && !is_literal_int(&declaration.to_expr, 1) { + return Err(CompilerError::Unsupported(format!( + "mode=transition on function '{}' may return a single State only when 'to' is the literal 1 or omitted", + policy.name + ))); + } } Ok(()) @@ -1028,6 +1038,10 @@ fn is_state_array_type_ref(type_ref: &TypeRef) -> bool { !type_ref.array_dims.is_empty() && matches!(&type_ref.base, TypeBase::Custom(name) if name == "State") } +fn is_literal_int(expr: &Expr<'_>, expected: i64) -> bool { + matches!(expr.kind, ExprKind::Int(value) if value == expected) +} + fn append_policy_call_and_capture_next_state<'i>( body: &mut Vec>, policy: &FunctionAst<'i>, diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index c957304..d35a9e4 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -1994,7 +1994,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { require(new_states.length == new_states.length); } - #[covenant(binding = auth, from = 1, to = max_outs, mode = transition)] + #[covenant(binding = auth, from = 1, to = 1, mode = transition)] function auth_transition(State prev_state, int fee) : (State) { return({ amount: prev_state.amount - fee, owner: prev_state.owner }); } @@ -2188,7 +2188,7 @@ fn build_sig_script_for_covenant_decl_supports_all_covenant_ast_examples() { int amount = init_amount; byte[32] owner = init_owner; - #[covenant(binding = auth, from = 1, to = max_outs, mode = transition)] + #[covenant(binding = auth, from = 1, to = 1, mode = transition)] function step(State prev_state, int fee) : (State) { return({ amount: prev_state.amount - fee, owner: prev_state.owner }); } diff --git a/silverscript-lang/tests/covenant_compiler_tests.rs b/silverscript-lang/tests/covenant_compiler_tests.rs index d0f28da..4d547cf 100644 --- a/silverscript-lang/tests/covenant_compiler_tests.rs +++ b/silverscript-lang/tests/covenant_compiler_tests.rs @@ -319,6 +319,100 @@ fn infers_transition_mode_when_mode_omitted_and_has_returns() { assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("roll") && f.entrypoint)); } +#[test] +fn rejects_auth_transition_single_state_return_when_to_is_not_literal_one() { + let source = r#" + contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { + int amount = init_amount; + byte[32] owner = init_owner; + + #[covenant(binding = auth, from = 1, to = max_outs, mode = transition)] + function step(State prev_state, int fee) : (State) { + return({ + amount: prev_state.amount - fee, + owner: prev_state.owner + }); + } + } + "#; + + 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"); + assert!(err.to_string().contains("may return a single State only when 'to' is the literal 1 or omitted")); +} + +#[test] +fn rejects_auth_transition_single_state_return_when_to_is_constant_one() { + let source = r#" + contract Decls(int init_value) { + int constant ONE = 1; + int value = init_value; + + #[covenant(binding = auth, from = 1, to = ONE, mode = transition)] + function roll(State prev_state, int x) : (State) { + return({ value: prev_state.value + x }); + } + } + "#; + + let err = compile_contract(source, &[Expr::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")); +} + +#[test] +fn allows_auth_transition_single_state_return_when_to_is_literal_one() { + let source = r#" + contract Decls(int init_value) { + int value = init_value; + + #[covenant(binding = auth, from = 1, to = 1, mode = transition)] + function roll(State prev_state, int x) : (State) { + return({ value: prev_state.value + x }); + } + } + "#; + + let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("roll") && f.entrypoint)); +} + +#[test] +fn allows_auth_transition_single_state_return_when_to_is_omitted() { + let source = r#" + contract Decls(int init_value) { + int value = init_value; + + #[covenant(binding = auth, from = 1, mode = transition)] + function roll(State prev_state, int x) : (State) { + return({ value: prev_state.value + x }); + } + } + "#; + + let compiled = compile_contract(source, &[Expr::int(3)], CompileOptions::default()).expect("compile succeeds"); + assert!(compiled.ast.functions.iter().any(|f| f.name == generated_covenant_auth_entrypoint_name("roll") && f.entrypoint)); +} + +#[test] +fn rejects_omitted_to_for_auth_transition_array_state_return() { + let source = r#" + contract Decls(int init_value) { + int value = init_value; + + #[covenant(binding = auth, from = 1, mode = transition)] + function fanout(State prev_state, State[] next_states) : (State[]) { + require(prev_state.value >= 0); + return(next_states); + } + } + "#; + + let err = compile_contract(source, &[Expr::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'")); +} + #[test] fn rejects_singleton_transition_array_returns_without_termination_allowed() { let source = r#" diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil index 3700132..b475203 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil @@ -1,8 +1,8 @@ -contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { +contract Matrix(int init_amount, byte[32] init_owner) { int amount = init_amount; byte[32] owner = init_owner; - #[covenant(binding = auth, from = 1, to = max_outs, mode = transition)] + #[covenant(binding = auth, from = 1, mode = transition)] function step(State prev_state, int fee) : (State) { return({ amount: prev_state.amount - fee, @@ -13,7 +13,7 @@ contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { // --- lowered --- -contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { +contract Matrix(int init_amount, byte[32] init_owner) { int amount = init_amount; byte[32] owner = init_owner; diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil index 8a53575..7526565 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil @@ -16,7 +16,7 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) require(new_states.length == new_states.length); } - #[covenant(binding = auth, from = 1, to = max_outs, mode = transition)] + #[covenant(binding = auth, from = 1, mode = transition)] function auth_transition(State prev_state, int fee) : (State) { return({ amount: prev_state.amount - fee, owner: prev_state.owner }); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_tests.rs b/silverscript-lang/tests/covenant_declaration_ast_tests.rs index 2f7b0eb..71320f5 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_tests.rs +++ b/silverscript-lang/tests/covenant_declaration_ast_tests.rs @@ -91,7 +91,7 @@ fixture_ast_test!(lowers_transition_array_return_to_exact_output_count_match, [E fixture_ast_test!(lowers_singleton_transition_with_termination_allowed_to_array_cardinality_checks, [Expr::int(10)]); fixture_ast_test!(lowers_auth_verification_groups_multiple_two_field_state, [Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_auth_verification_groups_single_two_field_state, [Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); -fixture_ast_test!(lowers_auth_transition_two_field_state, [Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); +fixture_ast_test!(lowers_auth_transition_two_field_state, [Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_verification_two_field_state, [Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_transition_two_field_state, [Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_transition_single_field_state, [Expr::int(2), Expr::int(2), Expr::int(10)]); @@ -135,7 +135,7 @@ fn covers_attribute_config_combinations_with_two_field_state() { require(new_states.length == new_states.length); } - #[covenant(binding = auth, from = 1, to = max_outs, mode = transition)] + #[covenant(binding = auth, from = 1, to = 1, mode = transition)] function auth_transition(State prev_state, int fee) : (State) { return({ amount: prev_state.amount - fee, owner: prev_state.owner }); } From 8adb68aca3b1a605e155c67123303420c573c23d Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 18 May 2026 17:48:41 +0300 Subject: [PATCH 03/13] Suggest to use binding=cov only if from is literal 1 --- silverscript-lang/src/compiler/covenant_declarations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 5cccec3..988065a 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -255,7 +255,7 @@ fn parse_covenant_declaration<'i>( if binding == CovenantBinding::Auth && from_value != 1 { return Err(CompilerError::Unsupported("binding=auth requires from = 1".to_string())); } - if binding == CovenantBinding::Cov && from_value == 1 && args_by_name.contains_key("binding") { + if binding == CovenantBinding::Cov && is_literal_int(&from_expr, 1) && args_by_name.contains_key("binding") { eprintln!( "warning: #[covenant(...)] on function '{}' uses binding=cov with from=1; binding=auth is usually a better default", function.name From 854b021a98171e2c7e3fba9bc6ede5cec7b580e7 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 18 May 2026 18:33:06 +0300 Subject: [PATCH 04/13] Remove redundant code --- .../src/compiler/covenant_declarations.rs | 258 +----------------- 1 file changed, 8 insertions(+), 250 deletions(-) diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 988065a..107e1bf 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -36,16 +36,6 @@ struct CovenantDeclaration<'i> { to_expr: Expr<'i>, } -#[derive(Debug, Clone)] -enum OutputStateSource<'i> { - Single(Expr<'i>), - PerOutputArrays { - // field_name -> array_binding_name - field_arrays: Vec<(String, String)>, - length_expr: Expr<'i>, - }, -} - pub(super) fn lower_covenant_declarations<'i>( contract: &ContractAst<'i>, constants: &HashMap>, @@ -550,7 +540,7 @@ fn build_auth_wrapper<'i>( } } else { let call_args: Vec> = policy.params.iter().map(|param| identifier_expr(¶m.name)).collect(); - let state_source = append_policy_call_and_capture_next_state( + append_policy_call_and_capture_next_state( &mut body, policy, policy_name, @@ -560,53 +550,7 @@ fn build_auth_wrapper<'i>( contract_fields, call_args, )?; - if !contract_fields.is_empty() { - match state_source { - OutputStateSource::Single(next_state_expr) => { - if declaration.mode == CovenantMode::Transition || declaration.singleton { - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(out_count_name), Expr::int(1)))); - let out_idx_name = "__cov_out_idx"; - body.push(var_def_statement( - int_type_ref(), - out_idx_name, - Expr::call("OpAuthOutputIdx", vec![active_input.clone(), Expr::int(0)]), - )); - body.push(call_statement("validateOutputState", vec![identifier_expr(out_idx_name), next_state_expr])); - } else { - body.push(require_statement(binary_expr( - BinaryOp::Le, - identifier_expr(out_count_name), - declaration.to_expr.clone(), - ))); - append_auth_output_state_checks( - &mut body, - &active_input, - out_count_name, - declaration.to_expr.clone(), - next_state_expr, - ); - } - } - OutputStateSource::PerOutputArrays { field_arrays, length_expr } => { - body.push(require_statement(binary_expr( - BinaryOp::Le, - identifier_expr(out_count_name), - declaration.to_expr.clone(), - ))); - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(out_count_name), length_expr.clone()))); - append_auth_output_array_state_checks( - &mut body, - &active_input, - out_count_name, - declaration.to_expr.clone(), - field_arrays, - contract_fields, - ); - } - } - } else { - body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(out_count_name), declaration.to_expr.clone()))); - } + body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(out_count_name), declaration.to_expr.clone()))); } Ok(generated_entrypoint(policy, entrypoint_name, entrypoint_params, body)) @@ -733,7 +677,7 @@ fn build_cov_wrapper<'i>( } else { append_cov_input_state_reads(&mut body, cov_id_name, in_count_name, declaration.from_expr.clone(), contract_fields); let call_args = policy.params.iter().map(|param| identifier_expr(¶m.name)).collect(); - let state_source = append_policy_call_and_capture_next_state( + append_policy_call_and_capture_next_state( &mut body, policy, policy_name, @@ -743,53 +687,7 @@ fn build_cov_wrapper<'i>( contract_fields, call_args, )?; - if !contract_fields.is_empty() { - match state_source { - OutputStateSource::Single(next_state_expr) => { - if declaration.mode == CovenantMode::Transition || declaration.singleton { - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(out_count_name), Expr::int(1)))); - let out_idx_name = "__cov_out_idx"; - body.push(var_def_statement( - int_type_ref(), - out_idx_name, - Expr::call("OpCovOutputIdx", vec![identifier_expr(cov_id_name), Expr::int(0)]), - )); - body.push(call_statement("validateOutputState", vec![identifier_expr(out_idx_name), next_state_expr])); - } else { - body.push(require_statement(binary_expr( - BinaryOp::Le, - identifier_expr(out_count_name), - declaration.to_expr.clone(), - ))); - append_cov_output_state_checks( - &mut body, - cov_id_name, - out_count_name, - declaration.to_expr.clone(), - next_state_expr, - ); - } - } - OutputStateSource::PerOutputArrays { field_arrays, length_expr } => { - body.push(require_statement(binary_expr( - BinaryOp::Le, - identifier_expr(out_count_name), - declaration.to_expr.clone(), - ))); - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(out_count_name), length_expr.clone()))); - append_cov_output_array_state_checks( - &mut body, - cov_id_name, - out_count_name, - declaration.to_expr.clone(), - field_arrays, - contract_fields, - ); - } - } - } else { - body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(out_count_name), declaration.to_expr.clone()))); - } + body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(out_count_name), declaration.to_expr.clone()))); } } @@ -970,53 +868,6 @@ fn state_object_expr_from_contract_fields<'i>(contract_fields: &[ContractFieldAs Expr::new(ExprKind::StateObject(fields), span::Span::default()) } -fn state_object_expr_from_field_bindings<'i>( - contract_fields: &[ContractFieldAst<'i>], - binding_by_field: &HashMap, -) -> Expr<'i> { - let fields = contract_fields - .iter() - .map(|field| { - let binding_name = binding_by_field - .get(&field.name) - .cloned() - .unwrap_or_else(|| panic!("missing state binding for field '{}'", field.name)); - StateFieldExpr { - name: field.name.clone(), - expr: identifier_expr(&binding_name), - span: span::Span::default(), - name_span: span::Span::default(), - } - }) - .collect(); - Expr::new(ExprKind::StateObject(fields), span::Span::default()) -} - -fn state_object_expr_from_field_arrays_at_index<'i>( - contract_fields: &[ContractFieldAst<'i>], - field_arrays: &[(String, String)], - index_expr: Expr<'i>, -) -> Expr<'i> { - let by_field = field_arrays.iter().cloned().collect::>(); - let fields = contract_fields - .iter() - .map(|field| { - let array_name = - by_field.get(&field.name).cloned().unwrap_or_else(|| panic!("missing state array binding for field '{}'", field.name)); - StateFieldExpr { - name: field.name.clone(), - expr: Expr::new( - ExprKind::ArrayIndex { source: Box::new(identifier_expr(&array_name)), index: Box::new(index_expr.clone()) }, - span::Span::default(), - ), - span: span::Span::default(), - name_span: span::Span::default(), - } - }) - .collect(); - Expr::new(ExprKind::StateObject(fields), span::Span::default()) -} - fn length_expr<'i>(expr: Expr<'i>) -> Expr<'i> { Expr::new( ExprKind::UnarySuffix { source: Box::new(expr), kind: UnarySuffixKind::Length, span: span::Span::default() }, @@ -1051,11 +902,11 @@ fn append_policy_call_and_capture_next_state<'i>( termination: CovenantTermination, contract_fields: &[ContractFieldAst<'i>], call_args: Vec>, -) -> Result, CompilerError> { +) -> Result<(), CompilerError> { match mode { CovenantMode::Verification => { body.push(call_statement(policy_name, call_args)); - Ok(OutputStateSource::Single(state_object_expr_from_contract_fields(contract_fields))) + Ok(()) } CovenantMode::Transition => { if policy.return_types.len() != contract_fields.len() { @@ -1095,7 +946,7 @@ fn append_policy_call_and_capture_next_state<'i>( body.push(function_call_assign_statement(bindings, policy_name, call_args)); if shape_is_single { - Ok(OutputStateSource::Single(state_object_expr_from_field_bindings(contract_fields, &binding_by_field))) + Ok(()) } else { let first_field = &contract_fields[0].name; let first_array_name = binding_by_field @@ -1114,43 +965,12 @@ fn append_policy_call_and_capture_next_state<'i>( expected_len_expr.clone(), ))); } - - let field_arrays = contract_fields - .iter() - .map(|field| { - let name = binding_by_field - .get(&field.name) - .cloned() - .unwrap_or_else(|| panic!("missing transition binding for field '{}'", field.name)); - (field.name.clone(), name) - }) - .collect(); - Ok(OutputStateSource::PerOutputArrays { field_arrays, length_expr: expected_len_expr }) + Ok(()) } } } } -fn append_auth_output_state_checks<'i>( - body: &mut Vec>, - active_input: &Expr<'i>, - out_count_name: &str, - to_expr: Expr<'i>, - next_state_expr: Expr<'i>, -) { - let loop_var = "__cov_k"; - let out_idx_name = "__cov_out_idx"; - let then_branch = vec![ - var_def_statement( - int_type_ref(), - out_idx_name, - Expr::call("OpAuthOutputIdx", vec![active_input.clone(), identifier_expr(loop_var)]), - ), - call_statement("validateOutputState", vec![identifier_expr(out_idx_name), next_state_expr]), - ]; - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, then_branch)); -} - fn append_cov_input_state_reads<'i>( body: &mut Vec>, cov_id_name: &str, @@ -1193,68 +1013,6 @@ fn append_cov_input_state_reads_into_state_array<'i>( body.push(for_statement(loop_var, Expr::int(0), identifier_expr(in_count_name), from_expr, then_branch)); } -fn append_cov_output_state_checks<'i>( - body: &mut Vec>, - cov_id_name: &str, - out_count_name: &str, - to_expr: Expr<'i>, - next_state_expr: Expr<'i>, -) { - let loop_var = "__cov_k"; - let out_idx_name = "__cov_out_idx"; - let then_branch = vec![ - var_def_statement( - int_type_ref(), - out_idx_name, - Expr::call("OpCovOutputIdx", vec![identifier_expr(cov_id_name), identifier_expr(loop_var)]), - ), - call_statement("validateOutputState", vec![identifier_expr(out_idx_name), next_state_expr]), - ]; - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, then_branch)); -} - -fn append_auth_output_array_state_checks<'i>( - body: &mut Vec>, - active_input: &Expr<'i>, - out_count_name: &str, - to_expr: Expr<'i>, - field_arrays: Vec<(String, String)>, - contract_fields: &[ContractFieldAst<'i>], -) { - let loop_var = "__cov_k"; - let out_idx_name = "__cov_out_idx"; - let mut then_branch = Vec::new(); - then_branch.push(var_def_statement( - int_type_ref(), - out_idx_name, - Expr::call("OpAuthOutputIdx", vec![active_input.clone(), identifier_expr(loop_var)]), - )); - let next_state_expr = state_object_expr_from_field_arrays_at_index(contract_fields, &field_arrays, identifier_expr(loop_var)); - then_branch.push(call_statement("validateOutputState", vec![identifier_expr(out_idx_name), next_state_expr])); - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, then_branch)); -} - -fn append_cov_output_array_state_checks<'i>( - body: &mut Vec>, - cov_id_name: &str, - out_count_name: &str, - to_expr: Expr<'i>, - field_arrays: Vec<(String, String)>, - contract_fields: &[ContractFieldAst<'i>], -) { - let loop_var = "__cov_k"; - let out_idx_name = "__cov_out_idx"; - let mut then_branch = Vec::new(); - then_branch.push(var_def_statement( - int_type_ref(), - out_idx_name, - Expr::call("OpCovOutputIdx", vec![identifier_expr(cov_id_name), identifier_expr(loop_var)]), - )); - let next_state_expr = state_object_expr_from_field_arrays_at_index(contract_fields, &field_arrays, identifier_expr(loop_var)); - then_branch.push(call_statement("validateOutputState", vec![identifier_expr(out_idx_name), next_state_expr])); - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, then_branch)); -} - fn append_auth_output_state_array_checks_from_state_array<'i>( body: &mut Vec>, active_input: &Expr<'i>, From 0e35fd39d3b52ae19b3e8ab8bc783de794cdb595 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 11:44:43 +0300 Subject: [PATCH 05/13] Disallow mode=transition for stateless contracts --- .../src/compiler/covenant_declarations.rs | 126 +++--------------- .../tests/covenant_compiler_tests.rs | 32 +++++ 2 files changed, 54 insertions(+), 104 deletions(-) diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 107e1bf..4fbd7b5 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -540,16 +540,16 @@ fn build_auth_wrapper<'i>( } } else { let call_args: Vec> = policy.params.iter().map(|param| identifier_expr(¶m.name)).collect(); - append_policy_call_and_capture_next_state( - &mut body, - policy, - policy_name, - declaration.mode, - declaration.singleton, - declaration.termination, - contract_fields, - call_args, - )?; + + match declaration.mode { + CovenantMode::Verification => { + body.push(call_statement(policy_name, call_args)); + } + CovenantMode::Transition => { + return Err(CompilerError::Unsupported("mode=tranisition is not supported when contract state is empty".to_string())); + } + } + body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(out_count_name), declaration.to_expr.clone()))); } @@ -677,16 +677,18 @@ fn build_cov_wrapper<'i>( } else { append_cov_input_state_reads(&mut body, cov_id_name, in_count_name, declaration.from_expr.clone(), contract_fields); let call_args = policy.params.iter().map(|param| identifier_expr(¶m.name)).collect(); - append_policy_call_and_capture_next_state( - &mut body, - policy, - policy_name, - declaration.mode, - declaration.singleton, - declaration.termination, - contract_fields, - call_args, - )?; + + match declaration.mode { + CovenantMode::Verification => { + body.push(call_statement(policy_name, call_args)); + } + CovenantMode::Transition => { + return Err(CompilerError::Unsupported( + "mode=tranisition is not supported when contract state is empty".to_string(), + )); + } + } + body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(out_count_name), declaration.to_expr.clone()))); } } @@ -875,12 +877,6 @@ fn length_expr<'i>(expr: Expr<'i>) -> Expr<'i> { ) } -fn return_type_is_per_output_array(return_type: &TypeRef, field_type: &TypeRef) -> bool { - return_type.base == field_type.base - && return_type.array_dims.len() == field_type.array_dims.len() + 1 - && return_type.array_dims[..field_type.array_dims.len()] == field_type.array_dims[..] -} - fn is_state_type_ref(type_ref: &TypeRef) -> bool { type_ref.array_dims.is_empty() && matches!(&type_ref.base, TypeBase::Custom(name) if name == "State") } @@ -893,84 +889,6 @@ fn is_literal_int(expr: &Expr<'_>, expected: i64) -> bool { matches!(expr.kind, ExprKind::Int(value) if value == expected) } -fn append_policy_call_and_capture_next_state<'i>( - body: &mut Vec>, - policy: &FunctionAst<'i>, - policy_name: &str, - mode: CovenantMode, - singleton: bool, - termination: CovenantTermination, - contract_fields: &[ContractFieldAst<'i>], - call_args: Vec>, -) -> Result<(), CompilerError> { - match mode { - CovenantMode::Verification => { - body.push(call_statement(policy_name, call_args)); - Ok(()) - } - CovenantMode::Transition => { - if policy.return_types.len() != contract_fields.len() { - return Err(CompilerError::Unsupported(format!( - "transition mode policy function '{}' must return exactly {} values (one per contract field)", - policy.name, - contract_fields.len() - ))); - } - - let mut shape_is_single = true; - let mut shape_is_per_output_arrays = true; - for (field, return_type) in contract_fields.iter().zip(policy.return_types.iter()) { - shape_is_single &= type_name_from_ref(return_type) == type_name_from_ref(&field.type_ref); - shape_is_per_output_arrays &= return_type_is_per_output_array(return_type, &field.type_ref); - } - if !shape_is_single && !shape_is_per_output_arrays { - return Err(CompilerError::Unsupported(format!( - "transition mode policy function '{}' returns must be either exactly State fields or per-field arrays", - policy.name - ))); - } - if singleton && shape_is_per_output_arrays && termination != CovenantTermination::Allowed { - return Err(CompilerError::Unsupported(format!( - "transition mode singleton policy function '{}' must return a single State (arrays are not allowed unless termination=allowed)", - policy.name - ))); - } - - let mut bindings = Vec::new(); - let mut binding_by_field = HashMap::new(); - for (field, return_type) in contract_fields.iter().zip(policy.return_types.iter()) { - let binding_name = format!("__cov_new_{}", field.name); - bindings.push(typed_binding(return_type.clone(), &binding_name)); - binding_by_field.insert(field.name.clone(), binding_name); - } - - body.push(function_call_assign_statement(bindings, policy_name, call_args)); - if shape_is_single { - Ok(()) - } else { - let first_field = &contract_fields[0].name; - let first_array_name = binding_by_field - .get(first_field) - .cloned() - .unwrap_or_else(|| panic!("missing transition binding for field '{}'", first_field)); - let expected_len_expr = length_expr(identifier_expr(&first_array_name)); - for field in contract_fields.iter().skip(1) { - let array_name = binding_by_field - .get(&field.name) - .cloned() - .unwrap_or_else(|| panic!("missing transition binding for field '{}'", field.name)); - body.push(require_statement(binary_expr( - BinaryOp::Eq, - length_expr(identifier_expr(&array_name)), - expected_len_expr.clone(), - ))); - } - Ok(()) - } - } - } -} - fn append_cov_input_state_reads<'i>( body: &mut Vec>, cov_id_name: &str, diff --git a/silverscript-lang/tests/covenant_compiler_tests.rs b/silverscript-lang/tests/covenant_compiler_tests.rs index 4d547cf..b143ddd 100644 --- a/silverscript-lang/tests/covenant_compiler_tests.rs +++ b/silverscript-lang/tests/covenant_compiler_tests.rs @@ -154,6 +154,38 @@ fn rejects_auth_transition_without_prev_state_shape() { assert!(err.to_string().contains("mode=transition with binding=auth")); } +#[test] +fn rejects_auth_transition_when_contract_state_is_empty() { + let source = r#" + contract Decls() { + #[covenant(binding = auth, from = 1, to = 1, mode = transition)] + function roll(int nonce) : (int) { + return(nonce); + } + } + "#; + + let err = compile_contract(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")); +} + +#[test] +fn rejects_cov_transition_when_contract_state_is_empty() { + let source = r#" + contract Decls() { + #[covenant(binding = cov, from = 2, to = 2, mode = transition)] + function roll(int nonce) : (int) { + return(nonce); + } + } + "#; + + let err = compile_contract(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")); +} + #[test] fn rejects_old_per_field_covenant_state_syntax() { let source = r#" From 5d2e4e73ae436d2bb6eb4480eeeb1837b1cb9b3b Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 12:17:33 +0300 Subject: [PATCH 06/13] Rename __cov_out_count -> __auth_out_count --- .../src/compiler/covenant_declarations.rs | 28 +++++----- .../lowers_auth_groups_single.sil | 10 ++-- ...lowers_auth_transition_two_field_state.sil | 4 +- ...cation_groups_multiple_two_field_state.sil | 8 +-- ...fication_groups_single_two_field_state.sil | 10 ++-- ...out_sugar_verification_two_field_state.sil | 8 +-- ...rred_auth_verification_two_field_state.sil | 8 +-- ...d_singleton_transition_two_field_state.sil | 4 +- ..._covenant_declarations_in_one_contract.sil | 54 +++++++++---------- ...on_termination_allowed_two_field_state.sil | 8 +-- ...leton_sugar_transition_two_field_state.sil | 4 +- ...tion_allowed_to_state_array_validation.sil | 8 +-- ...erification_to_single_state_validation.sil | 4 +- ...tion_uses_returned_state_in_validation.sil | 4 +- ...on_allowed_to_array_cardinality_checks.sil | 8 +-- ...lowed_in_transition_non_singleton_mode.sil | 8 +-- ...wed_in_verification_non_singleton_mode.sil | 8 +-- ...ray_return_to_exact_output_count_match.sil | 8 +-- 18 files changed, 97 insertions(+), 97 deletions(-) diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 4fbd7b5..551b8cb 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -424,19 +424,19 @@ fn build_auth_wrapper<'i>( let mut entrypoint_params = policy.params.clone(); let active_input = active_input_index_expr(); - let out_count_name = "__cov_out_count"; - body.push(var_def_statement(int_type_ref(), out_count_name, Expr::call("OpAuthOutputCount", vec![active_input.clone()]))); + let auth_out_count_name = "__auth_out_count"; + body.push(var_def_statement(int_type_ref(), auth_out_count_name, Expr::call("OpAuthOutputCount", vec![active_input.clone()]))); if declaration.groups == CovenantGroups::Single { let cov_id_name = "__cov_id"; body.push(var_def_statement(bytes32_type_ref(), cov_id_name, Expr::call("OpInputCovenantId", vec![active_input.clone()]))); - let cov_out_count_name = "__cov_shared_out_count"; + let cov_shared_out_count_name = "__cov_shared_out_count"; body.push(var_def_statement( int_type_ref(), - cov_out_count_name, + cov_shared_out_count_name, Expr::call("OpCovOutputCount", vec![identifier_expr(cov_id_name)]), )); - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(cov_out_count_name), identifier_expr(out_count_name)))); + body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(cov_shared_out_count_name), identifier_expr(auth_out_count_name)))); } if !contract_fields.is_empty() { @@ -452,7 +452,7 @@ fn build_auth_wrapper<'i>( body.push(call_statement(policy_name, policy.params.iter().map(|param| identifier_expr(¶m.name)).collect())); if declaration.singleton && declaration.termination != CovenantTermination::Allowed { let new_state_name = &policy.params[1].name; - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(out_count_name), Expr::int(1)))); + body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(auth_out_count_name), Expr::int(1)))); let out_idx_name = "__cov_out_idx"; body.push(var_def_statement( int_type_ref(), @@ -467,18 +467,18 @@ fn build_auth_wrapper<'i>( let new_states_name = &policy.params[1].name; body.push(require_statement(binary_expr( BinaryOp::Le, - identifier_expr(out_count_name), + identifier_expr(auth_out_count_name), declaration.to_expr.clone(), ))); body.push(require_statement(binary_expr( BinaryOp::Eq, - identifier_expr(out_count_name), + identifier_expr(auth_out_count_name), length_expr(identifier_expr(new_states_name)), ))); append_auth_output_state_array_checks_from_state_array( &mut body, &active_input, - out_count_name, + auth_out_count_name, declaration.to_expr.clone(), new_states_name, ); @@ -500,7 +500,7 @@ fn build_auth_wrapper<'i>( policy_name, call_args, )); - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(out_count_name), Expr::int(1)))); + body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(auth_out_count_name), Expr::int(1)))); let out_idx_name = "__cov_out_idx"; body.push(var_def_statement( int_type_ref(), @@ -520,18 +520,18 @@ fn build_auth_wrapper<'i>( )); body.push(require_statement(binary_expr( BinaryOp::Le, - identifier_expr(out_count_name), + identifier_expr(auth_out_count_name), declaration.to_expr.clone(), ))); body.push(require_statement(binary_expr( BinaryOp::Eq, - identifier_expr(out_count_name), + identifier_expr(auth_out_count_name), length_expr(identifier_expr(next_states_name)), ))); append_auth_output_state_array_checks_from_state_array( &mut body, &active_input, - out_count_name, + auth_out_count_name, declaration.to_expr.clone(), next_states_name, ); @@ -550,7 +550,7 @@ fn build_auth_wrapper<'i>( } } - body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(out_count_name), declaration.to_expr.clone()))); + body.push(require_statement(binary_expr(BinaryOp::Le, identifier_expr(auth_out_count_name), declaration.to_expr.clone()))); } Ok(generated_entrypoint(policy, entrypoint_name, entrypoint_params, body)) diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_groups_single.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_groups_single.sil index af5047b..91f64a7 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_groups_single.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_groups_single.sil @@ -17,18 +17,18 @@ contract Decls(int max_outs) { } entrypoint function split(State[] new_states, int amount) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); int cov_shared_out_count = OpCovOutputCount(cov_id); - require(cov_shared_out_count == cov_out_count); + require(cov_shared_out_count == auth_out_count); State prev_state = { value: value }; covenant_policy_split(prev_state, new_states, amount); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil index b475203..cdb2f64 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_transition_two_field_state.sil @@ -22,11 +22,11 @@ contract Matrix(int init_amount, byte[32] init_owner) { } entrypoint function step(int fee) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State cov_new_state) = covenant_policy_step(prev_state, fee); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, cov_new_state); diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_multiple_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_multiple_two_field_state.sil index 9bc2bb9..6e90198 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_multiple_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_multiple_two_field_state.sil @@ -19,14 +19,14 @@ contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { } entrypoint function step(State[] new_states, int nonce) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; covenant_policy_step(prev_state, new_states, nonce); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_single_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_single_two_field_state.sil index 1ed57d8..fe166c7 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_single_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_auth_verification_groups_single_two_field_state.sil @@ -19,18 +19,18 @@ contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { } entrypoint function step(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); int cov_shared_out_count = OpCovOutputCount(cov_id); - require(cov_shared_out_count == cov_out_count); + require(cov_shared_out_count == auth_out_count); State prev_state = { amount: amount, owner: owner }; covenant_policy_step(prev_state, new_states); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_fanout_sugar_verification_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_fanout_sugar_verification_two_field_state.sil index 621c0d3..8e7d29e 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_fanout_sugar_verification_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_fanout_sugar_verification_two_field_state.sil @@ -19,14 +19,14 @@ contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { } entrypoint function step(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; covenant_policy_step(prev_state, new_states); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_auth_verification_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_auth_verification_two_field_state.sil index 823c97f..0d9dde0 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_auth_verification_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_auth_verification_two_field_state.sil @@ -19,14 +19,14 @@ contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { } entrypoint function step(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; covenant_policy_step(prev_state, new_states); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_singleton_transition_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_singleton_transition_two_field_state.sil index 8d51b47..7f9ba9f 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_singleton_transition_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_inferred_singleton_transition_two_field_state.sil @@ -19,11 +19,11 @@ contract Matrix(int init_amount, byte[32] init_owner) { } entrypoint function step(int delta) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State cov_new_state) = covenant_policy_step(prev_state, delta); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, cov_new_state); diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil index 7526565..ef54239 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_many_covenant_declarations_in_one_contract.sil @@ -83,14 +83,14 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function auth_verification_multi(State[] new_states, int nonce) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; covenant_policy_auth_verification_multi(prev_state, new_states, nonce); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } @@ -101,18 +101,18 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function auth_verification_single(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); int cov_shared_out_count = OpCovOutputCount(cov_id); - require(cov_shared_out_count == cov_out_count); + require(cov_shared_out_count == auth_out_count); State prev_state = { amount: amount, owner: owner }; covenant_policy_auth_verification_single(prev_state, new_states); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } @@ -123,11 +123,11 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function auth_transition(int fee) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State cov_new_state) = covenant_policy_auth_transition(prev_state, fee); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, cov_new_state); @@ -211,14 +211,14 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function inferred_auth(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; covenant_policy_inferred_auth(prev_state, new_states); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } @@ -264,11 +264,11 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function inferred_transition(int delta) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State cov_new_state) = covenant_policy_inferred_transition(prev_state, delta); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, cov_new_state); @@ -279,11 +279,11 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function singleton_transition(int delta) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State cov_new_state) = covenant_policy_singleton_transition(prev_state, delta); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, cov_new_state); @@ -295,14 +295,14 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function singleton_terminate(State[] next_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State[] cov_new_states) = covenant_policy_singleton_terminate(prev_state, next_states); - require(cov_out_count <= 1); - require(cov_out_count == cov_new_states.length); + require(auth_out_count <= 1); + require(auth_out_count == cov_new_states.length); - for(cov_k, 0, cov_out_count, 1) { + for(cov_k, 0, auth_out_count, 1) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, cov_new_states[cov_k]); } @@ -313,14 +313,14 @@ contract Matrix(int max_ins, int max_outs, int init_amount, byte[32] init_owner) } entrypoint function fanout_verification(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; covenant_policy_fanout_verification(prev_state, new_states); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_termination_allowed_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_termination_allowed_two_field_state.sil index 03e7954..89ec4ac 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_termination_allowed_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_termination_allowed_two_field_state.sil @@ -25,14 +25,14 @@ contract Matrix(int init_amount, byte[32] init_owner) { } entrypoint function step(State[] next_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State[] cov_new_states) = covenant_policy_step(prev_state, next_states); - require(cov_out_count <= 1); - require(cov_out_count == cov_new_states.length); + require(auth_out_count <= 1); + require(auth_out_count == cov_new_states.length); - for(cov_k, 0, cov_out_count, 1) { + for(cov_k, 0, auth_out_count, 1) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, cov_new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_two_field_state.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_two_field_state.sil index c13a553..1d0fd5b 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_two_field_state.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_transition_two_field_state.sil @@ -19,11 +19,11 @@ contract Matrix(int init_amount, byte[32] init_owner) { } entrypoint function step(int delta) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; (State cov_new_state) = covenant_policy_step(prev_state, delta); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, cov_new_state); diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_termination_allowed_to_state_array_validation.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_termination_allowed_to_state_array_validation.sil index 67a5170..57b106e 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_termination_allowed_to_state_array_validation.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_termination_allowed_to_state_array_validation.sil @@ -17,14 +17,14 @@ contract Counter(int init_value) { } entrypoint function step(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { value: value }; covenant_policy_step(prev_state, new_states); - require(cov_out_count <= 1); - require(cov_out_count == new_states.length); + require(auth_out_count <= 1); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, 1) { + for(cov_k, 0, auth_out_count, 1) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_to_single_state_validation.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_to_single_state_validation.sil index a7b596c..5c1c878 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_to_single_state_validation.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_sugar_verification_to_single_state_validation.sil @@ -17,11 +17,11 @@ contract Counter(int init_value) { } entrypoint function step(State new_state) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { value: value }; covenant_policy_step(prev_state, new_state); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, new_state); diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil index b39aa62..2bf945b 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil @@ -17,11 +17,11 @@ contract Decls(int init_value) { } entrypoint function bump(int delta) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { value: value }; (State cov_new_state) = covenant_policy_bump(prev_state, delta); - require(cov_out_count == 1); + require(auth_out_count == 1); int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, 0); validateOutputState(cov_out_idx, cov_new_state); diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_with_termination_allowed_to_array_cardinality_checks.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_with_termination_allowed_to_array_cardinality_checks.sil index 5bb0e2f..13b680a 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_with_termination_allowed_to_array_cardinality_checks.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_with_termination_allowed_to_array_cardinality_checks.sil @@ -17,14 +17,14 @@ contract Decls(int init_value) { } entrypoint function bump_or_terminate(State[] next_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { value: value }; (State[] cov_new_states) = covenant_policy_bump_or_terminate(prev_state, next_states); - require(cov_out_count <= 1); - require(cov_out_count == cov_new_states.length); + require(auth_out_count <= 1); + require(auth_out_count == cov_new_states.length); - for(cov_k, 0, cov_out_count, 1) { + for(cov_k, 0, auth_out_count, 1) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, cov_new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_transition_non_singleton_mode.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_transition_non_singleton_mode.sil index 2b04320..2518dac 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_transition_non_singleton_mode.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_transition_non_singleton_mode.sil @@ -17,14 +17,14 @@ contract Decls(int max_outs, int init_value) { } entrypoint function fanout(State[] next_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { value: value }; (State[] cov_new_states) = covenant_policy_fanout(prev_state, next_states); - require(cov_out_count <= max_outs); - require(cov_out_count == cov_new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == cov_new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, cov_new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_verification_non_singleton_mode.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_verification_non_singleton_mode.sil index 823c97f..0d9dde0 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_verification_non_singleton_mode.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_termination_allowed_in_verification_non_singleton_mode.sil @@ -19,14 +19,14 @@ contract Matrix(int max_outs, int init_amount, byte[32] init_owner) { } entrypoint function step(State[] new_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { amount: amount, owner: owner }; covenant_policy_step(prev_state, new_states); - require(cov_out_count <= max_outs); - require(cov_out_count == new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, new_states[cov_k]); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil index 2b04320..2518dac 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil @@ -17,14 +17,14 @@ contract Decls(int max_outs, int init_value) { } entrypoint function fanout(State[] next_states) { - int cov_out_count = OpAuthOutputCount(this.activeInputIndex); + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); State prev_state = { value: value }; (State[] cov_new_states) = covenant_policy_fanout(prev_state, next_states); - require(cov_out_count <= max_outs); - require(cov_out_count == cov_new_states.length); + require(auth_out_count <= max_outs); + require(auth_out_count == cov_new_states.length); - for(cov_k, 0, cov_out_count, max_outs) { + for(cov_k, 0, auth_out_count, max_outs) { int cov_out_idx = OpAuthOutputIdx(this.activeInputIndex, cov_k); validateOutputState(cov_out_idx, cov_new_states[cov_k]); } From 0a74e797461bbf99e4a9f32c02d1edea76756a3e Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 17:58:22 +0300 Subject: [PATCH 07/13] Add test + minor changes --- .../src/compiler/covenant_declarations.rs | 36 +++++++++----- ...ers_cov_transition_single_state_return.sil | 48 +++++++++++++++++++ .../tests/covenant_declaration_ast_tests.rs | 1 + 3 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_single_state_return.sil diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 551b8cb..c230f2c 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -436,7 +436,11 @@ fn build_auth_wrapper<'i>( cov_shared_out_count_name, Expr::call("OpCovOutputCount", vec![identifier_expr(cov_id_name)]), )); - body.push(require_statement(binary_expr(BinaryOp::Eq, identifier_expr(cov_shared_out_count_name), identifier_expr(auth_out_count_name)))); + body.push(require_statement(binary_expr( + BinaryOp::Eq, + identifier_expr(cov_shared_out_count_name), + identifier_expr(auth_out_count_name), + ))); } if !contract_fields.is_empty() { @@ -675,7 +679,13 @@ fn build_cov_wrapper<'i>( } } } else { - append_cov_input_state_reads(&mut body, cov_id_name, in_count_name, declaration.from_expr.clone(), contract_fields); + append_cov_input_state_reads_without_assign( + &mut body, + cov_id_name, + in_count_name, + declaration.from_expr.clone(), + contract_fields, + ); let call_args = policy.params.iter().map(|param| identifier_expr(¶m.name)).collect(); match declaration.mode { @@ -889,7 +899,7 @@ fn is_literal_int(expr: &Expr<'_>, expected: i64) -> bool { matches!(expr.kind, ExprKind::Int(value) if value == expected) } -fn append_cov_input_state_reads<'i>( +fn append_cov_input_state_reads_without_assign<'i>( body: &mut Vec>, cov_id_name: &str, in_count_name: &str, @@ -901,8 +911,8 @@ fn append_cov_input_state_reads<'i>( } let loop_var = "__cov_in_k"; let in_idx_name = "__cov_in_idx"; - let mut then_branch = Vec::new(); - then_branch.push(var_def_statement( + let mut for_body = Vec::new(); + for_body.push(var_def_statement( int_type_ref(), in_idx_name, Expr::call("OpCovInputIdx", vec![identifier_expr(cov_id_name), identifier_expr(loop_var)]), @@ -911,8 +921,8 @@ fn append_cov_input_state_reads<'i>( .iter() .map(|field| state_binding(&field.name, field.type_ref.clone(), &format!("__cov_prev_{}", field.name))) .collect(); - then_branch.push(state_call_assign_statement(bindings, "readInputState", vec![identifier_expr(in_idx_name)])); - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(in_count_name), from_expr, then_branch)); + for_body.push(state_call_assign_statement(bindings, "readInputState", vec![identifier_expr(in_idx_name)])); + body.push(for_statement(loop_var, Expr::int(0), identifier_expr(in_count_name), from_expr, for_body)); } fn append_cov_input_state_reads_into_state_array<'i>( @@ -924,11 +934,11 @@ fn append_cov_input_state_reads_into_state_array<'i>( ) { let loop_var = "__cov_in_k"; body.push(var_decl_statement(state_array_type_ref(), prev_states_name)); - let then_branch = vec![array_append_statement( + let for_body = vec![array_append_statement( prev_states_name, Expr::call("readInputState", vec![Expr::call("OpCovInputIdx", vec![identifier_expr(cov_id_name), identifier_expr(loop_var)])]), )]; - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(in_count_name), from_expr, then_branch)); + body.push(for_statement(loop_var, Expr::int(0), identifier_expr(in_count_name), from_expr, for_body)); } fn append_auth_output_state_array_checks_from_state_array<'i>( @@ -940,7 +950,7 @@ fn append_auth_output_state_array_checks_from_state_array<'i>( ) { let loop_var = "__cov_k"; let out_idx_name = "__cov_out_idx"; - let then_branch = vec![ + let for_body = vec![ var_def_statement( int_type_ref(), out_idx_name, @@ -951,7 +961,7 @@ fn append_auth_output_state_array_checks_from_state_array<'i>( vec![identifier_expr(out_idx_name), array_index_expr(state_array_name, identifier_expr(loop_var))], ), ]; - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, then_branch)); + body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, for_body)); } fn append_cov_output_state_array_checks_from_state_array<'i>( @@ -963,7 +973,7 @@ fn append_cov_output_state_array_checks_from_state_array<'i>( ) { let loop_var = "__cov_k"; let out_idx_name = "__cov_out_idx"; - let then_branch = vec![ + let for_body = vec![ var_def_statement( int_type_ref(), out_idx_name, @@ -974,5 +984,5 @@ fn append_cov_output_state_array_checks_from_state_array<'i>( vec![identifier_expr(out_idx_name), array_index_expr(state_array_name, identifier_expr(loop_var))], ), ]; - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, then_branch)); + body.push(for_statement(loop_var, Expr::int(0), identifier_expr(out_count_name), to_expr, for_body)); } diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_single_state_return.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_single_state_return.sil new file mode 100644 index 0000000..5d4bef6 --- /dev/null +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_single_state_return.sil @@ -0,0 +1,48 @@ +contract Matrix(int max_ins, int init_amount, byte[32] init_owner) { + int amount = init_amount; + byte[32] owner = init_owner; + + #[covenant(binding = cov, from = max_ins, to = 1, mode = transition)] + function step(State[] prev_states, int fee) : (State) { + return({ amount: prev_states[0].amount - fee, owner: prev_states[0].owner }); + } +} + +// --- lowered --- + +contract Matrix(int max_ins, int init_amount, byte[32] init_owner) { + int amount = init_amount; + byte[32] owner = init_owner; + + function covenant_policy_step(State[] prev_states, int fee) : (State) { + return({ amount: prev_states[0].amount - fee, owner: prev_states[0].owner }); + } + + entrypoint function leader_step(int fee) { + byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); + + require(OpCovInputIdx(cov_id, 0) == this.activeInputIndex); + + int cov_in_count = OpCovInputCount(cov_id); + require(cov_in_count <= max_ins); + + int cov_out_count = OpCovOutputCount(cov_id); + + State[] prev_states; + for(cov_in_k, 0, cov_in_count, max_ins) { + prev_states = prev_states.append(readInputState(OpCovInputIdx(cov_id, cov_in_k))); + } + + (State cov_new_state) = covenant_policy_step(prev_states, fee); + require(cov_out_count == 1); + + int cov_out_idx = OpCovOutputIdx(cov_id, 0); + validateOutputState(cov_out_idx, cov_new_state); + } + + entrypoint function delegate_step() { + byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); + + require(OpCovInputIdx(cov_id, 0) != this.activeInputIndex); + } +} diff --git a/silverscript-lang/tests/covenant_declaration_ast_tests.rs b/silverscript-lang/tests/covenant_declaration_ast_tests.rs index 71320f5..12262d1 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_tests.rs +++ b/silverscript-lang/tests/covenant_declaration_ast_tests.rs @@ -94,6 +94,7 @@ fixture_ast_test!(lowers_auth_verification_groups_single_two_field_state, [Expr: fixture_ast_test!(lowers_auth_transition_two_field_state, [Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_verification_two_field_state, [Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_transition_two_field_state, [Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); +fixture_ast_test!(lowers_cov_transition_single_state_return, [Expr::int(2), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_transition_single_field_state, [Expr::int(2), Expr::int(2), Expr::int(10)]); fixture_ast_test!(lowers_inferred_auth_verification_two_field_state, [Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!( From f5ef0a4526aad41504f3176d51a68b7c90fee8fe Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 17:58:51 +0300 Subject: [PATCH 08/13] Remove append_cov_input_state_reads_without_assign --- .../src/compiler/covenant_declarations.rs | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index c230f2c..907d807 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -679,13 +679,6 @@ fn build_cov_wrapper<'i>( } } } else { - append_cov_input_state_reads_without_assign( - &mut body, - cov_id_name, - in_count_name, - declaration.from_expr.clone(), - contract_fields, - ); let call_args = policy.params.iter().map(|param| identifier_expr(¶m.name)).collect(); match declaration.mode { @@ -899,32 +892,6 @@ fn is_literal_int(expr: &Expr<'_>, expected: i64) -> bool { matches!(expr.kind, ExprKind::Int(value) if value == expected) } -fn append_cov_input_state_reads_without_assign<'i>( - body: &mut Vec>, - cov_id_name: &str, - in_count_name: &str, - from_expr: Expr<'i>, - contract_fields: &[ContractFieldAst<'i>], -) { - if contract_fields.is_empty() { - return; - } - let loop_var = "__cov_in_k"; - let in_idx_name = "__cov_in_idx"; - let mut for_body = Vec::new(); - for_body.push(var_def_statement( - int_type_ref(), - in_idx_name, - Expr::call("OpCovInputIdx", vec![identifier_expr(cov_id_name), identifier_expr(loop_var)]), - )); - let bindings = contract_fields - .iter() - .map(|field| state_binding(&field.name, field.type_ref.clone(), &format!("__cov_prev_{}", field.name))) - .collect(); - for_body.push(state_call_assign_statement(bindings, "readInputState", vec![identifier_expr(in_idx_name)])); - body.push(for_statement(loop_var, Expr::int(0), identifier_expr(in_count_name), from_expr, for_body)); -} - fn append_cov_input_state_reads_into_state_array<'i>( body: &mut Vec>, cov_id_name: &str, From 5a0d58192f9a5cfc3b356c3171fc22cfb74c09e1 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 18:06:55 +0300 Subject: [PATCH 09/13] Add silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_to_one_array_state_return.sil --- ...v_transition_to_one_array_state_return.sil | 51 +++++++++++++++++++ .../tests/covenant_declaration_ast_tests.rs | 1 + 2 files changed, 52 insertions(+) create mode 100644 silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_to_one_array_state_return.sil diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_to_one_array_state_return.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_to_one_array_state_return.sil new file mode 100644 index 0000000..27a0c42 --- /dev/null +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_cov_transition_to_one_array_state_return.sil @@ -0,0 +1,51 @@ +contract Matrix(int max_ins, int init_amount, byte[32] init_owner) { + int amount = init_amount; + byte[32] owner = init_owner; + + #[covenant(binding = cov, from = max_ins, to = 1, mode = transition)] + function step(State[] prev_states, int fee) : (State[]) { + return([{ amount: prev_states[0].amount - fee, owner: prev_states[0].owner }]); + } +} + +// --- lowered --- + +contract Matrix(int max_ins, int init_amount, byte[32] init_owner) { + int amount = init_amount; + byte[32] owner = init_owner; + + function covenant_policy_step(State[] prev_states, int fee) : (State[]) { + return([{ amount: prev_states[0].amount - fee, owner: prev_states[0].owner }]); + } + + entrypoint function leader_step(int fee) { + byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); + + require(OpCovInputIdx(cov_id, 0) == this.activeInputIndex); + + int cov_in_count = OpCovInputCount(cov_id); + require(cov_in_count <= max_ins); + + int cov_out_count = OpCovOutputCount(cov_id); + + State[] prev_states; + for(cov_in_k, 0, cov_in_count, max_ins) { + prev_states = prev_states.append(readInputState(OpCovInputIdx(cov_id, cov_in_k))); + } + + (State[] cov_new_states) = covenant_policy_step(prev_states, fee); + require(cov_out_count <= 1); + require(cov_out_count == cov_new_states.length); + + for(cov_k, 0, cov_out_count, 1) { + int cov_out_idx = OpCovOutputIdx(cov_id, cov_k); + validateOutputState(cov_out_idx, cov_new_states[cov_k]); + } + } + + entrypoint function delegate_step() { + byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); + + require(OpCovInputIdx(cov_id, 0) != this.activeInputIndex); + } +} diff --git a/silverscript-lang/tests/covenant_declaration_ast_tests.rs b/silverscript-lang/tests/covenant_declaration_ast_tests.rs index 12262d1..a3718b5 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_tests.rs +++ b/silverscript-lang/tests/covenant_declaration_ast_tests.rs @@ -95,6 +95,7 @@ fixture_ast_test!(lowers_auth_transition_two_field_state, [Expr::int(10), Expr:: fixture_ast_test!(lowers_cov_verification_two_field_state, [Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_transition_two_field_state, [Expr::int(2), Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_transition_single_state_return, [Expr::int(2), Expr::int(10), Expr::bytes(vec![7u8; 32])]); +fixture_ast_test!(lowers_cov_transition_to_one_array_state_return, [Expr::int(2), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!(lowers_cov_transition_single_field_state, [Expr::int(2), Expr::int(2), Expr::int(10)]); fixture_ast_test!(lowers_inferred_auth_verification_two_field_state, [Expr::int(4), Expr::int(10), Expr::bytes(vec![7u8; 32])]); fixture_ast_test!( From 8fdc5e43a6c0aa07c3095d5008b6e6e8c8c3a627 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 18:13:18 +0300 Subject: [PATCH 10/13] Add fixture tests for stateless contracts --- .../src/compiler/covenant_declarations.rs | 22 ------------- .../lowers_stateless_auth_verification.sil | 21 ++++++++++++ .../lowers_stateless_cov_verification.sil | 33 +++++++++++++++++++ .../tests/covenant_declaration_ast_tests.rs | 2 ++ 4 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_auth_verification.sil create mode 100644 silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_cov_verification.sil diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 907d807..5d8ae77 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -838,28 +838,6 @@ fn for_statement<'i>( } } -fn state_binding<'i>(field_name: &str, type_ref: TypeRef, name: &str) -> StateBindingAst<'i> { - StateBindingAst { - field_name: field_name.to_string(), - type_ref, - name: name.to_string(), - span: span::Span::default(), - field_span: span::Span::default(), - type_span: span::Span::default(), - name_span: span::Span::default(), - } -} - -fn state_call_assign_statement<'i>(bindings: Vec>, name: &str, args: Vec>) -> Statement<'i> { - Statement::StateFunctionCallAssign { - bindings, - name: name.to_string(), - args, - span: span::Span::default(), - name_span: span::Span::default(), - } -} - fn state_object_expr_from_contract_fields<'i>(contract_fields: &[ContractFieldAst<'i>]) -> Expr<'i> { let fields = contract_fields .iter() diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_auth_verification.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_auth_verification.sil new file mode 100644 index 0000000..8b7d3f6 --- /dev/null +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_auth_verification.sil @@ -0,0 +1,21 @@ +contract Decls(int max_outs) { + #[covenant(binding = auth, from = 1, to = max_outs, mode = verification)] + function check(int nonce) { + require(nonce >= 0); + } +} + +// --- lowered --- + +contract Decls(int max_outs) { + function covenant_policy_check(int nonce) { + require(nonce >= 0); + } + + entrypoint function check(int nonce) { + int auth_out_count = OpAuthOutputCount(this.activeInputIndex); + + covenant_policy_check(nonce); + require(auth_out_count <= max_outs); + } +} diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_cov_verification.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_cov_verification.sil new file mode 100644 index 0000000..1650fe4 --- /dev/null +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_stateless_cov_verification.sil @@ -0,0 +1,33 @@ +contract Decls(int max_ins, int max_outs) { + #[covenant(binding = cov, from = max_ins, to = max_outs, mode = verification)] + function check(int nonce) { + require(nonce >= 0); + } +} + +// --- lowered --- + +contract Decls(int max_ins, int max_outs) { + function covenant_policy_check(int nonce) { + require(nonce >= 0); + } + + entrypoint function leader_check(int nonce) { + byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); + + require(OpCovInputIdx(cov_id, 0) == this.activeInputIndex); + + int cov_in_count = OpCovInputCount(cov_id); + require(cov_in_count <= max_ins); + + int cov_out_count = OpCovOutputCount(cov_id); + covenant_policy_check(nonce); + require(cov_out_count <= max_outs); + } + + entrypoint function delegate_check() { + byte[32] cov_id = OpInputCovenantId(this.activeInputIndex); + + require(OpCovInputIdx(cov_id, 0) != this.activeInputIndex); + } +} diff --git a/silverscript-lang/tests/covenant_declaration_ast_tests.rs b/silverscript-lang/tests/covenant_declaration_ast_tests.rs index a3718b5..191fa11 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_tests.rs +++ b/silverscript-lang/tests/covenant_declaration_ast_tests.rs @@ -83,6 +83,8 @@ macro_rules! fixture_ast_test { } fixture_ast_test!(lowers_auth_groups_single, [Expr::int(4)]); +fixture_ast_test!(lowers_stateless_auth_verification, [Expr::int(4)]); +fixture_ast_test!(lowers_stateless_cov_verification, [Expr::int(2), Expr::int(4)]); fixture_ast_test!(lowers_cov_to_leader_and_delegate_expected_wrapper_ast, [Expr::int(2), Expr::int(3)]); fixture_ast_test!(lowers_singleton_sugar_verification_to_single_state_validation, [Expr::int(7)]); fixture_ast_test!(lowers_singleton_sugar_verification_termination_allowed_to_state_array_validation, [Expr::int(7)]); From 380fc60c8dbfdab25eaaaf47bcb0c457e2db3705 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 18:30:24 +0300 Subject: [PATCH 11/13] Add STATE_TYPE_NAME const --- silverscript-lang/src/ast/mod.rs | 6 ++++-- .../src/compiler/covenant_declarations.rs | 8 ++++---- silverscript-lang/src/compiler/mod.rs | 1 + silverscript-lang/src/compiler/static_check.rs | 6 +++--- silverscript-lang/src/compiler/structs.rs | 18 +++++++++--------- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/silverscript-lang/src/ast/mod.rs b/silverscript-lang/src/ast/mod.rs index f262f9e..ff9f50c 100644 --- a/silverscript-lang/src/ast/mod.rs +++ b/silverscript-lang/src/ast/mod.rs @@ -10,6 +10,8 @@ pub use crate::span::{Span, SpanUtils}; pub mod visit; +pub const STATE_TYPE_NAME: &str = "State"; + #[derive(Debug, Clone)] struct Identifier<'i> { name: String, @@ -1311,8 +1313,8 @@ fn parse_struct_definition<'i>(pair: Pair<'i, Rule>) -> Result, Co let mut inner = pair.into_inner(); let name_pair = inner.next().ok_or_else(|| CompilerError::Unsupported("missing struct name".to_string()))?; let Identifier { name, span: name_span } = parse_identifier(name_pair)?; - if name == "State" { - return Err(CompilerError::Unsupported("'State' is a reserved struct name".to_string()).with_span(&span)); + if name == STATE_TYPE_NAME { + return Err(CompilerError::Unsupported(format!("'{}' is a reserved struct name", STATE_TYPE_NAME)).with_span(&span)); } let mut fields = Vec::new(); for field_pair in inner { diff --git a/silverscript-lang/src/compiler/covenant_declarations.rs b/silverscript-lang/src/compiler/covenant_declarations.rs index 5d8ae77..c228d11 100644 --- a/silverscript-lang/src/compiler/covenant_declarations.rs +++ b/silverscript-lang/src/compiler/covenant_declarations.rs @@ -726,11 +726,11 @@ fn int_type_ref() -> TypeRef { } fn state_type_ref() -> TypeRef { - TypeRef { base: TypeBase::Custom("State".to_string()), array_dims: Vec::new() } + TypeRef { base: TypeBase::Custom(STATE_TYPE_NAME.to_string()), array_dims: Vec::new() } } fn state_array_type_ref() -> TypeRef { - TypeRef { base: TypeBase::Custom("State".to_string()), array_dims: vec![ArrayDim::Dynamic] } + TypeRef { base: TypeBase::Custom(STATE_TYPE_NAME.to_string()), array_dims: vec![ArrayDim::Dynamic] } } fn bytes32_type_ref() -> TypeRef { @@ -859,11 +859,11 @@ fn length_expr<'i>(expr: Expr<'i>) -> Expr<'i> { } fn is_state_type_ref(type_ref: &TypeRef) -> bool { - type_ref.array_dims.is_empty() && matches!(&type_ref.base, TypeBase::Custom(name) if name == "State") + type_ref.array_dims.is_empty() && matches!(&type_ref.base, TypeBase::Custom(name) if name == STATE_TYPE_NAME) } fn is_state_array_type_ref(type_ref: &TypeRef) -> bool { - !type_ref.array_dims.is_empty() && matches!(&type_ref.base, TypeBase::Custom(name) if name == "State") + !type_ref.array_dims.is_empty() && matches!(&type_ref.base, TypeBase::Custom(name) if name == STATE_TYPE_NAME) } fn is_literal_int(expr: &Expr<'_>, expected: i64) -> bool { diff --git a/silverscript-lang/src/compiler/mod.rs b/silverscript-lang/src/compiler/mod.rs index 90884b9..98c6ca5 100644 --- a/silverscript-lang/src/compiler/mod.rs +++ b/silverscript-lang/src/compiler/mod.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::ast::{ ArrayDim, BinaryOp, ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, IntrospectionKind, NullaryOp, ParamAst, SplitPart, StateBindingAst, StateFieldExpr, Statement, TimeVar, TypeBase, TypeRef, UnaryOp, UnarySuffixKind, + STATE_TYPE_NAME, parse_contract_ast, parse_type_ref, }; use crate::debug_info::{DebugInfo, DebugNamedValue}; diff --git a/silverscript-lang/src/compiler/static_check.rs b/silverscript-lang/src/compiler/static_check.rs index 69b1e4a..ce4ca70 100644 --- a/silverscript-lang/src/compiler/static_check.rs +++ b/silverscript-lang/src/compiler/static_check.rs @@ -1020,7 +1020,7 @@ fn infer_expr_type_ref_for_comparison_ref<'i>( (then_type == else_type).then_some(then_type) } ExprKind::Call { name, .. } if name == "readInputState" && !contract_fields.is_empty() => { - Some(TypeRef { base: TypeBase::Custom("State".to_string()), array_dims: Vec::new() }) + Some(TypeRef { base: TypeBase::Custom(STATE_TYPE_NAME.to_string()), array_dims: Vec::new() }) } ExprKind::Call { name, .. } => { let function = functions.get(name)?; @@ -1178,7 +1178,7 @@ fn infer_struct_destructure_expr_type<'i>( if contract_fields.is_empty() { return Err(CompilerError::Unsupported("readInputState requires contract fields".to_string())); } - Ok(TypeRef { base: TypeBase::Custom("State".to_string()), array_dims: Vec::new() }) + Ok(TypeRef { base: TypeBase::Custom(STATE_TYPE_NAME.to_string()), array_dims: Vec::new() }) } ExprKind::Call { name, .. } if name == "readInputStateWithTemplate" => Err(CompilerError::Unsupported( "readInputStateWithTemplate must be assigned to a struct variable or destructured directly".to_string(), @@ -1347,7 +1347,7 @@ fn validate_expr_assignable_to_type<'i>( if struct_name_from_type_ref(type_ref, structs).is_some() { if let ExprKind::Call { name, args, .. } = &expr.kind && name == "readInputState" - && struct_name_from_type_ref(type_ref, structs) == Some("State") + && struct_name_from_type_ref(type_ref, structs) == Some(STATE_TYPE_NAME) && !contract_fields.is_empty() && args.len() == 1 { diff --git a/silverscript-lang/src/compiler/structs.rs b/silverscript-lang/src/compiler/structs.rs index 20b94f1..26c2d31 100644 --- a/silverscript-lang/src/compiler/structs.rs +++ b/silverscript-lang/src/compiler/structs.rs @@ -5,7 +5,7 @@ use super::debug_value_types::infer_debug_expr_value_type; use super::*; use crate::ast::{ ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, ParamAst, StateBindingAst, StateFieldExpr, Statement, - TypeBase, TypeRef, parse_type_ref, + TypeBase, TypeRef, STATE_TYPE_NAME, parse_type_ref, }; use crate::span; @@ -30,8 +30,8 @@ pub(crate) type StructRegistry = HashMap; pub(crate) fn build_struct_registry<'i>(contract: &ContractAst<'i>) -> Result { let mut registry = HashMap::new(); for item in &contract.structs { - if item.name == "State" { - return Err(CompilerError::Unsupported("'State' is a reserved struct name".to_string())); + if item.name == STATE_TYPE_NAME { + return Err(CompilerError::Unsupported(format!("'{}' is a reserved struct name", STATE_TYPE_NAME))); } let mut names = HashSet::new(); let fields = item @@ -60,7 +60,7 @@ pub(crate) fn build_struct_registry<'i>(contract: &ContractAst<'i>) -> Result, CompilerError>>()?; - registry.insert("State".to_string(), StructSpec { fields: state_fields }); + registry.insert(STATE_TYPE_NAME.to_string(), StructSpec { fields: state_fields }); Ok(registry) } @@ -373,7 +373,7 @@ pub(crate) fn lower_struct_value_to_state_object_expr<'i>( .into_iter() .zip(lowered_values) .map(|((path, _), value)| StateFieldExpr { - name: if expected_struct_name == Some("State") { + name: if expected_struct_name == Some(STATE_TYPE_NAME) { match path.as_slice() { [root] => root.clone(), [root, rest @ ..] => flattened_struct_name(root, rest), @@ -403,8 +403,8 @@ pub(crate) fn lower_struct_value_expr<'i>( .ok_or_else(|| CompilerError::Unsupported(format!("expected struct type '{}'", expected_type.type_name())))?; match &expr.kind { ExprKind::Call { name, args, .. } if name == "readInputState" => { - if expected_struct_name != "State" { - return Err(CompilerError::Unsupported("readInputState returns State".to_string())); + if expected_struct_name != STATE_TYPE_NAME { + return Err(CompilerError::Unsupported(format!("readInputState returns {}", STATE_TYPE_NAME))); } if args.len() != 1 { return Err(CompilerError::Unsupported("readInputState(input_idx) expects 1 argument".to_string())); @@ -548,7 +548,7 @@ pub(crate) fn infer_struct_expr_type<'i>( if contract_fields.is_empty() { return Err(CompilerError::Unsupported("readInputState requires contract fields".to_string())); } - Ok(TypeRef { base: TypeBase::Custom("State".to_string()), array_dims: Vec::new() }) + Ok(TypeRef { base: TypeBase::Custom(STATE_TYPE_NAME.to_string()), array_dims: Vec::new() }) } ExprKind::Call { name, .. } if name == "readInputStateWithTemplate" => Err(CompilerError::Unsupported( "readInputStateWithTemplate must be assigned to a struct variable or destructured directly".to_string(), @@ -943,7 +943,7 @@ fn lower_call_args<'i>( lowered.push(Expr::new(ExprKind::StateObject(lowered_fields), arg.span)); } else { let state_type = if name == "validateOutputState" { - TypeRef { base: TypeBase::Custom("State".to_string()), array_dims: Vec::new() } + TypeRef { base: TypeBase::Custom(STATE_TYPE_NAME.to_string()), array_dims: Vec::new() } } else { infer_struct_expr_type(arg, scope, structs, contract_fields)? }; From edcb169f970a94311eb968aa3a91678c37524ccd Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 18:36:41 +0300 Subject: [PATCH 12/13] Make some examples with single-return state --- ...leton_transition_uses_returned_state_in_validation.sil | 8 ++++---- ...ransition_array_return_to_exact_output_count_match.sil | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil index 2bf945b..e9e9ba1 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_singleton_transition_uses_returned_state_in_validation.sil @@ -2,8 +2,8 @@ contract Decls(int init_value) { int value = init_value; #[covenant.singleton(mode = transition)] - function bump(State prev_state, int delta) : (State) { - return({ value: prev_state.value + delta }); + function bump(State prev_state, int delta) : State { + return { value: prev_state.value + delta }; } } @@ -12,8 +12,8 @@ contract Decls(int init_value) { contract Decls(int init_value) { int value = init_value; - function covenant_policy_bump(State prev_state, int delta) : (State) { - return({ value: prev_state.value + delta }); + function covenant_policy_bump(State prev_state, int delta) : State { + return { value: prev_state.value + delta }; } entrypoint function bump(int delta) { diff --git a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil index 2518dac..4da5977 100644 --- a/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil +++ b/silverscript-lang/tests/covenant_declaration_ast_fixtures/lowers_transition_array_return_to_exact_output_count_match.sil @@ -2,8 +2,8 @@ contract Decls(int max_outs, int init_value) { int value = init_value; #[covenant(from = 1, to = max_outs, mode = transition)] - function fanout(State prev_state, State[] next_states) : (State[]) { - return(next_states); + function fanout(State prev_state, State[] next_states) : State[] { + return next_states; } } @@ -12,8 +12,8 @@ contract Decls(int max_outs, int init_value) { contract Decls(int max_outs, int init_value) { int value = init_value; - function covenant_policy_fanout(State prev_state, State[] next_states) : (State[]) { - return(next_states); + function covenant_policy_fanout(State prev_state, State[] next_states) : State[] { + return next_states; } entrypoint function fanout(State[] next_states) { From 623fd77a63cfcbbe6e87a5c158bbe691c70b4eae Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 19 May 2026 18:47:04 +0300 Subject: [PATCH 13/13] fmt --- silverscript-lang/src/compiler/mod.rs | 5 ++--- silverscript-lang/src/compiler/structs.rs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/silverscript-lang/src/compiler/mod.rs b/silverscript-lang/src/compiler/mod.rs index 98c6ca5..930b76c 100644 --- a/silverscript-lang/src/compiler/mod.rs +++ b/silverscript-lang/src/compiler/mod.rs @@ -5,9 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::ast::{ ArrayDim, BinaryOp, ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, IntrospectionKind, NullaryOp, - ParamAst, SplitPart, StateBindingAst, StateFieldExpr, Statement, TimeVar, TypeBase, TypeRef, UnaryOp, UnarySuffixKind, - STATE_TYPE_NAME, - parse_contract_ast, parse_type_ref, + ParamAst, STATE_TYPE_NAME, SplitPart, StateBindingAst, StateFieldExpr, Statement, TimeVar, TypeBase, TypeRef, UnaryOp, + UnarySuffixKind, parse_contract_ast, parse_type_ref, }; use crate::debug_info::{DebugInfo, DebugNamedValue}; pub use crate::errors::{CompilerError, ErrorSpan}; diff --git a/silverscript-lang/src/compiler/structs.rs b/silverscript-lang/src/compiler/structs.rs index 26c2d31..ea2a6b8 100644 --- a/silverscript-lang/src/compiler/structs.rs +++ b/silverscript-lang/src/compiler/structs.rs @@ -4,8 +4,8 @@ use super::compile::{byte_sequence_cast_size, read_input_state_field_expr_symbol use super::debug_value_types::infer_debug_expr_value_type; use super::*; use crate::ast::{ - ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, ParamAst, StateBindingAst, StateFieldExpr, Statement, - TypeBase, TypeRef, STATE_TYPE_NAME, parse_type_ref, + ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, ParamAst, STATE_TYPE_NAME, StateBindingAst, + StateFieldExpr, Statement, TypeBase, TypeRef, parse_type_ref, }; use crate::span;