From f9f4a4b6ecf4a4ebee200ffbc0622e1f7aaef468 Mon Sep 17 00:00:00 2001 From: Thomas Niederberger Date: Tue, 11 Jun 2024 11:21:30 +0200 Subject: [PATCH 1/3] Implement functionality --- src/extrinsic/balances.rs | 21 ++++++++++ .../async/examples/dispatch_errors_tests.rs | 10 +++-- .../async/examples/pallet_balances_tests.rs | 41 ++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) 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..c1ab12e88 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] @@ -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..cfb6b1039 100644 --- a/testing/async/examples/pallet_balances_tests.rs +++ b/testing/async/examples/pallet_balances_tests.rs @@ -15,15 +15,52 @@ //! Tests for the pallet balances interface functions. +use sp_core::H256; +use sp_keyring::AccountKeyring; +use sp_runtime::MultiAddress; use substrate_api_client::{ - ac_primitives::AssetRuntimeConfig, rpc::JsonrpseeClient, Api, GetBalance, + ac_primitives::AssetRuntimeConfig, extrinsic::BalancesExtrinsics, rpc::JsonrpseeClient, Api, + Error, 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 alice_signer = AccountKeyring::Alice.pair(); + + 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); + + let one = AccountKeyring::One.to_account_id(); + + //BadOrigin + api.set_signer(alice_signer.into()); + //Can only be called by root + let xt = api + .balance_force_set_balance(MultiAddress::Id(alice.clone()), 100000000000000000) + .await + .unwrap(); + + let result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await; + match result { + Err(Error::FailedExtrinsic(extrinsic_error)) => { + let dispatch_error = extrinsic_error.dispatch_error(); + let report = extrinsic_error.get_report::().unwrap(); + assert!(report.block_hash.is_some()); + assert!(report.events.is_some()); + assert!(format!("{dispatch_error:?}").contains("BadOrigin")); + println!("{dispatch_error:?}"); + println!("[+] BadOrigin error: Bob can't force set balance"); + }, + _ => panic!("Expected Failed Extrinisc Error"), + } + + let balance_of_one = api.get_account_data(&one).await.unwrap().unwrap_or_default().free; + println!("[+] One's Free Balance is {}\n", balance_of_one); } From deaf479d5a1d021f75cdaad3cc2a4ea38d90f7bb Mon Sep 17 00:00:00 2001 From: Thomas Niederberger Date: Mon, 8 Jul 2024 10:17:30 +0200 Subject: [PATCH 2/3] Create test for balance_transfer_keep_alive --- .../async/examples/dispatch_errors_tests.rs | 2 +- .../async/examples/pallet_balances_tests.rs | 58 ++++++++++--------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/testing/async/examples/dispatch_errors_tests.rs b/testing/async/examples/dispatch_errors_tests.rs index c1ab12e88..aa740d924 100644 --- a/testing/async/examples/dispatch_errors_tests.rs +++ b/testing/async/examples/dispatch_errors_tests.rs @@ -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; diff --git a/testing/async/examples/pallet_balances_tests.rs b/testing/async/examples/pallet_balances_tests.rs index cfb6b1039..212f60e29 100644 --- a/testing/async/examples/pallet_balances_tests.rs +++ b/testing/async/examples/pallet_balances_tests.rs @@ -15,12 +15,10 @@ //! Tests for the pallet balances interface functions. -use sp_core::H256; use sp_keyring::AccountKeyring; -use sp_runtime::MultiAddress; use substrate_api_client::{ ac_primitives::AssetRuntimeConfig, extrinsic::BalancesExtrinsics, rpc::JsonrpseeClient, Api, - Error, GetAccountInformation, GetBalance, SubmitAndWatch, XtStatus, + GetAccountInformation, GetBalance, SubmitAndWatch, XtStatus, }; #[tokio::main] @@ -29,38 +27,44 @@ async fn main() { let client = JsonrpseeClient::with_default_url().await.unwrap(); let mut api = Api::::new(client).await.unwrap(); - let _ed = api.get_existential_deposit().await.unwrap(); - - let alice_signer = AccountKeyring::Alice.pair(); + 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 is {}\n", balance_of_alice); + println!("[+] Alice's Free Balance is {}\n", balance_of_alice); - let one = AccountKeyring::One.to_account_id(); + 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 two transactions + let fee_estimate = 2 * 2000000000000; - //BadOrigin - api.set_signer(alice_signer.into()); - //Can only be called by root let xt = api - .balance_force_set_balance(MultiAddress::Id(alice.clone()), 100000000000000000) + .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 result = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await; - match result { - Err(Error::FailedExtrinsic(extrinsic_error)) => { - let dispatch_error = extrinsic_error.dispatch_error(); - let report = extrinsic_error.get_report::().unwrap(); - assert!(report.block_hash.is_some()); - assert!(report.events.is_some()); - assert!(format!("{dispatch_error:?}").contains("BadOrigin")); - println!("{dispatch_error:?}"); - println!("[+] BadOrigin error: Bob can't force set balance"); - }, - _ => panic!("Expected Failed Extrinisc Error"), - } + let xt = api + .balance_transfer_allow_death(bob.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 = AccountKeyring::Alice.to_account_id(); + 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_one = api.get_account_data(&one).await.unwrap().unwrap_or_default().free; - println!("[+] One's Free Balance is {}\n", balance_of_one); + 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); } From 8b28f27fc0168d92fdf3e678a4b2d048e6a181ab Mon Sep 17 00:00:00 2001 From: Thomas Niederberger Date: Wed, 10 Jul 2024 08:44:22 +0200 Subject: [PATCH 3/3] Incorporate review feedback --- .../async/examples/pallet_balances_tests.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/testing/async/examples/pallet_balances_tests.rs b/testing/async/examples/pallet_balances_tests.rs index 212f60e29..f379a9e78 100644 --- a/testing/async/examples/pallet_balances_tests.rs +++ b/testing/async/examples/pallet_balances_tests.rs @@ -40,8 +40,19 @@ async fn main() { 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 two transactions - let fee_estimate = 2 * 2000000000000; + // 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) @@ -52,19 +63,17 @@ async fn main() { assert!(report.is_err()); let xt = api - .balance_transfer_allow_death(bob.into(), balance_of_alice - fee_estimate) + .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 = AccountKeyring::Alice.to_account_id(); 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 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); }