Skip to content

Feat/clarity cli execute json #2587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
199 changes: 187 additions & 12 deletions src/clarity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use std::path::PathBuf;
use std::process;

use util::log;
use util::hash::hex_bytes;

use chainstate::burn::BlockHeaderHash;
use chainstate::stacks::index::{storage::TrieFileStorage, MarfTrieId};
Expand All @@ -37,7 +38,7 @@ use rusqlite::{Connection, OpenFlags, NO_PARAMS};

use util::db::FromColumn;

use util::hash::Sha512Trunc256Sum;
use util::hash::{bytes_to_hex, Sha512Trunc256Sum};

use vm::analysis;
use vm::analysis::contract_interface_builder::build_contract_interface;
Expand All @@ -56,10 +57,14 @@ use vm::{execute as vm_execute, SymbolicExpression, SymbolicExpressionType, Valu
use address::c32::c32_address;

use burnchains::BurnchainHeaderHash;
use burnchains::Txid;
use chainstate::burn::VRFSeed;
use chainstate::stacks::StacksAddress;

use serde::Serialize;
use serde_json::json;

use net::StacksMessageCodec;

use crate::vm::database::marf::WritableMarfStore;

Expand All @@ -82,17 +87,19 @@ fn print_usage(invoked_by: &str) {
"Usage: {} [command]
where command is one of:

initialize to initialize a local VM state database.
check to typecheck a potential contract definition.
launch to launch a initialize a new contract in the local state database.
eval to evaluate (in read-only mode) a program in a given contract context.
eval_at_chaintip like `eval`, but does not advance to a new block.
eval_at_block like `eval_at_chaintip`, but accepts a index-block-hash to evaluate at,
must be passed eval string via stdin.
eval_raw to typecheck and evaluate an expression without a contract or database context.
repl to typecheck and evaluate expressions in a stdin/stdout loop.
execute to execute a public function of a defined contract.
generate_address to generate a random Stacks public address for testing purposes.
initialize to initialize a local VM state database.
check to typecheck a potential contract definition.
launch to launch a initialize a new contract in the local state database.
eval to evaluate (in read-only mode) a program in a given contract context.
eval_at_chaintip like `eval`, but does not advance to a new block.
eval_at_chaintip_json like `eval_at_chaintip`, but outputs JSON
eval_at_block like `eval_at_chaintip`, but accepts a index-block-hash to evaluate at,
must be passed eval string via stdin.
eval_raw to typecheck and evaluate an expression without a contract or database context.
repl to typecheck and evaluate expressions in a stdin/stdout loop.
execute to execute a public function of a defined contract.
execute_json to run `execute`, but outputs JSON
generate_address to generate a random Stacks public address for testing purposes.
",
invoked_by
);
Expand Down Expand Up @@ -824,6 +831,55 @@ pub fn invoke_command(invoked_by: &str, args: &[String]) {
}
}
}
"eval_at_chaintip_json" => {
let evalInput = get_eval_input(invoked_by, args);
let vm_filename = if args.len() == 3 { &args[2] } else { &args[3] };
let marf_kv = friendly_expect(
MarfedKV::open(vm_filename, None),
"Failed to open VM database.",
);
let header_db = CLIHeadersDB::new(&vm_filename);
let result = at_chaintip(vm_filename, marf_kv, |mut marf| {
let result = {
let db = marf.as_clarity_db(&header_db, &NULL_BURN_STATE_DB);
let mut vm_env = OwnedEnvironment::new_cost_limited(
false,
db,
LimitedCostTracker::new_free(),
);
vm_env
.get_exec_environment(None)
.eval_read_only(&evalInput.contract_identifier, &evalInput.content)
};
(marf, result)
});

match result {
Ok(x) => {
let result_raw = {
let bytes = x.serialize_to_vec();
bytes_to_hex(&bytes)
};
println!(
"{}",
json!({
"success": true,
"result_raw": result_raw,
})
);
}
Err(error) => {
println!(
"{}",
json!({
"success": false,
"error": format!("{}", error),
})
);
panic_test!();
}
}
}
"eval_at_block" => {
if args.len() != 4 {
eprintln!(
Expand Down Expand Up @@ -1033,6 +1089,112 @@ pub fn invoke_command(invoked_by: &str, args: &[String]) {
}
}
}
"execute_json" => {
if args.len() < 5 {
eprintln!("Usage: {} {} [vm-state.db] [contract-identifier] [public-function-name] [sender-address] [args...]", invoked_by, args[0]);
panic_test!();
}
let vm_filename = &args[1];
let marf_kv = friendly_expect(
MarfedKV::open(vm_filename, None),
"Failed to open VM database.",
);
let header_db = CLIHeadersDB::new(&vm_filename);

let contract_identifier = friendly_expect(
QualifiedContractIdentifier::parse(&args[2]),
"Failed to parse contract identifier.",
);

let tx_name = &args[3];
let sender_in = &args[4];

let sender = {
if let Ok(sender) = PrincipalData::parse_standard_principal(sender_in) {
PrincipalData::Standard(sender)
} else {
eprintln!("Unexpected result parsing sender: {}", sender_in);
panic_test!();
}
};

let arguments: Vec<_> = args[5..]
.iter()
.map(|argument| {
let argument_parsed = friendly_expect(
vm_execute(argument),
&format!("Error parsing argument \"{}\"", argument),
);
let argument_value = friendly_expect_opt(
argument_parsed,
&format!("Failed to parse a value from the argument: {}", argument),
);
SymbolicExpression::atom_value(argument_value)
})
.collect();

let result = in_block(vm_filename, marf_kv, |mut marf| {
let result = {
let db = marf.as_clarity_db(&header_db, &NULL_BURN_STATE_DB);
let mut vm_env = OwnedEnvironment::new_cost_limited(
false,
db,
LimitedCostTracker::new_free(),
);
vm_env.execute_transaction(sender, contract_identifier, &tx_name, &arguments)
};
(marf, result)
});

match result {
Ok((x, _, events)) => {
if let Value::Response(data) = x {
let result_raw = {
let bytes = (*data.data).serialize_to_vec();
bytes_to_hex(&bytes)
};
if data.committed {
let txid = Txid::from_bytes(
&hex_bytes("1bfa831b5fc56c858198acb8e77e5863c1e9d8ac26d49ddb914e24d8d4083562")
.unwrap());
let serialized_events: Vec<serde_json::Value> = events
.iter()
.enumerate()
.map(|(index, event)| {
event.json_serialize(index, &txid.unwrap(), data.committed)
})
.collect();

println!(
"{}",
json!({
"result_raw": result_raw,
"events": serialized_events,
"success": true,
})
);
} else {
println!(
"{}",
json!({
"success": false,
"result_raw": result_raw,
})
);
}
} else {
panic!(format!(
"Expected a ResponseType result from transaction. Found: {}",
x
));
}
}
Err(error) => {
eprintln!("Transaction execution error: \n{}", error);
panic_test!();
}
}
}
_ => print_usage(invoked_by),
}
}
Expand Down Expand Up @@ -1161,6 +1323,19 @@ mod test {
],
);

eprintln!("execute_json tokens");
invoke_command(
"test",
&[
"execute_json".to_string(),
db_name.clone(),
"S1G2081040G2081040G2081040G208105NK8PE5.tokens".to_string(),
"mint!".to_string(),
"SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR".to_string(),
"(+ u900 u100)".to_string(),
],
);

eprintln!("eval tokens");
invoke_command(
"test",
Expand Down