Skip to content

Commit 173a82c

Browse files
authored
Merge of #4055
2 parents 3e405ce + dc0904f commit 173a82c

File tree

4 files changed

+228
-22
lines changed

4 files changed

+228
-22
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- CLI: Allow to wrap a raw tx signed by someone else.
2+
([\#4055](https://github.com/anoma/namada/pull/4055))

crates/apps_lib/src/cli.rs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7557,26 +7557,14 @@ pub mod args {
75577557
))
75587558
.conflicts_with_all([EXPIRATION_OPT.name]),
75597559
)
7560-
.arg(
7561-
SIGNING_KEYS
7562-
.def()
7563-
.help(wrap!(
7564-
"Sign the transaction with the key for the given \
7565-
public key, public key hash or alias from your \
7566-
wallet."
7567-
))
7568-
.conflicts_with_all([SIGNATURES.name]),
7569-
)
7570-
.arg(
7571-
SIGNATURES
7572-
.def()
7573-
.help(wrap!(
7574-
"List of file paths containing a serialized signature \
7575-
to be attached to a transaction. Requires to provide \
7576-
a gas payer."
7577-
))
7578-
.conflicts_with_all([SIGNING_KEYS.name]),
7579-
)
7560+
.arg(SIGNING_KEYS.def().help(wrap!(
7561+
"Sign the transaction with the key for the given public key, \
7562+
public key hash or alias from your wallet."
7563+
)))
7564+
.arg(SIGNATURES.def().help(wrap!(
7565+
"List of file paths containing a serialized signature to be \
7566+
attached to a transaction. Requires to provide a gas payer."
7567+
)))
75807568
.arg(
75817569
WRAPPER_SIGNATURE_OPT
75827570
.def()

crates/sdk/src/signing.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,15 @@ pub async fn tx_signers(
170170
args: &args::Tx<SdkTypes>,
171171
default: Option<Address>,
172172
) -> Result<Vec<common::PublicKey>, Error> {
173-
let signer = if !&args.signing_keys.is_empty() {
173+
let signer = if !args.signing_keys.is_empty() {
174174
return Ok(args.signing_keys.clone());
175-
} else {
175+
} else if args.signatures.is_empty() {
176176
// Otherwise use the signer determined by the caller
177177
default
178+
} else {
179+
// If explicit signature(s) are provided signing keys are not required
180+
// anymore
181+
return Ok(vec![]);
178182
};
179183

180184
// Now actually fetch the signing key and apply it

crates/tests/src/integration/ledger_tests.rs

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use namada_node::shell::SnapshotSync;
2424
use namada_node::storage::DbSnapshot;
2525
use namada_sdk::account::AccountPublicKeysMap;
2626
use namada_sdk::collections::HashMap;
27+
use namada_sdk::error::TxSubmitError;
2728
use namada_sdk::migrations;
2829
use namada_sdk::queries::RPC;
2930
use namada_sdk::token::{self, DenominatedAmount};
@@ -2353,6 +2354,217 @@ fn scheduled_migration() -> Result<()> {
23532354
Ok(())
23542355
}
23552356

2357+
/// Test that a raw transaction can be wrapped and signed by someone else who
2358+
/// can pay for the gas fees for this tx.
2359+
///
2360+
/// 1. Create a new account
2361+
/// 2. Credit the new account with some tokens to reveal its PK and have tiny
2362+
/// amount left
2363+
/// 3. Reveal the PK of the new account
2364+
/// 4. Check that the new account doesn't have sufficient balance to submit a
2365+
/// transfer tx
2366+
/// 5. Dump a raw tx of a transfer from the new account
2367+
/// 6. Sign the raw transaction
2368+
/// 7. Wrap the raw transaction by another account and submit it
2369+
#[test]
2370+
fn wrap_tx_by_elsewho() -> Result<()> {
2371+
let (node, _services) = setup::setup()?;
2372+
2373+
// 1. Create a new account
2374+
let key_alias = "new-account";
2375+
let captured = CapturedOutput::of(|| {
2376+
run(
2377+
&node,
2378+
Bin::Wallet,
2379+
apply_use_device(vec![
2380+
"gen",
2381+
"--alias",
2382+
key_alias,
2383+
"--unsafe-dont-encrypt",
2384+
]),
2385+
)
2386+
});
2387+
assert!(captured.result.is_ok());
2388+
2389+
// 2. Credit the new account with some tokens to reveal its PK and have tiny
2390+
// amount left
2391+
let captured = CapturedOutput::of(|| {
2392+
run(
2393+
&node,
2394+
Bin::Client,
2395+
apply_use_device(vec![
2396+
"transparent-transfer",
2397+
"--source",
2398+
ALBERT,
2399+
"--target",
2400+
key_alias,
2401+
"--token",
2402+
NAM,
2403+
"--amount",
2404+
// transfer enough to cover reveal-pk gas fees (0.5) and to
2405+
// have only 0.000001 left after
2406+
"0.500001",
2407+
]),
2408+
)
2409+
});
2410+
assert!(captured.result.is_ok());
2411+
assert!(captured.contains(TX_APPLIED_SUCCESS));
2412+
2413+
// 3. Reveal the PK of the new account
2414+
let captured = CapturedOutput::of(|| {
2415+
run(
2416+
&node,
2417+
Bin::Client,
2418+
apply_use_device(vec!["reveal-pk", "--public-key", key_alias]),
2419+
)
2420+
});
2421+
assert!(captured.result.is_ok());
2422+
assert!(captured.contains(TX_APPLIED_SUCCESS));
2423+
2424+
// Assert that there's only the smallest possible non-zero amount left
2425+
let captured = CapturedOutput::of(|| {
2426+
run(
2427+
&node,
2428+
Bin::Client,
2429+
vec!["balance", "--owner", key_alias, "--token", NAM],
2430+
)
2431+
});
2432+
assert!(captured.result.is_ok());
2433+
assert!(captured.contains("nam: 0.000001"));
2434+
2435+
// 4. Check that the new account doesn't have sufficient balance to submit a
2436+
// transfer tx
2437+
let captured = CapturedOutput::of(|| {
2438+
run(
2439+
&node,
2440+
Bin::Client,
2441+
apply_use_device(vec![
2442+
"transparent-transfer",
2443+
"--source",
2444+
key_alias,
2445+
"--target",
2446+
ALBERT,
2447+
"--token",
2448+
NAM,
2449+
"--amount",
2450+
"0.000001",
2451+
]),
2452+
)
2453+
});
2454+
assert!(captured.result.is_err());
2455+
assert_matches!(
2456+
captured
2457+
.result
2458+
.unwrap_err()
2459+
.downcast_ref::<namada_sdk::error::Error>()
2460+
.unwrap(),
2461+
namada_sdk::error::Error::Tx(TxSubmitError::BalanceTooLowForFees(
2462+
_,
2463+
_,
2464+
_,
2465+
_
2466+
))
2467+
);
2468+
2469+
// 5. Dump a raw tx of a transfer from the new account
2470+
let output_folder = node.test_dir.path();
2471+
let captured = CapturedOutput::of(|| {
2472+
run(
2473+
&node,
2474+
Bin::Client,
2475+
apply_use_device(vec![
2476+
"transparent-transfer",
2477+
"--source",
2478+
key_alias,
2479+
"--target",
2480+
ALBERT,
2481+
"--token",
2482+
NAM,
2483+
"--amount",
2484+
"0.000001",
2485+
"--gas-payer",
2486+
CHRISTEL_KEY,
2487+
"--dump-tx",
2488+
"--output-folder-path",
2489+
&output_folder.to_str().unwrap(),
2490+
]),
2491+
)
2492+
});
2493+
assert!(captured.result.is_ok());
2494+
2495+
let tx = find_files_with_ext(output_folder, "tx")
2496+
.unwrap()
2497+
.first()
2498+
.expect("Offline tx should be found.")
2499+
.to_path_buf()
2500+
.display()
2501+
.to_string();
2502+
2503+
// 6. Sign the raw transaction
2504+
let captured = CapturedOutput::of(|| {
2505+
run(
2506+
&node,
2507+
Bin::Client,
2508+
apply_use_device(vec![
2509+
"utils",
2510+
"sign-offline",
2511+
"--data-path",
2512+
&tx,
2513+
"--secret-keys",
2514+
&key_alias,
2515+
"--output-folder-path",
2516+
&output_folder.to_str().unwrap(),
2517+
]),
2518+
)
2519+
});
2520+
assert!(captured.result.is_ok());
2521+
2522+
let sig_files = find_files_with_ext(output_folder, "sig").unwrap();
2523+
assert_eq!(sig_files.len(), 1);
2524+
let offline_sig = sig_files.first().unwrap().to_str().unwrap();
2525+
2526+
// 7. Wrap the raw transaction by another account and submit it
2527+
let captured = CapturedOutput::of(|| {
2528+
run(
2529+
&node,
2530+
Bin::Client,
2531+
apply_use_device(vec![
2532+
"tx",
2533+
"--tx-path",
2534+
&tx,
2535+
"--signatures",
2536+
&offline_sig,
2537+
"--gas-payer",
2538+
CHRISTEL_KEY,
2539+
]),
2540+
)
2541+
});
2542+
assert!(captured.result.is_ok());
2543+
assert!(captured.contains(TX_APPLIED_SUCCESS));
2544+
2545+
// Assert changed balances
2546+
let captured = CapturedOutput::of(|| {
2547+
run(
2548+
&node,
2549+
Bin::Client,
2550+
vec!["balance", "--owner", ALBERT, "--token", NAM],
2551+
)
2552+
});
2553+
assert!(captured.contains("nam: 1979999.5\n"));
2554+
2555+
let captured = CapturedOutput::of(|| {
2556+
run(
2557+
&node,
2558+
Bin::Client,
2559+
vec!["balance", "--owner", key_alias, "--token", NAM],
2560+
)
2561+
});
2562+
assert!(captured.result.is_ok());
2563+
assert!(captured.contains("nam: 0\n"));
2564+
2565+
Ok(())
2566+
}
2567+
23562568
fn make_migration_json() -> (Hash, tempfile::NamedTempFile) {
23572569
let file = tempfile::Builder::new().tempfile().expect("Test failed");
23582570
let updates = [migrations::DbUpdateType::Add {

0 commit comments

Comments
 (0)