From fbc88b83099558421aea6f62296d890c1c2c82df Mon Sep 17 00:00:00 2001 From: alnoki <43892045+alnoki@users.noreply.github.com> Date: Fri, 26 May 2023 19:41:15 -0500 Subject: [PATCH] [CLI] Add multisig governance tooling, docs (#8346) --- Cargo.lock | 1 + aptos-move/move-examples/cli_args/Move.toml | 8 +- .../cli_args/entry_function_arguments.json | 30 + .../cli_args/script_function_arguments.json | 20 + .../cli_args/scripts/set_vals.move | 20 + .../cli_args/sources/cli_args.move | 71 +- .../cli_args/view_function_arguments.json | 13 + crates/aptos/CHANGELOG.md | 8 + crates/aptos/Cargo.toml | 1 + crates/aptos/src/account/mod.rs | 4 + crates/aptos/src/account/multisig_account.rs | 184 ++- crates/aptos/src/common/types.rs | 304 ++++- crates/aptos/src/common/utils.rs | 47 +- crates/aptos/src/move_tool/mod.rs | 235 ++-- crates/aptos/src/test/mod.rs | 53 +- .../docs/move/move-on-aptos/cli.md | 1128 +++++++++++++++++ .../tools/aptos-cli-tool/use-aptos-cli.md | 52 +- developer-docs-site/package.json | 1 + developer-docs-site/pnpm-lock.yaml | 42 +- .../scripts/additional_dict.txt | 4 + 20 files changed, 1947 insertions(+), 279 deletions(-) create mode 100644 aptos-move/move-examples/cli_args/entry_function_arguments.json create mode 100644 aptos-move/move-examples/cli_args/script_function_arguments.json create mode 100644 aptos-move/move-examples/cli_args/scripts/set_vals.move create mode 100644 aptos-move/move-examples/cli_args/view_function_arguments.json diff --git a/Cargo.lock b/Cargo.lock index eb90fd7843b54..07a5017c92324 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,6 +201,7 @@ dependencies = [ "move-symbol-pool", "move-unit-test", "move-vm-runtime", + "once_cell", "rand 0.7.3", "regex", "reqwest", diff --git a/aptos-move/move-examples/cli_args/Move.toml b/aptos-move/move-examples/cli_args/Move.toml index c263bcb53d723..4135245a7f7fe 100644 --- a/aptos-move/move-examples/cli_args/Move.toml +++ b/aptos-move/move-examples/cli_args/Move.toml @@ -4,9 +4,7 @@ version = "0.1.0" upgrade_policy = "compatible" [addresses] -deploy_address = "_" -std = "0x1" -aptos_framework = "0x1" +test_account = "_" -[dependencies] -AptosFramework = { local = "../../framework/aptos-framework" } \ No newline at end of file +[dependencies.AptosFramework] +local = "../../framework/aptos-framework" \ No newline at end of file diff --git a/aptos-move/move-examples/cli_args/entry_function_arguments.json b/aptos-move/move-examples/cli_args/entry_function_arguments.json new file mode 100644 index 0000000000000..e37dc43edc1b1 --- /dev/null +++ b/aptos-move/move-examples/cli_args/entry_function_arguments.json @@ -0,0 +1,30 @@ +{ + "function_id": "::cli_args::set_vals", + "type_args": [ + "0x1::account::Account", + "0x1::chain_id::ChainId" + ], + "args": [ + { + "type": "u8", + "value": 123 + }, + { + "type": "bool", + "value": [false, true, false, false] + }, + { + "type": "address", + "value": [ + [ + "0xace", + "0xbee" + ], + [ + "0xcad" + ], + [] + ] + } + ] +} \ No newline at end of file diff --git a/aptos-move/move-examples/cli_args/script_function_arguments.json b/aptos-move/move-examples/cli_args/script_function_arguments.json new file mode 100644 index 0000000000000..b6e2914f0767b --- /dev/null +++ b/aptos-move/move-examples/cli_args/script_function_arguments.json @@ -0,0 +1,20 @@ +{ + "type_args": [ + "0x1::account::Account", + "0x1::chain_id::ChainId" + ], + "args": [ + { + "type": "u8", + "value": 123 + }, + { + "type": "u8", + "value": [122, 123, 124, 125] + }, + { + "type": "address", + "value": "0xace" + } + ] +} \ No newline at end of file diff --git a/aptos-move/move-examples/cli_args/scripts/set_vals.move b/aptos-move/move-examples/cli_args/scripts/set_vals.move new file mode 100644 index 0000000000000..b4db341a75e69 --- /dev/null +++ b/aptos-move/move-examples/cli_args/scripts/set_vals.move @@ -0,0 +1,20 @@ +// :!:>script +script { + use test_account::cli_args; + use std::vector; + + /// Get a `bool` vector where each element indicates `true` if the + /// corresponding element in `u8_vec` is greater than `u8_solo`. + /// Then pack `address_solo` in a `vector>` and + /// pass resulting argument set to public entry function. + fun set_vals( + account: signer, + u8_solo: u8, + u8_vec: vector, + address_solo: address, + ) { + let bool_vec = vector::map_ref(&u8_vec, |e_ref| *e_ref > u8_solo); + let addr_vec_vec = vector[vector[address_solo]]; + cli_args::set_vals(account, u8_solo, bool_vec, addr_vec_vec); + } +} // <:!:script diff --git a/aptos-move/move-examples/cli_args/sources/cli_args.move b/aptos-move/move-examples/cli_args/sources/cli_args.move index eea135dcdf264..2995f2a0894b7 100644 --- a/aptos-move/move-examples/cli_args/sources/cli_args.move +++ b/aptos-move/move-examples/cli_args/sources/cli_args.move @@ -1,37 +1,64 @@ -module deploy_address::cli_args { +// :!:>resource +module test_account::cli_args { use std::signer; + use aptos_std::type_info::{Self, TypeInfo}; - struct Holder has key { + + struct Holder has key, drop { u8_solo: u8, bool_vec: vector, address_vec_vec: vector>, - } + type_info_1: TypeInfo, + type_info_2: TypeInfo, + } //<:!:resource - #[view] - public fun reveal(host: address): (u8, vector, vector>) acquires Holder { - let holder_ref = borrow_global(host); - (holder_ref.u8_solo, holder_ref.bool_vec, holder_ref.address_vec_vec) - } - - public entry fun set_vals( + // :!:>setter + /// Set values in a `Holder` under `account`. + public entry fun set_vals( account: signer, u8_solo: u8, bool_vec: vector, address_vec_vec: vector>, ) acquires Holder { let account_addr = signer::address_of(&account); - if (!exists(account_addr)) { - move_to(&account, Holder { - u8_solo, - bool_vec, - address_vec_vec, - }) - } else { - let old_holder = borrow_global_mut(account_addr); - old_holder.u8_solo = u8_solo; - old_holder.bool_vec = bool_vec; - old_holder.address_vec_vec = address_vec_vec; + if (exists(account_addr)) { + move_from(account_addr); + }; + move_to(&account, Holder { + u8_solo, + bool_vec, + address_vec_vec, + type_info_1: type_info::type_of(), + type_info_2: type_info::type_of(), + }); + } //<:!:setter + + // :!:>view + struct RevealResult has drop { + u8_solo: u8, + bool_vec: vector, + address_vec_vec: vector>, + type_info_1_match: bool, + type_info_2_match: bool + } + + #[view] + /// Pack into a `RevealResult` the first three fields in host's + /// `Holder`, as well as two `bool` flags denoting if `T1` & `T2` + /// respectively match `Holder.type_info_1` & `Holder.type_info_2`, + /// then return the `RevealResult`. + public fun reveal(host: address): RevealResult acquires Holder { + let holder_ref = borrow_global(host); + RevealResult { + u8_solo: holder_ref.u8_solo, + bool_vec: holder_ref.bool_vec, + address_vec_vec: holder_ref.address_vec_vec, + type_info_1_match: + type_info::type_of() == holder_ref.type_info_1, + type_info_2_match: + type_info::type_of() == holder_ref.type_info_2 } } -} + +} //<:!:view diff --git a/aptos-move/move-examples/cli_args/view_function_arguments.json b/aptos-move/move-examples/cli_args/view_function_arguments.json new file mode 100644 index 0000000000000..5a9ca89ede412 --- /dev/null +++ b/aptos-move/move-examples/cli_args/view_function_arguments.json @@ -0,0 +1,13 @@ +{ + "function_id": "::cli_args::reveal", + "type_args": [ + "0x1::account::Account", + "0x1::account::Account" + ], + "args": [ + { + "type": "address", + "value": "" + } + ] +} \ No newline at end of file diff --git a/crates/aptos/CHANGELOG.md b/crates/aptos/CHANGELOG.md index 0520e6003d819..c1f5b5db04ae5 100644 --- a/crates/aptos/CHANGELOG.md +++ b/crates/aptos/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to the Aptos CLI will be captured in this file. This project - Add nested vector arg support - Updated DB bootstrap command with new DB restore features +## [1.0.14] - 2023/05/25 + +### Added +- Recursive nested vector parsing +- Multisig v2 governance support +- JSON support for both input files and CLI argument input + - **Breaking change**: You can no longer pass in a vector like this: `--arg vector
:0x1,0x2`, you must do it like this: `--arg 'address:["0x1", "0x2"]'` + ## [1.0.13] - 2023/04/27 ### Fixed * Previously `--skip-fetch-latest-git-deps` would not actually do anything when used with `aptos move test`. This has been fixed. diff --git a/crates/aptos/Cargo.toml b/crates/aptos/Cargo.toml index ff499765eca2e..f601f45b0a484 100644 --- a/crates/aptos/Cargo.toml +++ b/crates/aptos/Cargo.toml @@ -69,6 +69,7 @@ move-prover-boogie-backend = { workspace = true } move-symbol-pool = { workspace = true } move-unit-test = { workspace = true, features = [ "debugging" ] } move-vm-runtime = { workspace = true, features = [ "testing" ] } +once_cell = { workspace = true } rand = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } diff --git a/crates/aptos/src/account/mod.rs b/crates/aptos/src/account/mod.rs index 988f065a3783f..2421a74327360 100644 --- a/crates/aptos/src/account/mod.rs +++ b/crates/aptos/src/account/mod.rs @@ -52,7 +52,9 @@ pub enum MultisigAccountTool { CreateTransaction(multisig_account::CreateTransaction), Execute(multisig_account::Execute), ExecuteReject(multisig_account::ExecuteReject), + ExecuteWithPayload(multisig_account::ExecuteWithPayload), Reject(multisig_account::Reject), + VerifyProposal(multisig_account::VerifyProposal), } impl MultisigAccountTool { @@ -63,7 +65,9 @@ impl MultisigAccountTool { MultisigAccountTool::CreateTransaction(tool) => tool.execute_serialized().await, MultisigAccountTool::Execute(tool) => tool.execute_serialized().await, MultisigAccountTool::ExecuteReject(tool) => tool.execute_serialized().await, + MultisigAccountTool::ExecuteWithPayload(tool) => tool.execute_serialized().await, MultisigAccountTool::Reject(tool) => tool.execute_serialized().await, + MultisigAccountTool::VerifyProposal(tool) => tool.execute_serialized().await, } } } diff --git a/crates/aptos/src/account/multisig_account.rs b/crates/aptos/src/account/multisig_account.rs index 0d60380afd8a9..b7a8ccf204fa0 100644 --- a/crates/aptos/src/account/multisig_account.rs +++ b/crates/aptos/src/account/multisig_account.rs @@ -1,13 +1,19 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::common::types::{ - CliCommand, CliTypedResult, EntryFunctionArguments, MultisigAccount, TransactionOptions, - TransactionSummary, +use crate::common::{ + types::{ + CliCommand, CliError, CliTypedResult, EntryFunctionArguments, MultisigAccount, + MultisigAccountWithSequenceNumber, TransactionOptions, TransactionSummary, + }, + utils::view_json_option_str, }; use aptos_cached_packages::aptos_stdlib; +use aptos_crypto::HashValue; use aptos_rest_client::{ - aptos_api_types::{WriteResource, WriteSetChange}, + aptos_api_types::{ + EntryFunctionId, HexEncodedBytes, ViewRequest, WriteResource, WriteSetChange, + }, Transaction, }; use aptos_types::{ @@ -17,7 +23,12 @@ use aptos_types::{ use async_trait::async_trait; use bcs::to_bytes; use clap::Parser; +use once_cell::sync::Lazy; use serde::Serialize; +use serde_json::json; + +static GET_TRANSACTION_ENTRY_FUNCTION: Lazy = + Lazy::new(|| "0x1::multisig_account::get_transaction".parse().unwrap()); /// Create a new multisig account (v2) on-chain. /// @@ -107,6 +118,9 @@ pub struct CreateTransaction { pub(crate) txn_options: TransactionOptions, #[clap(flatten)] pub(crate) entry_function_args: EntryFunctionArguments, + /// Pass this flag if only storing transaction hash on-chain. Else full payload is stored + #[clap(long)] + pub(crate) store_hash_only: bool, } #[async_trait] @@ -116,19 +130,101 @@ impl CliCommand for CreateTransaction { } async fn execute(self) -> CliTypedResult { - let payload = MultisigTransactionPayload::EntryFunction( - self.entry_function_args.create_entry_function_payload()?, - ); - self.txn_options - .submit_transaction(aptos_stdlib::multisig_account_create_transaction( + let multisig_transaction_payload_bytes = + to_bytes::(&self.entry_function_args.try_into()?)?; + let transaction_payload = if self.store_hash_only { + aptos_stdlib::multisig_account_create_transaction_with_hash( self.multisig_account.multisig_address, - to_bytes(&payload)?, - )) + HashValue::sha3_256_of(&multisig_transaction_payload_bytes).to_vec(), + ) + } else { + aptos_stdlib::multisig_account_create_transaction( + self.multisig_account.multisig_address, + multisig_transaction_payload_bytes, + ) + }; + self.txn_options + .submit_transaction(transaction_payload) .await .map(|inner| inner.into()) } } +/// Verify entry function matches on-chain transaction proposal. +#[derive(Debug, Parser)] +pub struct VerifyProposal { + #[clap(flatten)] + pub(crate) multisig_account_with_sequence_number: MultisigAccountWithSequenceNumber, + #[clap(flatten)] + pub(crate) txn_options: TransactionOptions, + #[clap(flatten)] + pub(crate) entry_function_args: EntryFunctionArguments, +} + +#[async_trait] +impl CliCommand for VerifyProposal { + fn command_name(&self) -> &'static str { + "VerifyProposalMultisig" + } + + async fn execute(self) -> CliTypedResult { + // Get multisig transaction via view function. + let multisig_transaction = &self + .txn_options + .view(ViewRequest { + function: GET_TRANSACTION_ENTRY_FUNCTION.clone(), + type_arguments: vec![], + arguments: vec![ + serde_json::Value::String(String::from( + &self + .multisig_account_with_sequence_number + .multisig_account + .multisig_address, + )), + serde_json::Value::String( + self.multisig_account_with_sequence_number + .sequence_number + .to_string(), + ), + ], + }) + .await?[0]; + // Get expected multisig transaction payload hash hex from provided entry function. + let expected_payload_hash = HashValue::sha3_256_of( + &to_bytes::(&self.entry_function_args.try_into()?)?, + ) + .to_hex_literal(); + // Get on-chain payload hash. If full payload provided on-chain: + let actual_payload_hash = + if let Some(actual_payload) = view_json_option_str(&multisig_transaction["payload"])? { + // Actual payload hash is the hash of the on-chain payload. + HashValue::sha3_256_of(actual_payload.parse::()?.inner()) + .to_hex_literal() + // If full payload not provided, get payload hash directly from transaction proposal: + } else { + view_json_option_str(&multisig_transaction["payload_hash"])?.ok_or( + CliError::UnexpectedError( + "Neither payload nor payload hash provided on-chain".to_string(), + ), + )? + }; + // Get verification result based on if expected and actual payload hashes match. + if expected_payload_hash.eq(&actual_payload_hash) { + Ok(json!({ + "Status": "Transaction match", + "Multisig transaction": multisig_transaction + })) + } else { + Err(CliError::UnexpectedError(format!( + "Transaction mismatch: The transaction you provided has a payload hash of \ + {expected_payload_hash}, but the on-chain transaction proposal you specified has \ + a payload hash of {actual_payload_hash}. For more info, see \ + https://aptos.dev/move/move-on-aptos/cli#multisig-governance" + ))) + } + } +} + /// Approve a multisig transaction. /// /// As one of the owners of the multisig, approve a transaction proposed for the multisig. @@ -137,11 +233,7 @@ impl CliCommand for CreateTransaction { #[derive(Debug, Parser)] pub struct Approve { #[clap(flatten)] - pub(crate) multisig_account: MultisigAccount, - /// The sequence number of the multisig transaction to approve. The sequence number increments - /// for every new multisig transaction. - #[clap(long)] - pub(crate) sequence_number: u64, + pub(crate) multisig_account_with_sequence_number: MultisigAccountWithSequenceNumber, #[clap(flatten)] pub(crate) txn_options: TransactionOptions, } @@ -155,8 +247,10 @@ impl CliCommand for Approve { async fn execute(self) -> CliTypedResult { self.txn_options .submit_transaction(aptos_stdlib::multisig_account_approve_transaction( - self.multisig_account.multisig_address, - self.sequence_number, + self.multisig_account_with_sequence_number + .multisig_account + .multisig_address, + self.multisig_account_with_sequence_number.sequence_number, )) .await .map(|inner| inner.into()) @@ -171,11 +265,7 @@ impl CliCommand for Approve { #[derive(Debug, Parser)] pub struct Reject { #[clap(flatten)] - pub(crate) multisig_account: MultisigAccount, - /// The sequence number of the multisig transaction to reject. The sequence number increments - /// for every new multisig transaction. - #[clap(long)] - pub(crate) sequence_number: u64, + pub(crate) multisig_account_with_sequence_number: MultisigAccountWithSequenceNumber, #[clap(flatten)] pub(crate) txn_options: TransactionOptions, } @@ -189,18 +279,17 @@ impl CliCommand for Reject { async fn execute(self) -> CliTypedResult { self.txn_options .submit_transaction(aptos_stdlib::multisig_account_reject_transaction( - self.multisig_account.multisig_address, - self.sequence_number, + self.multisig_account_with_sequence_number + .multisig_account + .multisig_address, + self.multisig_account_with_sequence_number.sequence_number, )) .await .map(|inner| inner.into()) } } -/// Execute a proposed multisig transaction. -/// -/// The transaction to be executed needs to have as many approvals as the number of signatures -/// required. +/// Execute a proposed multisig transaction that has a full payload stored on-chain. #[derive(Debug, Parser)] pub struct Execute { #[clap(flatten)] @@ -216,13 +305,38 @@ impl CliCommand for Execute { } async fn execute(self) -> CliTypedResult { - let payload = TransactionPayload::Multisig(Multisig { - multisig_address: self.multisig_account.multisig_address, - // TODO: Support passing an explicit payload - transaction_payload: None, - }); self.txn_options - .submit_transaction(payload) + .submit_transaction(TransactionPayload::Multisig(Multisig { + multisig_address: self.multisig_account.multisig_address, + transaction_payload: None, + })) + .await + .map(|inner| inner.into()) + } +} + +/// Execute a proposed multisig transaction that has only a payload hash stored on-chain. +#[derive(Debug, Parser)] +pub struct ExecuteWithPayload { + #[clap(flatten)] + pub(crate) execute: Execute, + #[clap(flatten)] + pub(crate) entry_function_args: EntryFunctionArguments, +} + +#[async_trait] +impl CliCommand for ExecuteWithPayload { + fn command_name(&self) -> &'static str { + "ExecuteWithPayloadMultisig" + } + + async fn execute(self) -> CliTypedResult { + self.execute + .txn_options + .submit_transaction(TransactionPayload::Multisig(Multisig { + multisig_address: self.execute.multisig_account.multisig_address, + transaction_payload: Some(self.entry_function_args.try_into()?), + })) .await .map(|inner| inner.into()) } diff --git a/crates/aptos/src/common/types.rs b/crates/aptos/src/common/types.rs index d955a257d9688..449459999fb8c 100644 --- a/crates/aptos/src/common/types.rs +++ b/crates/aptos/src/common/types.rs @@ -6,15 +6,17 @@ use crate::{ init::Network, utils::{ check_if_file_exists, create_dir_if_not_exist, dir_default_to_current, - get_account_with_state, get_auth_key, get_sequence_number, prompt_yes_with_override, - read_from_file, start_logger, to_common_result, to_common_success_result, - write_to_file, write_to_file_with_opts, write_to_user_only_file, + get_account_with_state, get_auth_key, get_sequence_number, parse_json_file, + prompt_yes_with_override, read_from_file, start_logger, to_common_result, + to_common_success_result, write_to_file, write_to_file_with_opts, + write_to_user_only_file, }, }, config::GlobalConfig, genesis::git::from_yaml, - move_tool::{ArgWithType, MemberId}, + move_tool::{ArgWithType, FunctionArgType, MemberId}, }; +use anyhow::Context; use aptos_crypto::{ ed25519::{Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature}, x25519, PrivateKey, ValidCryptoMaterial, ValidCryptoMaterialStringExt, @@ -25,7 +27,7 @@ use aptos_global_constants::adjust_gas_headroom; use aptos_keygen::KeyGen; use aptos_logger::Level; use aptos_rest_client::{ - aptos_api_types::{HashValue, MoveType, ViewRequest}, + aptos_api_types::{EntryFunctionId, HashValue, MoveType, ViewRequest}, error::RestError, Client, Transaction, }; @@ -33,8 +35,8 @@ use aptos_sdk::{transaction_builder::TransactionFactory, types::LocalAccount}; use aptos_types::{ chain_id::ChainId, transaction::{ - authenticator::AuthenticationKey, EntryFunction, SignedTransaction, TransactionPayload, - TransactionStatus, + authenticator::AuthenticationKey, EntryFunction, MultisigTransactionPayload, Script, + SignedTransaction, TransactionArgument, TransactionPayload, TransactionStatus, }, }; use async_trait::async_trait; @@ -844,7 +846,7 @@ pub struct SaveFile { } impl SaveFile { - /// Check if the key file exists already + /// Check if the `output_file` exists already pub fn check_file(&self) -> CliTypedResult<()> { check_if_file_exists(self.output_file.as_path(), self.prompt_options) } @@ -1679,8 +1681,63 @@ pub struct RotationProofChallenge { pub new_public_key: Vec, } +/// Common options for interactions with a multisig account. +#[derive(Clone, Debug, Parser, Serialize)] +pub struct MultisigAccount { + /// The address of the multisig account to interact with + #[clap(long, parse(try_from_str=crate::common::types::load_account_arg))] + pub(crate) multisig_address: AccountAddress, +} + +#[derive(Clone, Debug, Parser, Serialize)] +pub struct MultisigAccountWithSequenceNumber { + #[clap(flatten)] + pub(crate) multisig_account: MultisigAccount, + /// Multisig account sequence number to interact with + #[clap(long)] + pub(crate) sequence_number: u64, +} + +#[derive(Debug, Parser)] +pub struct TypeArgVec { + /// TypeTag arguments separated by spaces. + /// + /// Example: `u8 u16 u32 u64 u128 u256 bool address vector signer` + #[clap(long, multiple_values = true)] + pub(crate) type_args: Vec, +} + +impl TryFrom<&Vec> for TypeArgVec { + type Error = CliError; + + fn try_from(value: &Vec) -> Result { + let mut type_args = vec![]; + for string_ref in value { + type_args.push( + MoveType::from_str(string_ref) + .map_err(|err| CliError::UnableToParse("type argument", err.to_string()))?, + ); + } + Ok(TypeArgVec { type_args }) + } +} + +impl TryInto> for TypeArgVec { + type Error = CliError; + + fn try_into(self) -> Result, Self::Error> { + let mut type_tags: Vec = vec![]; + for type_arg in self.type_args { + type_tags.push( + TypeTag::try_from(type_arg) + .map_err(|err| CliError::UnableToParse("type argument", err.to_string()))?, + ); + } + Ok(type_tags) + } +} + #[derive(Debug, Parser)] -/// This is used for both entry functions and scripts. pub struct ArgWithTypeVec { /// Arguments combined with their type separated by spaces. /// @@ -1690,62 +1747,223 @@ pub struct ArgWithTypeVec { /// quotes based on your shell interpreter) /// /// Example: `address:0x1 bool:true u8:0 u256:1234 "bool:[true, false]" 'address:[["0xace", "0xbee"], []]'` - /// - /// Vector is wrapped in a reusable struct for uniform CLI documentation. #[clap(long, multiple_values = true)] pub(crate) args: Vec, } +impl TryFrom<&Vec> for ArgWithTypeVec { + type Error = CliError; + + fn try_from(value: &Vec) -> Result { + let mut args = vec![]; + for arg_json_ref in value { + let function_arg_type = FunctionArgType::from_str(&arg_json_ref.arg_type)?; + args.push(function_arg_type.parse_arg_json(&arg_json_ref.value)?); + } + Ok(ArgWithTypeVec { args }) + } +} + +impl TryInto> for ArgWithTypeVec { + type Error = CliError; + + fn try_into(self) -> Result, Self::Error> { + let mut args = vec![]; + for arg in self.args { + args.push( + (&arg) + .try_into() + .context(format!("Failed to parse arg {:?}", arg)) + .map_err(|err| CliError::CommandArgumentError(err.to_string()))?, + ); + } + Ok(args) + } +} + +impl TryInto>> for ArgWithTypeVec { + type Error = CliError; + + fn try_into(self) -> Result>, Self::Error> { + Ok(self + .args + .into_iter() + .map(|arg_with_type| arg_with_type.arg) + .collect()) + } +} + +impl TryInto> for ArgWithTypeVec { + type Error = CliError; + + fn try_into(self) -> Result, Self::Error> { + let mut args = vec![]; + for arg in self.args { + args.push(arg.to_json()?); + } + Ok(args) + } +} + /// Common options for constructing an entry function transaction payload. #[derive(Debug, Parser)] pub struct EntryFunctionArguments { /// Function name as `
::::` /// /// Example: `0x842ed41fad9640a2ad08fdd7d3e4f7f505319aac7d67e1c0dd6a7cce8732c7e3::message::set_message` - #[clap(long)] - pub function_id: MemberId, + #[clap(long, required_unless_present = "json-file")] + pub function_id: Option, + #[clap(flatten)] + pub(crate) type_arg_vec: TypeArgVec, #[clap(flatten)] pub(crate) arg_vec: ArgWithTypeVec, - /// TypeTag arguments separated by spaces. - /// - /// Example: `u8 u16 u32 u64 u128 u256 bool address vector signer` - #[clap(long, multiple_values = true)] - pub type_args: Vec, + /// JSON file specifying public entry function ID, type arguments, and arguments. + #[clap(long, parse(from_os_str), conflicts_with_all = &["function-id", "args", "type-args"])] + pub(crate) json_file: Option, } impl EntryFunctionArguments { - /// Construct and return an entry function payload from function_id, args, and type_args. - pub fn create_entry_function_payload(self) -> CliTypedResult { - let args: Vec> = self - .arg_vec - .args - .into_iter() - .map(|arg_with_type| arg_with_type.arg) - .collect(); - - let mut parsed_type_args: Vec = Vec::new(); - // These TypeArgs are used for generics - for type_arg in self.type_args.into_iter() { - let type_tag = TypeTag::try_from(type_arg.clone()) - .map_err(|err| CliError::UnableToParse("--type-args", err.to_string()))?; - parsed_type_args.push(type_tag) + /// Get instance as if all fields passed from command line, parsing JSON input file if needed. + fn check_input_style(self) -> CliTypedResult { + if let Some(json_path) = self.json_file { + Ok(parse_json_file::(&json_path)?.try_into()?) + } else { + Ok(self) } + } +} +impl TryInto for EntryFunctionArguments { + type Error = CliError; + + fn try_into(self) -> Result { + let entry_function_args = self.check_input_style()?; + let function_id: MemberId = (&entry_function_args).try_into()?; Ok(EntryFunction::new( - self.function_id.module_id, - self.function_id.member_id, - parsed_type_args, - args, + function_id.module_id, + function_id.member_id, + entry_function_args.type_arg_vec.try_into()?, + entry_function_args.arg_vec.try_into()?, )) } } -/// Common options for interactions with a multisig account. -#[derive(Clone, Debug, Parser, Serialize)] -pub struct MultisigAccount { - /// The address of the multisig account to interact with. - #[clap(long, parse(try_from_str=crate::common::types::load_account_arg))] - pub(crate) multisig_address: AccountAddress, +impl TryInto for EntryFunctionArguments { + type Error = CliError; + + fn try_into(self) -> Result { + Ok(MultisigTransactionPayload::EntryFunction(self.try_into()?)) + } +} + +impl TryInto for &EntryFunctionArguments { + type Error = CliError; + + fn try_into(self) -> Result { + self.function_id + .clone() + .ok_or(CliError::CommandArgumentError( + "No function ID provided".to_string(), + )) + } +} + +impl TryInto for EntryFunctionArguments { + type Error = CliError; + + fn try_into(self) -> Result { + let entry_function_args = self.check_input_style()?; + let function_id: MemberId = (&entry_function_args).try_into()?; + Ok(ViewRequest { + function: EntryFunctionId { + module: function_id.module_id.into(), + name: function_id.member_id.into(), + }, + type_arguments: entry_function_args.type_arg_vec.type_args, + arguments: entry_function_args.arg_vec.try_into()?, + }) + } +} + +/// Common options for constructing a script payload +#[derive(Debug, Parser)] +pub struct ScriptFunctionArguments { + #[clap(flatten)] + pub(crate) type_arg_vec: TypeArgVec, + #[clap(flatten)] + pub(crate) arg_vec: ArgWithTypeVec, + + /// JSON file specifying type arguments and arguments. + #[clap(long, parse(from_os_str), conflicts_with_all = &["args", "type-args"])] + pub(crate) json_file: Option, +} + +impl ScriptFunctionArguments { + /// Get instance as if all fields passed from command line, parsing JSON input file if needed. + fn check_input_style(self) -> CliTypedResult { + if let Some(json_path) = self.json_file { + Ok(parse_json_file::(&json_path)?.try_into()?) + } else { + Ok(self) + } + } + + pub fn create_script_payload(self, bytecode: Vec) -> CliTypedResult { + let script_function_args = self.check_input_style()?; + Ok(TransactionPayload::Script(Script::new( + bytecode, + script_function_args.type_arg_vec.try_into()?, + script_function_args.arg_vec.try_into()?, + ))) + } +} + +#[derive(Deserialize, Serialize)] +/// JSON file format for function arguments. +pub struct ArgWithTypeJSON { + #[serde(rename = "type")] + pub(crate) arg_type: String, + pub(crate) value: serde_json::Value, +} + +#[derive(Deserialize, Serialize)] +/// JSON file format for entry function arguments. +pub struct EntryFunctionArgumentsJSON { + pub(crate) function_id: String, + pub(crate) type_args: Vec, + pub(crate) args: Vec, +} + +impl TryInto for EntryFunctionArgumentsJSON { + type Error = CliError; + + fn try_into(self) -> Result { + Ok(EntryFunctionArguments { + function_id: Some(MemberId::from_str(&self.function_id)?), + type_arg_vec: TypeArgVec::try_from(&self.type_args)?, + arg_vec: ArgWithTypeVec::try_from(&self.args)?, + json_file: None, + }) + } +} + +#[derive(Deserialize)] +/// JSON file format for script function arguments. +struct ScriptFunctionArgumentsJSON { + type_args: Vec, + args: Vec, +} + +impl TryInto for ScriptFunctionArgumentsJSON { + type Error = CliError; + + fn try_into(self) -> Result { + Ok(ScriptFunctionArguments { + type_arg_vec: TypeArgVec::try_from(&self.type_args)?, + arg_vec: ArgWithTypeVec::try_from(&self.args)?, + json_file: None, + }) + } } diff --git a/crates/aptos/src/common/utils.rs b/crates/aptos/src/common/utils.rs index 84819cb49121d..8bacbd1b4d849 100644 --- a/crates/aptos/src/common/utils.rs +++ b/crates/aptos/src/common/utils.rs @@ -23,7 +23,7 @@ use aptos_types::{ use itertools::Itertools; use move_core_types::account_address::AccountAddress; use reqwest::Url; -use serde::Serialize; +use serde::{Deserialize, Serialize}; #[cfg(unix)] use std::os::unix::fs::OpenOptionsExt; use std::{ @@ -473,3 +473,48 @@ pub async fn profile_or_submit( .map(TransactionSummary::from) } } + +/// Try parsing JSON in file at path into a specified type. +pub fn parse_json_file Deserialize<'a>>(path_ref: &Path) -> CliTypedResult { + serde_json::from_slice::(&read_from_file(path_ref)?).map_err(|err| { + CliError::UnableToReadFile(format!("{}", path_ref.display()), err.to_string()) + }) +} + +/// Convert a view function JSON field into a string option. +/// +/// A view function JSON return represents an option via an inner JSON array titled `vec`. +pub fn view_json_option_str(option_ref: &serde_json::Value) -> CliTypedResult> { + if let Some(vec_field) = option_ref.get("vec") { + if let Some(vec_array) = vec_field.as_array() { + if vec_array.is_empty() { + Ok(None) + } else if vec_array.len() > 1 { + Err(CliError::UnexpectedError(format!( + "JSON `vec` array has more than one element: {:?}", + vec_array + ))) + } else { + let option_val_ref = &vec_array[0]; + if let Some(inner_str) = option_val_ref.as_str() { + Ok(Some(inner_str.to_string())) + } else { + Err(CliError::UnexpectedError(format!( + "JSON option is not a string: {}", + option_val_ref + ))) + } + } + } else { + Err(CliError::UnexpectedError(format!( + "JSON `vec` field is not an array: {}", + vec_field + ))) + } + } else { + Err(CliError::UnexpectedError(format!( + "JSON field does not have an inner `vec` field: {}", + option_ref + ))) + } +} diff --git a/crates/aptos/src/move_tool/mod.rs b/crates/aptos/src/move_tool/mod.rs index 0b135e3986a87..ca18b93ea491d 100644 --- a/crates/aptos/src/move_tool/mod.rs +++ b/crates/aptos/src/move_tool/mod.rs @@ -14,9 +14,10 @@ use crate::{ account::derive_resource_account::ResourceAccountSeed, common::{ types::{ - load_account_arg, ArgWithTypeVec, CliConfig, CliError, CliTypedResult, - ConfigSearchMode, EntryFunctionArguments, MoveManifestAccountWrapper, MovePackageDir, - ProfileOptions, PromptOptions, RestOptions, TransactionOptions, TransactionSummary, + load_account_arg, ArgWithTypeJSON, CliConfig, CliError, CliTypedResult, + ConfigSearchMode, EntryFunctionArguments, EntryFunctionArgumentsJSON, + MoveManifestAccountWrapper, MovePackageDir, ProfileOptions, PromptOptions, RestOptions, + SaveFile, ScriptFunctionArguments, TransactionOptions, TransactionSummary, }, utils::{ check_if_file_exists, create_dir_if_not_exist, dir_default_to_current, @@ -37,11 +38,13 @@ use aptos_framework::{ prover::ProverOptions, BuildOptions, BuiltPackage, }; use aptos_gas::{AbstractValueSizeGasParameters, NativeGasParameters}; -use aptos_rest_client::aptos_api_types::{EntryFunctionId, MoveType, ViewRequest}; +use aptos_rest_client::aptos_api_types::{ + EntryFunctionId, HexEncodedBytes, IdentifierWrapper, MoveModuleId, +}; use aptos_transactional_test_harness::run_aptos_test; use aptos_types::{ account_address::{create_resource_address, AccountAddress}, - transaction::{Script, TransactionArgument, TransactionPayload}, + transaction::{TransactionArgument, TransactionPayload}, }; use async_trait::async_trait; use clap::{ArgEnum, Parser, Subcommand}; @@ -52,18 +55,14 @@ use codespan_reporting::{ use itertools::Itertools; use move_cli::{self, base::test::UnitTestResult}; use move_command_line_common::env::MOVE_HOME; -use move_core_types::{ - identifier::Identifier, - language_storage::{ModuleId, TypeTag}, - u256::U256, -}; +use move_core_types::{identifier::Identifier, language_storage::ModuleId, u256::U256}; use move_package::{source_package::layout::SourcePackageLayout, BuildConfig}; use move_unit_test::UnitTestingConfig; pub use package_hooks::*; use serde::{Deserialize, Serialize}; +use serde_json::json; use std::{ collections::BTreeMap, - convert::TryFrom, fmt::{Display, Formatter}, path::{Path, PathBuf}, str::FromStr, @@ -79,6 +78,7 @@ use transactional_tests_runner::TransactionalTestOpts; /// about this code. #[derive(Subcommand)] pub enum MoveTool { + BuildPublishPayload(BuildPublishPayload), Clean(CleanPackage), Compile(CompilePackage), CompileScript(CompileScript), @@ -105,6 +105,7 @@ pub enum MoveTool { impl MoveTool { pub async fn execute(self) -> CliResult { match self { + MoveTool::BuildPublishPayload(tool) => tool.execute_serialized().await, MoveTool::Clean(tool) => tool.execute_serialized().await, MoveTool::Compile(tool) => tool.execute_serialized().await, MoveTool::CompileScript(tool) => tool.execute_serialized().await, @@ -632,6 +633,61 @@ pub struct PublishPackage { pub(crate) txn_options: TransactionOptions, } +struct PackagePublicationData { + metadata_serialized: Vec, + compiled_units: Vec>, + payload: TransactionPayload, +} + +/// Build a publication transaction payload and store it in a JSON output file. +#[derive(Parser)] +pub struct BuildPublishPayload { + #[clap(flatten)] + publish_package: PublishPackage, + /// JSON output file to write publication transaction to + #[clap(long, parse(from_os_str))] + pub(crate) json_output_file: PathBuf, +} + +impl TryInto for &PublishPackage { + type Error = CliError; + + fn try_into(self) -> Result { + let package_path = self.move_options.get_package_path()?; + let options = self + .included_artifacts_args + .included_artifacts + .build_options( + self.move_options.skip_fetch_latest_git_deps, + self.move_options.named_addresses(), + self.move_options.bytecode_version, + ); + let package = BuiltPackage::build(package_path, options)?; + let compiled_units = package.extract_code(); + let metadata_serialized = + bcs::to_bytes(&package.extract_metadata()?).expect("PackageMetadata has BCS"); + let payload = aptos_cached_packages::aptos_stdlib::code_publish_package_txn( + metadata_serialized.clone(), + compiled_units.clone(), + ); + let size = bcs::serialized_size(&payload)?; + println!("package size {} bytes", size); + if !self.override_size_check && size > MAX_PUBLISH_PACKAGE_SIZE { + return Err(CliError::UnexpectedError(format!( + "The package is larger than {} bytes ({} bytes)! To lower the size \ + you may want to include less artifacts via `--included-artifacts`. \ + You can also override this check with `--override-size-check", + MAX_PUBLISH_PACKAGE_SIZE, size + ))); + } + Ok(PackagePublicationData { + metadata_serialized, + compiled_units, + payload, + }) + } +} + #[derive(ArgEnum, Clone, Copy, Debug)] pub enum IncludedArtifacts { None, @@ -717,38 +773,64 @@ impl CliCommand for PublishPackage { } async fn execute(self) -> CliTypedResult { - let PublishPackage { - move_options, - txn_options, - override_size_check, - included_artifacts_args, - } = self; - let package_path = move_options.get_package_path()?; - let options = included_artifacts_args.included_artifacts.build_options( - move_options.skip_fetch_latest_git_deps, - move_options.named_addresses(), - move_options.bytecode_version, - ); - let package = BuiltPackage::build(package_path, options)?; - let compiled_units = package.extract_code(); + let package_publication_data: PackagePublicationData = (&self).try_into()?; + profile_or_submit(package_publication_data.payload, &self.txn_options).await + } +} - // Send the compiled module and metadata using the code::publish_package_txn. - let metadata = package.extract_metadata()?; - let payload = aptos_cached_packages::aptos_stdlib::code_publish_package_txn( - bcs::to_bytes(&metadata).expect("PackageMetadata has BCS"), - compiled_units, - ); - let size = bcs::serialized_size(&payload)?; - println!("package size {} bytes", size); - if !override_size_check && size > MAX_PUBLISH_PACKAGE_SIZE { - return Err(CliError::UnexpectedError(format!( - "The package is larger than {} bytes ({} bytes)! To lower the size \ - you may want to include less artifacts via `--included-artifacts`. \ - You can also override this check with `--override-size-check", - MAX_PUBLISH_PACKAGE_SIZE, size - ))); - } - profile_or_submit(payload, &txn_options).await +#[async_trait] +impl CliCommand for BuildPublishPayload { + fn command_name(&self) -> &'static str { + "BuildPublishPayload" + } + + async fn execute(self) -> CliTypedResult { + let package_publication_data: PackagePublicationData = + (&self.publish_package).try_into()?; + // Extract entry function data from publication payload. + let entry_function = package_publication_data.payload.into_entry_function(); + let entry_function_id = EntryFunctionId { + module: MoveModuleId::from(entry_function.module().clone()), + name: IdentifierWrapper::from(entry_function.function()), + }; + let package_metadata_hex = + HexEncodedBytes(package_publication_data.metadata_serialized).to_string(); + let package_code_hex_vec: Vec = package_publication_data + .compiled_units + .into_iter() + .map(|element| HexEncodedBytes(element).to_string()) + .collect(); + // Construct entry function JSON file representation from entry function data. + let json = EntryFunctionArgumentsJSON { + function_id: entry_function_id.to_string(), + type_args: vec![], + args: vec![ + ArgWithTypeJSON { + arg_type: "hex".to_string(), + value: serde_json::Value::String(package_metadata_hex), + }, + ArgWithTypeJSON { + arg_type: "hex".to_string(), + value: json!(package_code_hex_vec), + }, + ], + }; + // Create save file options for checking and saving file to disk. + let save_file = SaveFile { + output_file: self.json_output_file, + prompt_options: self.publish_package.txn_options.prompt_options, + }; + save_file.check_file()?; + save_file.save_to_file( + "Publication entry function JSON file", + serde_json::to_string_pretty(&json) + .map_err(|err| CliError::UnexpectedError(format!("{}", err)))? + .as_bytes(), + )?; + Ok(format!( + "Publication payload entry function JSON file saved to {}", + save_file.output_file.display() + )) } } @@ -1102,10 +1184,11 @@ impl CliCommand for RunFunction { } async fn execute(self) -> CliTypedResult { - let payload = TransactionPayload::EntryFunction( - self.entry_function_args.create_entry_function_payload()?, - ); - profile_or_submit(payload, &self.txn_options).await + profile_or_submit( + TransactionPayload::EntryFunction(self.entry_function_args.try_into()?), + &self.txn_options, + ) + .await } } @@ -1125,21 +1208,9 @@ impl CliCommand> for ViewFunction { } async fn execute(self) -> CliTypedResult> { - let mut args: Vec = vec![]; - for arg in self.entry_function_args.arg_vec.args { - args.push(arg.to_json()?); - } - - let view_request = ViewRequest { - function: EntryFunctionId { - module: self.entry_function_args.function_id.module_id.into(), - name: self.entry_function_args.function_id.member_id.into(), - }, - type_arguments: self.entry_function_args.type_args, - arguments: args, - }; - - self.txn_options.view(view_request).await + self.txn_options + .view(self.entry_function_args.try_into()?) + .await } } @@ -1151,12 +1222,7 @@ pub struct RunScript { #[clap(flatten)] pub(crate) compile_proposal_args: CompileScriptFunction, #[clap(flatten)] - pub(crate) arg_vec: ArgWithTypeVec, - /// TypeTag arguments separated by spaces. - /// - /// Example: `u8 u16 u32 u64 u128 u256 bool address vector signer` - #[clap(long, multiple_values = true)] - pub(crate) type_args: Vec, + pub(crate) script_function_args: ScriptFunctionArguments, } #[async_trait] @@ -1170,23 +1236,11 @@ impl CliCommand for RunScript { .compile_proposal_args .compile("RunScript", self.txn_options.prompt_options)?; - let mut args: Vec = vec![]; - for arg in self.arg_vec.args { - args.push(arg.try_into()?); - } - - let mut type_args: Vec = Vec::new(); - - // These TypeArgs are used for generics - for type_arg in self.type_args.into_iter() { - let type_tag = TypeTag::try_from(type_arg) - .map_err(|err| CliError::UnableToParse("--type-args", err.to_string()))?; - type_args.push(type_tag) - } - - let payload = TransactionPayload::Script(Script::new(bytecode, type_args, args)); - - profile_or_submit(payload, &self.txn_options).await + profile_or_submit( + self.script_function_args.create_script_payload(bytecode)?, + &self.txn_options, + ) + .await } } @@ -1238,7 +1292,9 @@ impl FunctionArgType { ) .map_err(|err| CliError::BCS("arg", err)), FunctionArgType::Hex => bcs::to_bytes( - &hex::decode(arg).map_err(|err| CliError::UnableToParse("hex", err.to_string()))?, + HexEncodedBytes::from_str(arg) + .map_err(|err| CliError::UnableToParse("hex", err.to_string()))? + .inner(), ) .map_err(|err| CliError::BCS("arg", err)), FunctionArgType::String => bcs::to_bytes(arg).map_err(|err| CliError::BCS("arg", err)), @@ -1271,9 +1327,10 @@ impl FunctionArgType { .map_err(|err| CliError::UnableToParse("u256", err.to_string()))?, ) .map_err(|err| CliError::BCS("arg", err)), - FunctionArgType::Raw => { - hex::decode(arg).map_err(|err| CliError::UnableToParse("raw", err.to_string())) - }, + FunctionArgType::Raw => Ok(HexEncodedBytes::from_str(arg) + .map_err(|err| CliError::UnableToParse("raw", err.to_string()))? + .inner() + .to_vec()), } } @@ -1514,7 +1571,7 @@ impl FromStr for ArgWithType { } } -impl TryInto for ArgWithType { +impl TryInto for &ArgWithType { type Error = CliError; fn try_into(self) -> Result { diff --git a/crates/aptos/src/test/mod.rs b/crates/aptos/src/test/mod.rs index a4cb86416c74c..d2d97cf7ad352 100644 --- a/crates/aptos/src/test/mod.rs +++ b/crates/aptos/src/test/mod.rs @@ -16,7 +16,8 @@ use crate::{ CliTypedResult, EncodingOptions, EntryFunctionArguments, FaucetOptions, GasOptions, KeyType, MoveManifestAccountWrapper, MovePackageDir, OptionalPoolAddressArgs, PoolAddressArgs, PrivateKeyInputOptions, PromptOptions, PublicKeyInputOptions, - RestOptions, RngArgs, SaveFile, TransactionOptions, TransactionSummary, + RestOptions, RngArgs, SaveFile, ScriptFunctionArguments, TransactionOptions, + TransactionSummary, TypeArgVec, }, utils::write_to_file, }, @@ -310,22 +311,25 @@ impl CliTestFramework { ) -> CliTypedResult { RunFunction { entry_function_args: EntryFunctionArguments { - function_id: MemberId { + function_id: Some(MemberId { module_id: ModuleId::new(AccountAddress::ONE, ident_str!("coin").into()), member_id: ident_str!("transfer").into(), - }, + }), arg_vec: ArgWithTypeVec { args: vec![ ArgWithType::from_str("address:0xdeadbeefcafebabe").unwrap(), ArgWithType::from_str(&format!("u64:{}", amount)).unwrap(), ], }, - type_args: vec![MoveType::Struct(MoveStructTag::new( - AccountAddress::ONE.into(), - ident_str!("aptos_coin").into(), - ident_str!("AptosCoin").into(), - vec![], - ))], + type_arg_vec: TypeArgVec { + type_args: vec![MoveType::Struct(MoveStructTag::new( + AccountAddress::ONE.into(), + ident_str!("aptos_coin").into(), + ident_str!("AptosCoin").into(), + vec![], + ))], + }, + json_file: None, }, txn_options: self.transaction_options(sender_index, gas_options), } @@ -587,8 +591,9 @@ impl CliTestFramework { ) -> CliTypedResult { RunFunction { entry_function_args: EntryFunctionArguments { - function_id: MemberId::from_str("0x1::staking_contract::create_staking_contract") - .unwrap(), + function_id: Some( + MemberId::from_str("0x1::staking_contract::create_staking_contract").unwrap(), + ), arg_vec: ArgWithTypeVec { args: vec![ ArgWithType::address(self.account_id(operator_index)), @@ -598,7 +603,8 @@ impl CliTestFramework { ArgWithType::bytes(vec![]), ], }, - type_args: vec![], + type_arg_vec: TypeArgVec { type_args: vec![] }, + json_file: None, }, txn_options: self.transaction_options(owner_index, None), } @@ -909,12 +915,15 @@ impl CliTestFramework { } RunFunction { - txn_options: self.transaction_options(index, gas_options), entry_function_args: EntryFunctionArguments { - function_id, + function_id: Some(function_id), arg_vec: ArgWithTypeVec { args: parsed_args }, - type_args: parsed_type_args, + type_arg_vec: TypeArgVec { + type_args: parsed_type_args, + }, + json_file: None, }, + txn_options: self.transaction_options(index, gas_options), } .execute() .await @@ -976,8 +985,11 @@ impl CliTestFramework { framework_package_args, bytecode_version: None, }, - arg_vec: ArgWithTypeVec { args: Vec::new() }, - type_args: Vec::new(), + script_function_args: ScriptFunctionArguments { + type_arg_vec: TypeArgVec { type_args: vec![] }, + arg_vec: ArgWithTypeVec { args: vec![] }, + json_file: None, + }, } .execute() .await @@ -1002,8 +1014,11 @@ impl CliTestFramework { }, bytecode_version: None, }, - arg_vec: ArgWithTypeVec { args }, - type_args, + script_function_args: ScriptFunctionArguments { + type_arg_vec: TypeArgVec { type_args }, + arg_vec: ArgWithTypeVec { args }, + json_file: None, + }, } .execute() .await diff --git a/developer-docs-site/docs/move/move-on-aptos/cli.md b/developer-docs-site/docs/move/move-on-aptos/cli.md index 747ad61550095..d430866fcb589 100644 --- a/developer-docs-site/docs/move/move-on-aptos/cli.md +++ b/developer-docs-site/docs/move/move-on-aptos/cli.md @@ -2,6 +2,8 @@ title: "Aptos Move CLI" --- +import CodeBlock from '@theme/CodeBlock'; + # Use the Aptos Move CLI The `aptos` tool is a command line interface (CLI) for developing on the Aptos blockchain, debugging, and for node operations. This document describes how to use the `aptos` CLI tool. To download or build the CLI, follow [Install Aptos CLI](../../tools/install-cli/index.md). @@ -434,3 +436,1129 @@ $ aptos move run --function-id default::message::set_message --args string:hello } } ``` + +## Arguments in JSON + +### Package info + +This section references the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args), which contains the following manifest: + +import move_toml from '!!raw-loader!../../../../aptos-move/move-examples/cli_args/Move.toml'; + +{move_toml} + +Here, the package is deployed under the named address `test_account`. + +:::tip +Set your working directory to [`aptos-move/move-examples/cli_args`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args) to follow along: + +```bash +cd /aptos-core/aptos-move/move-examples/cli_args +``` +::: + +### Deploying the package + +Start by mining a vanity address for Ace, who will deploy the package: + + +```bash title=Command +aptos key generate \ + --vanity-prefix 0xace \ + --output-file ace.key +``` + +
Output + +```bash +{ + "Result": { + "Account Address:": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "PublicKey Path": "ace.key.pub", + "PrivateKey Path": "ace.key" + } +} +``` + +
+ +:::tip +The exact account address should vary for each run, though the vanity prefix should not. +::: + +Store Ace's address in a shell variable so you can call it inline later on: + +```bash +# Your exact address should vary +ace_addr=0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46 +``` + +Fund Ace's account with the faucet (either devnet or testnet): + +```bash title=Command +aptos account fund-with-faucet --account $ace_addr +``` + +
Output + +```bash +{ + "Result": "Added 100000000 Octas to account acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46" +} +``` + +
+ +Now publish the package under Ace's account: + +```bash title=Command +aptos move publish \ + --named-addresses test_account=$ace_addr \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0x1d7b074dd95724c5459a1c30fe4cb3875e7b0478cc90c87c8e3f21381625bec1", + "gas_used": 1294, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 0, + "success": true, + "timestamp_us": 1685077849297587, + "version": 528422121, + "vm_status": "Executed successfully" + } +} +``` + +
+ +### Entry functions + +The only module in the package, `cli_args.move`, defines a simple `Holder` resource with fields of various data types: + +```rust title="Holder in cli_args.move" +:!: static/move-examples/cli_args/sources/cli_args.move resource +``` + +A public entry function with multi-nested vectors can be used to set the fields: + +```rust title="Setter function in cli_args.move" +:!: static/move-examples/cli_args/sources/cli_args.move setter +``` + +After the package has been published, `aptos move run` can be used to call `set_vals()`: + +:::tip +To pass vectors (including nested vectors) as arguments from the command line, use JSON syntax escaped with quotes! +::: + +```bash title="Running function with nested vector arguments from CLI" +aptos move run \ + --function-id $ace_addr::cli_args::set_vals \ + --type-args \ + 0x1::account::Account \ + 0x1::chain_id::ChainId \ + --args \ + u8:123 \ + "bool:[false, true, false, false]" \ + 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0x5e141dc6c28e86fa9f5594de93d07a014264ebadfb99be6db922a929eb1da24f", + "gas_used": 504, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 1, + "success": true, + "timestamp_us": 1685077888820037, + "version": 528422422, + "vm_status": "Executed successfully" + } +} +``` + +
+ +The function ID, type arguments, and arguments can alternatively be specified in a JSON file: + +import entry_json_file from '!!raw-loader!../../../../aptos-move/move-examples/cli_args/entry_function_arguments.json'; + +{entry_json_file} + +Here, the call to `aptos move run` looks like: + +```bash title="Running function with JSON input file" +aptos move run \ + --json-file entry_function_arguments.json \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0x60a32315bb48bf6d31629332f6b1a3471dd0cb016fdee8d0bb7dcd0be9833e60", + "gas_used": 3, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 2, + "success": true, + "timestamp_us": 1685077961499641, + "version": 528422965, + "vm_status": "Executed successfully" + } +} +``` + +
+ +:::tip +If you are trying to run the example yourself don't forget to substitute Ace's actual address for `` in `entry_function_arguments.json`! +::: + +### View functions + +Once the values in a `Holder` have been set, the `reveal()` view function can be used to check the first three fields, and to compare type arguments against the last two fields: + +```rust title="View function" +:!: static/move-examples/cli_args/sources/cli_args.move view +``` + +This view function can be called with arguments specified either from the CLI or from a JSON file: + +```bash title="Arguments via CLI" +aptos move view \ + --function-id $ace_addr::cli_args::reveal \ + --type-args \ + 0x1::account::Account \ + 0x1::account::Account \ + --args address:$ace_addr +``` + +```bash title="Arguments via JSON file" +aptos move view --json-file view_function_arguments.json +``` + +:::tip +If you are trying to run the example yourself don't forget to substitute Ace's actual address for `` in `view_function_arguments.json` (twice)! +::: + +import view_json_file from '!!raw-loader!../../../../aptos-move/move-examples/cli_args/view_function_arguments.json'; + +{view_json_file} + +```bash title="Output" +{ + "Result": [ + { + "address_vec_vec": [ + [ + "0xace", + "0xbee" + ], + [ + "0xcad" + ], + [] + ], + "bool_vec": [ + false, + true, + false, + false + ], + "type_info_1_match": true, + "type_info_2_match": false, + "u8_solo": 123 + } + ] +} +``` + +### Script functions + +The package also contains a script, `set_vals.move`, which is a wrapper for the setter function: + +```rust title="script" +:!: static/move-examples/cli_args/scripts/set_vals.move script +``` + +First compile the package (this will compile the script): + +```bash title=Compilation +aptos move compile --named-addresses test_account=$ace_addr +``` + +
Output + +```bash +{ + "Result": [ + "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46::cli_args" + ] +} +``` + +
+ +Next, run `aptos move run-script`: + +```bash title="Arguments via CLI" +aptos move run-script \ + --compiled-script-path build/CliArgs/bytecode_scripts/set_vals.mv \ + --type-args \ + 0x1::account::Account \ + 0x1::chain_id::ChainId \ + --args \ + u8:123 \ + "u8:[122, 123, 124, 125]" \ + address:"0xace" \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0x1d644eba8187843cc43919469112339bc2c435a49a733ac813b7bc6c79770152", + "gas_used": 3, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 3, + "success": true, + "timestamp_us": 1685078415935612, + "version": 528426413, + "vm_status": "Executed successfully" + } +} +``` + +
+ +```bash title="Arguments via JSON file" +aptos move run-script \ + --compiled-script-path build/CliArgs/bytecode_scripts/set_vals.mv \ + --json-file script_function_arguments.json \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0x840e2d6a5ab80d5a570effb3665f775f1755e0fd8d76e52bfa7241aaade883d7", + "gas_used": 3, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 4, + "success": true, + "timestamp_us": 1685078516832128, + "version": 528427132, + "vm_status": "Executed successfully" + } +} +``` + +
+ +import script_json_file from '!!raw-loader!../../../../aptos-move/move-examples/cli_args/script_function_arguments.json'; + +{script_json_file} + +Both such script function invocations result in the following `reveal()` view function output: + +```bash title="View function call" +aptos move view \ + --function-id $ace_addr::cli_args::reveal \ + --type-args \ + 0x1::account::Account \ + 0x1::chain_id::ChainId \ + --args address:$ace_addr +``` + +```json title="View function output" +{ + "Result": [ + { + "address_vec_vec": [ + [ + "0xace" + ] + ], + "bool_vec": [ + false, + false, + true, + true + ], + "type_info_1_match": true, + "type_info_2_match": true, + "u8_solo": 123 + } + ] +} +``` + +:::note +As of the time of this writing, the `aptos` CLI only supports script function arguments for vectors of type `u8`, and only up to a vector depth of 1. Hence `vector
` and `vector>` are invalid script function argument types. +::: + + +## Multisig governance + +### Background + +This section builds upon the [Arguments in JSON](#arguments-in-json) section, and likewise references the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args). + +:::tip +If you would like to follow along, start by completing the [Arguments in JSON](#arguments-in-json) tutorial steps! +::: + +For this example, Ace and Bee will conduct governance operations from a 2-of-2 "multisig v2" account (an on-chain multisig account per [`multisig_account.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/multisig_account.move)) + +### Account creation + +Since Ace's account was created during the [Arguments in JSON](#arguments-in-json) tutorial, start by mining a vanity address account for Bee too: + +```bash title=Command +aptos key generate \ + --vanity-prefix 0xbee \ + --output-file bee.key +``` + +
Output + +```bash +{ + "Result": { + "PublicKey Path": "bee.key.pub", + "PrivateKey Path": "bee.key", + "Account Address:": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" + } +} +``` + +
+ +:::tip +The exact account address should vary for each run, though the vanity prefix should not. +::: + +Store Bee's address in a shell variable so you can call it inline later on: + +```bash +# Your exact address should vary +bee_addr=0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc +``` + +Fund Bee's account using the faucet: + +```bash title=Command +aptos account fund-with-faucet --account $bee_addr +``` + +
Output + +```bash +{ + "Result": "Added 100000000 Octas to account beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" +} +``` + +
+ +Ace can now create a multisig account: + +```bash title=Command +aptos multisig create \ + --additional-owners $bee_addr \ + --num-signatures-required 2 \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "multisig_address": "57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5", + "transaction_hash": "0x849cc756de2d3b57210f5d32ae4b5e7d1f80e5d376233885944b6f3cc2124a05", + "gas_used": 1524, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 5, + "success": true, + "timestamp_us": 1685078644186194, + "version": 528428043, + "vm_status": "Executed successfully" + } +} +``` + +
+ +Store the multisig address in a shell variable: + +```bash +# Your address should vary +multisig_addr=0x57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5 +``` + +### Inspect the multisig + +Use the assorted [`multisig_account.move` view functions](https://github.com/aptos-labs/aptos-core/blob/9fa0102c3e474d99ea35a0a85c6893604be41611/aptos-move/framework/aptos-framework/sources/multisig_account.move#L237) to inspect the multisig: + +```bash title="Number of signatures required" +aptos move view \ + --function-id 0x1::multisig_account::num_signatures_required \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + "2" + ] +} +``` + +
+ +```bash title="Owners" +aptos move view \ + --function-id 0x1::multisig_account::owners \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + [ + "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", + "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46" + ] + ] +} +``` + +
+ +```bash title="Last resolved sequence number" +aptos move view \ + --function-id 0x1::multisig_account::last_resolved_sequence_number \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + "0" + ] +} +``` + +
+ +```bash title="Next sequence number" +aptos move view \ + --function-id 0x1::multisig_account::next_sequence_number \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + "1" + ] +} +``` + +
+ +### Enqueue a publication transaction + +The first multisig transaction enqueued will be a transaction for publication of the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args). +First, generate a publication payload entry function JSON file: + +```bash title="Command" +aptos move build-publish-payload \ + --named-addresses test_account=$multisig_addr \ + --json-output-file publication.json \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": "Publication payload entry function JSON file saved to publication.json" +} +``` + +
+ +Now have Ace propose publication of the package from the multisig account, storing only the payload hash on-chain: + +```bash title="Command" +aptos multisig create-transaction \ + --multisig-address $multisig_addr \ + --json-file publication.json \ + --store-hash-only \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0x70c75903f8e1b1c0069f1e84ef9583ad8000f24124b33a746c88d2b031f7fe2c", + "gas_used": 510, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 6, + "success": true, + "timestamp_us": 1685078836492390, + "version": 528429447, + "vm_status": "Executed successfully" + } +} +``` + +
+ +Note that the last resolved sequence number is still 0 because no transactions have been resolved: + +```bash title="Last resolved sequence number" +aptos move view \ + --function-id 0x1::multisig_account::last_resolved_sequence_number \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + "0" + ] +} +``` + +
+ +However the next sequence number has been incremented because a transaction has been enqueued: + +```bash title="Next sequence number" +aptos move view \ + --function-id 0x1::multisig_account::next_sequence_number \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + "2" + ] +} +``` + +
+ +The multisig transaction enqueued on-chain can now be inspected: + +```bash title="Get transaction" +aptos move view \ + --function-id 0x1::multisig_account::get_transaction \ + --args \ + address:"$multisig_addr" \ + String:1 +``` + +
Output + +```bash +{ + "Result": [ + { + "creation_time_secs": "1685078836", + "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "payload": { + "vec": [] + }, + "payload_hash": { + "vec": [ + "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" + ] + }, + "votes": { + "data": [ + { + "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "value": true + } + ] + } + } + ] +} +``` + +
+ +Note from the above result that no payload is stored on-chain, and that Ace implicitly approved the transaction (voted `true`) upon the submission of the proposal. + +### Enqueue a governance parameter transaction + +Now have Bee enqueue a governance parameter setter transaction, storing the entire transaction payload on-chain: + +```bash title="Command" +aptos multisig create-transaction \ + --multisig-address $multisig_addr \ + --function-id $multisig_addr::cli_args::set_vals \ + --type-args \ + 0x1::account::Account \ + 0x1::chain_id::ChainId \ + --args \ + u8:123 \ + "bool:[false, true, false, false]" \ + 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ + --private-key-file bee.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0xd0a348072d5bfc5a2e5d444f92f0ecc10b978dad720b174303bc6d91342f27ec", + "gas_used": 511, + "gas_unit_price": 100, + "sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", + "sequence_number": 0, + "success": true, + "timestamp_us": 1685078954841650, + "version": 528430315, + "vm_status": "Executed successfully" + } +} +``` + +
+ +Note the next sequence number has been incremented again: + +```bash title="Next sequence number" +aptos move view \ + --function-id 0x1::multisig_account::next_sequence_number \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + "3" + ] +} +``` + +
+ +Now both the publication and parameter transactions are pending: + +```bash title="Get pending transactions" +aptos move view \ + --function-id 0x1::multisig_account::get_pending_transactions \ + --args \ + address:"$multisig_addr" +``` + +
Output + +```bash +{ + "Result": [ + [ + { + "creation_time_secs": "1685078836", + "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "payload": { + "vec": [] + }, + "payload_hash": { + "vec": [ + "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" + ] + }, + "votes": { + "data": [ + { + "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "value": true + } + ] + } + }, + { + "creation_time_secs": "1685078954", + "creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", + "payload": { + "vec": [ + "0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00" + ] + }, + "payload_hash": { + "vec": [] + }, + "votes": { + "data": [ + { + "key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", + "value": true + } + ] + } + } + ] + ] +} +``` + +
+ +### Execute the publication transaction + +Since only Ace has voted on the publication transaction (which he implicitly approved upon proposing) the transaction can't be executed yet: + +```bash title="Can be executed" +aptos move view \ + --function-id 0x1::multisig_account::can_be_executed \ + --args \ + address:"$multisig_addr" \ + String:1 +``` + +
Output + +```bash +{ + "Result": [ + false + ] +} +``` + +
+ +Before Bee votes, however, she verifies that the payload hash stored on-chain matches the publication entry function JSON file: + +```bash title="Verifying transaction proposal" +aptos multisig verify-proposal \ + --multisig-address $multisig_addr \ + --json-file publication.json \ + --sequence-number 1 +``` + +
Output + +```bash +{ + "Result": { + "Status": "Transaction match", + "Multisig transaction": { + "creation_time_secs": "1685078836", + "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "payload": { + "vec": [] + }, + "payload_hash": { + "vec": [ + "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" + ] + }, + "votes": { + "data": [ + { + "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "value": true + } + ] + } + } + } +} +``` + +
+ +Since Bee has verified that the on-chain payload hash checks out against her locally-compiled package publication JSON file, she votes yes: + + +```bash title="Approving transaction" +aptos multisig approve \ + --multisig-address $multisig_addr \ + --sequence-number 1 \ + --private-key-file bee.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0xa5fb49f1077de6aa6d976e6bcc05e4c50c6cd061f1c87e8f1ea74e7a04a06bd1", + "gas_used": 6, + "gas_unit_price": 100, + "sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", + "sequence_number": 1, + "success": true, + "timestamp_us": 1685079892130861, + "version": 528437204, + "vm_status": "Executed successfully" + } +} +``` + +
+ +Now the transaction can be executed: + +```bash title="Can be executed" +aptos move view \ + --function-id 0x1::multisig_account::can_be_executed \ + --args \ + address:"$multisig_addr" \ + String:1 +``` + +
Output + +```bash +{ + "Result": [ + true + ] +} +``` + +
+ +Now either Ace or Bee can invoke the publication transaction from the multisig account, passing the full transaction payload since only the hash was stored on-chain: + +```bash title="Publication" +aptos multisig execute-with-payload \ + --multisig-address $multisig_addr \ + --json-file publication.json \ + --private-key-file bee.key \ + --max-gas 10000 \ + --assume-yes +``` + +:::tip +Pending the resolution of [#8304](https://github.com/aptos-labs/aptos-core/issues/8304), the transaction simulator (which is used to estimate gas costs) is broken for multisig transactions, so you will have to manually specify a max gas amount. +::: + +
Output + +Also pending the resolution of [#8304](https://github.com/aptos-labs/aptos-core/issues/8304), the CLI output for a successful multisig publication transaction execution results in an API error if only the payload hash has been stored on-chain, but the transaction can be manually verified using an explorer. + +
+ +### Execute the governance parameter transaction + +Since only Bee has voted on the governance parameter transaction (which she implicitly approved upon proposing), the transaction can't be executed yet: + +```bash title="Can be executed" +aptos move view \ + --function-id 0x1::multisig_account::can_be_executed \ + --args \ + address:"$multisig_addr" \ + String:2 +``` + +
Output + +```bash +{ + "Result": [ + false + ] +} +``` + +
+ +Before Ace votes, however, he verifies that the payload stored on-chain matches the function arguments he expects: + +```bash title="Verifying transaction proposal" +aptos multisig verify-proposal \ + --multisig-address $multisig_addr \ + --function-id $multisig_addr::cli_args::set_vals \ + --type-args \ + 0x1::account::Account \ + 0x1::chain_id::ChainId \ + --args \ + u8:123 \ + "bool:[false, true, false, false]" \ + 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ + --sequence-number 2 +``` + +
Output + +```bash +{ + "Result": { + "Status": "Transaction match", + "Multisig transaction": { + "creation_time_secs": "1685078954", + "creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", + "payload": { + "vec": [ + "0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00" + ] + }, + "payload_hash": { + "vec": [] + }, + "votes": { + "data": [ + { + "key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", + "value": true + } + ] + } + } + } +} +``` + +
+ +Note that the verification fails if he modifies even a single argument: + +```bash title="Failed transaction verification with modified u8" +aptos multisig verify-proposal \ + --multisig-address $multisig_addr \ + --function-id $multisig_addr::cli_args::set_vals \ + --type-args \ + 0x1::account::Account \ + 0x1::chain_id::ChainId \ + --args \ + u8:200 \ + "bool:[false, true, false, false]" \ + 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ + --sequence-number 2 +``` + +
Output + +```bash +{ + "Error": "Unexpected error: Transaction mismatch: The transaction you provided has a payload hash of 0xe494b0072d6f940317344967cf0e818c80082375833708c773b0275f3ad07e51, but the on-chain transaction proposal you specified has a payload hash of 0x070ed7c3f812f25f585461305d507b96a4e756f784e01c8c59901871267a1580. For more info, see https://aptos.dev/move/move-on-aptos/cli#multisig-governance" +} +``` + +
+ +Ace approves the transaction: + +```bash title="Approving transaction" +aptos multisig approve \ + --multisig-address $multisig_addr \ + --sequence-number 2 \ + --private-key-file ace.key \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0x233427d95832234fa13dddad5e0b225d40168b4c2c6b84f5255eecc3e68401bf", + "gas_used": 6, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 7, + "success": true, + "timestamp_us": 1685080266378400, + "version": 528439883, + "vm_status": "Executed successfully" + } +} +``` + +
+ +Since the payload was stored on-chain, it is not required to execute the pending transaction: + +```bash title="Execution" +aptos multisig execute \ + --multisig-address $multisig_addr \ + --private-key-file ace.key \ + --max-gas 10000 \ + --assume-yes +``` + +
Output + +```bash +{ + "Result": { + "transaction_hash": "0xbc99f929708a1058b223aa880d04607a78ebe503367ec4dab23af4a3bdb541b2", + "gas_used": 505, + "gas_unit_price": 100, + "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", + "sequence_number": 8, + "success": true, + "timestamp_us": 1685080344045461, + "version": 528440423, + "vm_status": "Executed successfully" + +``` + +
diff --git a/developer-docs-site/docs/tools/aptos-cli-tool/use-aptos-cli.md b/developer-docs-site/docs/tools/aptos-cli-tool/use-aptos-cli.md index 7a247f2d90d46..948247c485cf1 100644 --- a/developer-docs-site/docs/tools/aptos-cli-tool/use-aptos-cli.md +++ b/developer-docs-site/docs/tools/aptos-cli-tool/use-aptos-cli.md @@ -29,8 +29,10 @@ SUBCOMMANDS: init Tool to initialize current directory for the aptos tool key Tool for generating, inspecting, and interacting with keys move Tool for Move related operations + multisig Tool for interacting with multisig accounts node Tool for operations related to nodes - stake Tool for manipulating stake + stake Tool for manipulating stake and stake pools + update Update the CLI itself ``` ### Command-specific help @@ -750,54 +752,6 @@ The `peer_config.yaml` file will be created in your current working directory, w Move examples can be found in the [Move section](../../move/move-on-aptos/cli). -You can also pass multi-nested vector arguments, like in the `cli_args` example from [move-examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples): - -:::tip -To pass vectors (including nested vectors) as arguments, use JSON syntax escaped with quotes! -::: - -```zsh -aptos move run \ - --function-id ::cli_args::set_vals \ - --private-key-file \ - --args \ - u8:123 \ - "bool:[false, true, false, false]" \ - 'address:[["0xace", "0xbee"], ["0xcad"], ["0xdee"], []]' -``` - -Then you can call the view function to see your arguments persisted on-chain! - -```zsh -aptos move view \ - --function-id ::cli_args::reveal \ - --args address: -{ - "Result": [ - 123, - [ - false, - true, - false, - false - ], - [ - [ - "0xace", - "0xbee" - ], - [ - "0xcad" - ], - [ - "0xdee" - ], - [] - ] - ] -} -``` - ## Node command examples This section summarizes how to run a local testnet with Aptos CLI. See [Run a Local Testnet with Aptos CLI](../../nodes/local-testnet/using-cli-to-run-a-local-testnet.md) for more details. diff --git a/developer-docs-site/package.json b/developer-docs-site/package.json index ca401dbb22b3b..ce25ae430e60c 100644 --- a/developer-docs-site/package.json +++ b/developer-docs-site/package.json @@ -41,6 +41,7 @@ "prism-react-renderer": "1.2.1", "process": "^0.11.10", "prop-types": "^15.8.1", + "raw-loader": "^4.0.2", "react": "17.0.2", "react-dom": "17.0.2", "react-markdown": "^8.0.7", diff --git a/developer-docs-site/pnpm-lock.yaml b/developer-docs-site/pnpm-lock.yaml index af7bf7ea7cf0d..d71b498db74bc 100644 --- a/developer-docs-site/pnpm-lock.yaml +++ b/developer-docs-site/pnpm-lock.yaml @@ -33,7 +33,7 @@ dependencies: version: 1.6.22(react@17.0.2) '@stoplight/elements': specifier: ^7.7.16 - version: 7.7.16(@babel/core@7.21.4)(react-dom@17.0.2)(react@17.0.2) + version: 7.7.16(react-dom@17.0.2)(react@17.0.2) '@types/prop-types': specifier: ^15.7.5 version: 15.7.5 @@ -64,6 +64,9 @@ dependencies: prop-types: specifier: ^15.8.1 version: 15.8.1 + raw-loader: + specifier: ^4.0.2 + version: 4.0.2(webpack@5.80.0) react: specifier: 17.0.2 version: 17.0.2 @@ -2901,7 +2904,7 @@ packages: webpack-sources: 3.2.3 dev: false - /@stoplight/elements-core@7.7.16(@babel/core@7.21.4)(react-dom@17.0.2)(react@17.0.2): + /@stoplight/elements-core@7.7.16(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-P1DF+jyx9nQna+S30eAyr8J6gPIzAYTwKJEBnSCjdCkashPXu3X3nqi0Go9ykKnNDgrvlJyW9eFzEbY0bI5pHQ==} engines: {node: '>=14.13'} peerDependencies: @@ -2912,7 +2915,7 @@ packages: '@stoplight/json': 3.20.2 '@stoplight/json-schema-ref-parser': 9.2.2 '@stoplight/json-schema-sampler': 0.2.3 - '@stoplight/json-schema-viewer': 4.9.0(@babel/core@7.21.4)(@stoplight/markdown-viewer@5.6.0)(@stoplight/mosaic-code-viewer@1.40.0)(@stoplight/mosaic@1.40.0)(react-dom@17.0.2)(react@17.0.2) + '@stoplight/json-schema-viewer': 4.9.0(@stoplight/markdown-viewer@5.6.0)(@stoplight/mosaic-code-viewer@1.40.0)(@stoplight/mosaic@1.40.0)(react-dom@17.0.2)(react@17.0.2) '@stoplight/markdown-viewer': 5.6.0(@stoplight/mosaic-code-viewer@1.40.0)(@stoplight/mosaic@1.40.0)(react-dom@17.0.2)(react@17.0.2) '@stoplight/mosaic': 1.40.0(react-dom@17.0.2)(react@17.0.2) '@stoplight/mosaic-code-editor': 1.40.0(react-dom@17.0.2)(react@17.0.2) @@ -2923,7 +2926,7 @@ packages: '@stoplight/yaml': 4.2.3 classnames: 2.3.2 httpsnippet-lite: 3.0.5 - jotai: 1.3.9(@babel/core@7.21.4)(react-query@3.39.3)(react@17.0.2) + jotai: 1.3.9(react-query@3.39.3)(react@17.0.2) json-schema: 0.4.0 lodash: 4.17.21 nanoid: 3.3.6 @@ -2960,14 +2963,14 @@ packages: - xstate dev: false - /@stoplight/elements@7.7.16(@babel/core@7.21.4)(react-dom@17.0.2)(react@17.0.2): + /@stoplight/elements@7.7.16(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-hIyzZkzC0Y/ehalfilW6b12NUtKmCAx86im/p/PVZ0xNgcDC2hPaYk6aVlzqxjWMHmzLQ6sxKtuynm5+0mafOw==} engines: {node: '>=14.13'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@stoplight/elements-core': 7.7.16(@babel/core@7.21.4)(react-dom@17.0.2)(react@17.0.2) + '@stoplight/elements-core': 7.7.16(react-dom@17.0.2)(react@17.0.2) '@stoplight/http-spec': 5.7.2 '@stoplight/json': 3.20.2 '@stoplight/mosaic': 1.40.0(react-dom@17.0.2)(react@17.0.2) @@ -3078,7 +3081,7 @@ packages: magic-error: 0.0.1 dev: false - /@stoplight/json-schema-viewer@4.9.0(@babel/core@7.21.4)(@stoplight/markdown-viewer@5.6.0)(@stoplight/mosaic-code-viewer@1.40.0)(@stoplight/mosaic@1.40.0)(react-dom@17.0.2)(react@17.0.2): + /@stoplight/json-schema-viewer@4.9.0(@stoplight/markdown-viewer@5.6.0)(@stoplight/mosaic-code-viewer@1.40.0)(@stoplight/mosaic@1.40.0)(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-xuOt1FFeFxiy/bJrD0++9rjKY0NlaxH7y6jDZGCMGmH0y2vykpC2ZbHji+ol7/rB5TvVp7oE0NwlpK8TVizinw==} engines: {node: '>=16'} peerDependencies: @@ -3097,7 +3100,7 @@ packages: '@types/json-schema': 7.0.11 classnames: 2.3.2 fnv-plus: 1.3.1 - jotai: 1.13.1(@babel/core@7.21.4)(react@17.0.2) + jotai: 1.13.1(react@17.0.2) lodash: 4.17.21 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -3942,10 +3945,8 @@ packages: indent-string: 4.0.0 dev: false - /ajv-formats@2.1.1(ajv@8.12.0): + /ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -6776,7 +6777,7 @@ packages: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 - /jotai@1.13.1(@babel/core@7.21.4)(react@17.0.2): + /jotai@1.13.1(react@17.0.2): resolution: {integrity: sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw==} engines: {node: '>=12.20.0'} peerDependencies: @@ -6816,11 +6817,10 @@ packages: jotai-zustand: optional: true dependencies: - '@babel/core': 7.21.4 react: 17.0.2 dev: false - /jotai@1.3.9(@babel/core@7.21.4)(react-query@3.39.3)(react@17.0.2): + /jotai@1.3.9(react-query@3.39.3)(react@17.0.2): resolution: {integrity: sha512-b6DvH9gf+7TfjaboCO54g+C0yhaakIaUBtjLf0dk1p15FWCzNw/93sezdXy9cCaZ8qcEdMLJcjBwQlORmIq29g==} engines: {node: '>=12.7.0'} peerDependencies: @@ -6854,7 +6854,6 @@ packages: xstate: optional: true dependencies: - '@babel/core': 7.21.4 react: 17.0.2 react-query: 3.39.3(react-dom@17.0.2)(react@17.0.2) dev: false @@ -8949,6 +8948,17 @@ packages: unpipe: 1.0.0 dev: false + /raw-loader@4.0.2(webpack@5.80.0): + resolution: {integrity: sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.1.2 + webpack: 5.80.0 + dev: false + /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -9688,7 +9698,7 @@ packages: dependencies: '@types/json-schema': 7.0.11 ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) + ajv-formats: 2.1.1 ajv-keywords: 5.1.0(ajv@8.12.0) dev: false diff --git a/developer-docs-site/scripts/additional_dict.txt b/developer-docs-site/scripts/additional_dict.txt index e72399693be73..8da06afc2b47a 100644 --- a/developer-docs-site/scripts/additional_dict.txt +++ b/developer-docs-site/scripts/additional_dict.txt @@ -43,6 +43,7 @@ CPU CPUs ChainID Clippy +CodeBlock CoinStore CoinType CollectionData @@ -103,6 +104,7 @@ EWITHDRAW EWRONG EZERO Eg +Enqueue EntryFunction EntryFunctionPayload Enums @@ -347,6 +349,8 @@ doesnt dr eg endian +enqueue +enqueued entrancy enum enums