extern crate regex;
pub mod diagnostic;
pub mod errors;
#[macro_use]
pub mod costs;
pub mod types;
pub mod contracts;
pub mod ast;
pub mod contexts;
pub mod database;
pub mod representations;
pub mod callables;
pub mod functions;
pub mod variables;
pub mod analysis;
pub mod docs;
pub mod version;
pub mod coverage;
pub mod events;
#[cfg(feature = "sqlite")]
pub mod tooling;
#[cfg(any(test, feature = "testing"))]
pub mod tests;
#[cfg(any(test, feature = "testing"))]
pub mod test_util;
pub mod clarity;
use serde_json;
use crate::types::StacksEpochId;
pub use crate::vm::database::clarity_db::StacksEpoch;
use crate::vm::callables::CallableType;
use crate::vm::contexts::GlobalContext;
pub use crate::vm::contexts::{CallStack, ContractContext, Environment, LocalContext};
use crate::vm::costs::{
cost_functions, runtime_cost, CostOverflowingMath, CostTracker, LimitedCostTracker,
MemoryConsumer,
};
use crate::vm::errors::{
CheckErrors, Error, InterpreterError, InterpreterResult as Result, RuntimeErrorType,
};
use crate::vm::functions::define::DefineResult;
pub use crate::vm::types::Value;
use crate::vm::types::{
PrincipalData, QualifiedContractIdentifier, TraitIdentifier, TypeSignature,
};
pub use crate::vm::representations::{
ClarityName, ContractName, SymbolicExpression, SymbolicExpressionType,
};
pub use crate::vm::contexts::MAX_CONTEXT_DEPTH;
use crate::vm::costs::cost_functions::ClarityCostFunction;
pub use crate::vm::functions::stx_transfer_consolidated;
pub use crate::vm::version::ClarityVersion;
use std::collections::BTreeMap;
use std::convert::{TryFrom, TryInto};
use self::analysis::ContractAnalysis;
use self::ast::ASTRules;
use self::ast::ContractAST;
use self::costs::ExecutionCost;
use self::diagnostic::Diagnostic;
use self::events::StacksTransactionEvent;
pub const MAX_CALL_STACK_DEPTH: usize = 64;
#[derive(Debug, Clone)]
pub struct ParsedContract {
pub contract_identifier: String,
pub code: String,
pub function_args: BTreeMap<String, Vec<String>>,
pub ast: ContractAST,
pub analysis: ContractAnalysis,
}
#[derive(Debug, Clone)]
pub struct ContractEvaluationResult {
pub result: Option<Value>,
pub contract: ParsedContract,
}
#[derive(Debug, Clone)]
pub struct SnippetEvaluationResult {
pub result: Value,
}
#[derive(Debug, Clone)]
pub enum EvaluationResult {
Contract(ContractEvaluationResult),
Snippet(SnippetEvaluationResult),
}
#[derive(Debug, Clone)]
pub struct ExecutionResult {
pub result: EvaluationResult,
pub events: Vec<StacksTransactionEvent>,
pub cost: Option<CostSynthesis>,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Clone, Debug)]
pub struct CostSynthesis {
pub total: ExecutionCost,
pub limit: ExecutionCost,
pub memory: u64,
pub memory_limit: u64,
}
impl CostSynthesis {
pub fn from_cost_tracker(cost_tracker: &LimitedCostTracker) -> CostSynthesis {
CostSynthesis {
total: cost_tracker.get_total(),
limit: cost_tracker.get_limit(),
memory: cost_tracker.get_memory(),
memory_limit: cost_tracker.get_memory_limit(),
}
}
}
pub trait EvalHook {
fn will_begin_eval(
&mut self,
_env: &mut Environment,
_context: &LocalContext,
_expr: &SymbolicExpression,
);
fn did_finish_eval(
&mut self,
_env: &mut Environment,
_context: &LocalContext,
_expr: &SymbolicExpression,
_res: &core::result::Result<Value, crate::vm::errors::Error>,
);
fn did_complete(&mut self, _result: core::result::Result<&mut ExecutionResult, String>);
}
fn lookup_variable(name: &str, context: &LocalContext, env: &mut Environment) -> Result<Value> {
if name.starts_with(char::is_numeric) || name.starts_with('\'') {
Err(InterpreterError::BadSymbolicRepresentation(format!(
"Unexpected variable name: {}",
name
))
.into())
} else {
if let Some(value) = variables::lookup_reserved_variable(name, context, env)? {
Ok(value)
} else {
runtime_cost(
ClarityCostFunction::LookupVariableDepth,
env,
context.depth(),
)?;
if let Some(value) = context
.lookup_variable(name)
.or_else(|| env.contract_context.lookup_variable(name))
{
runtime_cost(ClarityCostFunction::LookupVariableSize, env, value.size())?;
Ok(value.clone())
} else if let Some(callable_data) = context.lookup_callable_contract(name) {
if env.contract_context.get_clarity_version() < &ClarityVersion::Clarity2 {
Ok(callable_data.contract_identifier.clone().into())
} else {
Ok(Value::CallableContract(callable_data.clone()))
}
} else {
Err(CheckErrors::UndefinedVariable(name.to_string()).into())
}
}
}
}
pub fn lookup_function(name: &str, env: &mut Environment) -> Result<CallableType> {
runtime_cost(ClarityCostFunction::LookupFunction, env, 0)?;
if let Some(result) =
functions::lookup_reserved_functions(name, env.contract_context.get_clarity_version())
{
Ok(result)
} else {
let user_function = env
.contract_context
.lookup_function(name)
.ok_or(CheckErrors::UndefinedFunction(name.to_string()))?;
Ok(CallableType::UserFunction(user_function))
}
}
fn add_stack_trace(result: &mut Result<Value>, env: &Environment) {
if let Err(Error::Runtime(_, ref mut stack_trace)) = result {
if stack_trace.is_none() {
stack_trace.replace(env.call_stack.make_stack_trace());
}
}
}
pub fn apply(
function: &CallableType,
args: &[SymbolicExpression],
env: &mut Environment,
context: &LocalContext,
) -> Result<Value> {
let identifier = function.get_identifier();
let track_recursion = match function {
CallableType::UserFunction(_) => true,
_ => false,
};
if track_recursion && env.call_stack.contains(&identifier) {
return Err(CheckErrors::CircularReference(vec![identifier.to_string()]).into());
}
if env.call_stack.depth() >= MAX_CALL_STACK_DEPTH {
return Err(RuntimeErrorType::MaxStackDepthReached.into());
}
if let CallableType::SpecialFunction(_, function) = function {
env.call_stack.insert(&identifier, track_recursion);
let mut resp = function(args, env, context);
add_stack_trace(&mut resp, env);
env.call_stack.remove(&identifier, track_recursion)?;
resp
} else {
let mut used_memory = 0;
let mut evaluated_args = vec![];
env.call_stack.incr_apply_depth();
for arg_x in args.iter() {
let arg_value = match eval(arg_x, env, context) {
Ok(x) => x,
Err(e) => {
env.drop_memory(used_memory);
env.call_stack.decr_apply_depth();
return Err(e);
}
};
let arg_use = arg_value.get_memory_use();
match env.add_memory(arg_use) {
Ok(_x) => {}
Err(e) => {
env.drop_memory(used_memory);
env.call_stack.decr_apply_depth();
return Err(Error::from(e));
}
};
used_memory += arg_value.get_memory_use();
evaluated_args.push(arg_value);
}
env.call_stack.decr_apply_depth();
env.call_stack.insert(&identifier, track_recursion);
let mut resp = match function {
CallableType::NativeFunction(_, function, cost_function) => {
runtime_cost(*cost_function, env, evaluated_args.len())
.map_err(Error::from)
.and_then(|_| function.apply(evaluated_args, env))
}
CallableType::NativeFunction205(_, function, cost_function, cost_input_handle) => {
let cost_input = if env.epoch() >= &StacksEpochId::Epoch2_05 {
cost_input_handle(evaluated_args.as_slice())?
} else {
evaluated_args.len() as u64
};
runtime_cost(*cost_function, env, cost_input)
.map_err(Error::from)
.and_then(|_| function.apply(evaluated_args, env))
}
CallableType::UserFunction(function) => function.apply(&evaluated_args, env),
_ => panic!("Should be unreachable."),
};
add_stack_trace(&mut resp, env);
env.drop_memory(used_memory);
env.call_stack.remove(&identifier, track_recursion)?;
resp
}
}
pub fn eval<'a>(
exp: &SymbolicExpression,
env: &'a mut Environment,
context: &LocalContext,
) -> Result<Value> {
use crate::vm::representations::SymbolicExpressionType::{
Atom, AtomValue, Field, List, LiteralValue, TraitReference,
};
if let Some(mut eval_hooks) = env.global_context.eval_hooks.take() {
for hook in eval_hooks.iter_mut() {
hook.will_begin_eval(env, context, exp);
}
env.global_context.eval_hooks = Some(eval_hooks);
}
let res = match exp.expr {
AtomValue(ref value) | LiteralValue(ref value) => Ok(value.clone()),
Atom(ref value) => lookup_variable(&value, context, env),
List(ref children) => {
let (function_variable, rest) = children
.split_first()
.ok_or(CheckErrors::NonFunctionApplication)?;
let function_name = function_variable
.match_atom()
.ok_or(CheckErrors::BadFunctionName)?;
let f = lookup_function(&function_name, env)?;
apply(&f, &rest, env, context)
}
TraitReference(_, _) | Field(_) => unreachable!("can't be evaluated"),
};
if let Some(mut eval_hooks) = env.global_context.eval_hooks.take() {
for hook in eval_hooks.iter_mut() {
hook.did_finish_eval(env, context, exp, &res);
}
env.global_context.eval_hooks = Some(eval_hooks);
}
res
}
pub fn is_reserved(name: &str, version: &ClarityVersion) -> bool {
if let Some(_result) = functions::lookup_reserved_functions(name, version) {
true
} else if variables::is_reserved_name(name, version) {
true
} else {
false
}
}
pub fn eval_all(
expressions: &[SymbolicExpression],
contract_context: &mut ContractContext,
global_context: &mut GlobalContext,
sponsor: Option<PrincipalData>,
) -> Result<Option<Value>> {
let mut last_executed = None;
let context = LocalContext::new();
let mut total_memory_use = 0;
let publisher: PrincipalData = contract_context.contract_identifier.issuer.clone().into();
finally_drop_memory!(global_context, total_memory_use; {
for exp in expressions {
let try_define = global_context.execute(|context| {
let mut call_stack = CallStack::new();
let mut env = Environment::new(
context, contract_context, &mut call_stack, Some(publisher.clone()), Some(publisher.clone()), sponsor.clone());
functions::define::evaluate_define(exp, &mut env)
})?;
match try_define {
DefineResult::Variable(name, value) => {
runtime_cost(ClarityCostFunction::BindName, global_context, 0)?;
let value_memory_use = value.get_memory_use();
global_context.add_memory(value_memory_use)?;
total_memory_use += value_memory_use;
contract_context.variables.insert(name, value);
},
DefineResult::Function(name, value) => {
runtime_cost(ClarityCostFunction::BindName, global_context, 0)?;
contract_context.functions.insert(name, value);
},
DefineResult::PersistedVariable(name, value_type, value) => {
runtime_cost(ClarityCostFunction::CreateVar, global_context, value_type.size())?;
contract_context.persisted_names.insert(name.clone());
global_context.add_memory(value_type.type_size()
.expect("type size should be realizable") as u64)?;
global_context.add_memory(value.size() as u64)?;
let data_type = global_context.database.create_variable(&contract_context.contract_identifier, &name, value_type);
global_context.database.set_variable(&contract_context.contract_identifier, &name, value, &data_type)?;
contract_context.meta_data_var.insert(name, data_type);
},
DefineResult::Map(name, key_type, value_type) => {
runtime_cost(ClarityCostFunction::CreateMap, global_context,
u64::from(key_type.size()).cost_overflow_add(
u64::from(value_type.size()))?)?;
contract_context.persisted_names.insert(name.clone());
global_context.add_memory(key_type.type_size()
.expect("type size should be realizable") as u64)?;
global_context.add_memory(value_type.type_size()
.expect("type size should be realizable") as u64)?;
let data_type = global_context.database.create_map(&contract_context.contract_identifier, &name, key_type, value_type);
contract_context.meta_data_map.insert(name, data_type);
},
DefineResult::FungibleToken(name, total_supply) => {
runtime_cost(ClarityCostFunction::CreateFt, global_context, 0)?;
contract_context.persisted_names.insert(name.clone());
global_context.add_memory(TypeSignature::UIntType.type_size()
.expect("type size should be realizable") as u64)?;
let data_type = global_context.database.create_fungible_token(&contract_context.contract_identifier, &name, &total_supply);
contract_context.meta_ft.insert(name, data_type);
},
DefineResult::NonFungibleAsset(name, asset_type) => {
runtime_cost(ClarityCostFunction::CreateNft, global_context, asset_type.size())?;
contract_context.persisted_names.insert(name.clone());
global_context.add_memory(asset_type.type_size()
.expect("type size should be realizable") as u64)?;
let data_type = global_context.database.create_non_fungible_token(&contract_context.contract_identifier, &name, &asset_type);
contract_context.meta_nft.insert(name, data_type);
},
DefineResult::Trait(name, trait_type) => {
contract_context.defined_traits.insert(name, trait_type);
},
DefineResult::UseTrait(_name, _trait_identifier) => {},
DefineResult::ImplTrait(trait_identifier) => {
contract_context.implemented_traits.insert(trait_identifier);
},
DefineResult::NoDefine => {
global_context.execute(|global_context| {
let mut call_stack = CallStack::new();
let mut env = Environment::new(
global_context, contract_context, &mut call_stack, Some(publisher.clone()), Some(publisher.clone()), sponsor.clone());
let result = eval(exp, &mut env, &context)?;
last_executed = Some(result);
Ok(())
})?;
}
}
}
contract_context.data_size = total_memory_use;
Ok(last_executed)
})
}
#[cfg(any(test, feature = "testing"))]
pub fn execute_on_network(program: &str, use_mainnet: bool) -> Result<Option<Value>> {
let epoch_200_result = execute_with_parameters(
program,
ClarityVersion::Clarity2,
StacksEpochId::Epoch20,
ast::ASTRules::PrecheckSize,
use_mainnet,
);
let epoch_205_result = execute_with_parameters(
program,
ClarityVersion::Clarity2,
StacksEpochId::Epoch2_05,
ast::ASTRules::PrecheckSize,
use_mainnet,
);
assert_eq!(
epoch_200_result, epoch_205_result,
"Epoch 2.0 and 2.05 should have same execution result, but did not for program `{}`",
program
);
epoch_205_result
}
#[cfg(any(test, feature = "testing"))]
pub fn execute_with_parameters(
program: &str,
clarity_version: ClarityVersion,
epoch: StacksEpochId,
ast_rules: ast::ASTRules,
use_mainnet: bool,
) -> Result<Option<Value>> {
use crate::vm::database::MemoryBackingStore;
use crate::vm::tests::test_only_mainnet_to_chain_id;
let contract_id = QualifiedContractIdentifier::transient();
let mut contract_context = ContractContext::new(contract_id.clone(), clarity_version);
let mut marf = MemoryBackingStore::new();
let conn = marf.as_clarity_db();
let chain_id = test_only_mainnet_to_chain_id(use_mainnet);
let mut global_context = GlobalContext::new(
use_mainnet,
chain_id,
conn,
LimitedCostTracker::new_free(),
epoch,
);
global_context.execute(|g| {
let parsed = ast::build_ast_with_rules(
&contract_id,
program,
&mut (),
clarity_version,
epoch,
ast_rules,
)?
.expressions;
eval_all(&parsed, &mut contract_context, g, None)
})
}
#[cfg(any(test, feature = "testing"))]
pub fn execute_against_version(program: &str, version: ClarityVersion) -> Result<Option<Value>> {
execute_with_parameters(
program,
version,
StacksEpochId::Epoch20,
ast::ASTRules::PrecheckSize,
false,
)
}
#[cfg(any(test, feature = "testing"))]
pub fn execute(program: &str) -> Result<Option<Value>> {
execute_with_parameters(
program,
ClarityVersion::Clarity1,
StacksEpochId::Epoch20,
ast::ASTRules::PrecheckSize,
false,
)
}
#[cfg(any(test, feature = "testing"))]
pub fn execute_v2(program: &str) -> Result<Option<Value>> {
execute_with_parameters(
program,
ClarityVersion::Clarity2,
StacksEpochId::Epoch21,
ASTRules::PrecheckSize,
false,
)
}
#[cfg(test)]
mod test {
use crate::types::StacksEpochId;
use crate::vm::callables::{DefineType, DefinedFunction};
use crate::vm::costs::LimitedCostTracker;
use crate::vm::database::MemoryBackingStore;
use crate::vm::errors::RuntimeErrorType;
use crate::vm::eval;
use crate::vm::execute;
use crate::vm::types::{QualifiedContractIdentifier, TypeSignature};
use crate::vm::{
CallStack, ContractContext, Environment, GlobalContext, LocalContext, SymbolicExpression,
Value,
};
use std::collections::HashMap;
use super::ClarityVersion;
use stacks_common::consts::CHAIN_ID_TESTNET;
#[test]
fn test_simple_user_function() {
let content = [SymbolicExpression::list(Box::new([
SymbolicExpression::atom("do_work".into()),
SymbolicExpression::atom("a".into()),
]))];
let func_body = SymbolicExpression::list(Box::new([
SymbolicExpression::atom("+".into()),
SymbolicExpression::atom_value(Value::Int(5)),
SymbolicExpression::atom("x".into()),
]));
let func_args = vec![("x".into(), TypeSignature::IntType)];
let user_function = DefinedFunction::new(
func_args,
func_body,
DefineType::Private,
&"do_work".into(),
&"",
);
let context = LocalContext::new();
let mut contract_context = ContractContext::new(
QualifiedContractIdentifier::transient(),
ClarityVersion::Clarity1,
);
let mut marf = MemoryBackingStore::new();
let mut global_context = GlobalContext::new(
false,
CHAIN_ID_TESTNET,
marf.as_clarity_db(),
LimitedCostTracker::new_free(),
StacksEpochId::Epoch2_05,
);
contract_context
.variables
.insert("a".into(), Value::Int(59));
contract_context
.functions
.insert("do_work".into(), user_function);
let mut call_stack = CallStack::new();
let mut env = Environment::new(
&mut global_context,
&contract_context,
&mut call_stack,
None,
None,
None,
);
assert_eq!(Ok(Value::Int(64)), eval(&content[0], &mut env, &context));
}
}