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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions silverscript-lang/src/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::{HashMap, HashSet};

use kaspa_txscript::opcodes::codes::*;
use kaspa_txscript::script_builder::ScriptBuilder;
use kaspa_txscript::script_builder::{ScriptBuilder, ScriptBuilderError};
use kaspa_txscript::serialize_i64;
use serde::{Deserialize, Serialize};

Expand All @@ -27,6 +27,23 @@ pub struct CompileOptions {
pub record_debug_infos: bool,
}

trait ScriptBuilderExt {
fn add_data_preserving_single_zero(&mut self, data: &[u8]) -> Result<&mut Self, ScriptBuilderError>;
}

impl ScriptBuilderExt for ScriptBuilder {
fn add_data_preserving_single_zero(&mut self, data: &[u8]) -> Result<&mut Self, ScriptBuilderError> {
if data.len() == 1 && data[0] == 0 {
self.add_i64(0)?;
self.add_i64(1)?;
self.add_op(OpNum2Bin)?;
Ok(self)
} else {
self.add_data(data)
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FunctionInputAbi {
pub name: String,
Expand Down Expand Up @@ -725,10 +742,10 @@ impl<'i> CompiledContract<'i> {
.iter()
.filter_map(|value| if let ExprKind::Byte(byte) = &value.kind { Some(*byte) } else { None })
.collect();
builder.add_data(&bytes)?;
builder.add_data_preserving_single_zero(&bytes)?;
} else {
let bytes = encode_array_literal(&values, &input.type_name)?;
builder.add_data(&bytes)?;
builder.add_data_preserving_single_zero(&bytes)?;
}
}
_ => {
Expand Down Expand Up @@ -759,15 +776,15 @@ fn push_sigscript_arg<'i>(builder: &mut ScriptBuilder, arg: Expr<'i>) -> Result<
builder.add_i64(if value { 1 } else { 0 })?;
}
ExprKind::String(value) => {
builder.add_data(value.as_bytes())?;
builder.add_data_preserving_single_zero(value.as_bytes())?;
}
ExprKind::Byte(value) => {
builder.add_data(&[value])?;
builder.add_data_preserving_single_zero(&[value])?;
}
ExprKind::Array(values) if values.iter().all(|value| matches!(&value.kind, ExprKind::Byte(_))) => {
let bytes: Vec<u8> =
values.iter().filter_map(|value| if let ExprKind::Byte(byte) = &value.kind { Some(*byte) } else { None }).collect();
builder.add_data(&bytes)?;
builder.add_data_preserving_single_zero(&bytes)?;
}
ExprKind::DateLiteral(value) => {
builder.add_i64(value)?;
Expand Down Expand Up @@ -2431,29 +2448,29 @@ fn compile_expr<'i>(
Ok(())
}
ExprKind::Byte(byte) => {
builder.add_data(&[*byte])?;
builder.add_data_preserving_single_zero(&[*byte])?;
*stack_depth += 1;
Ok(())
}
ExprKind::Array(values) => {
// TODO: this particular handling should be done in encode_array_literal to unify the behavior
if values.is_empty() {
builder.add_data(&[])?;
builder.add_data_preserving_single_zero(&[])?;
*stack_depth += 1;
return Ok(());
}
let inferred_type = infer_fixed_array_literal_type(values)
.ok_or_else(|| CompilerError::Unsupported("array literal type cannot be inferred".to_string()))?;
let encoded = encode_array_literal(values, &inferred_type)?;
builder.add_data(&encoded)?;
builder.add_data_preserving_single_zero(&encoded)?;
*stack_depth += 1;
Ok(())
}
ExprKind::StateObject(_) => {
Err(CompilerError::Unsupported("state object literals are only supported in validateOutputState".to_string()))
}
ExprKind::String(value) => {
builder.add_data(value.as_bytes())?;
builder.add_data_preserving_single_zero(value.as_bytes())?;
*stack_depth += 1;
Ok(())
}
Expand All @@ -2466,7 +2483,7 @@ fn compile_expr<'i>(
if let ExprKind::Array(values) = &expr.kind {
if is_array_type(type_name) {
let encoded = encode_array_literal(values, type_name)?;
builder.add_data(&encoded)?;
builder.add_data_preserving_single_zero(&encoded)?;
*stack_depth += 1;
visiting.remove(name);
return Ok(());
Expand Down Expand Up @@ -3410,14 +3427,14 @@ fn compile_call_expr<'i>(
}
match &args[0].kind {
ExprKind::String(value) => {
builder.add_data(value.as_bytes())?;
builder.add_data_preserving_single_zero(value.as_bytes())?;
*stack_depth += 1;
Ok(())
}
ExprKind::Identifier(name) => {
if let Some(expr) = scope.env.get(name) {
if let ExprKind::String(value) = &expr.kind {
builder.add_data(value.as_bytes())?;
builder.add_data_preserving_single_zero(value.as_bytes())?;
*stack_depth += 1;
return Ok(());
}
Expand Down Expand Up @@ -3779,10 +3796,10 @@ fn build_null_data_script<'i>(arg: &Expr<'i>) -> Result<Vec<u8>, CompilerError>
.iter()
.filter_map(|value| if let ExprKind::Byte(byte) = &value.kind { Some(*byte) } else { None })
.collect();
builder.add_data(&bytes)?;
builder.add_data_preserving_single_zero(&bytes)?;
}
ExprKind::String(value) => {
builder.add_data(value.as_bytes())?;
builder.add_data_preserving_single_zero(value.as_bytes())?;
}
ExprKind::Call { name, args, .. } if name == "bytes" || name == "byte[]" => {
if args.len() != 1 {
Expand All @@ -3792,7 +3809,7 @@ fn build_null_data_script<'i>(arg: &Expr<'i>) -> Result<Vec<u8>, CompilerError>
}
match &args[0].kind {
ExprKind::String(value) => {
builder.add_data(value.as_bytes())?;
builder.add_data_preserving_single_zero(value.as_bytes())?;
}
_ => {
return Err(CompilerError::Unsupported(
Expand Down
58 changes: 58 additions & 0 deletions silverscript-lang/tests/compiler_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,64 @@ fn allows_concat_of_byte_arrays_with_plus() {
assert!(result.is_ok(), "byte[] concatenation runtime failed: {}", result.unwrap_err());
}

#[test]
fn allows_concat_of_byte_arrays_with_zero_byte_between_them() {
let source = r#"
contract Arrays() {
entrypoint function main() {
byte[] prefix = 0x0102;
byte middle = 0x00;
byte[] suffix = 0x0304;
byte[5] out = prefix + middle + suffix;

require(out.length == 5);
require(out == 0x0102000304);
}
}
"#;

let options = CompileOptions::default();
let compiled = compile_contract(source, &[], options).expect("compile succeeds");
let sigscript = ScriptBuilder::new().drain();
let result = run_script_with_sigscript(compiled.script, sigscript);
assert!(result.is_ok(), "byte[] + zero-byte + byte[] concatenation runtime failed: {}", result.unwrap_err());
}

#[test]
fn allows_concat_with_single_zero_byte_in_byte_fixed_and_dynamic_forms() {
let source = r#"
contract Arrays() {
entrypoint function main() {
byte[] prefix = 0xaa;
byte[] suffix = 0xbb;

byte middle_byte = 0x00;
byte[1] middle_fixed = 0x00;
byte[] middle_dynamic = 0x00;

byte[3] from_byte = prefix + middle_byte + suffix;
byte[3] from_fixed = prefix + middle_fixed + suffix;
byte[3] from_dynamic = prefix + middle_dynamic + suffix;

require(middle_fixed.length == 1);
require(middle_dynamic.length == 1);
require(from_byte.length == 3);
require(from_fixed.length == 3);
require(from_dynamic.length == 3);
require(from_byte == 0xaa00bb);
require(from_fixed == 0xaa00bb);
require(from_dynamic == 0xaa00bb);
}
}
"#;

let options = CompileOptions::default();
let compiled = compile_contract(source, &[], options).expect("compile succeeds");
let sigscript = ScriptBuilder::new().drain();
let result = run_script_with_sigscript(compiled.script, sigscript);
assert!(result.is_ok(), "single-zero-byte concat in byte/byte[1]/byte[] forms failed: {}", result.unwrap_err());
}

#[test]
fn allows_concat_of_fixed_size_byte_array_elements_with_plus() {
let source = r#"
Expand Down