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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 6 additions & 15 deletions debugger/session/src/covenant.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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}")
}
6 changes: 4 additions & 2 deletions silverscript-lang/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1311,8 +1313,8 @@ fn parse_struct_definition<'i>(pair: Pair<'i, Rule>) -> Result<StructAst<'i>, 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 {
Expand Down
511 changes: 78 additions & 433 deletions silverscript-lang/src/compiler/covenant_declarations.rs

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions silverscript-lang/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +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,
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};
Expand Down Expand Up @@ -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 {
Expand All @@ -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}")
}

Expand Down Expand Up @@ -210,7 +211,7 @@ impl<'i> CompiledContract<'i> {
args: Vec<Expr<'i>>,
options: CovenantDeclCallOptions,
) -> Result<Vec<u8>, 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);
}
Expand Down
6 changes: 3 additions & 3 deletions silverscript-lang/src/compiler/static_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
{
Expand Down
20 changes: 10 additions & 10 deletions silverscript-lang/src/compiler/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, parse_type_ref,
ConstantAst, ContractAst, ContractFieldAst, Expr, ExprKind, FunctionAst, ParamAst, STATE_TYPE_NAME, StateBindingAst,
StateFieldExpr, Statement, TypeBase, TypeRef, parse_type_ref,
};
use crate::span;

Expand All @@ -30,8 +30,8 @@ pub(crate) type StructRegistry = HashMap<String, StructSpec>;
pub(crate) fn build_struct_registry<'i>(contract: &ContractAst<'i>) -> Result<StructRegistry, CompilerError> {
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
Expand Down Expand Up @@ -60,7 +60,7 @@ pub(crate) fn build_struct_registry<'i>(contract: &ContractAst<'i>) -> Result<St
Ok(StructFieldSpec { name: field.name.clone(), type_ref: field.type_ref.clone() })
})
.collect::<Result<Vec<_>, CompilerError>>()?;
registry.insert("State".to_string(), StructSpec { fields: state_fields });
registry.insert(STATE_TYPE_NAME.to_string(), StructSpec { fields: state_fields });

Ok(registry)
}
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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)?
};
Expand Down
21 changes: 14 additions & 7 deletions silverscript-lang/tests/compiler_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -1993,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 });
}
Expand Down Expand Up @@ -2187,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 });
}
Expand Down Expand Up @@ -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);
}
}
Expand Down
Loading