diff --git a/silverscript-lang/src/ast.rs b/silverscript-lang/src/ast.rs index 58dab383..ec6f3f84 100644 --- a/silverscript-lang/src/ast.rs +++ b/silverscript-lang/src/ast.rs @@ -214,6 +214,7 @@ impl<'de> Deserialize<'de> for TypeBase { #[serde(tag = "kind", content = "value", rename_all = "snake_case")] pub enum ArrayDim { Dynamic, + Inferred, Fixed(usize), Constant(String), } @@ -224,6 +225,7 @@ impl TypeRef { for dim in &self.array_dims { match dim { ArrayDim::Dynamic => out.push_str("[]"), + ArrayDim::Inferred => out.push_str("[_]"), ArrayDim::Fixed(size) => out.push_str(&format!("[{size}]")), ArrayDim::Constant(name) => out.push_str(&format!("[{name}]")), } @@ -1159,7 +1161,13 @@ fn parse_type_name_pair(pair: Pair<'_, Rule>) -> Result Some(size_pair) => match size_pair.as_rule() { Rule::array_size => { let raw = size_pair.as_str().trim(); - if let Ok(size) = raw.parse::() { ArrayDim::Fixed(size) } else { ArrayDim::Constant(raw.to_string()) } + if raw == "_" { + ArrayDim::Inferred + } else if let Ok(size) = raw.parse::() { + ArrayDim::Fixed(size) + } else { + ArrayDim::Constant(raw.to_string()) + } } Rule::Identifier => ArrayDim::Constant(size_pair.as_str().to_string()), _ => return Err(CompilerError::Unsupported("invalid array dimension".to_string())), @@ -2077,15 +2085,8 @@ fn parse_cast<'i>(pair: Pair<'i, Rule>) -> Result, CompilerError> { return Ok(Expr::new(ExprKind::Call { name: type_name, args, name_span: type_span }, span)); } - // Handle single byte cast (duplicate check removed above) - // Support type[N] syntax - if let Some(bracket_pos) = type_name.find('[') { - if type_name.ends_with(']') { - let size_str = &type_name[bracket_pos + 1..type_name.len() - 1]; - if size_str.is_empty() || size_str.parse::().is_ok() { - return Ok(Expr::new(ExprKind::Call { name: type_name, args, name_span: type_span }, span)); - } - } + if parse_type_ref(&type_name).is_ok() { + return Ok(Expr::new(ExprKind::Call { name: type_name, args, name_span: type_span }, span)); } Err(CompilerError::Unsupported(format!("cast type not supported: {type_name}"))) diff --git a/silverscript-lang/src/compiler.rs b/silverscript-lang/src/compiler.rs index a47ce4bb..b5038f18 100644 --- a/silverscript-lang/src/compiler.rs +++ b/silverscript-lang/src/compiler.rs @@ -6,9 +6,9 @@ use kaspa_txscript::serialize_i64; use serde::{Deserialize, Serialize}; use crate::ast::{ - ArrayDim, BinaryOp, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, IntrospectionKind, NullaryOp, SplitPart, - StateBindingAst, StateFieldExpr, Statement, TimeVar, TypeBase, TypeRef, UnaryOp, UnarySuffixKind, parse_contract_ast, - parse_type_ref, + ArrayDim, BinaryOp, ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, IntrospectionKind, NullaryOp, + ParamAst, SplitPart, StateBindingAst, StateFieldExpr, Statement, TimeVar, TypeBase, TypeRef, UnaryOp, UnarySuffixKind, + parse_contract_ast, parse_type_ref, }; use crate::debug_info::{DebugInfo, RuntimeBinding, SourceSpan}; pub use crate::errors::{CompilerError, ErrorSpan}; @@ -393,14 +393,24 @@ fn lower_expr<'i>(expr: &Expr<'i>, scope: &LoweringScope, structs: &StructRegist ExprKind::StateObject(_) => { Err(CompilerError::Unsupported("struct literals are only supported in struct-typed positions".to_string())) } - ExprKind::Call { name, args, name_span } => Ok(Expr::new( - ExprKind::Call { - name: name.clone(), - args: args.iter().map(|arg| lower_expr(arg, scope, structs)).collect::, _>>()?, - name_span: *name_span, - }, - span, - )), + ExprKind::Call { name, args, name_span } => { + let lowered_args = args.iter().map(|arg| lower_expr(arg, scope, structs)).collect::, _>>()?; + if name.starts_with("byte[") && name.ends_with(']') { + let size_part = &name[5..name.len() - 1]; + if !size_part.is_empty() && lowered_args.len() == 1 { + let size = + size_part.parse::().map_err(|_| CompilerError::Unsupported(format!("{name}() is not supported")))?; + if let Some(source_type) = infer_lowered_expr_type_name(&lowered_args[0], scope) + && let Some(source_size) = byte_sequence_cast_size(&source_type) + && let Some(source_size) = source_size + && source_size != size + { + return Err(CompilerError::Unsupported(format!("cannot cast {source_type} to {name}"))); + } + } + } + Ok(Expr::new(ExprKind::Call { name: name.clone(), args: lowered_args, name_span: *name_span }, span)) + } ExprKind::New { name, args, name_span } => Ok(Expr::new( ExprKind::New { name: name.clone(), @@ -955,7 +965,9 @@ fn compile_contract_impl<'i>( compiled_entrypoints.push(compile_entrypoint_function( func, index, + &lowered_contract.params, &lowered_contract.fields, + &lowered_contract.constants, contract_field_prefix_len, &constants, options, @@ -1300,6 +1312,79 @@ fn expr_matches_type_ref<'i>(expr: &Expr<'i>, type_ref: &TypeRef) -> bool { } } +fn infer_expr_type_ref_for_comparison<'i>( + expr: &Expr<'i>, + env: &HashMap>, + types: &HashMap, +) -> Option { + if let ExprKind::Identifier(name) = &expr.kind { + if let Some(type_ref) = types.get(name).and_then(|type_name| parse_type_ref(type_name).ok()) { + return Some(type_ref); + } + } + if let Some((name, _)) = env.iter().find(|(_, value)| value.kind == expr.kind) { + if let Some(type_ref) = types.get(name).and_then(|type_name| parse_type_ref(type_name).ok()) { + return Some(type_ref); + } + } + if let ExprKind::Call { name, .. } = &expr.kind { + let is_builtin_cast = matches!(name.as_str(), "int" | "bool" | "byte" | "string" | "pubkey" | "sig" | "datasig") + || (name.contains('[') && parse_type_ref(name).ok().is_some_and(|type_ref| !matches!(type_ref.base, TypeBase::Custom(_)))); + let is_known_builtin = matches!( + name.as_str(), + "int" + | "bool" + | "byte" + | "string" + | "pubkey" + | "sig" + | "datasig" + | "bytes" + | "blake2b" + | "sha256" + | "OpSha256" + | "OpTxSubnetId" + | "OpTxPayloadSubstr" + | "OpOutpointTxId" + | "OpTxInputScriptSigSubstr" + | "OpTxInputSeq" + | "OpTxInputSpkSubstr" + | "OpTxOutputSpkSubstr" + | "OpNum2Bin" + | "OpBin2Num" + | "OpChainblockSeqCommit" + | "LockingBytecodeNullData" + | "ScriptPubKeyP2PK" + | "ScriptPubKeyP2SH" + | "ScriptPubKeyP2SHFromRedeemScript" + | "OpInputCovenantId" + | "OpTxGas" + | "OpTxPayloadLen" + | "OpTxInputIndex" + | "OpTxInputIsCoinbase" + | "OpTxInputScriptSigLen" + | "OpTxInputSpkLen" + | "OpOutpointIndex" + | "OpTxOutputSpkLen" + | "OpAuthOutputCount" + | "OpAuthOutputIdx" + | "OpCovInputCount" + | "OpCovInputIdx" + | "OpCovOutputCount" + | "OpCovOutputIdx" + ); + if !is_builtin_cast && !is_known_builtin { + return None; + } + } + let type_name = infer_debug_expr_value_type(expr, env, types, &mut HashSet::new()).ok()?; + parse_type_ref(&type_name).ok() +} + +fn comparison_types_compatible(left_type: &TypeRef, right_type: &TypeRef) -> bool { + type_name_from_ref(left_type) == type_name_from_ref(right_type) +} + fn array_literal_matches_type_ref<'i>(values: &[Expr<'i>], type_ref: &TypeRef) -> bool { let Some(element_type) = array_element_type_ref(type_ref) else { return false; @@ -1393,6 +1478,11 @@ fn lower_runtime_expr<'i>( lower_expr(expr, &scope, structs) } +fn infer_lowered_expr_type_name<'i>(expr: &Expr<'i>, scope: &LoweringScope) -> Option { + let types = scope.vars.iter().map(|(name, type_ref)| (name.clone(), type_name_from_ref(type_ref))).collect::>(); + infer_debug_expr_value_type(expr, &HashMap::new(), &types, &mut HashSet::new()).ok() +} + fn lower_runtime_struct_expr<'i>( expr: &Expr<'i>, expected_type: &TypeRef, @@ -1701,7 +1791,7 @@ fn array_size_with_constants_ref<'i>(type_ref: &TypeRef, constants: &HashMap None, + ArrayDim::Dynamic | ArrayDim::Inferred => None, } } @@ -1945,7 +2035,11 @@ fn validate_return_types<'i>( } fn has_explicit_array_size_ref(type_ref: &TypeRef) -> bool { - !matches!(type_ref.array_size(), Some(ArrayDim::Dynamic) | None) + !matches!(type_ref.array_size(), Some(ArrayDim::Dynamic | ArrayDim::Inferred) | None) +} + +fn has_inferred_array_size_ref(type_ref: &TypeRef) -> bool { + matches!(type_ref.array_size(), Some(ArrayDim::Inferred)) } fn is_array_type_assignable_ref<'i>(actual: &TypeRef, expected: &TypeRef, constants: &HashMap>) -> bool { @@ -2007,13 +2101,33 @@ fn expr_matches_return_type_ref_hint<'i>(expr: &Expr<'i>, type_ref: &TypeRef) -> } } +fn coerce_expr_for_declared_scalar_type<'i>(expr: Expr<'i>, type_name: &str) -> Expr<'i> { + if type_name == "byte" + && let ExprKind::Int(value) = expr.kind + && (0..=255).contains(&value) + { + return Expr::new(ExprKind::Byte(value as u8), expr.span); + } + expr +} + +fn coerce_rhs_byte_literal_for_comparison<'i>(left_type: Option<&TypeRef>, right: &Expr<'i>) -> Expr<'i> { + if left_type.is_some_and(|type_ref| matches!(type_ref.base, TypeBase::Byte) && type_ref.array_dims.is_empty()) + && let ExprKind::Int(value) = right.kind + && (0..=255).contains(&value) + { + return Expr::new(ExprKind::Byte(value as u8), right.span); + } + right.clone() +} + fn infer_fixed_array_type_from_initializer_ref<'i>( declared_type: &TypeRef, initializer: Option<&Expr<'i>>, types: &HashMap, constants: &HashMap>, ) -> Option { - if !declared_type.array_size().is_some_and(|dim| matches!(dim, ArrayDim::Dynamic)) { + if !has_inferred_array_size_ref(declared_type) { return None; } @@ -2439,7 +2553,9 @@ pub fn function_branch_index<'i>(contract: &ContractAst<'i>, function_name: &str fn compile_entrypoint_function<'i>( function: &FunctionAst<'i>, function_index: usize, + contract_params: &[ParamAst<'i>], contract_fields: &[ContractFieldAst<'i>], + contract_constants: &[ConstantAst<'i>], contract_field_prefix_len: usize, constants: &HashMap>, options: CompileOptions, @@ -2452,6 +2568,30 @@ fn compile_entrypoint_function<'i>( let contract_field_count = contract_fields.len(); let mut flattened_param_names = Vec::new(); let mut types = HashMap::new(); + for param in contract_params { + let param_type_name = type_name_from_ref(¶m.type_ref); + types.insert(param.name.clone(), param_type_name.clone()); + if struct_name_from_type_ref(¶m.type_ref, structs).is_some() + || struct_array_name_from_type_ref(¶m.type_ref, structs).is_some() + { + for (path, field_type) in flatten_type_ref_leaves(¶m.type_ref, structs)? { + let leaf_name = flattened_struct_name(¶m.name, &path); + types.insert(leaf_name, type_name_from_ref(&field_type)); + } + } + } + for constant in contract_constants { + let constant_type_name = type_name_from_ref(&constant.type_ref); + types.insert(constant.name.clone(), constant_type_name.clone()); + if struct_name_from_type_ref(&constant.type_ref, structs).is_some() + || struct_array_name_from_type_ref(&constant.type_ref, structs).is_some() + { + for (path, field_type) in flatten_type_ref_leaves(&constant.type_ref, structs)? { + let leaf_name = flattened_struct_name(&constant.name, &path); + types.insert(leaf_name, type_name_from_ref(&field_type)); + } + } + } for param in &function.params { let param_type_name = type_name_from_ref(¶m.type_ref); types.insert(param.name.clone(), param_type_name.clone()); @@ -2759,13 +2899,16 @@ fn compile_statement<'i>( } let type_name = type_name_from_ref(type_ref); - let effective_type_name = - if is_array_type(&type_name) && array_size_with_constants(&type_name, contract_constants).is_none() { - infer_fixed_array_type_from_initializer(&type_name, expr.as_ref(), types, contract_constants) - .unwrap_or_else(|| type_name.clone()) - } else { - type_name.clone() - }; + let effective_type_name = if has_inferred_array_size_ref(type_ref) { + infer_fixed_array_type_from_initializer(&type_name, expr.as_ref(), types, contract_constants).ok_or_else(|| { + CompilerError::Unsupported(format!( + "variable '{}' requires an initializer with inferrable size for type {}", + name, type_name + )) + })? + } else { + type_name.clone() + }; // Check if this is a fixed-size array (e.g., byte[N]) or dynamic array (e.g., byte[]) let is_fixed_size_array = @@ -2847,7 +2990,7 @@ fn compile_statement<'i>( } else { let expr = expr.clone().ok_or_else(|| CompilerError::Unsupported("variable definition requires initializer".to_string()))?; - let expr = lower_runtime_expr(&expr, types, structs)?; + let expr = coerce_expr_for_declared_scalar_type(lower_runtime_expr(&expr, types, structs)?, &effective_type_name); let expected_type_ref = parse_type_ref(&effective_type_name)?; if !expr_matches_return_type_ref(&expr, &expected_type_ref, types, contract_constants) { return Err(CompilerError::Unsupported(format!( @@ -3405,7 +3548,7 @@ fn compile_statement<'i>( // If this is a stack-bound scalar local, compile a real mutation instead of // rewriting `env[name]` (which can explode under unrolled control flow). if stack_bindings.contains(name) && is_not_struct { - let lowered_expr = lower_runtime_expr(expr, types, structs)?; + let lowered_expr = coerce_expr_for_declared_scalar_type(lower_runtime_expr(expr, types, structs)?, type_name); if !expr_matches_return_type_ref(&lowered_expr, &expected_type_ref, types, contract_constants) { return Err(CompilerError::Unsupported(format!( "variable '{}' expects {}{}", @@ -3535,7 +3678,7 @@ fn compile_statement<'i>( } } } - let lowered_expr = lower_runtime_expr(expr, types, structs)?; + let lowered_expr = coerce_expr_for_declared_scalar_type(lower_runtime_expr(expr, types, structs)?, type_name); if !expr_matches_return_type_ref(&lowered_expr, &expected_type_ref, types, contract_constants) { return Err(CompilerError::Unsupported(format!( "variable '{}' expects {}{}", @@ -6012,8 +6155,35 @@ fn compile_expr<'i>( Ok(()) } ExprKind::Binary { op, left, right } => { - let bytes_eq = - matches!(op, BinaryOp::Eq | BinaryOp::Ne) && (expr_is_bytes(left, env, types) || expr_is_bytes(right, env, types)); + let left_cmp_type = infer_expr_type_ref_for_comparison(left, env, types); + let coerced_right = + if matches!(op, BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge) { + coerce_rhs_byte_literal_for_comparison(left_cmp_type.as_ref(), right) + } else { + right.as_ref().clone() + }; + if matches!(op, BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge) { + if let (Some(left_type), Some(right_type)) = + (left_cmp_type.clone(), infer_expr_type_ref_for_comparison(&coerced_right, env, types)) + { + if !comparison_types_compatible(&left_type, &right_type) { + return Err(CompilerError::Unsupported(format!( + "type mismatch: cannot compare {} and {}", + type_name_from_ref(&left_type), + type_name_from_ref(&right_type) + ))); + } + } + } + let left_value_type = infer_debug_expr_value_type(left, env, types, &mut HashSet::new()).ok(); + let right_value_type = infer_debug_expr_value_type(&coerced_right, env, types, &mut HashSet::new()).ok(); + if matches!(op, BinaryOp::Add) + && (left_value_type.as_deref() == Some("byte") || right_value_type.as_deref() == Some("byte")) + { + return Err(CompilerError::Unsupported("byte values do not support '+'".to_string())); + } + let bytes_eq = matches!(op, BinaryOp::Eq | BinaryOp::Ne) + && (expr_is_bytes(left, env, types) || expr_is_bytes(&coerced_right, env, types)); let bytes_add = matches!(op, BinaryOp::Add) && (expr_is_bytes(left, env, types) || expr_is_bytes(right, env, types)); if bytes_add { compile_concat_operand( @@ -6054,7 +6224,7 @@ fn compile_expr<'i>( contract_constants, )?; compile_expr( - right, + &coerced_right, env, stack_bindings, types, @@ -6364,7 +6534,9 @@ fn expr_is_bytes_inner<'i>( match &expr.kind { ExprKind::Byte(_) => true, ExprKind::String(_) => true, - ExprKind::Array(values) => values.iter().all(|value| matches!(&value.kind, ExprKind::Byte(_))), + // Array literals are encoded to their packed byte representation at compile time, + // regardless of element type, so downstream bytewise ops must treat them as bytes. + ExprKind::Array(_) => true, ExprKind::Slice { .. } => true, ExprKind::New { name, .. } => matches!( name.as_str(), @@ -7006,6 +7178,24 @@ fn compile_call_expr<'i>( )?; Ok(()) } + "bool" | "string" => { + if args.len() != 1 { + return Err(CompilerError::Unsupported(format!("{name}() expects a single argument"))); + } + compile_expr( + &args[0], + scope.env, + scope.stack_bindings, + scope.types, + builder, + options, + visiting, + stack_depth, + script_size, + contract_constants, + )?; + Ok(()) + } "sig" | "pubkey" | "datasig" => { if args.len() != 1 { return Err(CompilerError::Unsupported(format!("{name}() expects a single argument"))); @@ -7068,6 +7258,29 @@ fn compile_call_expr<'i>( if args.len() != 1 { return Err(CompilerError::Unsupported(format!("{name}() expects a single argument"))); } + let source_type = infer_debug_expr_value_type(&args[0], scope.env, scope.types, &mut HashSet::new()).ok(); + if let Some(source_type) = source_type.as_deref() { + if let Some(source_size) = byte_sequence_cast_size(source_type) { + if let Some(source_size) = source_size { + if source_size != size { + return Err(CompilerError::Unsupported(format!("cannot cast {source_type} to {name}"))); + } + } + compile_expr( + &args[0], + scope.env, + scope.stack_bindings, + scope.types, + builder, + options, + visiting, + stack_depth, + script_size, + contract_constants, + )?; + return Ok(()); + } + } compile_expr( &args[0], scope.env, @@ -7087,6 +7300,24 @@ fn compile_call_expr<'i>( Ok(()) } } + name if parse_type_ref(name).is_ok_and(|type_ref| is_array_type_ref(&type_ref)) => { + if args.len() != 1 { + return Err(CompilerError::Unsupported(format!("{name}() expects a single argument"))); + } + compile_expr( + &args[0], + scope.env, + scope.stack_bindings, + scope.types, + builder, + options, + visiting, + stack_depth, + script_size, + contract_constants, + )?; + Ok(()) + } "blake2b" => { if args.len() != 1 { return Err(CompilerError::Unsupported("blake2b() expects a single argument".to_string())); @@ -7237,6 +7468,20 @@ fn is_bytes_type(type_name: &str) -> bool { is_array_type(type_name) } +fn byte_sequence_cast_size(type_name: &str) -> Option> { + match type_name { + "bytes" | "byte[]" | "string" => Some(None), + "byte" => Some(Some(1)), + "pubkey" => Some(Some(32)), + "sig" => Some(Some(65)), + "datasig" => Some(Some(64)), + _ => match array_element_type(type_name).as_deref() { + Some("byte") => Some(array_size(type_name).map(|size| size as i64)), + _ => None, + }, + } +} + fn build_null_data_script<'i>(arg: &Expr<'i>) -> Result, CompilerError> { let elements = match &arg.kind { ExprKind::Array(items) => items, diff --git a/silverscript-lang/src/compiler/debug_value_types.rs b/silverscript-lang/src/compiler/debug_value_types.rs index 6cf43b6f..171eab2c 100644 --- a/silverscript-lang/src/compiler/debug_value_types.rs +++ b/silverscript-lang/src/compiler/debug_value_types.rs @@ -1,9 +1,9 @@ use std::collections::{HashMap, HashSet}; -use crate::ast::{BinaryOp, Expr, ExprKind, IntrospectionKind, NullaryOp, UnaryOp, UnarySuffixKind}; +use crate::ast::{BinaryOp, Expr, ExprKind, IntrospectionKind, NullaryOp, TypeBase, UnaryOp, UnarySuffixKind}; use crate::errors::CompilerError; -use super::{array_element_type, is_bytes_type}; +use super::{array_element_type, is_bytes_type, parse_type_ref}; fn nullary_value_type(op: NullaryOp) -> &'static str { match op { @@ -32,11 +32,31 @@ fn introspection_value_type(kind: IntrospectionKind) -> &'static str { fn builtin_call_value_type(name: &str) -> &'static str { match name { + "int" => "int", + "bool" => "bool", "byte" => "byte", + "string" => "string", + "pubkey" => "pubkey", + "sig" => "sig", + "datasig" => "datasig", + "OpBin2Num" + | "OpTxInputDaaScore" + | "OpTxGas" + | "OpTxPayloadLen" + | "OpTxInputIndex" + | "OpTxInputScriptSigLen" + | "OpTxInputSpkLen" + | "OpOutpointIndex" + | "OpTxOutputSpkLen" + | "OpAuthOutputCount" + | "OpAuthOutputIdx" + | "OpCovInputCount" + | "OpCovInputIdx" + | "OpCovOutputCount" + | "OpCovOutputIdx" => "int", + "OpTxInputIsCoinbase" => "bool", + "blake2b" | "sha256" | "OpSha256" => "byte[32]", "bytes" - | "blake2b" - | "sha256" - | "OpSha256" | "OpTxSubnetId" | "OpTxPayloadSubstr" | "OpOutpointTxId" @@ -50,14 +70,25 @@ fn builtin_call_value_type(name: &str) -> &'static str { | "ScriptPubKeyP2PK" | "ScriptPubKeyP2SH" | "ScriptPubKeyP2SHFromRedeemScript" => "byte[]", - "OpTxInputDaaScore" | "OpAuthOutputCount" | "OpCovInputCount" | "OpCovInputIdx" | "OpCovOutputCount" | "OpCovOutputIdx" => { - "int" - } "OpInputCovenantId" => "byte[32]", _ => "byte[]", } } +fn is_builtin_cast_type_name(name: &str) -> bool { + if matches!(name, "int" | "bool" | "byte" | "string" | "pubkey" | "sig" | "datasig") { + return true; + } + if !name.contains('[') { + return false; + } + let Ok(type_ref) = parse_type_ref(name) else { + return false; + }; + + !matches!(type_ref.base, TypeBase::Custom(_)) +} + pub(super) fn infer_debug_expr_value_type<'i>( expr: &Expr<'i>, env: &HashMap>, @@ -94,6 +125,8 @@ pub(super) fn infer_debug_expr_value_type<'i>( let right_type = infer_debug_expr_value_type(right, env, types, visiting)?; if left_type == "string" || right_type == "string" { Ok("string".to_string()) + } else if left_type == "byte" || right_type == "byte" { + Ok("int".to_string()) } else if is_bytes_type(&left_type) { Ok(left_type) } else if is_bytes_type(&right_type) { @@ -102,9 +135,12 @@ pub(super) fn infer_debug_expr_value_type<'i>( Ok("int".to_string()) } } - BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::BitAnd | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => { - Ok("int".to_string()) + BinaryOp::BitOr | BinaryOp::BitXor | BinaryOp::BitAnd => { + let left_type = infer_debug_expr_value_type(left, env, types, visiting)?; + let right_type = infer_debug_expr_value_type(right, env, types, visiting)?; + if left_type == right_type && is_bytes_type(&left_type) { Ok(left_type) } else { Ok("int".to_string()) } } + BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => Ok("int".to_string()), }, ExprKind::IfElse { then_expr, else_expr, .. } => { let then_type = infer_debug_expr_value_type(then_expr, env, types, visiting)?; @@ -130,7 +166,7 @@ pub(super) fn infer_debug_expr_value_type<'i>( ExprKind::Nullary(kind) => Ok(nullary_value_type(*kind).to_string()), ExprKind::Introspection { kind, .. } => Ok(introspection_value_type(*kind).to_string()), ExprKind::Call { name, .. } => { - if name.starts_with("byte[") { + if is_builtin_cast_type_name(name) { Ok(name.clone()) } else { Ok(builtin_call_value_type(name).to_string()) diff --git a/silverscript-lang/src/silverscript.pest b/silverscript-lang/src/silverscript.pest index ae9ca907..3920d212 100644 --- a/silverscript-lang/src/silverscript.pest +++ b/silverscript-lang/src/silverscript.pest @@ -144,7 +144,7 @@ type_name = { base_type ~ array_suffix* } base_type = { builtin_type | Identifier } builtin_type = { "int" | "bool" | "string" | "pubkey" | "sig" | "datasig" | "byte" } array_suffix = { "[" ~ array_size? ~ "]" } -array_size = { Identifier | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) } +array_size = { "_" | Identifier | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) } VersionLiteral = @{ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ } diff --git a/silverscript-lang/tests/apps/chess/chess_castle.sil b/silverscript-lang/tests/apps/chess/chess_castle.sil index 92803d8c..9d308a48 100644 --- a/silverscript-lang/tests/apps/chess/chess_castle.sil +++ b/silverscript-lang/tests/apps/chess/chess_castle.sil @@ -226,7 +226,7 @@ contract ChessCastle( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_castle_challenge.sil b/silverscript-lang/tests/apps/chess/chess_castle_challenge.sil index 3fcdc5da..dddff28d 100644 --- a/silverscript-lang/tests/apps/chess/chess_castle_challenge.sil +++ b/silverscript-lang/tests/apps/chess/chess_castle_challenge.sil @@ -201,7 +201,7 @@ contract ChessCastleChallengePrep( int hash_start = selector * 32; int hash_end = hash_start + 32; - byte[32] target_template = route_templates.slice(hash_start, hash_end); + byte[32] target_template = byte[32](route_templates.slice(hash_start, hash_end)); // After prep the challenger is committed to this proof move. The next // worker sees recent_castle != 0 and must settle immediately on success. @@ -252,7 +252,7 @@ contract ChessCastleChallengePrep( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_diag.sil b/silverscript-lang/tests/apps/chess/chess_diag.sil index e053f326..5cfdda64 100644 --- a/silverscript-lang/tests/apps/chess/chess_diag.sil +++ b/silverscript-lang/tests/apps/chess/chess_diag.sil @@ -245,7 +245,7 @@ contract ChessDiag( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_horiz.sil b/silverscript-lang/tests/apps/chess/chess_horiz.sil index 5b19a782..ba90d2df 100644 --- a/silverscript-lang/tests/apps/chess/chess_horiz.sil +++ b/silverscript-lang/tests/apps/chess/chess_horiz.sil @@ -260,7 +260,7 @@ contract ChessHoriz( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_king.sil b/silverscript-lang/tests/apps/chess/chess_king.sil index 30880817..9bd288c0 100644 --- a/silverscript-lang/tests/apps/chess/chess_king.sil +++ b/silverscript-lang/tests/apps/chess/chess_king.sil @@ -245,7 +245,7 @@ contract ChessKing( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_knight.sil b/silverscript-lang/tests/apps/chess/chess_knight.sil index 5db48484..6caebad3 100644 --- a/silverscript-lang/tests/apps/chess/chess_knight.sil +++ b/silverscript-lang/tests/apps/chess/chess_knight.sil @@ -231,7 +231,7 @@ contract ChessKnight( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_mux.sil b/silverscript-lang/tests/apps/chess/chess_mux.sil index 4c8cd99a..52978f0f 100644 --- a/silverscript-lang/tests/apps/chess/chess_mux.sil +++ b/silverscript-lang/tests/apps/chess/chess_mux.sil @@ -213,7 +213,7 @@ contract ChessMux( byte[] all_route_templates = move_route_templates + mux_template; int hash_start = selector * 32; int hash_end = hash_start + 32; - byte[32] target_template = all_route_templates.slice(hash_start, hash_end); + byte[32] target_template = byte[32](all_route_templates.slice(hash_start, hash_end)); validateOutputStateWithTemplate( output_idx, next_state, @@ -261,7 +261,7 @@ contract ChessMux( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, @@ -278,7 +278,7 @@ contract ChessMux( entrypoint function settle(byte[32] player_template, byte[] settle_prefix, byte[] settle_suffix) { require(status != 0 /*LIVE*/); byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_pawn.sil b/silverscript-lang/tests/apps/chess/chess_pawn.sil index eebe4365..060eca9b 100644 --- a/silverscript-lang/tests/apps/chess/chess_pawn.sil +++ b/silverscript-lang/tests/apps/chess/chess_pawn.sil @@ -343,7 +343,7 @@ contract ChessPawn( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/chess_vert.sil b/silverscript-lang/tests/apps/chess/chess_vert.sil index cea98b14..84a25a27 100644 --- a/silverscript-lang/tests/apps/chess/chess_vert.sil +++ b/silverscript-lang/tests/apps/chess/chess_vert.sil @@ -260,7 +260,7 @@ contract ChessVert( } byte[32] settle_template = blake2b(settle_prefix + settle_suffix); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState next_state = { player_template: player_template, diff --git a/silverscript-lang/tests/apps/chess/player.sil b/silverscript-lang/tests/apps/chess/player.sil index 184035e3..52c48e7f 100644 --- a/silverscript-lang/tests/apps/chess/player.sil +++ b/silverscript-lang/tests/apps/chess/player.sil @@ -189,7 +189,7 @@ contract Player( int leader_input_idx = OpCovInputIdx(cov_id, 0); require(blake2b(route_templates) == routes_commitment); - require(blake2b(settle_template + player_template) == route_templates.slice(256, 288)); + require(blake2b(settle_template + player_template) == byte[32](route_templates.slice(256, 288))); SettleState leader = readInputStateWithTemplate(leader_input_idx, settle_prefix_len, settle_suffix_len, settle_template); // Settlement is driven by a terminal settle worker state, not by a diff --git a/silverscript-lang/tests/chess_apps_tests.rs b/silverscript-lang/tests/chess_apps_tests.rs index 59ea464d..23ddee33 100644 --- a/silverscript-lang/tests/chess_apps_tests.rs +++ b/silverscript-lang/tests/chess_apps_tests.rs @@ -678,58 +678,58 @@ fn size_snapshots() -> Vec { SizeSnapshot { name: "chess_pawn.sil", ctor: pawn_constructor_args, - expected_script_len: 1846, - expected_instruction_count: 1219, + expected_script_len: 1833, + expected_instruction_count: 1208, expected_charged_op_count: 794, }, SizeSnapshot { name: "chess_knight.sil", ctor: pawn_constructor_args, - expected_script_len: 1392, - expected_instruction_count: 801, - expected_charged_op_count: 529, + expected_script_len: 1383, + expected_instruction_count: 794, + expected_charged_op_count: 527, }, SizeSnapshot { name: "chess_vert.sil", ctor: pawn_constructor_args, - expected_script_len: 2060, - expected_instruction_count: 1416, - expected_charged_op_count: 925, + expected_script_len: 2035, + expected_instruction_count: 1393, + expected_charged_op_count: 915, }, SizeSnapshot { name: "chess_horiz.sil", ctor: pawn_constructor_args, - expected_script_len: 2060, - expected_instruction_count: 1416, - expected_charged_op_count: 925, + expected_script_len: 2035, + expected_instruction_count: 1393, + expected_charged_op_count: 915, }, SizeSnapshot { name: "chess_diag.sil", ctor: pawn_constructor_args, - expected_script_len: 1823, - expected_instruction_count: 1217, - expected_charged_op_count: 795, + expected_script_len: 1814, + expected_instruction_count: 1210, + expected_charged_op_count: 793, }, SizeSnapshot { name: "chess_king.sil", ctor: pawn_constructor_args, - expected_script_len: 1537, - expected_instruction_count: 944, - expected_charged_op_count: 621, + expected_script_len: 1512, + expected_instruction_count: 921, + expected_charged_op_count: 611, }, SizeSnapshot { name: "chess_castle.sil", ctor: pawn_constructor_args, - expected_script_len: 1548, - expected_instruction_count: 951, - expected_charged_op_count: 617, + expected_script_len: 1523, + expected_instruction_count: 928, + expected_charged_op_count: 608, }, SizeSnapshot { name: "chess_castle_challenge.sil", ctor: pawn_constructor_args, - expected_script_len: 1762, - expected_instruction_count: 1149, - expected_charged_op_count: 744, + expected_script_len: 1735, + expected_instruction_count: 1124, + expected_charged_op_count: 733, }, ] } diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index 44a85a6c..d2db2cbb 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -868,6 +868,142 @@ fn build_sig_script_builds_expected_script() { assert_eq!(sigscript, expected); } +#[test] +fn byte_variable_from_int_literal_uses_raw_byte_push() { + let source = r#" + contract Bytes() { + entrypoint function main() { + byte x = 5; + require(OpBin2Num(x) == 5); + } + } + "#; + + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte int literal should compile"); + let expected = ScriptBuilder::new() + .add_data(&[5u8]) + .unwrap() + .add_op(OpBin2Num) + .unwrap() + .add_i64(5) + .unwrap() + .add_op(OpNumEqual) + .unwrap() + .add_op(OpVerify) + .unwrap() + .add_op(OpTrue) + .unwrap() + .drain(); + assert_eq!(compiled.script, expected); + assert!(run_script_with_selector(compiled.script, None).is_ok(), "byte int literal script should execute"); +} + +#[test] +fn byte_variable_from_out_of_range_int_literal_is_rejected() { + let source = r#" + contract Bytes() { + entrypoint function main() { + byte x = 256; + require(true); + } + } + "#; + + assert!(compile_contract(source, &[], CompileOptions::default()).is_err(), "byte x = 256 should be rejected"); +} + +#[test] +fn byte_equality_uses_op_equal_not_op_numequal() { + let source = r#" + contract Bytes() { + entrypoint function main() { + byte x = 5; + byte y = 7; + require(x == y); + } + } + "#; + + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte equality should compile"); + assert!(compiled.script.iter().copied().any(|op| op == OpEqual), "byte equality should use OP_EQUAL"); + assert!(!compiled.script.iter().copied().any(|op| op == OpNumEqual), "byte equality should not use OP_NUMEQUAL"); +} + +#[test] +fn byte_equality_with_rhs_int_literal_uses_raw_byte_push() { + let source = r#" + contract Bytes() { + entrypoint function main() { + byte x = 1; + require(x == 1); + } + } + "#; + + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte equality with rhs literal should compile"); + let expected = ScriptBuilder::new() + .add_data(&[1u8]) + .unwrap() + .add_data(&[1u8]) + .unwrap() + .add_op(OpEqual) + .unwrap() + .add_op(OpVerify) + .unwrap() + .add_op(OpTrue) + .unwrap() + .drain(); + assert_eq!(compiled.script, expected); + assert!(run_script_with_selector(compiled.script, None).is_ok(), "byte equality with rhs literal should execute"); +} + +#[test] +fn byte_equality_with_out_of_range_rhs_int_literal_is_rejected() { + let source = r#" + contract Bytes() { + entrypoint function main() { + byte x = 5; + require(x == 256); + } + } + "#; + + assert!(compile_contract(source, &[], CompileOptions::default()).is_err(), "x == 256 should be rejected when x is a byte"); +} + +#[test] +fn rejects_adding_byte_values() { + let source = r#" + contract Bytes() { + entrypoint function main() { + byte x = 5; + byte y = 7; + require(x + y > 0); + } + } + "#; + + let err = compile_contract(source, &[], CompileOptions::default()).expect_err("byte addition should be rejected"); + assert!(err.to_string().contains("byte values do not support '+'"), "unexpected error: {err}"); +} + +#[test] +fn rejects_assigning_sum_of_byte_values_to_byte() { + let source = r#" + contract Bytes() { + entrypoint function main() { + byte x = 5; + byte y = 7; + byte z = x + y; + require(OpBin2Num(z) == 12); + } + } + "#; + + let err = compile_contract(source, &[], CompileOptions::default()).expect_err("byte addition assignment should be rejected"); + assert!(err.to_string().contains("byte values do not support '+'"), "unexpected error: {err}"); +} + #[test] fn build_sig_script_rejects_unknown_function() { let source = r#" @@ -882,6 +1018,251 @@ fn build_sig_script_rejects_unknown_function() { assert!(result.is_err()); } +#[test] +fn disallow_comparing_byte_array_to_byte_constant() { + let source = r#" + contract Test(byte[32] genesisPk, byte genesisIdentifierType) { + byte[32] ownerIdentifier = genesisPk; + byte identifierType = genesisIdentifierType; + byte constant ZERO = 0x00; + + entrypoint function main() { + if (ownerIdentifier == ZERO) { + require(true); + } + } + } + "#; + + assert!( + compile_contract(source, &[Expr::bytes(vec![1u8; 32]), Expr::byte(0)], CompileOptions::default()).is_err(), + "comparing byte[32] to byte should be rejected without cast" + ); +} + +#[test] +fn disallow_comparing_dynamic_and_fixed_byte_arrays_without_cast_in_contract_scope() { + let source = r#" + contract Test(byte[] x) { + byte[2] y = 0x1234; + + entrypoint function main() { + require(x == y); + } + } + "#; + + assert!( + compile_contract(source, &[Expr::bytes(vec![0x12])], CompileOptions::default()).is_err(), + "comparing byte[] to byte[2] should be rejected without cast" + ); +} + +#[test] +fn allow_comparing_dynamic_and_fixed_byte_arrays_with_cast_in_contract_scope() { + let source = r#" + contract Test(byte[] x) { + byte[2] y = 0x1234; + + entrypoint function main() { + require(x == byte[](y)); + } + } + "#; + + compile_contract(source, &[Expr::bytes(vec![0x12])], CompileOptions::default()) + .expect("comparing byte[] to byte[2] should be allowed with cast"); +} + +#[test] +fn rejects_comparing_different_scalar_types_without_cast() { + let source = r#" + contract Reproduce() { + entrypoint function main() { + if (1 == true) { + require(false); + } + } + } + "#; + + let result = compile_contract(source, &[], CompileOptions::default()); + assert!(result.is_err(), "int == bool should be rejected"); +} + +#[test] +fn disallow_comparing_dynamic_and_fixed_int_arrays_without_cast() { + let source = r#" + contract Arrays() { + entrypoint function main() { + int[] x = [1]; + int[1] y = [1]; + require(x == y); + } + } + "#; + + assert!(compile_contract(source, &[], CompileOptions::default()).is_err(), "int[] == int[1] should be rejected"); +} + +#[test] +fn allows_comparing_dynamic_and_fixed_int_arrays_with_cast() { + let source = r#" + contract Arrays() { + entrypoint function main() { + int[] x = [1]; + int[1] y = [1]; + require(x == int[](y)); + } + } + "#; + + assert!(compile_contract(source, &[], CompileOptions::default()).is_ok(), "int[] == int[](int[1]) should compile"); +} + +#[test] +fn allows_comparing_inferred_and_fixed_byte_arrays_when_sizes_match() { + let source = r#" + contract Arrays() { + entrypoint function main() { + byte[_] x = 0x1256; + byte[2] y = 0x1234; + require(x == y); + } + } + "#; + + assert!(compile_contract(source, &[], CompileOptions::default()).is_ok(), "byte[_] should infer to byte[2]"); +} + +#[test] +fn rejects_comparing_inferred_and_fixed_byte_arrays_when_sizes_differ() { + let source = r#" + contract Arrays() { + entrypoint function main() { + byte[_] x = 0x12; + byte[2] y = 0x1234; + require(x == y); + } + } + "#; + + assert!( + compile_contract(source, &[], CompileOptions::default()).is_err(), + "byte[_] inferred as byte[1] should not compare to byte[2]" + ); +} + +#[test] +fn infers_fixed_sizes_for_multiple_array_element_types() { + let source = r#" + contract Arrays() { + entrypoint function main() { + int[_] ints = [1, 2, 3, 4]; + int[4] ints_expected = [1, 2, 3, 4]; + bool[_] flags = [true, false]; + bool[2] flags_expected = [true, false]; + pubkey[_] keys = [ + 0x0101010101010101010101010101010101010101010101010101010101010101, + 0x0202020202020202020202020202020202020202020202020202020202020202 + ]; + pubkey[2] keys_expected = [ + 0x0303030303030303030303030303030303030303030303030303030303030303, + 0x0404040404040404040404040404040404040404040404040404040404040404 + ]; + require(ints == ints_expected); + require(flags == flags_expected); + require(keys == keys_expected); + } + } + "#; + + assert!( + compile_contract(source, &[], CompileOptions::default()).is_ok(), + "type[_] should infer fixed sizes across supported element types" + ); +} + +#[test] +fn rejects_comparing_dynamic_and_fixed_arrays_without_cast_in_function_scope() { + let source = r#" + contract Arrays() { + entrypoint function main(byte[] x) { + byte[2] y = 0x1234; + require(x == y); + } + } + "#; + + assert!( + compile_contract(source, &[], CompileOptions::default()).is_err(), + "byte[] param should not compare to byte[2] without cast" + ); +} + +#[test] +fn allows_comparing_dynamic_and_fixed_arrays_with_cast_in_function_scope() { + let source = r#" + contract Arrays() { + entrypoint function main(byte[] x) { + byte[2] y = 0x1234; + require(x == byte[](y)); + } + } + "#; + + assert!(compile_contract(source, &[], CompileOptions::default()).is_ok(), "byte[] param should compare to byte[](byte[2])"); +} + +#[test] +fn byte_array_to_fixed_byte_array_cast_compiles_without_num2bin() { + let source = r#" + contract Arrays() { + entrypoint function main() { + byte[] route_templates = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f; + byte[32] target_template = byte[32](route_templates.slice(16, 48)); + require(byte[](target_template) == route_templates.slice(16, 48)); + } + } + "#; + + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("byte[] to byte[32] cast should compile"); + assert!(!compiled.script.iter().copied().any(|op| op == OpNum2Bin), "byte[] to byte[32] cast should not emit OpNum2Bin"); + assert!(run_script_with_selector(compiled.script, None).is_ok(), "byte[] to byte[32] cast should execute"); +} + +#[test] +fn rejects_cast_between_different_fixed_byte_array_sizes() { + let source = r#" + contract Arrays() { + entrypoint function main() { + byte[32] hash = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f; + byte[31] truncated = byte[31](hash); + require(truncated.length == 31); + } + } + "#; + + let err = compile_contract(source, &[], CompileOptions::default()).expect_err("byte[32] to byte[31] cast should be rejected"); + assert!(err.to_string().contains("cannot cast byte[32] to byte[31]"), "unexpected error: {err}"); +} + +#[test] +fn rejects_cast_from_smaller_fixed_byte_array_to_larger_fixed_byte_array() { + let source = r#" + contract Arrays() { + entrypoint function main() { + byte[31] hash = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e; + byte[32] padded = byte[32](hash); + require(padded.length == 32); + } + } + "#; + + let err = compile_contract(source, &[], CompileOptions::default()).expect_err("byte[31] to byte[32] cast should be rejected"); + assert!(err.to_string().contains("cannot cast byte[31] to byte[32]"), "unexpected error: {err}"); +} + #[test] fn build_sig_script_rejects_wrong_argument_count() { let source = r#" @@ -2901,6 +3282,24 @@ fn runs_array_runtime_examples() { assert!(result.is_ok(), "array runtime example failed: {}", result.unwrap_err()); } +#[test] +fn runs_int_array_push_length_runtime_example() { + let source = r#" + contract Arrays() { + entrypoint function main() { + int[] x = [1, 2, 3]; + x.push(4); + require(x.length == 4); + } + } + "#; + + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("compile succeeds"); + let sigscript = ScriptBuilder::new().drain(); + let result = run_script_with_sigscript(compiled.script, sigscript); + assert!(result.is_ok(), "int[] push length runtime example failed: {}", result.unwrap_err()); +} + #[test] fn runs_slice_with_explicit_end_bounds() { let source = r#" @@ -2909,7 +3308,7 @@ fn runs_slice_with_explicit_end_bounds() { byte[] data = 0x0102030405060708090a; byte[] segment = data.slice(3, 8); require(segment.length == 5); - require(segment == 0x0405060708); + require(segment == byte[](0x0405060708)); } } "#; @@ -2929,8 +3328,8 @@ fn runs_slice_reconstruction_and_compare_runtime_example() { byte[] right = data.slice(4, 10); byte[] rebuilt = left + right; - require(left == 0x01020304); - require(right == 0x05060708090a); + require(left == byte[](0x01020304)); + require(right == byte[](0x05060708090a)); require(rebuilt.length == data.length); require(rebuilt == data); } @@ -4620,7 +5019,7 @@ fn read_input_state_accepts_pubkey_and_bool_fields_under_selector_dispatch() { entrypoint function main() { State s = readInputState(this.activeInputIndex); require(s.flag); - require(s.owner == 0x0202020202020202020202020202020202020202020202020202020202020202); + require(s.owner == pubkey(0x0202020202020202020202020202020202020202020202020202020202020202)); } } "#; @@ -5609,7 +6008,7 @@ fn compiles_opcode_builtins() { r#" contract Test() { entrypoint function main() { - require(OpSha256(bytes("msg")) == bytes("hash")); + require(byte[](OpSha256(bytes("msg"))) == byte[]("hash")); } } "#, @@ -5862,7 +6261,30 @@ fn compiles_opcode_builtins() { r#" contract Test() { entrypoint function main() { - require(OpTxInputIsCoinbase(0) == 0); + require(OpTxInputDaaScore(0) == 0); + } + } + "#, + ScriptBuilder::new() + .add_i64(0) + .unwrap() + .add_op(OpTxInputDaaScore) + .unwrap() + .add_i64(0) + .unwrap() + .add_op(OpNumEqual) + .unwrap() + .add_op(OpVerify) + .unwrap() + .add_op(OpTrue) + .unwrap() + .drain(), + ), + ( + r#" + contract Test() { + entrypoint function main() { + require(OpTxInputIsCoinbase(0) == false); } } "#, @@ -6033,7 +6455,7 @@ fn compiles_opcode_builtins() { r#" contract Test() { entrypoint function main() { - require(OpInputCovenantId(0) == bytes("cov")); + require(byte[](OpInputCovenantId(0)) == bytes("cov")); } } "#, @@ -6346,7 +6768,7 @@ fn executes_opcode_builtins_basic() { r#" contract Test() { entrypoint function main() { - require(OpTxInputIsCoinbase(0) == 0); + require(OpTxInputIsCoinbase(0) == bool(0)); } } "#, @@ -6420,7 +6842,7 @@ fn executes_opcode_builtins_covenants() { entrypoint function main() { require(OpAuthOutputCount(0) == 2); require(OpAuthOutputIdx(0, 1) == 2); - require(OpInputCovenantId(0) == bytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); + require(byte[](OpInputCovenantId(0)) == bytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); require(OpCovInputCount(bytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")) == 2); require(OpCovInputIdx(bytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 1) == 2); require(OpCovOutputCount(bytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")) == 2); @@ -6903,8 +7325,8 @@ fn compile_time_length_for_inferred_array_sizes() { let source = r#" contract Test() { entrypoint function test() { - byte[] data = 0x1234abcd; - int[] nums = [1, 2, 3]; + byte[_] data = 0x1234abcd; + int[_] nums = [1, 2, 3]; require(data.length == 4); require(nums.length == 3); } diff --git a/silverscript-lang/tests/examples/comments.sil b/silverscript-lang/tests/examples/comments.sil index d54d0abb..8b28884d 100644 --- a/silverscript-lang/tests/examples/comments.sil +++ b/silverscript-lang/tests/examples/comments.sil @@ -19,6 +19,6 @@ contract Test(int x) { require(i < 20); require(checkSig(s, pk)); } else - require(b == 0x01); + require(b == byte[](0x01)); } } diff --git a/silverscript-lang/tests/examples/covenant.sil b/silverscript-lang/tests/examples/covenant.sil index ec967431..ce3aac94 100644 --- a/silverscript-lang/tests/examples/covenant.sil +++ b/silverscript-lang/tests/examples/covenant.sil @@ -3,6 +3,6 @@ pragma silverscript ^0.1.0; contract Covenant(int requiredVersion) { entrypoint function spend() { require(tx.version == requiredVersion); - require(this.activeScriptPubKey == 0x00); + require(this.activeScriptPubKey == byte[](0x00)); } } diff --git a/silverscript-lang/tests/examples/covenant_escrow.sil b/silverscript-lang/tests/examples/covenant_escrow.sil index 3049cd3d..ab7f80eb 100644 --- a/silverscript-lang/tests/examples/covenant_escrow.sil +++ b/silverscript-lang/tests/examples/covenant_escrow.sil @@ -13,8 +13,8 @@ contract Escrow(byte[32] arbiter, pubkey buyer, pubkey seller) { // Check that the transaction sends to either the buyer or the seller byte[34] buyerLock = new ScriptPubKeyP2PK(buyer); byte[34] sellerLock = new ScriptPubKeyP2PK(seller); - bool sendsToBuyer = tx.outputs[0].scriptPubKey == buyerLock; - bool sendsToSeller = tx.outputs[0].scriptPubKey == sellerLock; + bool sendsToBuyer = tx.outputs[0].scriptPubKey == byte[](buyerLock); + bool sendsToSeller = tx.outputs[0].scriptPubKey == byte[](sellerLock); require(sendsToBuyer || sendsToSeller); } } diff --git a/silverscript-lang/tests/examples/covenant_mecenas.sil b/silverscript-lang/tests/examples/covenant_mecenas.sil index 2c6cb8f5..f0edcdc6 100644 --- a/silverscript-lang/tests/examples/covenant_mecenas.sil +++ b/silverscript-lang/tests/examples/covenant_mecenas.sil @@ -6,7 +6,7 @@ contract Mecenas(pubkey recipient, byte[32] funder, int pledge, int period) { // Check that the first output sends to the recipient byte[34] recipientScriptPubKey = new ScriptPubKeyP2PK(recipient); - require(tx.outputs[0].scriptPubKey == recipientScriptPubKey); + require(tx.outputs[0].scriptPubKey == byte[](recipientScriptPubKey)); // Calculate the value that's left int minerFee = 1000; diff --git a/silverscript-lang/tests/examples/double_split.sil b/silverscript-lang/tests/examples/double_split.sil index 6d748a63..a247e757 100644 --- a/silverscript-lang/tests/examples/double_split.sil +++ b/silverscript-lang/tests/examples/double_split.sil @@ -3,6 +3,6 @@ pragma silverscript ^0.1.0; contract DoubleSplit(byte[20] pkh) { entrypoint function spend() { byte[] actualPkh = tx.inputs[this.activeInputIndex].scriptPubKey.split(23)[0].split(3)[1]; - require(pkh == actualPkh); + require(byte[](pkh) == actualPkh); } } diff --git a/silverscript-lang/tests/examples/mecenas.sil b/silverscript-lang/tests/examples/mecenas.sil index b80c83ee..519e8eb9 100644 --- a/silverscript-lang/tests/examples/mecenas.sil +++ b/silverscript-lang/tests/examples/mecenas.sil @@ -5,7 +5,7 @@ contract Mecenas(pubkey recipient, byte[32] funder, int pledge/*, int period */) // require(this.age >= period); // Check that the first output sends to the recipient - require(tx.outputs[0].scriptPubKey == new ScriptPubKeyP2PK(recipient)); + require(tx.outputs[0].scriptPubKey == byte[](new ScriptPubKeyP2PK(recipient))); int minerFee = 1000; int currentValue = tx.inputs[this.activeInputIndex].value; diff --git a/silverscript-lang/tests/examples/mecenas_locktime.sil b/silverscript-lang/tests/examples/mecenas_locktime.sil index f275463d..1310a49f 100644 --- a/silverscript-lang/tests/examples/mecenas_locktime.sil +++ b/silverscript-lang/tests/examples/mecenas_locktime.sil @@ -8,7 +8,7 @@ contract Mecenas( ) { entrypoint function receive() { byte[34] recipientScriptPubKey = new ScriptPubKeyP2PK(recipient); - require(tx.outputs[0].scriptPubKey == recipientScriptPubKey); + require(tx.outputs[0].scriptPubKey == byte[](recipientScriptPubKey)); int initial = int(initialBlock); require(tx.time >= initial); @@ -28,7 +28,7 @@ contract Mecenas( byte[] bcValue = 8 + byte[8](tx.locktime) + this.activeScriptPubKey.split(9)[1]; byte[35] lockValue = new ScriptPubKeyP2SH(blake2b(bcValue)); - require(tx.outputs[1].scriptPubKey == lockValue); + require(tx.outputs[1].scriptPubKey == byte[](lockValue)); } } diff --git a/silverscript-lang/tests/examples/p2pkh_invalid.sil b/silverscript-lang/tests/examples/p2pkh_invalid.sil index 730ebf38..7b8cf926 100644 --- a/silverscript-lang/tests/examples/p2pkh_invalid.sil +++ b/silverscript-lang/tests/examples/p2pkh_invalid.sil @@ -2,7 +2,7 @@ pragma silverscript ^0.1.0; contract P2PKH(byte[20] pkh) { entrypoint function spend(pubkey pk, sig s) { - require(blake2b(pk) == s); + require(byte[](blake2b(pk)) == byte[](s)); require(checkSig(s, pk)); } } diff --git a/silverscript-lang/tests/examples/simple_splice.sil b/silverscript-lang/tests/examples/simple_splice.sil index 3ae463be..fca3bca0 100644 --- a/silverscript-lang/tests/examples/simple_splice.sil +++ b/silverscript-lang/tests/examples/simple_splice.sil @@ -4,6 +4,6 @@ contract Test(byte[] b) { entrypoint function spend() { byte[] x = b.split(5)[1]; require(x != b); - require (b.split(4)[0] != x); + require (byte[](b.split(4)[0]) != x); } } diff --git a/silverscript-lang/tests/examples/simulating_state.sil b/silverscript-lang/tests/examples/simulating_state.sil index f71fdc92..641b22cf 100644 --- a/silverscript-lang/tests/examples/simulating_state.sil +++ b/silverscript-lang/tests/examples/simulating_state.sil @@ -9,7 +9,7 @@ contract SimulatingState( entrypoint function receive() { // Check that the first output sends to the recipient byte[34] recipientScriptPubKey = new ScriptPubKeyP2PK(recipient); - require(tx.outputs[0].scriptPubKey == recipientScriptPubKey); + require(tx.outputs[0].scriptPubKey == byte[](recipientScriptPubKey)); // Check that time has passed and that time locks are enabled int initial = int(initialBlock); @@ -43,7 +43,7 @@ contract SimulatingState( // Create the locking bytecode for the new contract and check that // the change output sends to that contract byte[35] newContractLock = new ScriptPubKeyP2SH(blake2b(newContract)); - require(tx.outputs[1].scriptPubKey == newContractLock); + require(tx.outputs[1].scriptPubKey == byte[](newContractLock)); } } diff --git a/silverscript-lang/tests/examples/slice.sil b/silverscript-lang/tests/examples/slice.sil index ae356263..5db81067 100644 --- a/silverscript-lang/tests/examples/slice.sil +++ b/silverscript-lang/tests/examples/slice.sil @@ -3,6 +3,6 @@ pragma silverscript ^0.1.0; contract Slice(byte[20] pkh) { entrypoint function spend() { byte[] actualPkh = tx.inputs[this.activeInputIndex].scriptPubKey.slice(3, 23); - require(pkh == actualPkh); + require(byte[](pkh) == actualPkh); } } diff --git a/silverscript-lang/tests/examples/slice_variable_parameter.sil b/silverscript-lang/tests/examples/slice_variable_parameter.sil index ae0c04d6..6d2c7f18 100644 --- a/silverscript-lang/tests/examples/slice_variable_parameter.sil +++ b/silverscript-lang/tests/examples/slice_variable_parameter.sil @@ -4,6 +4,6 @@ contract Slice(byte[20] pkh) { entrypoint function spend() { int x = 3; byte[] actualPkh = tx.inputs[this.activeInputIndex].scriptPubKey.slice(x, 23); - require(pkh == actualPkh); + require(byte[](pkh) == actualPkh); } } diff --git a/silverscript-lang/tests/examples/split_or_slice_signature.sil b/silverscript-lang/tests/examples/split_or_slice_signature.sil index 88c455c0..b51c2f1e 100644 --- a/silverscript-lang/tests/examples/split_or_slice_signature.sil +++ b/silverscript-lang/tests/examples/split_or_slice_signature.sil @@ -5,7 +5,7 @@ contract Test(sig signature) { // Assume Schnorr byte[] hashtype1 = signature.split(64)[1]; byte[1] hashtype2 = signature.slice(64, 65); - require(hashtype1 == 0x01); + require(hashtype1 == byte[](0x01)); require(hashtype2 == 0x01); } } diff --git a/silverscript-lang/tests/examples/split_size.sil b/silverscript-lang/tests/examples/split_size.sil index 934e2436..3e4f02a9 100644 --- a/silverscript-lang/tests/examples/split_size.sil +++ b/silverscript-lang/tests/examples/split_size.sil @@ -4,6 +4,6 @@ contract SplitSize(byte[] b) { entrypoint function spend() { byte[] x = b.split(b.length / 2)[1]; require(x != b); - require(b.split(4)[0] != x); + require(byte[](b.split(4)[0]) != x); } } diff --git a/silverscript-lang/tests/examples/split_typed.sil b/silverscript-lang/tests/examples/split_typed.sil index 9af7d485..66b04f7e 100644 --- a/silverscript-lang/tests/examples/split_typed.sil +++ b/silverscript-lang/tests/examples/split_typed.sil @@ -3,6 +3,6 @@ pragma silverscript ^0.1.0; contract SplitTyped(byte[] b) { entrypoint function spend() { byte[4] x = b.split(4)[0]; - require(x != b); + require(byte[](x) != b); } }