diff --git a/src/extrinsic/balances.rs b/src/extrinsic/balances.rs index 709a9d816..ebb4a2c57 100644 --- a/src/extrinsic/balances.rs +++ b/src/extrinsic/balances.rs @@ -30,11 +30,15 @@ use codec::{Compact, Encode}; pub const BALANCES_MODULE: &str = "Balances"; pub const TRANSFER_ALLOW_DEATH: &str = "transfer_allow_death"; +pub const TRANSFER_KEEP_ALIVE: &str = "transfer_keep_alive"; pub const FORCE_SET_BALANCE: &str = "force_set_balance"; /// Call for a balance transfer. pub type TransferAllowDeathCall = (CallIndex, Address, Compact); +/// Call for a balance transfer. +pub type TransferKeepAliveCall = (CallIndex, Address, Compact); + /// Call to the balance of an account. pub type ForceSetBalanceCall = (CallIndex, Address, Compact); @@ -52,6 +56,14 @@ pub trait BalancesExtrinsics { amount: Self::Balance, ) -> Option>>; + /// Transfer some liquid free balance to another account. + #[allow(clippy::type_complexity)] + async fn balance_transfer_keep_alive( + &self, + to: Self::Address, + amount: Self::Balance, + ) -> Option>>; + /// Set the balances of a given account. #[allow(clippy::type_complexity)] async fn balance_force_set_balance( @@ -85,6 +97,15 @@ where compose_extrinsic!(self, BALANCES_MODULE, TRANSFER_ALLOW_DEATH, to, Compact(amount)) } + #[allow(clippy::type_complexity)] + async fn balance_transfer_keep_alive( + &self, + to: Self::Address, + amount: Self::Balance, + ) -> Option>> { + compose_extrinsic!(self, BALANCES_MODULE, TRANSFER_KEEP_ALIVE, to, Compact(amount)) + } + async fn balance_force_set_balance( &self, who: Self::Address, diff --git a/testing/async/examples/dispatch_errors_tests.rs b/testing/async/examples/dispatch_errors_tests.rs index 0ebc516b7..aa740d924 100644 --- a/testing/async/examples/dispatch_errors_tests.rs +++ b/testing/async/examples/dispatch_errors_tests.rs @@ -20,7 +20,7 @@ use sp_keyring::AccountKeyring; use sp_runtime::MultiAddress; use substrate_api_client::{ ac_primitives::AssetRuntimeConfig, extrinsic::BalancesExtrinsics, rpc::JsonrpseeClient, Api, - Error, GetAccountInformation, SubmitAndWatch, XtStatus, + Error, GetAccountInformation, GetBalance, SubmitAndWatch, XtStatus, }; #[tokio::main] @@ -33,7 +33,7 @@ async fn main() { let alice = AccountKeyring::Alice.to_account_id(); let balance_of_alice = api.get_account_data(&alice).await.unwrap().unwrap().free; - println!("[+] Alice's Free Balance is is {}\n", balance_of_alice); + println!("[+] Alice's Free Balance is {}\n", balance_of_alice); let bob = AccountKeyring::Bob.to_account_id(); let balance_of_bob = api.get_account_data(&bob).await.unwrap().unwrap_or_default().free; @@ -59,10 +59,10 @@ async fn main() { assert!(report.block_hash.is_some()); assert!(report.events.is_some()); assert!(format!("{dispatch_error:?}").contains("BadOrigin")); + println!("[+] BadOrigin error: Bob can't force set balance"); }, _ => panic!("Expected Failed Extrinisc Error"), } - println!("[+] BadOrigin error: Bob can't force set balance"); //BelowMinimum api.set_signer(alice_signer.into()); @@ -80,5 +80,9 @@ async fn main() { }, _ => panic!("Expected Failed Extrinisc Error"), } - println!("[+] BelowMinimum error: balance (999999) is below the existential deposit"); + let existential_deposit = api.get_existential_deposit().await.unwrap(); + println!( + "[+] BelowMinimum error: balance (999999) is below the existential deposit ({})", + &existential_deposit + ); } diff --git a/testing/async/examples/pallet_balances_tests.rs b/testing/async/examples/pallet_balances_tests.rs index 0eae6abca..f379a9e78 100644 --- a/testing/async/examples/pallet_balances_tests.rs +++ b/testing/async/examples/pallet_balances_tests.rs @@ -15,15 +15,65 @@ //! Tests for the pallet balances interface functions. +use sp_keyring::AccountKeyring; use substrate_api_client::{ - ac_primitives::AssetRuntimeConfig, rpc::JsonrpseeClient, Api, GetBalance, + ac_primitives::AssetRuntimeConfig, extrinsic::BalancesExtrinsics, rpc::JsonrpseeClient, Api, + GetAccountInformation, GetBalance, SubmitAndWatch, XtStatus, }; #[tokio::main] async fn main() { // Setup let client = JsonrpseeClient::with_default_url().await.unwrap(); - let api = Api::::new(client).await.unwrap(); + let mut api = Api::::new(client).await.unwrap(); - let _ed = api.get_existential_deposit().await.unwrap(); + let ed = api.get_existential_deposit().await.unwrap(); + println!("[+] Existential deposit is {}\n", ed); + + let alice = AccountKeyring::Alice.to_account_id(); + let alice_signer = AccountKeyring::Alice.pair(); + api.set_signer(alice_signer.into()); + let balance_of_alice = api.get_account_data(&alice).await.unwrap().unwrap().free; + println!("[+] Alice's Free Balance is {}\n", balance_of_alice); + + let bob = AccountKeyring::Bob.to_account_id(); + let balance_of_bob = api.get_account_data(&bob).await.unwrap().unwrap_or_default().free; + println!("[+] Bob's Free Balance is {}\n", balance_of_bob); + + // Rough estimate of fees for three transactions + let fee_estimate = 3 * 2000000000000; + + let xt = api + .balance_transfer_keep_alive(bob.clone().into(), balance_of_alice / 2 - fee_estimate) + .await + .unwrap(); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Finalized).await; + // This call should succeed as alice has enough money + assert!(report.is_ok()); + + let balance_of_alice = api.get_account_data(&alice).await.unwrap().unwrap().free; + println!("[+] Alice's Free Balance is {}\n", balance_of_alice); + + let xt = api + .balance_transfer_keep_alive(bob.clone().into(), balance_of_alice - fee_estimate) + .await + .unwrap(); + let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Finalized).await; + // This call should fail as alice would fall below the existential deposit + assert!(report.is_err()); + + let xt = api + .balance_transfer_allow_death(bob.clone().into(), balance_of_alice - fee_estimate) + .await + .unwrap(); + let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::Finalized).await; + // With allow_death the call should succeed + assert!(result.is_ok()); + + let alice_account = api.get_account_data(&alice).await.unwrap(); + // Alice account should not exist anymore so we excpect an error + assert!(alice_account.is_none()); + + let balance_of_bob = api.get_account_data(&bob).await.unwrap().unwrap_or_default().free; + println!("[+] Bob's Free Balance is {}\n", balance_of_bob); }