diff --git a/components/clarity-repl/src/repl/logger_hook.rs b/components/clarity-repl/src/repl/logger_hook.rs new file mode 100644 index 000000000..8f757d594 --- /dev/null +++ b/components/clarity-repl/src/repl/logger_hook.rs @@ -0,0 +1,59 @@ +use clarity::vm::errors::Error; +use clarity::vm::functions::NativeFunctions; +use clarity::vm::representations::Span; +use clarity::vm::types::QualifiedContractIdentifier; +use clarity::vm::{ + contexts::{Environment, LocalContext}, + types::Value, + EvalHook, ExecutionResult, SymbolicExpression, + SymbolicExpressionType::List, +}; + +pub struct ContractLog { + contract_id: QualifiedContractIdentifier, + span: Span, + result: Value, +} + +#[derive(Default)] +pub struct LoggerHook { + logs: Vec, +} + +impl LoggerHook { + pub fn new() -> Self { + LoggerHook::default() + } +} + +impl EvalHook for LoggerHook { + fn will_begin_eval(&mut self, _: &mut Environment, _: &LocalContext, _: &SymbolicExpression) {} + + fn did_finish_eval( + &mut self, + env: &mut Environment, + _context: &LocalContext, + expr: &SymbolicExpression, + res: &Result, + ) { + if let List(list) = &expr.expr { + if let Some((function_name, _args)) = list.split_first() { + if let Some(function_name) = function_name.match_atom() { + if let Some(NativeFunctions::Print) = + NativeFunctions::lookup_by_name(function_name) + { + if let Ok(value) = res { + self.logs.push(ContractLog { + contract_id: env.contract_context.contract_identifier.clone(), + span: expr.span.clone(), + result: value.clone(), + }); + } + } + } + } + } + } + + fn did_complete(&mut self, _: Result<&mut ExecutionResult, String>) {} +} diff --git a/components/clarity-repl/src/repl/mod.rs b/components/clarity-repl/src/repl/mod.rs index 255bc40a5..913e4a851 100644 --- a/components/clarity-repl/src/repl/mod.rs +++ b/components/clarity-repl/src/repl/mod.rs @@ -3,6 +3,7 @@ pub mod clarity_values; pub mod datastore; pub mod diagnostic; pub mod interpreter; +pub mod logger_hook; pub mod session; pub mod settings; pub mod tracer; diff --git a/components/clarity-repl/src/repl/session.rs b/components/clarity-repl/src/repl/session.rs index f00a0fced..c50295023 100644 --- a/components/clarity-repl/src/repl/session.rs +++ b/components/clarity-repl/src/repl/session.rs @@ -1,5 +1,6 @@ use super::boot::{STACKS_BOOT_CODE_MAINNET, STACKS_BOOT_CODE_TESTNET}; use super::diagnostic::output_diagnostic; +use super::logger_hook::LoggerHook; use super::{ClarityCodeSource, ClarityContract, ClarityInterpreter, ContractDeployer}; use crate::analysis::coverage::CoverageHook; use crate::repl::clarity_values::value_to_string; @@ -1802,3 +1803,56 @@ mod tests { assert!(time_block_2 - time_block_1 == 600); } } +#[cfg(test)] +mod logger_hook_tests { + + use crate::{repl::DEFAULT_EPOCH, test_fixtures::clarity_contract::ClarityContractBuilder}; + + use super::*; + + #[test] + fn can_retrieve_print_values() { + let settings = SessionSettings::default(); + let mut session = Session::new(settings); + session.start().expect("session could not start"); + session.update_epoch(DEFAULT_EPOCH); + + // session.deploy_contract(contract, eval_hooks, cost_track, test_name, ast) + let snippet = [ + "(define-public (print-and-return (input (response uint uint)))", + " (begin", + " (match input x (print x) y (print y))", + " input", + " )", + ")", + ] + .join("\n"); + + let contract = ClarityContractBuilder::new().code_source(snippet).build(); + + let _ = session.deploy_contract(&contract, false, None); + let arg = SymbolicExpression::atom_value(Value::okay(Value::UInt(42)).unwrap()); + let res = session.call_contract_fn( + "contract", + "print-and-return", + &[arg], + &session.get_tx_sender(), + false, + false, + ); + + println!("{:?}", res); + + let arg = SymbolicExpression::atom_value(Value::error(Value::UInt(404)).unwrap()); + let res = session.call_contract_fn( + "contract", + "print-and-return", + &[arg], + &session.get_tx_sender(), + false, + false, + ); + + println!("{:?}", res); + } +}