Skip to content

feat: add --hex-file <file_path> option to blockstack-cli contract-call #6195

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

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE

- Added a new RPC endpoint `/v3/health` to query the node's health status. The endpoint returns a 200 status code with relevant synchronization information (including the node's current Stacks tip height, the maximum Stacks tip height among its neighbors, and the difference between these two). A user can use the `difference_from_max_peer` value to decide what is a good threshold for them before considering the node out of sync. The endpoint returns a 500 status code if the query cannot retrieve viable data.

- Added a new option `--hex-file <file_path>` to `blockstack-cli contract-call` command, that allows to pass a serialized Clarity value by file.

### Changed

- Changed default mempool walk strategy to `NextNonceWithHighestFeeRate`
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions stackslib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ rstest_reuse = "0.5.0"
mutants = "0.0.3"
rlimit = "0.10.2"
chrono = "0.4.19"
tempfile = "3.3"

[features]
default = []
Expand Down
121 changes: 116 additions & 5 deletions stackslib/src/blockstack_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,18 @@ Arguments are supplied in one of two ways: through script evaluation or via hex
of the value serialization format. The method for supplying arguments is chosen by
prefacing each argument with a flag:

-e indicates the argument should be _evaluated_
-x indicates the argument that a serialized Clarity value is being passed (hex-serialized)
-e indicates the argument should be _evaluated_
-x indicates the argument that a serialized Clarity value is being passed (hex-serialized)
--hex-file <file_path> same as `-x`, but reads the serialized Clarity value from a file

e.g.,

blockstack-cli contract-call $secret_key 10 0 SPJT598WY1RJN792HRKRHRQYFB7RJ5ZCG6J6GEZ4 foo-contract \\
transfer-fookens -e \\'SPJT598WY1RJN792HRKRHRQYFB7RJ5ZCG6J6GEZ4 \\
-e \"(+ 1 2)\" \\
-x 0000000000000000000000000000000001 \\
-x 050011deadbeef11ababffff11deadbeef11ababffff
-x 050011deadbeef11ababffff11deadbeef11ababffff \\
--hex-file /path/to/value.hex
";

const TOKEN_TRANSFER_USAGE: &str = "blockstack-cli (options) token-transfer [origin-secret-key-hex] [fee-rate] [nonce] [recipient-address] [amount] [memo] [args...]
Expand Down Expand Up @@ -453,7 +455,7 @@ fn handle_contract_call(

if val_args.len() % 2 != 0 {
return Err(
"contract-call arguments must be supplied as a list of `-e ...` or `-x 0000...` pairs"
"contract-call arguments must be supplied as a list of `-e ...` or `-x 0000...` or `--hex-file <file_path>` pairs"
.into(),
);
}
Expand All @@ -471,8 +473,16 @@ fn handle_contract_call(
vm_execute(input, clarity_version)?
.ok_or("Supplied argument did not evaluate to a Value")?
},
"--hex-file" => {
let content = fs::read_to_string(input)
.map_err(|e| {
let err_msg = format!("Cannot read file: {input}. Reason: {e}");
CliError::Message(err_msg)
})?;
Value::try_deserialize_hex_untyped(&content)?
}
_ => {
return Err("contract-call arguments must be supplied as a list of `-e ...` or `-x 0000...` pairs".into())
return Err("contract-call arguments must be supplied as a list of `-e ...` or `-x 0000...` or `--hex-file <file_path>` pairs".into())
}
};

Expand Down Expand Up @@ -897,6 +907,7 @@ fn main_handler(mut argv: Vec<String>) -> Result<String, CliError> {
#[cfg(test)]
mod test {
use stacks_common::util::cargo_workspace;
use tempfile::NamedTempFile;

use super::*;

Expand Down Expand Up @@ -1184,6 +1195,106 @@ mod test {
);
}

#[test]
fn test_contract_call_with_serialized_arg_from_file_ok() {
let mut file = NamedTempFile::new().expect("Cannot create tempfile!");
write!(file, "0000000000000000000000000000000001").expect("Cannot Write to temp file");

let file_path = file.path().to_str().unwrap();
let cc_args = [
"contract-call",
"043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3",
"1",
"0",
"SPJT598WY1RJN792HRKRHRQYFB7RJ5ZCG6J6GEZ4",
"foo-contract",
"transfer-fookens",
"--hex-file",
file_path,
];

let result = main_handler(to_string_vec(&cc_args));
assert!(result.is_ok(), "Result should be ok!");

let expected_tx = "0000000001040021a3c334fc0ee50359353799e8b2605ac6be1fe400000000000000000000000000000001010011db0868db0cd44c463b3a8a8b3b428ddaad15661e7b7d8c92c814c142c526e30abffe74e1e098f517037a1ee74969f4db27630407f4c958cb0d6e1d7485fe06030200000000021625a2a51cf0712a9d228e2788e2fe7acf8917ec810c666f6f2d636f6e7472616374107472616e736665722d666f6f6b656e73000000010000000000000000000000000000000001";
assert_eq!(expected_tx, result.unwrap());
}

#[test]
fn test_contract_call_with_serialized_arg_from_file_fails_due_to_file() {
let file_path = "/tmp/this-file-not-exists";
let cc_args = [
"contract-call",
"043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3",
"1",
"0",
"SPJT598WY1RJN792HRKRHRQYFB7RJ5ZCG6J6GEZ4",
"foo-contract",
"transfer-fookens",
"--hex-file",
file_path,
];

let result = main_handler(to_string_vec(&cc_args));
assert!(result.is_err(), "Result should be err!");

let expected_msg = format!("Cannot read file: {}. Reason: ", file_path);
assert!(result.unwrap_err().to_string().starts_with(&expected_msg));
}

#[test]
fn test_contract_call_with_serialized_arg_from_file_fails_due_to_bad_hex() {
let mut file = NamedTempFile::new().expect("Cannot create tempfile!");
// Bad hex string but (good except for the \n)
write!(file, "0000000000000000000000000000000001\n").expect("Cannot Write to temp file");
let file_path = file.path().to_str().unwrap();

let cc_args = [
"contract-call",
"043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3",
"1",
"0",
"SPJT598WY1RJN792HRKRHRQYFB7RJ5ZCG6J6GEZ4",
"foo-contract",
"transfer-fookens",
"--hex-file",
&file_path,
];

let result = main_handler(to_string_vec(&cc_args));
assert!(result.is_err(), "Result should be err!");

let expected_msg = "Failed to deserialize: Deserialization error: Bad hex string";
assert_eq!(expected_msg, result.unwrap_err().to_string());
}

#[test]
fn test_contract_call_with_serialized_arg_from_file_fails_due_to_short_buffer() {
let mut file = NamedTempFile::new().expect("Cannot create tempfile!");
// hex buffer is short
write!(file, "0101").expect("Cannot Write to temp file");
let file_path = file.path().to_str().unwrap();

let cc_args = [
"contract-call",
"043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3",
"1",
"0",
"SPJT598WY1RJN792HRKRHRQYFB7RJ5ZCG6J6GEZ4",
"foo-contract",
"transfer-fookens",
"--hex-file",
&file_path,
];

let result = main_handler(to_string_vec(&cc_args));
assert!(result.is_err(), "Result should be err!");

let expected_msg =
"Failed to deserialize: Serialization error caused by IO: failed to fill whole buffer";
assert_eq!(expected_msg, result.unwrap_err().to_string());
}

#[test]
fn simple_addresses() {
let addr_args = [
Expand Down
Loading