diff --git a/client/offchain/src/api/http.rs b/client/offchain/src/api/http.rs index e3872614eae4d..1c0d1f8921fe0 100644 --- a/client/offchain/src/api/http.rs +++ b/client/offchain/src/api/http.rs @@ -162,12 +162,12 @@ impl HttpApi { self.requests .insert(new_id, HttpApiRequest::NotDispatched(request, body_sender)); - tracing::error!( + tracing::trace!( target: LOG_TARGET, id = %new_id.0, %method, %uri, - "Requested started", + "Request started", ); Ok(new_id) diff --git a/frame/assets/src/benchmarking.rs b/frame/assets/src/benchmarking.rs index 4fe951f56edf3..7f9c31bf04837 100644 --- a/frame/assets/src/benchmarking.rs +++ b/frame/assets/src/benchmarking.rs @@ -52,6 +52,7 @@ fn create_default_asset, I: 'static>( caller_lookup.clone(), is_sufficient, 1u32.into(), + true, ) .is_ok()); (asset_id, caller, caller_lookup) @@ -140,7 +141,7 @@ benchmarks_instance_pallet! { let caller = T::CreateOrigin::ensure_origin(origin, &asset_id.into()).unwrap(); let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, 1u32.into()) + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, 1u32.into(), true) verify { assert_last_event::(Event::Created { asset_id: asset_id.into(), creator: caller.clone(), owner: caller }.into()); } @@ -149,7 +150,7 @@ benchmarks_instance_pallet! { let asset_id = default_asset_id::(); let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, asset_id, caller_lookup, true, 1u32.into()) + }: _(SystemOrigin::Root, asset_id, caller_lookup, true, 1u32.into(), true) verify { assert_last_event::(Event::ForceCreated { asset_id: asset_id.into(), owner: caller }.into()); } diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index f970ce6cf5640..c2a1164d6f64b 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -515,7 +515,7 @@ impl, I: 'static> Pallet { /// /// Will fail if the amount transferred is so small that it cannot create the destination due /// to minimum balance requirements. - pub(super) fn do_transfer( + pub fn do_transfer( id: T::AssetId, source: &T::AccountId, dest: &T::AccountId, @@ -635,11 +635,13 @@ impl, I: 'static> Pallet { /// this asset. /// * `min_balance`: The minimum balance a user is allowed to have of this asset before they are /// considered dust and cleaned up. + /// * `transferable`: Whether the new asset is transferable or not. pub(super) fn do_force_create( id: T::AssetId, owner: T::AccountId, is_sufficient: bool, min_balance: T::Balance, + transferable: bool, ) -> DispatchResult { ensure!(!Asset::::contains_key(id), Error::::InUse); ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); @@ -659,6 +661,7 @@ impl, I: 'static> Pallet { sufficients: 0, approvals: 0, status: AssetStatus::Live, + is_transferable: transferable, }, ); Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() }); diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index f420ea02c31e9..fd9341be54671 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -179,7 +179,7 @@ impl, I: 'static> fungibles::Create for Pallet is_sufficient: bool, min_balance: Self::Balance, ) -> DispatchResult { - Self::do_force_create(id, admin, is_sufficient, min_balance) + Self::do_force_create(id, admin, is_sufficient, min_balance, true) } } diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 894f8f3a4ab93..c1b4d74816c4b 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -356,8 +356,8 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { - /// Genesis assets: id, owner, is_sufficient, min_balance - pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>, + /// Genesis assets: id, owner, is_sufficient, min_balance, transferable + pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance, bool)>, /// Genesis metadata: id, name, symbol, decimals pub metadata: Vec<(T::AssetId, Vec, Vec, u8)>, /// Genesis accounts: id, account_id, balance @@ -378,7 +378,7 @@ pub mod pallet { #[pallet::genesis_build] impl, I: 'static> GenesisBuild for GenesisConfig { fn build(&self) { - for (id, owner, is_sufficient, min_balance) in &self.assets { + for (id, owner, is_sufficient, min_balance, transferable) in &self.assets { assert!(!Asset::::contains_key(id), "Asset id already in use"); assert!(!min_balance.is_zero(), "Min balance should not be zero"); Asset::::insert( @@ -396,6 +396,7 @@ pub mod pallet { sufficients: 0, approvals: 0, status: AssetStatus::Live, + is_transferable: *transferable, // This line has been added by Fragnova }, ); } @@ -559,6 +560,10 @@ pub mod pallet { IncorrectStatus, /// The asset should be frozen before the given operation. NotFrozen, + /// Cannot transfer an asset that is not transferable. + /// + /// Note: This error has been added by Fragnova + CannotTransferThisFragnovaAsset, } #[pallet::call] @@ -578,6 +583,7 @@ pub mod pallet { /// member of the asset class's admin team. /// - `min_balance`: The minimum balance of this new asset that any single account must /// have. If an account's balance is reduced below this, then it collapses to zero. + /// - `transferable`: Whether the new asset is transferable or not. Note: This parameter has been added by Fragnova. /// /// Emits `Created` event when successful. /// @@ -589,6 +595,7 @@ pub mod pallet { id: T::AssetIdParameter, admin: AccountIdLookupOf, min_balance: T::Balance, + transferable: bool, // Note: This parameter has been added by Fragnova ) -> DispatchResult { let id: T::AssetId = id.into(); let owner = T::CreateOrigin::ensure_origin(origin, &id)?; @@ -615,6 +622,7 @@ pub mod pallet { sufficients: 0, approvals: 0, status: AssetStatus::Live, + is_transferable: transferable, // This line has been added by Fragnova }, ); @@ -643,6 +651,7 @@ pub mod pallet { /// `transfer_ownership` and `set_team`. /// - `min_balance`: The minimum balance of this new asset that any single account must /// have. If an account's balance is reduced below this, then it collapses to zero. + ///- `transferable`: Whether the asset is transferable or not. Note: This parameter has been added by Fragnova. /// /// Emits `ForceCreated` event when successful. /// @@ -655,11 +664,12 @@ pub mod pallet { owner: AccountIdLookupOf, is_sufficient: bool, #[pallet::compact] min_balance: T::Balance, + transferable: bool, // Note: This parameter has been added by Fragnova. ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; let id: T::AssetId = id.into(); - Self::do_force_create(id, owner, is_sufficient, min_balance) + Self::do_force_create(id, owner, is_sufficient, min_balance, transferable) } /// Start the process of destroying a fungible asset class. @@ -839,6 +849,10 @@ pub mod pallet { let dest = T::Lookup::lookup(target)?; let id: T::AssetId = id.into(); + // These 2 lines have been added by Fragnova + let info = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(info.is_transferable, Error::::CannotTransferThisFragnovaAsset); + let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ()) } @@ -873,6 +887,10 @@ pub mod pallet { let dest = T::Lookup::lookup(target)?; let id: T::AssetId = id.into(); + // These 2 lines have been added by Fragnova + let info = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(info.is_transferable, Error::::CannotTransferThisFragnovaAsset); + let f = TransferFlags { keep_alive: true, best_effort: false, burn_dust: false }; Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ()) } @@ -1068,7 +1086,7 @@ pub mod pallet { ensure!(details.status == AssetStatus::Live, Error::::LiveAsset); ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { - return Ok(()) + return Ok(()); } let metadata_deposit = Metadata::::get(id).deposit; @@ -1358,6 +1376,11 @@ pub mod pallet { let owner = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; let id: T::AssetId = id.into(); + + // These 2 lines have been added by Fragnova + let info = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(info.is_transferable, Error::::CannotTransferThisFragnovaAsset); + Self::do_approve_transfer(id, &owner, &delegate, amount) } @@ -1474,6 +1497,11 @@ pub mod pallet { let owner = T::Lookup::lookup(owner)?; let destination = T::Lookup::lookup(destination)?; let id: T::AssetId = id.into(); + + // These 2 lines have been added by Fragnova + let info = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(info.is_transferable, Error::::CannotTransferThisFragnovaAsset); + Self::do_transfer_approved(id, &owner, &delegate, &destination, amount) } diff --git a/frame/assets/src/migration.rs b/frame/assets/src/migration.rs index 71da8823e92ef..ab853d30a661e 100644 --- a/frame/assets/src/migration.rs +++ b/frame/assets/src/migration.rs @@ -42,6 +42,7 @@ pub mod v1 { impl OldAssetDetails { fn migrate_to_v1(self) -> AssetDetails { let status = if self.is_frozen { AssetStatus::Frozen } else { AssetStatus::Live }; + let is_transferable = false; AssetDetails { owner: self.owner, @@ -56,6 +57,7 @@ pub mod v1 { sufficients: self.sufficients, approvals: self.approvals, status, + is_transferable, } } } diff --git a/frame/assets/src/mock.rs b/frame/assets/src/mock.rs index 14795b10a7e15..60f4e1afe92c9 100644 --- a/frame/assets/src/mock.rs +++ b/frame/assets/src/mock.rs @@ -87,6 +87,7 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; + type IsTransferable = frame_support::traits::ConstBool; // This line was added by Fragnova } pub struct AssetsCallbackHandle; @@ -173,8 +174,8 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let config: pallet_assets::GenesisConfig = pallet_assets::GenesisConfig { assets: vec![ - // id, owner, is_sufficient, min_balance - (999, 0, true, 1), + // id, owner, is_sufficient, min_balance, transferable + (999, 0, true, 1, true), ], metadata: vec![ // id, name, symbol, decimals diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index 6a3fd672a759c..3adf3fb1a4a1a 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -36,7 +36,7 @@ fn asset_ids() -> Vec { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); @@ -48,9 +48,9 @@ fn basic_minting_should_work() { #[test] fn minting_too_many_insufficient_assets_fails() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1, true)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1, true)); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); @@ -66,9 +66,9 @@ fn minting_too_many_insufficient_assets_fails() { #[test] fn minting_insufficient_asset_with_deposit_should_work_when_consumers_exhausted() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1)); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, false, 1, true)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 2, 1, false, 1, true)); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); @@ -84,7 +84,7 @@ fn minting_insufficient_asset_with_deposit_should_work_when_consumers_exhausted( #[test] fn minting_insufficient_assets_with_deposit_without_consumer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); @@ -97,7 +97,7 @@ fn minting_insufficient_assets_with_deposit_without_consumer_should_work() { #[test] fn refunding_asset_deposit_with_burn_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); @@ -110,7 +110,7 @@ fn refunding_asset_deposit_with_burn_should_work() { #[test] fn refunding_asset_deposit_with_burn_disallowed_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); @@ -121,7 +121,7 @@ fn refunding_asset_deposit_with_burn_disallowed_should_fail() { #[test] fn refunding_asset_deposit_without_burn_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); assert_noop!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100), TokenError::CannotCreate); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); @@ -141,7 +141,7 @@ fn refunding_asset_deposit_without_burn_should_work() { #[test] fn refunding_calls_died_hook() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); @@ -162,7 +162,7 @@ fn approval_lifecycle_works() { Error::::Unknown ); // so we create it :) - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -188,7 +188,7 @@ fn transfer_approved_all_funds() { Error::::Unknown ); // so we create it :) - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -207,7 +207,7 @@ fn transfer_approved_all_funds() { #[test] fn approval_deposits_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); let e = BalancesError::::InsufficientBalance; assert_noop!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), e); @@ -228,7 +228,7 @@ fn approval_deposits_work() { #[test] fn cannot_transfer_more_than_approved() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -240,7 +240,7 @@ fn cannot_transfer_more_than_approved() { #[test] fn cannot_transfer_more_than_exists() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 101)); @@ -252,7 +252,7 @@ fn cannot_transfer_more_than_exists() { #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -282,7 +282,7 @@ fn cancel_approval_works() { #[test] fn force_cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -315,7 +315,7 @@ fn force_cancel_approval_works() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1, true)); assert_eq!(Balances::reserved_balance(&1), 1); assert!(Asset::::contains_key(0)); @@ -341,7 +341,7 @@ fn lifecycle_should_work() { assert!(!Metadata::::contains_key(0)); assert_eq!(Account::::iter_prefix(0).count(), 0); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1, true)); assert_eq!(Balances::reserved_balance(&1), 1); assert!(Asset::::contains_key(0)); @@ -371,7 +371,7 @@ fn lifecycle_should_work() { fn destroy_should_refund_approvals() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 10, 100)); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 3, 50)); @@ -397,7 +397,7 @@ fn destroy_should_refund_approvals() { #[test] fn partial_destroy_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 10)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 10)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 10)); @@ -449,7 +449,7 @@ fn partial_destroy_should_work() { #[test] fn non_providing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1, true)); Balances::make_free_balance_be(&0, 100); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 0, 100)); @@ -478,7 +478,7 @@ fn non_providing_should_work() { #[test] fn min_balance_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Asset::::get(0).unwrap().accounts, 1); @@ -523,7 +523,7 @@ fn min_balance_should_work() { #[test] fn querying_total_supply_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -541,7 +541,7 @@ fn querying_total_supply_should_work() { #[test] fn transferring_amount_below_available_balance_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -553,7 +553,7 @@ fn transferring_amount_below_available_balance_should_work() { #[test] fn transferring_enough_to_kill_source_when_keep_alive_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_noop!( @@ -571,7 +571,7 @@ fn transferring_enough_to_kill_source_when_keep_alive_should_fail() { #[test] fn transferring_frozen_user_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 1)); @@ -584,7 +584,7 @@ fn transferring_frozen_user_should_not_work() { #[test] fn transferring_frozen_asset_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); @@ -601,7 +601,7 @@ fn transferring_frozen_asset_should_not_work() { fn approve_transfer_frozen_asset_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); @@ -617,7 +617,7 @@ fn approve_transfer_frozen_asset_should_not_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_noop!( Assets::transfer_ownership(RuntimeOrigin::signed(2), 0, 2), @@ -653,7 +653,7 @@ fn transfer_owner_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1, true)); assert_eq!(asset_ids(), vec![0, 999]); assert_eq!(Balances::reserved_balance(&1), 1); @@ -684,7 +684,7 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); assert_ok!(Assets::mint(RuntimeOrigin::signed(2), 0, 2, 100)); @@ -698,7 +698,7 @@ fn set_team_should_work() { #[test] fn transferring_to_frozen_account_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); assert_eq!(Assets::balance(0, 1), 100); @@ -712,7 +712,7 @@ fn transferring_to_frozen_account_should_work() { #[test] fn transferring_amount_more_than_available_balance_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); @@ -734,7 +734,7 @@ fn transferring_amount_more_than_available_balance_should_not_work() { #[test] fn transferring_less_than_one_unit_is_fine() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 0)); @@ -746,7 +746,7 @@ fn transferring_less_than_one_unit_is_fine() { #[test] fn transferring_more_units_than_total_supply_should_not_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_noop!( @@ -759,7 +759,7 @@ fn transferring_more_units_than_total_supply_should_not_work() { #[test] fn burning_asset_balance_with_positive_balance_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); @@ -770,7 +770,7 @@ fn burning_asset_balance_with_positive_balance_should_work() { #[test] fn burning_asset_balance_with_zero_balance_does_nothing() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 2), 0); assert_noop!( @@ -790,7 +790,7 @@ fn set_metadata_should_work() { Assets::set_metadata(RuntimeOrigin::signed(1), 0, vec![0u8; 10], vec![0u8; 10], 12), Error::::Unknown, ); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); // Cannot add metadata to unowned asset assert_noop!( Assets::set_metadata(RuntimeOrigin::signed(2), 0, vec![0u8; 10], vec![0u8; 10], 12), @@ -858,7 +858,7 @@ fn set_metadata_should_work() { #[test] fn destroy_accounts_calls_died_hooks() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50, true)); // Create account 1 and 2. assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); @@ -876,7 +876,7 @@ fn destroy_accounts_calls_died_hooks() { #[test] fn finish_destroy_asset_destroys_asset() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 50, true)); // Destroy the accounts. assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); @@ -890,7 +890,7 @@ fn finish_destroy_asset_destroys_asset() { #[test] fn freezer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 10, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); @@ -932,7 +932,7 @@ fn imbalances_should_work() { use frame_support::traits::tokens::fungibles::Balanced; new_test_ext().execute_with(|| { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); let imb = Assets::issue(0, 100); assert_eq!(Assets::total_supply(0), 100); @@ -955,7 +955,7 @@ fn imbalances_should_work() { fn force_metadata_should_work() { new_test_ext().execute_with(|| { // force set metadata works - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::force_set_metadata( RuntimeOrigin::root(), 0, @@ -1034,7 +1034,7 @@ fn force_asset_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 10); Balances::make_free_balance_be(&2, 10); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 30)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 30, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 50)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 150)); @@ -1097,9 +1097,9 @@ fn balance_conversion_should_work() { use frame_support::traits::tokens::BalanceConversion; let id = 42; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), id, 1, true, 10)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), id, 1, true, 10, true)); let not_sufficient = 23; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), not_sufficient, 1, false, 10)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), not_sufficient, 1, false, 10, true)); assert_eq!(asset_ids(), vec![23, 42, 999]); assert_eq!( BalanceToAssetBalance::::to_asset_balance(100, 1234), @@ -1134,7 +1134,7 @@ fn assets_from_genesis_should_exist() { fn querying_name_symbol_and_decimals_should_work() { new_test_ext().execute_with(|| { use frame_support::traits::tokens::fungibles::metadata::Inspect; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::force_set_metadata( RuntimeOrigin::root(), 0, @@ -1153,7 +1153,7 @@ fn querying_name_symbol_and_decimals_should_work() { fn querying_allowance_should_work() { new_test_ext().execute_with(|| { use frame_support::traits::tokens::fungibles::approvals::{Inspect, Mutate}; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); Balances::make_free_balance_be(&1, 1); assert_ok!(Assets::approve(0, &1, &2, 50)); @@ -1168,7 +1168,7 @@ fn querying_allowance_should_work() { fn transfer_large_asset() { new_test_ext().execute_with(|| { let amount = u64::pow(2, 63) + 2; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, amount)); assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, amount - 1)); }) @@ -1178,7 +1178,7 @@ fn transfer_large_asset() { fn querying_roles_should_work() { new_test_ext().execute_with(|| { use frame_support::traits::tokens::fungibles::roles::Inspect; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert_ok!(Assets::set_team( RuntimeOrigin::signed(1), 0, @@ -1203,7 +1203,7 @@ fn normal_asset_create_and_destroy_callbacks_should_work() { assert!(storage::get(b"asset_destroyed").is_none()); Balances::make_free_balance_be(&1, 100); - assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1, true)); assert!(storage::get(b"asset_created").is_some()); assert!(storage::get(b"asset_destroyed").is_none()); @@ -1219,8 +1219,43 @@ fn normal_asset_create_and_destroy_callbacks_should_work() { fn root_asset_create_should_work() { new_test_ext().execute_with(|| { assert!(storage::get(b"asset_created").is_none()); - assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, true)); assert!(storage::get(b"asset_created").is_some()); assert!(storage::get(b"asset_destroyed").is_none()); }); } + +/// This unit test function was added by Fragnova +#[test] +fn untransferable_asset_test() { + new_test_ext().execute_with(|| { + let amount = u64::pow(2, 63) + 2; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, false)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, amount)); + let e = Error::::CannotTransferThisFragnovaAsset; + assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, amount - 1), e); + }) +} + +/// This unit test function was added by Fragnova +#[test] +fn untransferable_asset_test_keepalive() { + new_test_ext().execute_with(|| { + let amount = u64::pow(2, 63) + 2; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, false)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, amount)); + let e = Error::::CannotTransferThisFragnovaAsset; + assert_noop!(Assets::transfer_keep_alive(RuntimeOrigin::signed(1), 0, 2, amount - 1), e); + }) +} + +/// This unit test function was added by Fragnova +#[test] +fn untransferable_asset_test_admin() { + new_test_ext().execute_with(|| { + let amount = u64::pow(2, 63) + 2; + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1, false)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, amount)); + assert_ok!(Assets::force_transfer(RuntimeOrigin::signed(1), 0, 1, 2, amount - 1)); + }) +} diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index d50461ba59898..3cbe1f4daf474 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -69,6 +69,10 @@ pub struct AssetDetails { pub(super) approvals: u32, /// The status of the asset pub(super) status: AssetStatus, + /// Whether the asset is transferable or not + /// + /// Note: This struct field has been added by Fragnova + pub(super) is_transferable: bool, } /// Data concerning an approval. @@ -102,7 +106,7 @@ pub enum ExistenceReason { impl ExistenceReason { pub(crate) fn take_deposit(&mut self) -> Option { if !matches!(self, ExistenceReason::DepositHeld(_)) { - return None + return None; } if let ExistenceReason::DepositHeld(deposit) = sp_std::mem::replace(self, ExistenceReason::DepositRefunded) @@ -175,7 +179,7 @@ impl FrozenBalance for } #[derive(Copy, Clone, PartialEq, Eq)] -pub(super) struct TransferFlags { +pub struct TransferFlags { /// The debited account must stay alive at the end of the operation; an error is returned if /// this cannot be achieved legally. pub(super) keep_alive: bool, diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index f02c7f9c0c579..eabdc356ac93b 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -246,6 +246,12 @@ pub mod pallet { /// The id type for named reserves. type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; + + /// Whether an account can voluntarily transfer any of its balance to another account + /// + /// Note: This type has been added by Fragnova + #[pallet::constant] + type IsTransferable: Get; } /// The current storage version. @@ -288,6 +294,9 @@ pub mod pallet { dest: AccountIdLookupOf, #[pallet::compact] value: T::Balance, ) -> DispatchResultWithPostInfo { + // TODO Review - Do we want to even prevent the root account from voluntarily transferring NOVA? + ensure!(T::IsTransferable::get(), Error::::CannotTransferNOVA); // This line has been added by Fragnova + let transactor = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; >::transfer( @@ -393,6 +402,8 @@ pub mod pallet { dest: AccountIdLookupOf, #[pallet::compact] value: T::Balance, ) -> DispatchResultWithPostInfo { + ensure!(T::IsTransferable::get(), Error::::CannotTransferNOVA); // This line has been added by Fragnova + let transactor = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; >::transfer(&transactor, &dest, value, KeepAlive)?; @@ -423,6 +434,8 @@ pub mod pallet { keep_alive: bool, ) -> DispatchResult { use fungible::Inspect; + ensure!(T::IsTransferable::get(), Error::::CannotTransferNOVA); // This line has been added by Fragnova + let transactor = ensure_signed(origin)?; let reducible_balance = Self::reducible_balance(&transactor, keep_alive); let dest = T::Lookup::lookup(dest)?; @@ -498,6 +511,10 @@ pub mod pallet { DeadAccount, /// Number of named reserves exceed MaxReserves TooManyReserves, + /// No account can voluntarily transfer any of its balance to another account + /// + /// Note: This error has been added by Fragnova + CannotTransferNOVA, } /// The total units issued in the system. @@ -654,7 +671,7 @@ impl BitOr for Reasons { type Output = Reasons; fn bitor(self, other: Reasons) -> Reasons { if self == other { - return self + return self; } Reasons::All } @@ -798,11 +815,11 @@ impl, I: 'static> Pallet { mint: bool, ) -> DepositConsequence { if amount.is_zero() { - return DepositConsequence::Success + return DepositConsequence::Success; } if mint && TotalIssuance::::get().checked_add(&amount).is_none() { - return DepositConsequence::Overflow + return DepositConsequence::Overflow; } let new_total_balance = match account.total().checked_add(&amount) { @@ -811,7 +828,7 @@ impl, I: 'static> Pallet { }; if new_total_balance < T::ExistentialDeposit::get() { - return DepositConsequence::BelowMinimum + return DepositConsequence::BelowMinimum; } // NOTE: We assume that we are a provider, so don't need to do any checks in the @@ -826,11 +843,11 @@ impl, I: 'static> Pallet { account: &AccountData, ) -> WithdrawConsequence { if amount.is_zero() { - return WithdrawConsequence::Success + return WithdrawConsequence::Success; } if TotalIssuance::::get().checked_sub(&amount).is_none() { - return WithdrawConsequence::Underflow + return WithdrawConsequence::Underflow; } let new_total_balance = match account.total().checked_sub(&amount) { @@ -847,7 +864,7 @@ impl, I: 'static> Pallet { if frame_system::Pallet::::can_dec_provider(who) { WithdrawConsequence::ReducedToZero(new_total_balance) } else { - return WithdrawConsequence::WouldDie + return WithdrawConsequence::WouldDie; } } else { WithdrawConsequence::Success @@ -862,7 +879,7 @@ impl, I: 'static> Pallet { // Eventual free funds must be no less than the frozen balance. let min_balance = account.frozen(Reasons::All); if new_free_balance < min_balance { - return WithdrawConsequence::Frozen + return WithdrawConsequence::Frozen; } success @@ -1005,14 +1022,14 @@ impl, I: 'static> Pallet { status: Status, ) -> Result { if value.is_zero() { - return Ok(Zero::zero()) + return Ok(Zero::zero()); } if slashed == beneficiary { return match status { Status::Free => Ok(value.saturating_sub(Self::unreserve(slashed, value))), Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))), - } + }; } let ((actual, _maybe_one_dust), _maybe_other_dust) = Self::try_mutate_account_with_dust( @@ -1025,16 +1042,18 @@ impl, I: 'static> Pallet { let actual = cmp::min(from_account.reserved, value); ensure!(best_effort || actual == value, Error::::InsufficientBalance); match status { - Status::Free => + Status::Free => { to_account.free = to_account .free .checked_add(&actual) - .ok_or(ArithmeticError::Overflow)?, - Status::Reserved => + .ok_or(ArithmeticError::Overflow)? + }, + Status::Reserved => { to_account.reserved = to_account .reserved .checked_add(&actual) - .ok_or(ArithmeticError::Overflow)?, + .ok_or(ArithmeticError::Overflow)? + }, } from_account.reserved -= actual; Ok(actual) @@ -1096,7 +1115,7 @@ impl, I: 'static> fungible::Inspect for Pallet impl, I: 'static> fungible::Mutate for Pallet { fn mint_into(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { - return Ok(()) + return Ok(()); } Self::try_mutate_account(who, |account, _is_new| -> DispatchResult { Self::deposit_consequence(who, amount, account, true).into_result()?; @@ -1113,7 +1132,7 @@ impl, I: 'static> fungible::Mutate for Pallet { amount: Self::Balance, ) -> Result { if amount.is_zero() { - return Ok(Self::Balance::zero()) + return Ok(Self::Balance::zero()); } let actual = Self::try_mutate_account( who, @@ -1180,7 +1199,7 @@ impl, I: 'static> fungible::InspectHold for Pallet, I: 'static> fungible::InspectHold for Pallet, I: 'static> fungible::MutateHold for Pallet { fn hold(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { - return Ok(()) + return Ok(()); } ensure!(Self::can_reserve(who, amount), Error::::InsufficientBalance); Self::mutate_account(who, |a| { @@ -1209,7 +1228,7 @@ impl, I: 'static> fungible::MutateHold for Pallet Result { if amount.is_zero() { - return Ok(amount) + return Ok(amount); } // Done on a best-effort basis. Self::try_mutate_account(who, |a, _| { @@ -1415,7 +1434,7 @@ where // Check if `value` amount of free balance can be slashed from `who`. fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { if value.is_zero() { - return true + return true; } Self::free_balance(who) >= value } @@ -1444,7 +1463,7 @@ where // Is a no-op if amount to be burned is zero. fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { if amount.is_zero() { - return PositiveImbalance::zero() + return PositiveImbalance::zero(); } >::mutate(|issued| { *issued = issued.checked_sub(&amount).unwrap_or_else(|| { @@ -1460,7 +1479,7 @@ where // Is a no-op if amount to be issued it zero. fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { if amount.is_zero() { - return NegativeImbalance::zero() + return NegativeImbalance::zero(); } >::mutate(|issued| { *issued = issued.checked_add(&amount).unwrap_or_else(|| { @@ -1489,7 +1508,7 @@ where new_balance: T::Balance, ) -> DispatchResult { if amount.is_zero() { - return Ok(()) + return Ok(()); } let min_balance = Self::account(who).frozen(reasons.into()); ensure!(new_balance >= min_balance, Error::::LiquidityRestrictions); @@ -1505,7 +1524,7 @@ where existence_requirement: ExistenceRequirement, ) -> DispatchResult { if value.is_zero() || transactor == dest { - return Ok(()) + return Ok(()); } Self::try_mutate_account_with_dust( @@ -1558,7 +1577,6 @@ where to: dest.clone(), amount: value, }); - Ok(()) } @@ -1573,10 +1591,10 @@ where /// inconsistent or `can_slash` wasn't used appropriately. fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) + return (NegativeImbalance::zero(), Zero::zero()); } if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value) + return (NegativeImbalance::zero(), value); } for attempt in 0..2 { @@ -1625,7 +1643,7 @@ where who: who.clone(), amount: value.saturating_sub(not_slashed), }); - return (imbalance, not_slashed) + return (imbalance, not_slashed); }, Err(_) => (), } @@ -1643,7 +1661,7 @@ where value: Self::Balance, ) -> Result { if value.is_zero() { - return Ok(PositiveImbalance::zero()) + return Ok(PositiveImbalance::zero()); } Self::try_mutate_account( @@ -1668,7 +1686,7 @@ where /// - `value` is so large it would cause the balance of `who` to overflow. fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { if value.is_zero() { - return Self::PositiveImbalance::zero() + return Self::PositiveImbalance::zero(); } Self::try_mutate_account( @@ -1701,7 +1719,7 @@ where liveness: ExistenceRequirement, ) -> result::Result { if value.is_zero() { - return Ok(NegativeImbalance::zero()) + return Ok(NegativeImbalance::zero()); } Self::try_mutate_account( @@ -1774,7 +1792,7 @@ where /// Always `true` if value to be reserved is zero. fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { if value.is_zero() { - return true + return true; } Self::account(who).free.checked_sub(&value).map_or(false, |new_balance| { Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance).is_ok() @@ -1790,7 +1808,7 @@ where /// Is a no-op if value to be reserved is zero. fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { if value.is_zero() { - return Ok(()) + return Ok(()); } Self::try_mutate_account(who, |account, _| -> DispatchResult { @@ -1812,10 +1830,10 @@ where /// NOTE: returns amount value which wasn't successfully unreserved. fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { if value.is_zero() { - return Zero::zero() + return Zero::zero(); } if Self::total_balance(who).is_zero() { - return value + return value; } let actual = match Self::mutate_account(who, |account| { @@ -1831,7 +1849,7 @@ where // This should never happen since we don't alter the total amount in the account. // If it ever does, then we should fail gracefully though, indicating that nothing // could be done. - return value + return value; }, }; @@ -1848,10 +1866,10 @@ where value: Self::Balance, ) -> (Self::NegativeImbalance, Self::Balance) { if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) + return (NegativeImbalance::zero(), Zero::zero()); } if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value) + return (NegativeImbalance::zero(), value); } // NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an @@ -1880,7 +1898,7 @@ where who: who.clone(), amount: value.saturating_sub(not_slashed), }); - return (imbalance, not_slashed) + return (imbalance, not_slashed); }, Err(_) => (), } @@ -1929,7 +1947,7 @@ where value: Self::Balance, ) -> DispatchResult { if value.is_zero() { - return Ok(()) + return Ok(()); } Reserves::::try_mutate(who, |reserves| -> DispatchResult { @@ -1958,7 +1976,7 @@ where value: Self::Balance, ) -> Self::Balance { if value.is_zero() { - return Zero::zero() + return Zero::zero(); } Reserves::::mutate_exists(who, |maybe_reserves| -> Self::Balance { @@ -2005,7 +2023,7 @@ where value: Self::Balance, ) -> (Self::NegativeImbalance, Self::Balance) { if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) + return (NegativeImbalance::zero(), Zero::zero()); } Reserves::::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) { @@ -2044,15 +2062,16 @@ where status: Status, ) -> Result { if value.is_zero() { - return Ok(Zero::zero()) + return Ok(Zero::zero()); } if slashed == beneficiary { return match status { Status::Free => Ok(Self::unreserve_named(id, slashed, value)), - Status::Reserved => - Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))), - } + Status::Reserved => { + Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))) + }, + }; } Reserves::::try_mutate(slashed, |reserves| -> Result { @@ -2150,7 +2169,7 @@ where reasons: WithdrawReasons, ) { if amount.is_zero() || reasons.is_empty() { - return + return; } let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); let mut locks = Self::locks(who) @@ -2172,7 +2191,7 @@ where reasons: WithdrawReasons, ) { if amount.is_zero() || reasons.is_empty() { - return + return; } let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); let mut locks = Self::locks(who) diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs index 6f371f1e23010..52f19f5deb73e 100644 --- a/frame/balances/src/tests_composite.rs +++ b/frame/balances/src/tests_composite.rs @@ -51,6 +51,7 @@ parameter_types! { frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), ); pub static ExistentialDeposit: u64 = 0; + pub static IsTransferable: bool = true; } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; @@ -98,6 +99,7 @@ impl Config for Test { type MaxReserves = ConstU32<2>; type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type IsTransferable = IsTransferable; } pub struct ExtBuilder { diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs index 0d57a33200147..4665720623b0f 100644 --- a/frame/balances/src/tests_local.rs +++ b/frame/balances/src/tests_local.rs @@ -100,6 +100,7 @@ impl Config for Test { type MaxReserves = ConstU32<2>; type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type IsTransferable = frame_support::traits::ConstBool; // This line was added by Fragnova } pub struct ExtBuilder { diff --git a/frame/balances/src/tests_reentrancy.rs b/frame/balances/src/tests_reentrancy.rs index 1699a879638e2..6035b269102d6 100644 --- a/frame/balances/src/tests_reentrancy.rs +++ b/frame/balances/src/tests_reentrancy.rs @@ -101,6 +101,7 @@ impl Config for Test { type MaxReserves = ConstU32<2>; type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type IsTransferable = frame_support::traits::ConstBool; // This line was added by Fragnova } pub struct ExtBuilder { diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index a115cf119ceef..b0ebddea2f2c2 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -319,6 +319,12 @@ pub mod pallet { /// The maximum length of the debug buffer in bytes. #[pallet::constant] type MaxDebugBufferLen: Get; + + /// Whether an account can voluntarily transfer any of its balance to another account + /// + /// Note: This type has been added by Fragnova + #[pallet::constant] + type IsTransferable: Get; } #[pallet::hooks] @@ -613,6 +619,8 @@ pub mod pallet { storage_deposit_limit: Option< as codec::HasCompact>::Type>, data: Vec, ) -> DispatchResultWithPostInfo { + ensure!(T::IsTransferable::get() || (!T::IsTransferable::get() && value == as sp_runtime::traits::Zero>::zero()), Error::::CannotTransferNOVA); // This line has been added by Fragnova + let gas_limit: Weight = gas_limit.into(); let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; @@ -674,6 +682,8 @@ pub mod pallet { data: Vec, salt: Vec, ) -> DispatchResultWithPostInfo { + ensure!(T::IsTransferable::get() || (!T::IsTransferable::get() && value == as sp_runtime::traits::Zero>::zero()), Error::::CannotTransferNOVA); // This line has been added by Fragnova + let origin = ensure_signed(origin)?; let code_len = code.len() as u32; let data_len = data.len() as u32; @@ -717,6 +727,8 @@ pub mod pallet { data: Vec, salt: Vec, ) -> DispatchResultWithPostInfo { + ensure!(T::IsTransferable::get() || (!T::IsTransferable::get() && value == as sp_runtime::traits::Zero>::zero()), Error::::CannotTransferNOVA); // This line has been added by Fragnova + let origin = ensure_signed(origin)?; let data_len = data.len() as u32; let salt_len = salt.len() as u32; @@ -893,6 +905,10 @@ pub mod pallet { /// A more detailed error can be found on the node console if debug messages are enabled /// by supplying `-lruntime::contracts=debug`. CodeRejected, + /// No account can voluntarily transfer any of its balance to another account + /// + /// Note: This error has been added by Fragnova + CannotTransferNOVA, /// An indetermistic code was used in a context where this is not permitted. Indeterministic, } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index bf1bd4f65442f..2750f82bd689a 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -36,7 +36,7 @@ use frame_support::{ parameter_types, storage::child, traits::{ - ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, Get, LockableCurrency, + ConstBool, ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, Get, LockableCurrency, OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons, }, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, @@ -318,6 +318,7 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type IsTransferable = ConstBool; } impl pallet_timestamp::Config for Test { @@ -405,6 +406,7 @@ impl Config for Test { type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = UnstableInterface; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; + type IsTransferable = ConstBool; } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index c26a53d1a0c40..d118db9f04b5d 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -772,7 +772,7 @@ impl Pallet { Ok(Proxies::::get(real).0.into_iter().find(f).ok_or(Error::::NotProxy)?) } - fn do_proxy( + pub fn do_proxy( def: ProxyDefinition, real: T::AccountId, call: ::RuntimeCall, diff --git a/frame/proxy/src/tests.rs b/frame/proxy/src/tests.rs index c49c344aca107..a3432061e1dda 100644 --- a/frame/proxy/src/tests.rs +++ b/frame/proxy/src/tests.rs @@ -29,7 +29,7 @@ use frame_support::{ traits::{ConstU32, ConstU64, Contains}, RuntimeDebug, }; -use sp_core::H256; +use sp_core::{H256, ConstBool}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -88,6 +88,7 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type IsTransferable = ConstBool; } impl pallet_utility::Config for Test { type RuntimeEvent = RuntimeEvent;