// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Tests for Nft fractionalization pallet. use crate::{mock::*, *}; use frame_support::{ assert_noop, assert_ok, traits::{ fungible::{hold::Inspect as InspectHold, Mutate as MutateFungible}, fungibles::{metadata::Inspect, InspectEnumerable}, }, }; use pallet_nfts::CollectionConfig; use sp_runtime::{DispatchError, ModuleError, TokenError::FundsUnavailable}; fn assets() -> Vec { let mut s: Vec<_> = <::Assets>::asset_ids().collect(); s.sort(); s } fn events() -> Vec> { let result = System::events() .into_iter() .map(|r| r.event) .filter_map(|e| { if let mock::RuntimeEvent::NftFractionalization(inner) = e { Some(inner) } else { None } }) .collect(); System::reset_events(); result } type AccountIdOf = ::AccountId; fn account(id: u8) -> AccountIdOf { [id; 32].into() } #[test] fn fractionalize_should_work() { new_test_ext().execute_with(|| { let nft_collection_id = 0; let nft_id = 0; let asset_id = 0; let fractions = 1000; Balances::set_balance(&account(1), 100); Balances::set_balance(&account(2), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), account(1), CollectionConfig::default(), )); assert_ok!(Nfts::mint( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, account(1), None, )); assert_ok!(NftFractionalization::fractionalize( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(2), fractions, )); assert_eq!(assets(), vec![asset_id]); assert_eq!(Assets::balance(asset_id, account(2)), fractions); assert_eq!(Balances::total_balance_on_hold(&account(1)), 2); assert_eq!(String::from_utf8(Assets::name(0)).unwrap(), "Frac 0-0"); assert_eq!(String::from_utf8(Assets::symbol(0)).unwrap(), "FRAC"); assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(1))); assert_noop!( Nfts::transfer( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, account(2), ), DispatchError::Module(ModuleError { index: 4, error: [12, 0, 0, 0], message: Some("ItemLocked") }) ); let details = NftToAsset::::get((&nft_collection_id, &nft_id)).unwrap(); assert_eq!(details.asset, asset_id); assert_eq!(details.fractions, fractions); assert!(events().contains(&Event::::NftFractionalized { nft_collection: nft_collection_id, nft: nft_id, fractions, asset: asset_id, beneficiary: account(2), })); // owner can't burn an already fractionalized NFT assert_noop!( Nfts::burn(RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id), DispatchError::Module(ModuleError { index: 4, error: [12, 0, 0, 0], message: Some("ItemLocked") }) ); // can't fractionalize twice assert_noop!( NftFractionalization::fractionalize( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id + 1, account(2), fractions, ), DispatchError::Module(ModuleError { index: 4, error: [12, 0, 0, 0], message: Some("ItemLocked") }) ); let nft_id = nft_id + 1; assert_noop!( NftFractionalization::fractionalize( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(2), fractions, ), Error::::NftNotFound ); assert_ok!(Nfts::mint( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, account(2), None )); assert_noop!( NftFractionalization::fractionalize( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(2), fractions, ), Error::::NoPermission ); }); } #[test] fn unify_should_work() { new_test_ext().execute_with(|| { let nft_collection_id = 0; let nft_id = 0; let asset_id = 0; let fractions = 1000; Balances::set_balance(&account(1), 100); Balances::set_balance(&account(2), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), account(1), CollectionConfig::default(), )); assert_ok!(Nfts::mint( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, account(1), None, )); assert_ok!(NftFractionalization::fractionalize( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(2), fractions, )); assert_noop!( NftFractionalization::unify( RuntimeOrigin::signed(account(2)), nft_collection_id + 1, nft_id, asset_id, account(1), ), Error::::NftNotFractionalized ); assert_noop!( NftFractionalization::unify( RuntimeOrigin::signed(account(2)), nft_collection_id, nft_id, asset_id + 1, account(1), ), Error::::IncorrectAssetId ); // can't unify the asset a user doesn't hold assert_noop!( NftFractionalization::unify( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(1), ), DispatchError::Token(FundsUnavailable) ); assert_ok!(NftFractionalization::unify( RuntimeOrigin::signed(account(2)), nft_collection_id, nft_id, asset_id, account(1), )); assert_eq!(Assets::balance(asset_id, account(2)), 0); assert_eq!(Balances::reserved_balance(&account(1)), 1); assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(1))); assert!(!NftToAsset::::contains_key((&nft_collection_id, &nft_id))); assert!(events().contains(&Event::::NftUnified { nft_collection: nft_collection_id, nft: nft_id, asset: asset_id, beneficiary: account(1), })); // validate we need to hold the full balance to un-fractionalize the NFT let asset_id = asset_id + 1; assert_ok!(NftFractionalization::fractionalize( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(1), fractions, )); assert_ok!(Assets::transfer(RuntimeOrigin::signed(account(1)), asset_id, account(2), 1)); assert_eq!(Assets::balance(asset_id, account(1)), fractions - 1); assert_eq!(Assets::balance(asset_id, account(2)), 1); assert_noop!( NftFractionalization::unify( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(1), ), DispatchError::Token(FundsUnavailable) ); assert_ok!(Assets::transfer(RuntimeOrigin::signed(account(2)), asset_id, account(1), 1)); assert_ok!(NftFractionalization::unify( RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id, asset_id, account(2), )); assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(2))); }); }