@@ -24,6 +24,7 @@ use namada_node::shell::SnapshotSync;
2424use namada_node:: storage:: DbSnapshot ;
2525use namada_sdk:: account:: AccountPublicKeysMap ;
2626use namada_sdk:: collections:: HashMap ;
27+ use namada_sdk:: error:: TxSubmitError ;
2728use namada_sdk:: migrations;
2829use namada_sdk:: queries:: RPC ;
2930use 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+
23562568fn 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