Skip to content

feat: invoke from XDR args#2460

Open
ifropc wants to merge 2 commits intostellar:mainfrom
ifropc:invoke-xdr
Open

feat: invoke from XDR args#2460
ifropc wants to merge 2 commits intostellar:mainfrom
ifropc:invoke-xdr

Conversation

@ifropc
Copy link
Contributor

@ifropc ifropc commented Mar 23, 2026

What

Add ability to call contract invoke with base64 encoded InvokeContractArgs

Why

This is very useful for invoking contracts when arguments are already in JSON or XDR
One particular case is replaying transactions (e.g. after testnet reset one can simply re-apply all previous transactions by simply reading the chain and storing all XDRs)
This is much more simple that writing custom scripts where invoker must know all argument names, and correctly transform JSON XDR into the format the cli expects

Known limitations

Blocked by #2459 as all address arguments are regular public keys, so it's impossible to properly invoke (with sign and sending) such transactions

Testing

Example:

  1. Encode arguments into XDR
image
AAAAAUX05neMv38MV/DiJGmlSlHA3w+b3GqbZnnPr7mPVHm+AAAABmRlcGxveQAAAAAABgAAAA4AAAADYWNjAAAAAAEAAAAOAAAAGWFjY190ZXN0X25ld192ZXJzaW9uX21vcmUAAAAAAAASAAAAAAAAAAAebE8Ewg9uzvHRAl+UmvP0kixsyp238kkz0zOy+91FeQAAAAEAAAAB
  1. > cargo run contract invoke --source-account alice --id CBC7JZTXRS7X6DCX6DRCI2NFJJI4BXYPTPOGVG3GPHH27OMPKR434BSX --invoke-contract-args AAAAAUX05neMv38MV/DiJGmlSlHA3w+b3GqbZnnPr7mPVHm+AAAABmRlcGxveQAAAAAABgAAAA4AAAADYWNjAAAAAAEAAAAOAAAAGWFjY190ZXN0X25ld192ZXJzaW9uX21vcmUAAAAAAAASAAAAAAAAAAAebE8Ewg9uzvHRAl+UmvP0kixsyp238kkz0zOy+91FeQAAAAEAAAAB --build-only
AAAAAgAAAAAebE8Ewg9uzvHRAl+UmvP0kixsyp238kkz0zOy+91FeQAAAGQABNeOAAAAIwAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAFF9OZ3jL9/DFfw4iRppUpRwN8Pm9xqm2Z5z6+5j1R5vgAAAAZkZXBsb3kAAAAAAAYAAAAOAAAAA2FjYwAAAAABAAAADgAAABlhY2NfdGVzdF9uZXdfdmVyc2lvbl9tb3JlAAAAAAAAEgAAAAAAAAAAHmxPBMIPbs7x0QJflJrz9JIsbMqdt/JJM9MzsvvdRXkAAAABAAAAAQAAAAAAAAAAAAAAAA==
  1. > cargo run contract invoke --source-account alice --id CBC7JZTXRS7X6DCX6DRCI2NFJJI4BXYPTPOGVG3GPHH27OMPKR434BSX --build-only -- deploy --wasm-name acc --contract-name acc_test_new_version_more --admin GAPGYTYEYIHW5TXR2EBF7FE26P2JELDMZKO3P4SJGPJTHMX33VCXSKV7
AAAAAgAAAAAebE8Ewg9uzvHRAl+UmvP0kixsyp238kkz0zOy+91FeQAAAGQABNeOAAAAIwAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAFF9OZ3jL9/DFfw4iRppUpRwN8Pm9xqm2Z5z6+5j1R5vgAAAAZkZXBsb3kAAAAAAAYAAAAOAAAAA2FjYwAAAAABAAAADgAAABlhY2NfdGVzdF9uZXdfdmVyc2lvbl9tb3JlAAAAAAAAEgAAAAAAAAAAHmxPBMIPbs7x0QJflJrz9JIsbMqdt/JJM9MzsvvdRXkAAAABAAAAAQAAAAAAAAAAAAAAAA==
  1. Verify output is identical
image

Copilot AI review requested due to automatic review settings March 23, 2026 04:22
@github-project-automation github-project-automation bot moved this to Backlog (Not Ready) in DevX Mar 23, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an alternate contract invocation flow that accepts pre-encoded base64 XDR arguments (string or file input), and removes a local whitespace-stripping reader now that stellar-xdr provides SkipWhitespace.

Changes:

  • Use stellar_xdr::curr::SkipWhitespace directly (remove local SkipWhitespace shim).
  • Add --invoke-contract-args to stellar contract invoke and make the existing “slop” args optional/alternative.
  • Implement XDR decoding helpers in contract arg parsing to build invoke parameters from base64 XDR.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
cmd/soroban-cli/src/commands/tx/xdr.rs Switches to upstream SkipWhitespace and removes local implementation.
cmd/soroban-cli/src/commands/contract/invoke.rs Adds --invoke-contract-args and routes invocation parameter building accordingly.
cmd/soroban-cli/src/commands/contract/arg_parsing.rs Adds XDR decoding path for InvokeContractArgs and plumbing to build host function parameters from it.

let mut lim = Limited::new(SkipWhitespace::new(read), Limits::none());
InvokeContractArgs::read_xdr_base64_to_end(&mut lim).map_err(|e| CannotParseXDR { error: e })
}

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New XDR-based invocation path (build_host_function_parameters_from_string_xdr / invoke_contract_args_from_input) isn’t covered by tests in this module. Since this file already has a #[cfg(test)] suite, please add tests for parsing from (1) a base64 string and (2) a file path, plus an invalid-XDR case to assert the CannotParseXDR error formatting.

Suggested change
#[cfg(test)]
mod xdr_invoke_tests {
use super::{invoke_contract_args_from_input, Error};
use crate::xdr::{self, Hash, InvokeContractArgs, ScAddress, ScVal, ScVec, Symbol, WriteXdr};
use std::ffi::OsString;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn make_test_invoke_args() -> InvokeContractArgs {
InvokeContractArgs {
contract_address: ScAddress::Contract(Hash([0; 32])),
function_name: Symbol::try_from("test_func").unwrap(),
args: ScVec(Some(vec![ScVal::I32(7)])),
}
}
#[test]
fn parse_invoke_args_from_base64_string() {
let invoke = make_test_invoke_args();
let b64 = invoke.to_xdr_base64().expect("serialize to base64");
let input = OsString::from(b64);
let parsed = invoke_contract_args_from_input(&input).expect("parse from base64 string");
assert_eq!(parsed.function_name, invoke.function_name);
assert_eq!(parsed.args.0.as_ref().unwrap().len(), 1);
assert_eq!(parsed.args.0.as_ref().unwrap()[0], ScVal::I32(7));
}
#[test]
fn parse_invoke_args_from_file_path() {
let invoke = make_test_invoke_args();
let b64 = invoke.to_xdr_base64().expect("serialize to base64");
let mut path = std::env::temp_dir();
path.push("soroban_invoke_args_test.xdr");
{
let mut file = File::create(&path).expect("create temp file");
file.write_all(b64.as_bytes())
.expect("write base64 XDR to temp file");
}
let input = OsString::from(path.as_os_str());
let parsed = invoke_contract_args_from_input(&input).expect("parse from file path");
assert_eq!(parsed.function_name, invoke.function_name);
assert_eq!(parsed.args.0.as_ref().unwrap().len(), 1);
assert_eq!(parsed.args.0.as_ref().unwrap()[0], ScVal::I32(7));
}
#[test]
fn invalid_xdr_reports_cannot_parse_xdr_error() {
let input = OsString::from("not-a-valid-base64-xdr");
let err = invoke_contract_args_from_input(&input).expect_err("expected parse error");
match err {
Error::CannotParseXDR { .. } => {
let msg = format!("{err}");
// Ensure the formatted error message clearly indicates an XDR parse failure.
assert!(
msg.contains("XDR"),
"expected error message to mention XDR, got: {msg}"
);
}
other => panic!("expected CannotParseXDR, got: {other:?}"),
}
}
}

Copilot uses AI. Check for mistakes.
@@ -55,9 +56,16 @@ pub struct Cmd {
#[arg(long, env = "STELLAR_INVOKE_VIEW")]
pub is_view: bool,

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new --invoke-contract-args option is missing a doc comment/help text. Please document what value it expects (base64 XDR string vs path to a file containing base64 XDR) and, if applicable, whether whitespace/newlines are allowed.

Suggested change
/// Raw base64-encoded XDR `InvokeContractArgs` to invoke directly, instead of specifying a
/// function name and arguments. The value must be a single base64 string (no whitespace or
/// newlines) and is interpreted as the XDR payload itself, not as a path to a file.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +68
#[arg(long, conflicts_with = "CONTRACT_FN_AND_ARGS")]
pub invoke_contract_args: Option<OsString>,

/// Function name as subcommand, then arguments for that function as `--arg-name value`
#[arg(last = true, id = "CONTRACT_FN_AND_ARGS")]
pub slop: Vec<OsString>,
#[arg(
last = true,
id = "CONTRACT_FN_AND_ARGS",
conflicts_with = "invoke_contract_args"
)]
pub slop: Option<Vec<OsString>>,
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description still contains TODO placeholders for the What/Why/Known limitations sections. Please fill these in so reviewers/users understand the intent and any constraints of adding --invoke-contract-args support.

Copilot uses AI. Check for mistakes.
Comment on lines +166 to +175
let read: &mut dyn Read = {
let exist = Path::new(input).try_exists();
if let Ok(true) = exist {
&mut File::open(input)?
} else {
&mut Cursor::new(input.clone().into_encoded_bytes())
}
};

let mut lim = Limited::new(SkipWhitespace::new(read), Limits::none());
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invoke_contract_args_from_input returns &mut dyn Read pointing at temporaries created in a block (&mut File::open(...) / &mut Cursor::new(...)). This pattern can fail to compile with “temporary value dropped while borrowed” (temporary lifetime extension doesn’t reliably apply through a block), and it’s fragile to refactors. Prefer an owned reader (e.g., Box<dyn Read> or an enum) and then pass &mut *reader into SkipWhitespace::new/Limited::new.

Suggested change
let read: &mut dyn Read = {
let exist = Path::new(input).try_exists();
if let Ok(true) = exist {
&mut File::open(input)?
} else {
&mut Cursor::new(input.clone().into_encoded_bytes())
}
};
let mut lim = Limited::new(SkipWhitespace::new(read), Limits::none());
let mut reader: Box<dyn Read> = {
let exist = Path::new(input).try_exists();
if let Ok(true) = exist {
Box::new(File::open(input)?)
} else {
Box::new(Cursor::new(input.clone().into_encoded_bytes()))
}
};
let mut lim = Limited::new(SkipWhitespace::new(&mut *reader), Limits::none());

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

2 participants