Skip to content

Commit 2e6ea8f

Browse files
committed
fix: chain & contract manual interaction
1 parent 2004ce6 commit 2e6ea8f

File tree

5 files changed

+187
-40
lines changed

5 files changed

+187
-40
lines changed

crates/pop-chains/src/call/metadata/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,13 +591,13 @@ pub fn find_callable_by_name(
591591
) -> Result<CallItem, Error> {
592592
let pallet = find_pallet_by_name(pallets, pallet_name)?;
593593
if let Some(function) = pallet.functions.iter().find(|&e| e.name == function_name) {
594-
return Ok(CallItem::Function(function.clone()))
594+
return Ok(CallItem::Function(function.clone()));
595595
}
596596
if let Some(constant) = pallet.constants.iter().find(|&e| e.name == function_name) {
597-
return Ok(CallItem::Constant(constant.clone()))
597+
return Ok(CallItem::Constant(constant.clone()));
598598
}
599599
if let Some(storage) = pallet.state.iter().find(|&e| e.name == function_name) {
600-
return Ok(CallItem::Storage(storage.clone()))
600+
return Ok(CallItem::Storage(storage.clone()));
601601
}
602602
Err(Error::FunctionNotFound(format!(
603603
"Could not find a function, constant or storage with the name \"{function_name}\""

crates/pop-cli/src/commands/call/chain.rs

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
use anyhow::{Result, anyhow};
1515
use clap::Args;
1616
use pop_chains::{
17-
Action, CallData, CallItem, DynamicPayload, OnlineClient, Pallet, Param, Payload,
17+
Action, CallData, CallItem, DynamicPayload, Function, OnlineClient, Pallet, Param, Payload,
1818
SubstrateConfig, construct_extrinsic, construct_sudo_extrinsic, decode_call_data,
1919
encode_call_data, find_callable_by_name, find_pallet_by_name, raw_value_to_string,
2020
render_storage_key_values, sign_and_submit_extrinsic, supported_actions, type_to_param,
@@ -272,16 +272,7 @@ impl CallChainCommand {
272272
}
273273

274274
// Resolve dispatchable function arguments.
275-
let args = if self.args.is_empty() {
276-
let mut args = Vec::new();
277-
for param in &function.params {
278-
let input = prompt_for_param(cli, param, true)?;
279-
args.push(input);
280-
}
281-
args
282-
} else {
283-
self.expand_file_arguments()?
284-
};
275+
let args = self.resolve_function_args(function, cli)?;
285276

286277
// If the chain has sudo prompt the user to confirm if they want to execute the
287278
// call via sudo.
@@ -493,6 +484,36 @@ impl CallChainCommand {
493484
})
494485
.collect()
495486
}
487+
488+
/// Resolves dispatchable arguments by leveraging CLI-provided values when available.
489+
fn resolve_function_args(
490+
&mut self,
491+
function: &Function,
492+
cli: &mut impl Cli,
493+
) -> Result<Vec<String>> {
494+
let expanded_args = self.expand_file_arguments()?;
495+
if expanded_args.len() > function.params.len() {
496+
return Err(anyhow!(
497+
"Expected {} arguments for `{}`, but received {}. Remove the extra values or run \
498+
without `--args` to be prompted.",
499+
function.params.len(),
500+
function.name,
501+
expanded_args.len()
502+
));
503+
}
504+
505+
let mut resolved_args = Vec::with_capacity(function.params.len());
506+
for (idx, param) in function.params.iter().enumerate() {
507+
if let Some(value) = expanded_args.get(idx) {
508+
resolved_args.push(value.clone());
509+
} else {
510+
resolved_args.push(prompt_for_param(cli, param, true)?);
511+
}
512+
}
513+
514+
self.args = resolved_args.clone();
515+
Ok(resolved_args)
516+
}
496517
}
497518

498519
/// Represents a configured dispatchable function call, including the pallet, function, arguments,
@@ -1129,6 +1150,45 @@ mod tests {
11291150
Ok(())
11301151
}
11311152

1153+
#[test]
1154+
fn resolve_function_args_preserves_cli_values() -> Result<()> {
1155+
let function = Function {
1156+
pallet: "System".to_string(),
1157+
name: "remark".to_string(),
1158+
params: vec![Param { name: "remark".to_string(), ..Default::default() }],
1159+
is_supported: true,
1160+
..Default::default()
1161+
};
1162+
let mut call_config =
1163+
CallChainCommand { args: vec!["0x11".to_string()], ..Default::default() };
1164+
let mut cli = MockCli::new();
1165+
let resolved = call_config.resolve_function_args(&function, &mut cli)?;
1166+
assert_eq!(resolved, vec!["0x11".to_string()]);
1167+
cli.verify()
1168+
}
1169+
1170+
#[test]
1171+
fn resolve_function_args_prompts_for_missing_values() -> Result<()> {
1172+
let function = Function {
1173+
pallet: "System".to_string(),
1174+
name: "remark".to_string(),
1175+
params: vec![
1176+
Param { name: "first".to_string(), ..Default::default() },
1177+
Param { name: "second".to_string(), ..Default::default() },
1178+
],
1179+
is_supported: true,
1180+
..Default::default()
1181+
};
1182+
let mut call_config =
1183+
CallChainCommand { args: vec!["0x11".to_string()], ..Default::default() };
1184+
let mut cli =
1185+
MockCli::new().expect_input("Enter the value for the parameter: second", "0x22".into());
1186+
let resolved = call_config.resolve_function_args(&function, &mut cli)?;
1187+
assert_eq!(resolved, vec!["0x11".to_string(), "0x22".to_string()]);
1188+
assert_eq!(call_config.args, resolved);
1189+
cli.verify()
1190+
}
1191+
11321192
#[test]
11331193
fn parse_pallet_name_works() -> Result<()> {
11341194
assert_eq!(parse_pallet_name("system").unwrap(), "System");

crates/pop-cli/src/commands/call/contract.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ impl CallContractCommand {
246246
}
247247

248248
fn configure_message(&mut self, message: &ContractFunction, cli: &mut impl Cli) -> Result<()> {
249-
// Resolve message arguments.
250-
self.args = request_contract_function_args(message, cli)?;
249+
let resolved_args = request_contract_function_args(message, cli, Some(&self.args))?;
250+
self.args = resolved_args;
251251

252252
// Resolve value.
253253
if message.payable && self.value == DEFAULT_PAYABLE_VALUE {
@@ -647,7 +647,7 @@ mod tests {
647647
cli::MockCli,
648648
common::{urls, wallet::USE_WALLET_PROMPT},
649649
};
650-
use pop_contracts::{mock_build_process, new_environment};
650+
use pop_contracts::{Param, mock_build_process, new_environment};
651651
use std::{env, fs::write};
652652
use url::Url;
653653

@@ -733,6 +733,36 @@ mod tests {
733733
cli.verify()
734734
}
735735

736+
#[test]
737+
fn configure_message_prompts_for_remaining_args() -> Result<()> {
738+
let message = ContractFunction {
739+
label: "run".into(),
740+
payable: false,
741+
args: vec![
742+
Param { label: "first".into(), type_name: "u32".into() },
743+
Param { label: "second".into(), type_name: "u32".into() },
744+
],
745+
docs: String::new(),
746+
default: false,
747+
mutates: true,
748+
};
749+
750+
let mut command = CallContractCommand {
751+
args: vec!["10".to_string()],
752+
value: DEFAULT_PAYABLE_VALUE.to_string(),
753+
dev_mode: true,
754+
..Default::default()
755+
};
756+
757+
let mut cli =
758+
MockCli::new().expect_input("Enter the value for the parameter: second", "20".into());
759+
760+
command.configure_message(&message, &mut cli)?;
761+
762+
assert_eq!(command.args, vec!["10".to_string(), "20".to_string()]);
763+
cli.verify()
764+
}
765+
736766
// This test only covers the interactive portion of the call contract command, without actually
737767
// calling the contract.
738768
#[tokio::test]

crates/pop-cli/src/commands/up/contract.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,8 @@ impl UpContractCommand {
343343

344344
let function =
345345
extract_function(self.path.clone(), &self.constructor, FunctionType::Constructor)?;
346-
if self.args.is_empty() && !function.args.is_empty() {
347-
self.args = request_contract_function_args(&function, &mut Cli)?;
346+
if !function.args.is_empty() {
347+
self.args = request_contract_function_args(&function, &mut Cli, Some(&self.args))?;
348348
}
349349
normalize_call_args(&mut self.args, &function);
350350
// Otherwise instantiate.

crates/pop-cli/src/common/contracts.rs

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,26 @@ pub fn has_contract_been_built(path: &Path) -> bool {
114114
pub fn request_contract_function_args(
115115
function: &ContractFunction,
116116
cli: &mut impl Cli,
117+
existing_args: Option<&[String]>,
117118
) -> anyhow::Result<Vec<String>> {
118-
let mut user_provided_args = Vec::new();
119-
for arg in &function.args {
119+
let mut resolved_args = Vec::with_capacity(function.args.len());
120+
let provided_args = existing_args.unwrap_or(&[]);
121+
122+
if provided_args.len() > function.args.len() {
123+
return Err(anyhow::anyhow!(
124+
"Expected {} arguments for `{}`, but received {}. Remove the extra values or run \
125+
without `--args` to be prompted.",
126+
function.args.len(),
127+
function.label,
128+
provided_args.len()
129+
));
130+
}
131+
132+
for (idx, arg) in function.args.iter().enumerate() {
133+
if let Some(value) = provided_args.get(idx) {
134+
resolved_args.push(value.clone());
135+
continue;
136+
}
120137
let mut input = cli
121138
.input(format!("Enter the value for the parameter: {}", arg.label))
122139
.placeholder(&format!("Type required: {}", arg.type_name));
@@ -125,9 +142,10 @@ pub fn request_contract_function_args(
125142
if arg.type_name.starts_with("Option<") {
126143
input = input.default_input("");
127144
}
128-
user_provided_args.push(input.interact()?);
145+
resolved_args.push(input.interact()?);
129146
}
130-
Ok(user_provided_args)
147+
148+
Ok(resolved_args)
131149
}
132150

133151
/// Normalizes contract arguments before execution.
@@ -184,13 +202,8 @@ mod tests {
184202
use cliclack::spinner;
185203
use duct::cmd;
186204
use pop_common::{find_free_port, set_executable_permission};
187-
use pop_contracts::{
188-
FunctionType, Param, extract_function, is_chain_alive, run_eth_rpc_node, run_ink_node,
189-
};
190-
use std::{
191-
env,
192-
fs::{self, File},
193-
};
205+
use pop_contracts::{Param, is_chain_alive, run_eth_rpc_node, run_ink_node};
206+
use std::fs::{self, File};
194207
use url::Url;
195208

196209
#[test]
@@ -214,18 +227,62 @@ mod tests {
214227
Ok(())
215228
}
216229

217-
#[tokio::test]
218-
async fn request_contract_function_args_works() -> anyhow::Result<()> {
219-
let mut current_dir = env::current_dir().expect("Failed to get current directory");
220-
current_dir.pop();
230+
#[test]
231+
fn request_contract_function_args_works() -> anyhow::Result<()> {
232+
let function = ContractFunction {
233+
label: "new".to_string(),
234+
payable: false,
235+
args: vec![Param { label: "init_value".into(), type_name: "bool".into() }],
236+
docs: String::new(),
237+
default: false,
238+
mutates: true,
239+
};
221240
let mut cli = MockCli::new()
222241
.expect_input("Enter the value for the parameter: init_value", "true".into());
223-
let function = extract_function(
224-
current_dir.join("pop-contracts/tests/files/testing.json"),
225-
"new",
226-
FunctionType::Constructor,
227-
)?;
228-
assert_eq!(request_contract_function_args(&function, &mut cli)?, vec!["true"]);
242+
assert_eq!(request_contract_function_args(&function, &mut cli, None)?, vec!["true"]);
243+
cli.verify()
244+
}
245+
246+
#[test]
247+
fn request_contract_function_args_respects_existing() -> anyhow::Result<()> {
248+
let mut cli =
249+
MockCli::new().expect_input("Enter the value for the parameter: number", "2".into());
250+
let function = ContractFunction {
251+
label: "specific_flip".to_string(),
252+
payable: true,
253+
args: vec![
254+
Param { label: "new_value".into(), type_name: "bool".into() },
255+
Param { label: "number".into(), type_name: "Option<u32>".into() },
256+
],
257+
docs: String::new(),
258+
default: false,
259+
mutates: true,
260+
};
261+
let existing = vec!["true".to_string()];
262+
assert_eq!(
263+
request_contract_function_args(&function, &mut cli, Some(&existing))?,
264+
vec!["true", "2"]
265+
);
266+
cli.verify()
267+
}
268+
269+
#[test]
270+
fn request_contract_function_args_preserves_preprovided_args() -> anyhow::Result<()> {
271+
let function = ContractFunction {
272+
label: "specific_flip".into(),
273+
payable: true,
274+
args: vec![
275+
Param { label: "new_value".into(), type_name: "bool".into() },
276+
Param { label: "number".into(), type_name: "Option<u32>".into() },
277+
],
278+
docs: String::new(),
279+
default: false,
280+
mutates: true,
281+
};
282+
283+
let mut cli = MockCli::new();
284+
let existing = vec!["true".to_string(), "Some(2)".to_string()];
285+
assert_eq!(request_contract_function_args(&function, &mut cli, Some(&existing))?, existing);
229286
cli.verify()
230287
}
231288

0 commit comments

Comments
 (0)