use crate::vm::analysis;
use crate::vm::analysis::ContractAnalysis;
use crate::vm::analysis::{AnalysisDatabase, CheckError, CheckErrors};
use crate::vm::ast::errors::{ParseError, ParseErrors};
use crate::vm::ast::ASTRules;
use crate::vm::ast::ContractAST;
use crate::vm::contexts::Environment;
use crate::vm::contexts::{AssetMap, OwnedEnvironment};
use crate::vm::costs::ExecutionCost;
use crate::vm::costs::LimitedCostTracker;
use crate::vm::database::ClarityDatabase;
use crate::vm::errors::Error as InterpreterError;
use crate::vm::events::StacksTransactionEvent;
use crate::vm::types::{BuffData, PrincipalData, QualifiedContractIdentifier};
use crate::vm::ClarityVersion;
use crate::vm::ContractContext;
use crate::vm::{ast, SymbolicExpression, Value};
use stacks_common::types::StacksEpochId;
use std::fmt;
#[derive(Debug)]
pub enum Error {
Analysis(CheckError),
Parse(ParseError),
Interpreter(InterpreterError),
BadTransaction(String),
CostError(ExecutionCost, ExecutionCost),
AbortedByCallback(Option<Value>, AssetMap, Vec<StacksTransactionEvent>),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::CostError(ref a, ref b) => {
write!(f, "Cost Error: {} cost exceeded budget of {} cost", a, b)
}
Error::Analysis(ref e) => fmt::Display::fmt(e, f),
Error::Parse(ref e) => fmt::Display::fmt(e, f),
Error::AbortedByCallback(..) => write!(f, "Post condition aborted transaction"),
Error::Interpreter(ref e) => fmt::Display::fmt(e, f),
Error::BadTransaction(ref s) => fmt::Display::fmt(s, f),
}
}
}
impl std::error::Error for Error {
fn cause(&self) -> Option<&dyn std::error::Error> {
match *self {
Error::CostError(ref _a, ref _b) => None,
Error::AbortedByCallback(..) => None,
Error::Analysis(ref e) => Some(e),
Error::Parse(ref e) => Some(e),
Error::Interpreter(ref e) => Some(e),
Error::BadTransaction(ref _s) => None,
}
}
}
impl From<CheckError> for Error {
fn from(e: CheckError) -> Self {
match e.err {
CheckErrors::CostOverflow => {
Error::CostError(ExecutionCost::max_value(), ExecutionCost::max_value())
}
CheckErrors::CostBalanceExceeded(a, b) => Error::CostError(a, b),
CheckErrors::MemoryBalanceExceeded(_a, _b) => {
Error::CostError(ExecutionCost::max_value(), ExecutionCost::max_value())
}
_ => Error::Analysis(e),
}
}
}
impl From<InterpreterError> for Error {
fn from(e: InterpreterError) -> Self {
match &e {
InterpreterError::Unchecked(CheckErrors::CostBalanceExceeded(a, b)) => {
Error::CostError(a.clone(), b.clone())
}
InterpreterError::Unchecked(CheckErrors::CostOverflow) => {
Error::CostError(ExecutionCost::max_value(), ExecutionCost::max_value())
}
_ => Error::Interpreter(e),
}
}
}
impl From<ParseError> for Error {
fn from(e: ParseError) -> Self {
match e.err {
ParseErrors::CostOverflow => {
Error::CostError(ExecutionCost::max_value(), ExecutionCost::max_value())
}
ParseErrors::CostBalanceExceeded(a, b) => Error::CostError(a, b),
ParseErrors::MemoryBalanceExceeded(_a, _b) => {
Error::CostError(ExecutionCost::max_value(), ExecutionCost::max_value())
}
_ => Error::Parse(e),
}
}
}
pub trait ClarityConnection {
fn with_clarity_db_readonly_owned<F, R>(&mut self, to_do: F) -> R
where
F: FnOnce(ClarityDatabase) -> (R, ClarityDatabase);
fn with_analysis_db_readonly<F, R>(&mut self, to_do: F) -> R
where
F: FnOnce(&mut AnalysisDatabase) -> R;
fn get_epoch(&self) -> StacksEpochId;
fn with_clarity_db_readonly<F, R>(&mut self, to_do: F) -> R
where
F: FnOnce(&mut ClarityDatabase) -> R,
{
self.with_clarity_db_readonly_owned(|mut db| (to_do(&mut db), db))
}
fn with_readonly_clarity_env<F, R>(
&mut self,
mainnet: bool,
chain_id: u32,
clarity_version: ClarityVersion,
sender: PrincipalData,
sponsor: Option<PrincipalData>,
cost_track: LimitedCostTracker,
to_do: F,
) -> Result<R, InterpreterError>
where
F: FnOnce(&mut Environment) -> Result<R, InterpreterError>,
{
let epoch_id = self.get_epoch();
self.with_clarity_db_readonly_owned(|clarity_db| {
let initial_context =
ContractContext::new(QualifiedContractIdentifier::transient(), clarity_version);
let mut vm_env = OwnedEnvironment::new_cost_limited(
mainnet, chain_id, clarity_db, cost_track, epoch_id,
);
let result = vm_env
.execute_in_env(sender, sponsor, Some(initial_context), to_do)
.map(|(result, _, _)| result);
let (db, _) = vm_env
.destruct()
.expect("Failed to recover database reference after executing transaction");
(result, db)
})
}
}
pub trait TransactionConnection: ClarityConnection {
fn with_abort_callback<F, A, R, E>(
&mut self,
to_do: F,
abort_call_back: A,
) -> Result<(R, AssetMap, Vec<StacksTransactionEvent>, bool), E>
where
A: FnOnce(&AssetMap, &mut ClarityDatabase) -> bool,
F: FnOnce(&mut OwnedEnvironment) -> Result<(R, AssetMap, Vec<StacksTransactionEvent>), E>;
fn with_analysis_db<F, R>(&mut self, to_do: F) -> R
where
F: FnOnce(&mut AnalysisDatabase, LimitedCostTracker) -> (LimitedCostTracker, R);
fn analyze_smart_contract(
&mut self,
identifier: &QualifiedContractIdentifier,
clarity_version: ClarityVersion,
contract_content: &str,
ast_rules: ASTRules,
) -> Result<(ContractAST, ContractAnalysis), Error> {
let epoch_id = self.get_epoch();
self.with_analysis_db(|db, mut cost_track| {
let ast_result = ast::build_ast_with_rules(
identifier,
contract_content,
&mut cost_track,
clarity_version,
epoch_id,
ast_rules,
);
let mut contract_ast = match ast_result {
Ok(x) => x,
Err(e) => return (cost_track, Err(e.into())),
};
let result = analysis::run_analysis(
identifier,
&mut contract_ast.expressions,
db,
false,
cost_track,
epoch_id,
clarity_version,
);
match result {
Ok(mut contract_analysis) => {
let cost_track = contract_analysis.take_contract_cost_tracker();
(cost_track, Ok((contract_ast, contract_analysis)))
}
Err((e, cost_track)) => (cost_track, Err(e.into())),
}
})
}
fn save_analysis(
&mut self,
identifier: &QualifiedContractIdentifier,
contract_analysis: &ContractAnalysis,
) -> Result<(), CheckError> {
self.with_analysis_db(|db, cost_tracker| {
db.begin();
let result = db.insert_contract(identifier, contract_analysis);
match result {
Ok(_) => {
db.commit();
(cost_tracker, Ok(()))
}
Err(e) => {
db.roll_back();
(cost_tracker, Err(e))
}
}
})
}
fn run_stx_transfer(
&mut self,
from: &PrincipalData,
to: &PrincipalData,
amount: u128,
memo: &BuffData,
) -> Result<(Value, AssetMap, Vec<StacksTransactionEvent>), Error> {
self.with_abort_callback(
|vm_env| {
vm_env
.stx_transfer(from, to, amount, memo)
.map_err(Error::from)
},
|_, _| false,
)
.and_then(|(value, assets, events, _)| Ok((value, assets, events)))
}
fn run_contract_call<F>(
&mut self,
sender: &PrincipalData,
sponsor: Option<&PrincipalData>,
contract: &QualifiedContractIdentifier,
public_function: &str,
args: &[Value],
abort_call_back: F,
) -> Result<(Value, AssetMap, Vec<StacksTransactionEvent>), Error>
where
F: FnOnce(&AssetMap, &mut ClarityDatabase) -> bool,
{
let expr_args: Vec<_> = args
.iter()
.map(|x| SymbolicExpression::atom_value(x.clone()))
.collect();
self.with_abort_callback(
|vm_env| {
vm_env
.execute_transaction(
sender.clone(),
sponsor.cloned(),
contract.clone(),
public_function,
&expr_args,
)
.map_err(Error::from)
},
abort_call_back,
)
.and_then(|(value, assets, events, aborted)| {
if aborted {
Err(Error::AbortedByCallback(Some(value), assets, events))
} else {
Ok((value, assets, events))
}
})
}
fn initialize_smart_contract<F>(
&mut self,
identifier: &QualifiedContractIdentifier,
clarity_version: ClarityVersion,
contract_ast: &ContractAST,
contract_str: &str,
sponsor: Option<PrincipalData>,
abort_call_back: F,
) -> Result<(AssetMap, Vec<StacksTransactionEvent>), Error>
where
F: FnOnce(&AssetMap, &mut ClarityDatabase) -> bool,
{
let (_, asset_map, events, aborted) = self.with_abort_callback(
|vm_env| {
vm_env
.initialize_contract_from_ast(
identifier.clone(),
clarity_version,
contract_ast,
contract_str,
sponsor,
)
.map_err(Error::from)
},
abort_call_back,
)?;
if aborted {
Err(Error::AbortedByCallback(None, asset_map, events))
} else {
Ok((asset_map, events))
}
}
}