From 7fca81bb5544b40743290d3a951c1f1ff31a95d9 Mon Sep 17 00:00:00 2001 From: FroVolod Date: Tue, 26 Mar 2024 23:48:12 +0200 Subject: [PATCH] feat: Added ability to select contract function from NEAR ABI functions (#314) --- Cargo.lock | 8 +- Cargo.toml | 4 +- .../call_function/as_read_only/mod.rs | 72 +++++++++---- .../call_function/as_transaction/mod.rs | 100 ++++++++++++------ src/commands/contract/call_function/mod.rs | 67 +++++++++++- 5 files changed, 194 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1552ed24..057ad24ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,9 +1907,9 @@ dependencies = [ [[package]] name = "interactive-clap" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f547ee8ecc587e3b832ca0c6a343225ff96ff7ca51dc14c34a321fbeb6c6c" +checksum = "77bb929d523b1d6327de5ccd55052fe339f32a263f9a58c2104d32d874d5d92e" dependencies = [ "interactive-clap-derive", "strum", @@ -1918,9 +1918,9 @@ dependencies = [ [[package]] name = "interactive-clap-derive" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d19b60b6a42c672d30bbc6816f7ed89aee477d8229666e3e39fb9319e8b179" +checksum = "09a31fb5111f8e16f1a776e45d67d7b87e1a85f16e7d83af6051f4211ee50b6b" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 5dd8073b9..d5722d556 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,8 +89,8 @@ near-abi = "0.4.2" zstd = "0.11" keyring = "2.0.5" -interactive-clap = "0.2.8" -interactive-clap-derive = "0.2.8" +interactive-clap = "0.2.9" +interactive-clap-derive = "0.2.9" [features] default = ["ledger", "self-update"] diff --git a/src/commands/contract/call_function/as_read_only/mod.rs b/src/commands/contract/call_function/as_read_only/mod.rs index ac32b04ff..684cd92af 100644 --- a/src/commands/contract/call_function/as_read_only/mod.rs +++ b/src/commands/contract/call_function/as_read_only/mod.rs @@ -10,6 +10,45 @@ pub struct CallFunctionView { #[interactive_clap(skip_default_input_arg)] /// What is the contract account ID? contract_account_id: crate::types::account_id::AccountId, + #[interactive_clap(flatten)] + /// Select function + function: Function, +} + +#[derive(Clone)] +pub struct CallFunctionViewContext { + global_context: crate::GlobalContext, + contract_account_id: near_primitives::types::AccountId, +} + +impl CallFunctionViewContext { + pub fn from_previous_context( + previous_context: crate::GlobalContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + Ok(Self { + global_context: previous_context, + contract_account_id: scope.contract_account_id.clone().into(), + }) + } +} + +impl CallFunctionView { + pub fn input_contract_account_id( + context: &crate::GlobalContext, + ) -> color_eyre::eyre::Result> { + crate::common::input_non_signer_account_id_from_used_account_list( + &context.config.credentials_home_dir, + "What is the contract account ID?", + ) + } +} + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = CallFunctionViewContext)] +#[interactive_clap(output_context = FunctionContext)] +pub struct Function { + #[interactive_clap(skip_default_input_arg)] /// What is the name of the function? function_name: String, #[interactive_clap(value_enum)] @@ -24,17 +63,17 @@ pub struct CallFunctionView { } #[derive(Clone)] -pub struct CallFunctionViewContext(crate::network_view_at_block::ArgsForViewContext); +pub struct FunctionContext(crate::network_view_at_block::ArgsForViewContext); -impl CallFunctionViewContext { +impl FunctionContext { pub fn from_previous_context( - previous_context: crate::GlobalContext, - scope: &::InteractiveClapContextScope, + previous_context: CallFunctionViewContext, + scope: &::InteractiveClapContextScope, ) -> color_eyre::eyre::Result { let on_after_getting_block_reference_callback: crate::network_view_at_block::OnAfterGettingBlockReferenceCallback = std::sync::Arc::new({ let function_args = scope.function_args.clone(); let function_args_type = scope.function_args_type.clone(); - let account_id: near_primitives::types::AccountId = scope.contract_account_id.clone().into(); + let account_id: near_primitives::types::AccountId = previous_context.contract_account_id.clone(); let function_name = scope.function_name.clone(); move |network_config, block_reference| { @@ -75,32 +114,29 @@ impl CallFunctionViewContext { }); Ok(Self(crate::network_view_at_block::ArgsForViewContext { - config: previous_context.config, - interacting_with_account_ids: vec![scope.contract_account_id.clone().into()], + config: previous_context.global_context.config, + interacting_with_account_ids: vec![previous_context.contract_account_id], on_after_getting_block_reference_callback, })) } } -impl From for crate::network_view_at_block::ArgsForViewContext { - fn from(item: CallFunctionViewContext) -> Self { +impl From for crate::network_view_at_block::ArgsForViewContext { + fn from(item: FunctionContext) -> Self { item.0 } } -impl CallFunctionView { +impl Function { fn input_function_args_type( - _context: &crate::GlobalContext, + _context: &CallFunctionViewContext, ) -> color_eyre::eyre::Result> { super::call_function_args_type::input_function_args_type() } - pub fn input_contract_account_id( - context: &crate::GlobalContext, - ) -> color_eyre::eyre::Result> { - crate::common::input_non_signer_account_id_from_used_account_list( - &context.config.credentials_home_dir, - "What is the contract account ID?", - ) + fn input_function_name( + context: &CallFunctionViewContext, + ) -> color_eyre::eyre::Result> { + super::input_view_function_name(&context.global_context, &context.contract_account_id) } } diff --git a/src/commands/contract/call_function/as_transaction/mod.rs b/src/commands/contract/call_function/as_transaction/mod.rs index 6a7747784..1367509a6 100644 --- a/src/commands/contract/call_function/as_transaction/mod.rs +++ b/src/commands/contract/call_function/as_transaction/mod.rs @@ -2,11 +2,50 @@ use inquire::CustomType; #[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(input_context = crate::GlobalContext)] -#[interactive_clap(output_context = CallFunctionPropertiesContext)] -pub struct CallFunctionProperties { +#[interactive_clap(output_context = CallFunctionContext)] +pub struct CallFunction { #[interactive_clap(skip_default_input_arg)] /// What is the contract account ID? contract_account_id: crate::types::account_id::AccountId, + #[interactive_clap(flatten)] + /// Select function + function: Function, +} + +#[derive(Debug, Clone)] +pub struct CallFunctionContext { + global_context: crate::GlobalContext, + contract_account_id: near_primitives::types::AccountId, +} + +impl CallFunctionContext { + pub fn from_previous_context( + previous_context: crate::GlobalContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + Ok(Self { + global_context: previous_context, + contract_account_id: scope.contract_account_id.clone().into(), + }) + } +} + +impl CallFunction { + pub fn input_contract_account_id( + context: &crate::GlobalContext, + ) -> color_eyre::eyre::Result> { + crate::common::input_non_signer_account_id_from_used_account_list( + &context.config.credentials_home_dir, + "What is the contract account ID?", + ) + } +} + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = CallFunctionContext)] +#[interactive_clap(output_context = FunctionContext)] +pub struct Function { + #[interactive_clap(skip_default_input_arg)] /// What is the name of the function? function_name: String, #[interactive_clap(value_enum)] @@ -20,51 +59,48 @@ pub struct CallFunctionProperties { prepaid_gas: PrepaidGas, } -#[derive(Debug, Clone)] -pub struct CallFunctionPropertiesContext { +#[derive(Clone)] +pub struct FunctionContext { global_context: crate::GlobalContext, - receiver_account_id: near_primitives::types::AccountId, + contract_account_id: near_primitives::types::AccountId, function_name: String, function_args: Vec, } -impl CallFunctionPropertiesContext { +impl FunctionContext { pub fn from_previous_context( - previous_context: crate::GlobalContext, - scope: &::InteractiveClapContextScope, + previous_context: CallFunctionContext, + scope: &::InteractiveClapContextScope, ) -> color_eyre::eyre::Result { let function_args = super::call_function_args_type::function_args( scope.function_args.clone(), scope.function_args_type.clone(), )?; Ok(Self { - global_context: previous_context, - receiver_account_id: scope.contract_account_id.clone().into(), + global_context: previous_context.global_context, + contract_account_id: previous_context.contract_account_id, function_name: scope.function_name.clone(), function_args, }) } } -impl CallFunctionProperties { - pub fn input_contract_account_id( - context: &crate::GlobalContext, - ) -> color_eyre::eyre::Result> { - crate::common::input_non_signer_account_id_from_used_account_list( - &context.config.credentials_home_dir, - "What is the contract account ID?", - ) - } - +impl Function { fn input_function_args_type( - _context: &crate::GlobalContext, + _context: &CallFunctionContext, ) -> color_eyre::eyre::Result> { super::call_function_args_type::input_function_args_type() } + + fn input_function_name( + context: &CallFunctionContext, + ) -> color_eyre::eyre::Result> { + super::input_call_function_name(&context.global_context, &context.contract_account_id) + } } #[derive(Debug, Clone, interactive_clap::InteractiveClap)] -#[interactive_clap(input_context = CallFunctionPropertiesContext)] +#[interactive_clap(input_context = FunctionContext)] #[interactive_clap(output_context = PrepaidGasContext)] pub struct PrepaidGas { #[interactive_clap(skip_default_input_arg)] @@ -78,7 +114,7 @@ pub struct PrepaidGas { #[derive(Debug, Clone)] pub struct PrepaidGasContext { global_context: crate::GlobalContext, - receiver_account_id: near_primitives::types::AccountId, + contract_account_id: near_primitives::types::AccountId, function_name: String, function_args: Vec, gas: crate::common::NearGas, @@ -86,12 +122,12 @@ pub struct PrepaidGasContext { impl PrepaidGasContext { pub fn from_previous_context( - previous_context: CallFunctionPropertiesContext, + previous_context: FunctionContext, scope: &::InteractiveClapContextScope, ) -> color_eyre::eyre::Result { Ok(Self { global_context: previous_context.global_context, - receiver_account_id: previous_context.receiver_account_id, + contract_account_id: previous_context.contract_account_id, function_name: previous_context.function_name, function_args: previous_context.function_args, gas: scope.gas, @@ -101,7 +137,7 @@ impl PrepaidGasContext { impl PrepaidGas { fn input_gas( - _context: &CallFunctionPropertiesContext, + _context: &FunctionContext, ) -> color_eyre::eyre::Result> { eprintln!(); Ok(Some( @@ -138,7 +174,7 @@ pub struct Deposit { #[derive(Debug, Clone)] pub struct DepositContext { global_context: crate::GlobalContext, - receiver_account_id: near_primitives::types::AccountId, + contract_account_id: near_primitives::types::AccountId, function_name: String, function_args: Vec, gas: crate::common::NearGas, @@ -152,7 +188,7 @@ impl DepositContext { ) -> color_eyre::eyre::Result { Ok(Self { global_context: previous_context.global_context, - receiver_account_id: previous_context.receiver_account_id, + contract_account_id: previous_context.contract_account_id, function_name: previous_context.function_name, function_args: previous_context.function_args, gas: previous_context.gas, @@ -189,7 +225,7 @@ pub struct SignerAccountId { #[derive(Debug, Clone)] pub struct SignerAccountIdContext { global_context: crate::GlobalContext, - receiver_account_id: near_primitives::types::AccountId, + contract_account_id: near_primitives::types::AccountId, function_name: String, function_args: Vec, gas: crate::common::NearGas, @@ -204,7 +240,7 @@ impl SignerAccountIdContext { ) -> color_eyre::eyre::Result { Ok(Self { global_context: previous_context.global_context, - receiver_account_id: previous_context.receiver_account_id, + contract_account_id: previous_context.contract_account_id, function_name: previous_context.function_name, function_args: previous_context.function_args, gas: previous_context.gas, @@ -219,7 +255,7 @@ impl From for crate::commands::ActionContext { let on_after_getting_network_callback: crate::commands::OnAfterGettingNetworkCallback = std::sync::Arc::new({ let signer_account_id = item.signer_account_id.clone(); - let receiver_account_id = item.receiver_account_id.clone(); + let receiver_account_id = item.contract_account_id.clone(); move |_network_config| { Ok(crate::commands::PrepopulatedTransaction { @@ -239,7 +275,7 @@ impl From for crate::commands::ActionContext { Self { global_context: item.global_context, - interacting_with_account_ids: vec![item.signer_account_id, item.receiver_account_id], + interacting_with_account_ids: vec![item.signer_account_id, item.contract_account_id], on_after_getting_network_callback, on_before_signing_callback: std::sync::Arc::new( |_prepolulated_unsinged_transaction, _network_config| Ok(()), diff --git a/src/commands/contract/call_function/mod.rs b/src/commands/contract/call_function/mod.rs index 539256cef..7d0834524 100644 --- a/src/commands/contract/call_function/mod.rs +++ b/src/commands/contract/call_function/mod.rs @@ -1,3 +1,4 @@ +use inquire::{Select, Text}; use strum::{EnumDiscriminants, EnumIter, EnumMessage}; mod as_read_only; @@ -21,5 +22,69 @@ pub enum CallFunctionActions { AsReadOnly(self::as_read_only::CallFunctionView), #[strum_discriminants(strum(message = "as-transaction - Calling a change method"))] /// Calling a change method - AsTransaction(self::as_transaction::CallFunctionProperties), + AsTransaction(self::as_transaction::CallFunction), +} + +pub fn input_call_function_name( + global_context: &crate::GlobalContext, + contract_account_id: &near_primitives::types::AccountId, +) -> color_eyre::eyre::Result> { + input_function_name( + global_context, + contract_account_id, + near_abi::AbiFunctionKind::Call, + "Select the as-transaction function for your contract:", + ) +} + +pub fn input_view_function_name( + global_context: &crate::GlobalContext, + contract_account_id: &near_primitives::types::AccountId, +) -> color_eyre::eyre::Result> { + input_function_name( + global_context, + contract_account_id, + near_abi::AbiFunctionKind::View, + "Select the viewing function for your contract:", + ) +} + +fn input_function_name( + global_context: &crate::GlobalContext, + contract_account_id: &near_primitives::types::AccountId, + function_kind: near_abi::AbiFunctionKind, + message: &str, +) -> color_eyre::eyre::Result> { + let network_config = crate::common::find_network_where_account_exist( + global_context, + contract_account_id.clone(), + ); + + if let Some(network_config) = network_config { + let json_rpc_client = network_config.json_rpc_client(); + if let Ok(contract_abi) = + tokio::runtime::Runtime::new() + .unwrap() + .block_on(super::inspect::get_contract_abi( + &json_rpc_client, + &near_primitives::types::Finality::Final.into(), + contract_account_id, + )) + { + let function_names = contract_abi + .body + .functions + .into_iter() + .filter(|function| function_kind == function.kind) + .map(|function| function.name) + .collect::>(); + return Ok(Some( + Select::new(message, function_names).prompt()?.to_string(), + )); + } + } + + Ok(Some( + Text::new("What is the name of the function?").prompt()?, + )) }